-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
With a few TODOs here and there, but hey, no more login-in!
- Loading branch information
1 parent
d0382c5
commit 01316f0
Showing
17 changed files
with
342 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { Effect } from "effect"; | ||
|
||
/** | ||
* Service that encapsulates the orchestration for initializing the application | ||
* during a cold boot. | ||
*/ | ||
export type IAppInit = { | ||
/** | ||
* Initializes the application by performing reading the last known state of | ||
* the providers from the storage and re-starting them with the last | ||
* used credentials. If the credentials have expired, then the initialization | ||
* of the provider will fail and the user will have to re-add the provider. | ||
*/ | ||
readonly init: Effect.Effect<void>; | ||
}; | ||
|
||
/** | ||
* Tag to identify the AppInit service. | ||
*/ | ||
export class AppInit extends Effect.Tag("@echo/core-types/AppInit")< | ||
AppInit, | ||
IAppInit | ||
>() {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { Effect, Option } from "effect"; | ||
|
||
/** | ||
* Defines all the possible namespaces that can be used to store data in the | ||
* local storage. | ||
*/ | ||
export type LocalStorageNamespace = "media-provider-start-args"; | ||
|
||
/** | ||
* Service that allows to cache active media providers and observe changes to | ||
* them. | ||
*/ | ||
export type ILocalStorage = { | ||
/** | ||
* Sets the given value in the local storage under the specified namespace and | ||
* key, overwriting any existing value. The value must be serializable, since | ||
* it will always be stored as a string. | ||
*/ | ||
readonly set: <T>( | ||
namespace: LocalStorageNamespace, | ||
key: string, | ||
value: T, | ||
) => Effect.Effect<void>; | ||
|
||
/** | ||
* Attempts to retrieve and deserialize a value from the local storage under | ||
* the specified namespace and key. If the value does not exist, `None` will | ||
* be returned. | ||
*/ | ||
readonly get: <T>( | ||
namespace: LocalStorageNamespace, | ||
key: string, | ||
) => Effect.Effect<Option.Option<T>>; | ||
|
||
/** | ||
* Deletes the value stored in the local storage under the specified namespace | ||
* and key. If the value does not exist, this operation is a no-op. | ||
*/ | ||
readonly remove: ( | ||
namespace: LocalStorageNamespace, | ||
key: string, | ||
) => Effect.Effect<void>; | ||
}; | ||
|
||
/** | ||
* Tag to identify the LocalStorage service. | ||
*/ | ||
export class LocalStorage extends Effect.Tag("@echo/core-types/LocalStorage")< | ||
LocalStorage, | ||
ILocalStorage | ||
>() {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { LocalStorage, type LocalStorageNamespace } from "@echo/core-types"; | ||
import { Effect, Layer, Option } from "effect"; | ||
|
||
const createKey = (namespace: LocalStorageNamespace, key: string) => | ||
`${namespace}:${key}`; | ||
|
||
const make = LocalStorage.of({ | ||
set: (namespace, key, value) => | ||
Effect.sync(() => { | ||
localStorage.setItem(createKey(namespace, key), JSON.stringify(value)); | ||
}), | ||
|
||
get: <T>(namespace: LocalStorageNamespace, key: string) => | ||
Effect.sync(() => { | ||
const item = localStorage.getItem(createKey(namespace, key)); | ||
|
||
// TODO: Use Effect's schema here to ensure that we're actually properly parsing the value. | ||
return Option.fromNullable(item).pipe( | ||
Option.map((value) => JSON.parse(value) as unknown as T), | ||
); | ||
}), | ||
|
||
remove: (namespace, key) => | ||
Effect.sync(() => { | ||
localStorage.removeItem(createKey(namespace, key)); | ||
}), | ||
}); | ||
|
||
/** | ||
* Implementation of the local storage service that uses the browser's local | ||
* storage to store data. | ||
*/ | ||
export const BrowserLocalStorageLive = Layer.succeed(LocalStorage, make); |
15 changes: 15 additions & 0 deletions
15
packages/infrastructure/browser-local-storage/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"name": "@echo/infrastructure-browser-local-storage", | ||
"private": true, | ||
"version": "1.0.0", | ||
"description": "Contains the BrowserLocalStorage related infrastructure", | ||
"main": "index.js", | ||
"scripts": { | ||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", | ||
"typecheck": "tsc --noEmit" | ||
}, | ||
"dependencies": { | ||
"@echo/core-types": "^1.0.0", | ||
"effect": "^3.6.5" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"extends": "../../../tsconfig.json", | ||
"include": [ | ||
"src", | ||
"index.ts" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { AppInitLive } from "./src/app-init"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"name": "@echo/services-app-init", | ||
"private": true, | ||
"version": "1.0.0", | ||
"description": "Contains the implementation for the AppInit service", | ||
"main": "index.js", | ||
"scripts": { | ||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", | ||
"typecheck": "tsc --noEmit" | ||
}, | ||
"dependencies": { | ||
"@echo/core-auth": "^1.0.0", | ||
"@echo/core-types": "^1.0.0", | ||
"@echo/services-bootstrap": "^1.0.0", | ||
"effect": "^3.6.5" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import { isValidToken } from "@echo/core-auth"; | ||
import { | ||
AppInit, | ||
AvailableProviders, | ||
LocalStorage, | ||
MediaPlayerFactory, | ||
MediaProviderFactory, | ||
MediaProviderMainThreadBroadcastChannel, | ||
type ProviderStartArgs, | ||
type BroadcastChannel, | ||
type ILocalStorage, | ||
type MediaProviderBroadcastSchema, | ||
type ProviderId, | ||
ActiveMediaProviderCache, | ||
type IActiveMediaProviderCache, | ||
} from "@echo/core-types"; | ||
import { | ||
LazyLoadedMediaPlayer, | ||
LazyLoadedProvider, | ||
} from "@echo/services-bootstrap"; | ||
import { Effect, Layer, Option } from "effect"; | ||
|
||
const make = Effect.gen(function* () { | ||
const activeMediaProviderCache = yield* ActiveMediaProviderCache; | ||
const broadcastChannel = yield* MediaProviderMainThreadBroadcastChannel; | ||
const lazyLoadedProvider = yield* LazyLoadedProvider; | ||
const lazyLoaderMediaPlayer = yield* LazyLoadedMediaPlayer; | ||
const localStorage = yield* LocalStorage; | ||
|
||
return AppInit.of({ | ||
init: Effect.gen(function* () { | ||
const allProviderStates = yield* retrieveAllProviderArgs(localStorage); | ||
|
||
return yield* Effect.all( | ||
allProviderStates.map((providerStartArgs) => | ||
Effect.gen(function* () { | ||
const retrievedMetadata = { | ||
id: providerStartArgs.value.metadata.id, | ||
type: providerStartArgs.value.metadata.type, | ||
}; | ||
|
||
const providerFactory = | ||
yield* lazyLoadedProvider.load(retrievedMetadata); | ||
const mediaPlayerFactory = | ||
yield* lazyLoaderMediaPlayer.load(retrievedMetadata); | ||
|
||
return yield* reinitializeProvider( | ||
providerStartArgs.value, | ||
providerFactory.createMediaProvider, | ||
mediaPlayerFactory.createMediaPlayer, | ||
broadcastChannel, | ||
activeMediaProviderCache, | ||
); | ||
}), | ||
), | ||
); | ||
}), | ||
}); | ||
}); | ||
|
||
const retrieveAllProviderArgs = (localStorage: ILocalStorage) => | ||
Effect.gen(function* () { | ||
const allProviders = yield* Effect.all( | ||
AvailableProviders.map((provider) => | ||
retrieveProviderArgs(provider.id, localStorage), | ||
), | ||
); | ||
return allProviders.filter(Option.isSome); | ||
}); | ||
|
||
const retrieveProviderArgs = ( | ||
providerId: ProviderId, | ||
localStorage: ILocalStorage, | ||
) => | ||
localStorage.get<ProviderStartArgs>("media-provider-start-args", providerId); | ||
|
||
const reinitializeProvider = ( | ||
startArgs: ProviderStartArgs, | ||
createMediaProvider: MediaProviderFactory["createMediaProvider"], | ||
createMediaPlayer: MediaPlayerFactory["createMediaPlayer"], | ||
broadcastChannel: BroadcastChannel< | ||
MediaProviderBroadcastSchema["mainThread"] | ||
>, | ||
activeMediaProviderCache: IActiveMediaProviderCache, | ||
) => | ||
Effect.gen(function* () { | ||
// TODO: This should attempt to refresh the token if it's expired. | ||
// TODO: Instead of ignoring the initialization with a warning, we should notify the user. | ||
if (!isValidToken(startArgs.authInfo)) { | ||
yield* Effect.logWarning( | ||
`The retrieved token for ${startArgs.metadata.id} is invalid, ignoring initialization.`, | ||
); | ||
} | ||
|
||
const mediaProvider = createMediaProvider(startArgs.authInfo); | ||
const mediaPlayer = yield* createMediaPlayer(startArgs.authInfo); | ||
|
||
yield* broadcastChannel.send("start", startArgs); | ||
yield* activeMediaProviderCache.add( | ||
startArgs.metadata, | ||
mediaProvider, | ||
mediaPlayer, | ||
); | ||
}); | ||
|
||
export const AppInitLive = Layer.effect(AppInit, make); |
Oops, something went wrong.