Skip to content

Commit

Permalink
Refactor add provider logic into service
Browse files Browse the repository at this point in the history
  • Loading branch information
sleepyfran committed Aug 24, 2024
1 parent ef7ceae commit a6d3d0d
Show file tree
Hide file tree
Showing 32 changed files with 378 additions and 210 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"prepare": "husky"
},
"dependencies": {
"@effect/schema": "^0.67.18",
"effect": "^3.5.8",
"@effect/schema": "^0.71.1",
"effect": "^3.6.5",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
Expand Down
3 changes: 2 additions & 1 deletion packages/components/add-provider/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
"@echo/core-types": "^1.0.0",
"@echo/services-bootstrap": "^1.0.0",
"@echo/services-bootstrap-services": "^1.0.0",
"@echo/services-add-provider-workflow": "^1.0.0",
"@effect-rx/rx": "^0.33.8",
"@effect-rx/rx-react": "^0.30.11",
"effect": "^3.2.8"
"effect": "^3.6.5"
},
"devDependencies": {
"@types/react": "^18.2.66",
Expand Down
190 changes: 19 additions & 171 deletions packages/components/add-provider/src/AddProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,22 @@
import {
ActiveMediaProviderCache,
AddProviderWorkflow,
AvailableProviders,
MediaProviderMainThreadBroadcastChannel,
type Authentication,
type AuthenticationInfo,
type FolderMetadata,
type MediaPlayer,
type MediaProvider,
type ProviderMetadata,
} from "@echo/core-types";
import { LazyLoadedProvider } from "@echo/services-bootstrap";
import { AddProviderWorkflowLive } from "@echo/services-add-provider-workflow";
import { AppLive } from "@echo/services-bootstrap-services";
import { LazyLoadedMediaPlayer } from "@echo/services-bootstrap/src/loaders";
import { Rx } from "@effect-rx/rx";
import { useRx } from "@effect-rx/rx-react";
import { Effect, Match } from "effect";
import { useCallback, useEffect, useMemo } from "react";
import { Layer, Match } from "effect";
import { useCallback } from "react";

const runtime = Rx.runtime(AppLive);
const loadProviderFn = runtime.fn(LazyLoadedProvider.load);
const loadMediaPlayerFn = runtime.fn(
({
metadata,
authInfo,
}: {
metadata: ProviderMetadata;
authInfo: AuthenticationInfo;
}) =>
Effect.gen(function* () {
const { createMediaPlayer } = yield* LazyLoadedMediaPlayer.load(metadata);
return yield* createMediaPlayer(authInfo);
}),
const runtime = Rx.runtime(
AddProviderWorkflowLive.pipe(Layer.provide(AppLive)),
);
const loadProviderFn = runtime.fn(AddProviderWorkflow.loadProvider);
const connectToProviderFn = runtime.fn(AddProviderWorkflow.connectToProvider);
const selectRootFn = runtime.fn(AddProviderWorkflow.selectRoot);

export const AddProvider = () => {
const [loadStatus, loadProvider] = useRx(loadProviderFn);
Expand All @@ -47,16 +32,7 @@ export const AddProvider = () => {
Match.tag("Initial", () => (
<ProviderSelector onProviderSelected={onProviderSelected} />
)),
Match.tag(
"Success",
({ value: { metadata, authentication, createMediaProvider } }) => (
<ProviderAuthenticator
metadata={metadata}
authentication={authentication}
createMediaProvider={createMediaProvider}
/>
),
),
Match.tag("Success", () => <ProviderAuthenticator />),
Match.tag("Failure", () => (
<div style={{ color: "red" }}>Failed to load provider.</div>
)),
Expand All @@ -77,35 +53,18 @@ const ProviderSelector = ({
</button>
));

const authenticateFn = runtime.fn(
(authentication: Authentication) => authentication.connect,
);

const ProviderAuthenticator = ({
metadata,
authentication,
createMediaProvider,
}: {
metadata: ProviderMetadata;
authentication: Authentication;
createMediaProvider: (authInfo: AuthenticationInfo) => MediaProvider;
}) => {
const [connectionStatus, connectToProvider] = useRx(authenticateFn);
const _connectToProvider = () => connectToProvider(authentication);
const ProviderAuthenticator = () => {
const [connectionStatus, connectToProvider] = useRx(connectToProviderFn);
const _connectToProvider = () => connectToProvider();

return (
<div>
{metadata.id}
{Match.value(connectionStatus).pipe(
Match.tag("Initial", () => (
<button onClick={_connectToProvider}>Login</button>
)),
Match.tag("Success", ({ value: authInfo }) => (
<SelectRoot
authInfo={authInfo}
metadata={metadata}
createMediaProvider={createMediaProvider}
/>
Match.tag("Success", ({ value: rootFolderContent }) => (
<SelectRoot rootFolderContent={rootFolderContent} />
)),
Match.tag("Failure", (error) => (
<div>Error: {JSON.stringify(error)}</div>
Expand All @@ -116,128 +75,17 @@ const ProviderAuthenticator = ({
);
};

const listRootFn = runtime.fn(
(mediaProvider: MediaProvider) => mediaProvider.listRoot,
);

const addMediaProviderToCacheFn = runtime.fn(
({
metadata,
provider,
player,
}: {
metadata: ProviderMetadata;
provider: MediaProvider;
player: MediaPlayer;
}) => ActiveMediaProviderCache.add(metadata, provider, player),
);

const SelectRoot = ({
authInfo,
metadata,
createMediaProvider,
}: {
authInfo: AuthenticationInfo;
metadata: ProviderMetadata;
createMediaProvider: (authInfo: AuthenticationInfo) => MediaProvider;
}) => {
const [mediaPlayerStatus, createMediaPlayer] = useRx(loadMediaPlayerFn);

const mediaProvider = useMemo(
() => createMediaProvider(authInfo),
[createMediaProvider, authInfo],
);

const [listStatus, listRoot] = useRx(listRootFn);
const [, addMediaProviderToCache] = useRx(addMediaProviderToCacheFn);

useEffect(() => {
listRoot(mediaProvider);
createMediaPlayer({ metadata, authInfo });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

// TODO: Refactor this mess somehow?
useEffect(() => {
Match.value(mediaPlayerStatus).pipe(
Match.tag("Success", ({ value: player }) => {
addMediaProviderToCache({
metadata,
provider: mediaProvider,
player,
});
}),
);
}, [mediaPlayerStatus, mediaProvider, metadata, addMediaProviderToCache]);

return (
<div>
{Match.value(listStatus).pipe(
Match.tag("Initial", () => (
<div>Loading root of media provider...</div>
)),
Match.tag("Success", ({ value: folders }) => (
<FolderSelector
authInfo={authInfo}
metadata={metadata}
folders={folders}
/>
)),
Match.tag("Failure", (error) => (
<div>Error: {JSON.stringify(error)}</div>
)),
Match.exhaustive,
)}
</div>
);
};

const startMediaProviderEffect = (
authInfo: AuthenticationInfo,
metadata: ProviderMetadata,
rootFolder: FolderMetadata,
) =>
Effect.gen(function* () {
const broadcastChannel = yield* MediaProviderMainThreadBroadcastChannel;

yield* broadcastChannel.send("start", {
_tag: "file-based",
metadata,
authInfo,
rootFolder,
});
});

const startMediaProviderFn = runtime.fn(
({
authInfo,
metadata,
rootFolder,
}: {
authInfo: AuthenticationInfo;
metadata: ProviderMetadata;
rootFolder: FolderMetadata;
}) => startMediaProviderEffect(authInfo, metadata, rootFolder),
);

const FolderSelector = ({
authInfo,
folders,
metadata,
rootFolderContent: folders,
}: {
authInfo: AuthenticationInfo;
folders: FolderMetadata[];
metadata: ProviderMetadata;
rootFolderContent: FolderMetadata[];
}) => {
const [selectRootStatus, selectRoot] = useRx(startMediaProviderFn);
const [selectRootStatus, selectRoot] = useRx(selectRootFn);

return Match.value(selectRootStatus).pipe(
Match.tag("Initial", () =>
folders.map((folder) => (
<button
onClick={() => selectRoot({ authInfo, metadata, rootFolder: folder })}
key={folder.id}
>
<button onClick={() => selectRoot(folder)} key={folder.id}>
{folder.name}
</button>
)),
Expand Down
2 changes: 1 addition & 1 deletion packages/components/library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@echo/services-player": "^1.0.0",
"@effect-rx/rx": "^0.33.8",
"@effect-rx/rx-react": "^0.30.11",
"effect": "^3.2.8"
"effect": "^3.6.5"
},
"devDependencies": {
"@types/react": "^18.2.66",
Expand Down
2 changes: 1 addition & 1 deletion packages/components/provider-status/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"@echo/services-bootstrap-services": "^1.0.0",
"@effect-rx/rx": "^0.33.8",
"@effect-rx/rx-react": "^0.30.11",
"effect": "^3.2.8"
"effect": "^3.6.5"
},
"devDependencies": {
"@types/react": "^18.2.66",
Expand Down
4 changes: 2 additions & 2 deletions packages/core/types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@effect/schema": "^0.67.18",
"effect": "^3.5.8"
"@effect/schema": "^0.71.1",
"effect": "^3.6.5"
}
}
4 changes: 2 additions & 2 deletions packages/core/types/src/model/app-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ export const AppConfigSchema = Schema.Struct({
/**
* The client ID of the application registered in Azure AD.
*/
clientId: Schema.String.pipe(Schema.nonEmpty()),
clientId: Schema.String.pipe(Schema.nonEmptyString()),

/**
* The redirect URI of the application registered in Azure AD.
*/
redirectUri: Schema.String.pipe(
Schema.nonEmpty(),
Schema.nonEmptyString(),
Schema.filter(
(url) => url.startsWith("http://") || url.startsWith("https://"),
),
Expand Down
2 changes: 1 addition & 1 deletion packages/core/types/src/model/authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const AuthenticationInfoSchema = S.Struct({
/**
* Token that can be used to authenticate the user.
*/
accessToken: S.String.pipe(S.nonEmpty()),
accessToken: S.String.pipe(S.nonEmptyString()),

/**
* Date in which the token expires.
Expand Down
1 change: 1 addition & 0 deletions packages/core/types/src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from "./media-player";
export * from "./media-provider";
export * from "./player";
export * from "./provider-status";
export * from "./workflows";
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Effect } from "effect";
import type {
ProviderMetadata,
FolderMetadata,
AuthenticationError,
} from "../../model";
import type { FileBasedProviderError } from "../media-provider";

/**
* Empty record used to represent the absence of data, either input or output.
*/
export type Empty = Record<string, never>;

/**
* Workflow that orchestrates the process of adding a new provider to the application.
*/
export type IAddProviderWorkflow = {
readonly loadProvider: (metadata: ProviderMetadata) => Effect.Effect<Empty>;
readonly connectToProvider: () => Effect.Effect<
FolderMetadata[],
AuthenticationError | FileBasedProviderError
>;
readonly selectRoot: (rootFolder: FolderMetadata) => Effect.Effect<Empty>;
};

/**
* Tag to identify the operations that can be performed by the AddProviderWorkflow.
*/
export class AddProviderWorkflow extends Effect.Tag(
"@echo/services-add-provider-fsm/AddProviderWorkflow",
)<AddProviderWorkflow, IAddProviderWorkflow>() {}
1 change: 1 addition & 0 deletions packages/core/types/src/services/workflows/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./add-provider.workflow";
2 changes: 1 addition & 1 deletion packages/infrastructure/broadcast-channel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
},
"dependencies": {
"@echo/core-types": "^1.0.0",
"effect": "^3.5.8"
"effect": "^3.6.5"
}
}
2 changes: 1 addition & 1 deletion packages/infrastructure/browser-crypto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
},
"dependencies": {
"@echo/core-types": "^1.0.0",
"effect": "^3.5.8"
"effect": "^3.6.5"
}
}
2 changes: 1 addition & 1 deletion packages/infrastructure/dexie-database/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
"@echo/core-strings": "^1.0.0",
"@echo/core-types": "^1.0.0",
"dexie": "^4.0.7",
"effect": "^3.5.8"
"effect": "^3.6.5"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
},
"dependencies": {
"@echo/core-types": "^1.0.0",
"effect": "^3.2.8"
"effect": "^3.6.5"
}
}
2 changes: 1 addition & 1 deletion packages/infrastructure/mmb-metadata-provider/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"dependencies": {
"@echo/core-types": "^1.0.0",
"buffer": "^6.0.3",
"effect": "^3.5.8",
"effect": "^3.6.5",
"music-metadata": "^10.0.0",
"process": "^0.11.10"
}
Expand Down
Loading

0 comments on commit a6d3d0d

Please sign in to comment.