You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

174 lines
6.0 KiB

import React, { useState, useContext, createContext, useReducer, useEffect } from "react";
import Integration from "./Integration";
import * as serverApi from '../../api';
import SpotifyClientCreds from "./spotify/SpotifyClientCreds";
import * as backend from "../backend/integrations";
import { handleNotLoggedIn, NotLoggedInError } from "../backend/request";
import { useAuth } from "../useAuth";
import YoutubeMusicWebScraper from "./youtubemusic/YoutubeMusicWebScraper";
export type IntegrationState = {
id: number,
integration: Integration,
properties: serverApi.CreateIntegrationRequest,
};
export type IntegrationsState = IntegrationState[] | "Loading";
export function isIntegrationState(v: any): v is IntegrationState {
return 'id' in v && 'integration' in v && 'properties' in v;
}
export interface Integrations {
state: IntegrationsState,
addIntegration: (v: serverApi.CreateIntegrationRequest) => Promise<number>,
deleteIntegration: (id: number) => Promise<void>,
modifyIntegration: (id: number, v: serverApi.CreateIntegrationRequest) => Promise<void>,
updateFromUpstream: () => Promise<void>,
};
export const IntegrationClasses: Record<any, any> = {
[serverApi.IntegrationType.SpotifyClientCredentials]: SpotifyClientCreds,
[serverApi.IntegrationType.YoutubeWebScraper]: YoutubeMusicWebScraper,
}
export function makeDefaultIntegrationProperties(type: serverApi.IntegrationType):
serverApi.CreateIntegrationRequest {
switch (type) {
case serverApi.IntegrationType.SpotifyClientCredentials: {
return {
name: "Spotify App",
type: type,
details: { clientId: "" },
secretDetails: { clientSecret: "" },
}
}
case serverApi.IntegrationType.YoutubeWebScraper: {
return {
name: "Youtube Music Web Scraper",
type: type,
details: {},
secretDetails: {},
}
}
default: {
throw new Error("Unimplemented default integration.")
}
}
}
export function makeIntegration(p: serverApi.CreateIntegrationRequest, id: number) {
switch (p.type) {
case serverApi.IntegrationType.SpotifyClientCredentials: {
return new SpotifyClientCreds(id);
}
case serverApi.IntegrationType.YoutubeWebScraper: {
return new YoutubeMusicWebScraper(id);
}
default: {
throw new Error("Unimplemented integration type.")
}
}
}
const integrationsContext = createContext<Integrations>({
state: [],
addIntegration: async () => 0,
modifyIntegration: async () => { },
deleteIntegration: async () => { },
updateFromUpstream: async () => { },
});
export function ProvideIntegrations(props: { children: any }) {
const integrations = useProvideIntegrations();
return <integrationsContext.Provider value={integrations}>{props.children}</integrationsContext.Provider>;
}
export const useIntegrations = () => {
return useContext(integrationsContext);
};
function useProvideIntegrations(): Integrations {
let auth = useAuth();
enum IntegrationsActions {
SetItem = "SetItem",
Set = "Set",
DeleteItem = "DeleteItem",
AddItem = "AddItem",
}
let IntegrationsReducer = (state: IntegrationsState, action: any): IntegrationsState => {
switch (action.type) {
case IntegrationsActions.SetItem: {
if (state !== "Loading") {
return state.map((item: any) => {
return (item.id === action.id) ? action.value : item;
})
}
return state;
}
case IntegrationsActions.Set: {
return action.value;
}
case IntegrationsActions.DeleteItem: {
if (state !== "Loading") {
const newState = [...state];
return newState.filter((item: any) => item.id !== action.id);
}
return state;
}
case IntegrationsActions.AddItem: {
return [...state, action.value];
}
default:
throw new Error("Unimplemented Integrations state update.")
}
}
const [state, dispatch] = useReducer(IntegrationsReducer, [])
let updateFromUpstream = async () => {
return await backend.getIntegrations()
.then((integrations: serverApi.ListIntegrationsResponse) => {
dispatch({
type: IntegrationsActions.Set,
value: integrations.map((i: any) => {
return {
integration: new (IntegrationClasses[i.type])(i.id),
properties: { ...i },
id: i.id,
}
})
});
})
.catch((e) => handleNotLoggedIn(auth, e));
}
let addIntegration = async (v: serverApi.CreateIntegrationRequest) => {
const id = await backend.createIntegration(v).catch((e: any) => { handleNotLoggedIn(auth, e) });
await updateFromUpstream();
return id;
}
let deleteIntegration = async (id: number) => {
await backend.deleteIntegration(id).catch((e: any) => { handleNotLoggedIn(auth, e) });
await updateFromUpstream();
}
let modifyIntegration = async (id: number, v: serverApi.CreateIntegrationRequest) => {
await backend.modifyIntegration(id, v).catch((e: any) => { handleNotLoggedIn(auth, e) });
await updateFromUpstream();
}
useEffect(() => {
if (auth.user) {
updateFromUpstream()
}
}, [auth]);
return {
state: state,
addIntegration: addIntegration,
modifyIntegration: modifyIntegration,
deleteIntegration: deleteIntegration,
updateFromUpstream: updateFromUpstream,
}
}