@aerogel/plugin-solid 0.0.1-next.2fb667e321e0740b4db1a21bc948232a9bf219d6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/InruptAuthenticator.lazy.hash.cjs.js +2 -0
- package/dist/InruptAuthenticator.lazy.hash.cjs.js.map +1 -0
- package/dist/InruptAuthenticator.lazy.hash.esm.js +2 -0
- package/dist/InruptAuthenticator.lazy.hash.esm.js.map +1 -0
- package/dist/aerogel-plugin-solid.cjs.js +2 -0
- package/dist/aerogel-plugin-solid.cjs.js.map +1 -0
- package/dist/aerogel-plugin-solid.d.ts +266 -0
- package/dist/aerogel-plugin-solid.esm.js +2 -0
- package/dist/aerogel-plugin-solid.esm.js.map +1 -0
- package/noeldemartin.config.js +2 -0
- package/package.json +49 -0
- package/src/auth/Authenticator.ts +123 -0
- package/src/auth/authenticators/InruptAuthenticator.lazy.ts +1 -0
- package/src/auth/authenticators/InruptAuthenticator.ts +83 -0
- package/src/auth/index.ts +38 -0
- package/src/components/AGSolidStatus.ts +5 -0
- package/src/components/AGSolidStatus.vue +60 -0
- package/src/components/index.ts +4 -0
- package/src/errors/AuthenticationCancelledError.ts +3 -0
- package/src/errors/ContainerAlreadyInUse.ts +11 -0
- package/src/errors/index.ts +4 -0
- package/src/main.ts +51 -0
- package/src/services/Solid.mock.ts +38 -0
- package/src/services/Solid.state.ts +54 -0
- package/src/services/Solid.ts +431 -0
- package/src/types/shims.d.ts +7 -0
- package/tsconfig.json +11 -0
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aerogel/plugin-solid",
|
|
3
|
+
"description": "Aerogel Solid plugin",
|
|
4
|
+
"version": "0.0.1-next.2fb667e321e0740b4db1a21bc948232a9bf219d6",
|
|
5
|
+
"main": "dist/aerogel-plugin-solid.cjs.js",
|
|
6
|
+
"module": "dist/aerogel-plugin-solid.esm.js",
|
|
7
|
+
"types": "dist/aerogel-plugin-solid.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "rm dist -rf && npm run build:js && npm run build:types",
|
|
11
|
+
"build:js": "noeldemartin-build-javascript",
|
|
12
|
+
"build:types": "noeldemartin-build-types",
|
|
13
|
+
"publish-next": "noeldemartin-publish-next",
|
|
14
|
+
"lint": "noeldemartin-lint src"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18.x"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/NoelDeMartin/aerogeljs.git"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"javascript",
|
|
25
|
+
"aerogel"
|
|
26
|
+
],
|
|
27
|
+
"author": "Noel De Martin",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/NoelDeMartin/aerogeljs/issues"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@aerogel/core": "*",
|
|
34
|
+
"@aerogel/vite": "*",
|
|
35
|
+
"soukai": "*",
|
|
36
|
+
"soukai-solid": "*"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@inrupt/solid-client-authn-browser": "^1.17.3",
|
|
40
|
+
"@noeldemartin/solid-utils": "0.4.0-next.852c9f9e65275fc2a2e67a9750784fb43a0fd64b",
|
|
41
|
+
"@noeldemartin/utils": "0.5.1-next.49cc6c9b4a20930cbf922a949135981791acc5c3",
|
|
42
|
+
"vue": "^3.3.0"
|
|
43
|
+
},
|
|
44
|
+
"eslintConfig": {
|
|
45
|
+
"extends": [
|
|
46
|
+
"@noeldemartin/eslint-config-vue"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { PromisedValue, arr, fail } from '@noeldemartin/utils';
|
|
2
|
+
import { SolidEngine } from 'soukai-solid';
|
|
3
|
+
import type { Closure, FluentArray, GetClosureArgs } from '@noeldemartin/utils';
|
|
4
|
+
import type { Engine } from 'soukai';
|
|
5
|
+
import type { Fetch } from 'soukai-solid';
|
|
6
|
+
import type { SolidUserProfile } from '@noeldemartin/solid-utils';
|
|
7
|
+
import type { ErrorSource } from '@aerogel/core';
|
|
8
|
+
|
|
9
|
+
import type { AuthenticatorName } from '@/auth';
|
|
10
|
+
|
|
11
|
+
type ListenerEvent = keyof AuthenticatorListener;
|
|
12
|
+
type ListenerEventPayload<
|
|
13
|
+
Event extends ListenerEvent,
|
|
14
|
+
Callback = AuthenticatorListener[Event]
|
|
15
|
+
> = Callback extends Closure ? GetClosureArgs<Callback> : never;
|
|
16
|
+
|
|
17
|
+
export interface AuthSession {
|
|
18
|
+
user: SolidUserProfile;
|
|
19
|
+
loginUrl: string;
|
|
20
|
+
authenticator: Authenticator;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AuthenticatorListener {
|
|
24
|
+
onSessionStarted?: (session: AuthSession) => Promise<void> | void;
|
|
25
|
+
onSessionFailed?: (loginUrl: string, error: ErrorSource) => Promise<void> | void;
|
|
26
|
+
onSessionEnded?: () => Promise<void> | void;
|
|
27
|
+
onAuthenticatedFetchReady?: (fetch: Fetch) => Promise<void> | void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default abstract class Authenticator {
|
|
31
|
+
|
|
32
|
+
public name!: AuthenticatorName;
|
|
33
|
+
|
|
34
|
+
private authenticatedFetch?: Fetch;
|
|
35
|
+
private booted?: PromisedValue<void>;
|
|
36
|
+
private listeners: FluentArray<AuthenticatorListener> = arr<AuthenticatorListener>([]);
|
|
37
|
+
private _engine?: SolidEngine;
|
|
38
|
+
|
|
39
|
+
public abstract login(loginUrl: string, user?: SolidUserProfile): Promise<AuthSession>;
|
|
40
|
+
|
|
41
|
+
public abstract logout(): Promise<void>;
|
|
42
|
+
|
|
43
|
+
public get engine(): Engine {
|
|
44
|
+
this._engine = this._engine ?? new SolidEngine(this.requireAuthenticatedFetch());
|
|
45
|
+
|
|
46
|
+
return this._engine;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public getAuthenticatedFetch(): Fetch | null {
|
|
50
|
+
return this.authenticatedFetch ?? null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public requireAuthenticatedFetch(): Fetch {
|
|
54
|
+
return this.authenticatedFetch ?? fail('Authenticated fetch is not ready');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public addListener(listener: AuthenticatorListener): () => void {
|
|
58
|
+
if (!this.listeners.includes(listener)) this.listeners.push(listener);
|
|
59
|
+
|
|
60
|
+
return () => {
|
|
61
|
+
this.removeListener(listener);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public removeListener(listener: AuthenticatorListener): void {
|
|
66
|
+
if (!this.listeners.includes(listener)) return;
|
|
67
|
+
|
|
68
|
+
this.listeners.remove(listener);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public async boot(): Promise<void> {
|
|
72
|
+
if (this.booted) {
|
|
73
|
+
return this.booted;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.booted = new PromisedValue();
|
|
77
|
+
|
|
78
|
+
await this.restoreSession();
|
|
79
|
+
|
|
80
|
+
this.booted.resolve();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
protected abstract restoreSession(): Promise<void>;
|
|
84
|
+
|
|
85
|
+
protected async initAuthenticatedFetch(fetch: Fetch): Promise<void> {
|
|
86
|
+
this.authenticatedFetch = fetch;
|
|
87
|
+
|
|
88
|
+
await this.notifyListeners('onAuthenticatedFetchReady', fetch);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
protected async startSession(sessionData: Omit<AuthSession, 'authenticator'>): Promise<AuthSession> {
|
|
92
|
+
const session: AuthSession = {
|
|
93
|
+
authenticator: this,
|
|
94
|
+
...sessionData,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
await this.notifyListeners('onSessionStarted', session);
|
|
98
|
+
|
|
99
|
+
return session;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
protected async failSession(loginUrl: string, error: ErrorSource): Promise<void> {
|
|
103
|
+
await this.notifyListeners('onSessionFailed', loginUrl, error);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
protected async endSession(): Promise<void> {
|
|
107
|
+
await this.notifyListeners('onSessionEnded');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
protected async notifyListeners<Event extends ListenerEvent>(
|
|
111
|
+
event: Event,
|
|
112
|
+
...payload: ListenerEventPayload<Event>
|
|
113
|
+
): Promise<void> {
|
|
114
|
+
await Promise.all(
|
|
115
|
+
this.listeners.toArray().map(async (listener) => {
|
|
116
|
+
const callback = listener[event] as Closure | undefined;
|
|
117
|
+
|
|
118
|
+
await callback?.(...payload);
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { fetch, handleIncomingRedirect, login, logout } from '@inrupt/solid-client-authn-browser';
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Storage, after, fail } from '@noeldemartin/utils';
|
|
2
|
+
import type { Fetch } from 'soukai-solid';
|
|
3
|
+
import type { ILoginInputOptions, handleIncomingRedirect, login, logout } from '@inrupt/solid-client-authn-browser';
|
|
4
|
+
|
|
5
|
+
import Authenticator from '@/auth/Authenticator';
|
|
6
|
+
import type { AuthSession } from '@/auth/Authenticator';
|
|
7
|
+
|
|
8
|
+
import AerogelSolid from 'virtual:aerogel-solid';
|
|
9
|
+
|
|
10
|
+
const STORAGE_KEY = 'inrupt-authenticator';
|
|
11
|
+
|
|
12
|
+
export default class InruptAuthenticator extends Authenticator {
|
|
13
|
+
|
|
14
|
+
private _fetch!: Fetch;
|
|
15
|
+
private _login!: typeof login;
|
|
16
|
+
private _logout!: typeof logout;
|
|
17
|
+
private _handleIncomingRedirect!: typeof handleIncomingRedirect;
|
|
18
|
+
|
|
19
|
+
public async login(loginUrl: string): Promise<AuthSession> {
|
|
20
|
+
Storage.set<boolean>(STORAGE_KEY, true);
|
|
21
|
+
|
|
22
|
+
const options: ILoginInputOptions = {
|
|
23
|
+
oidcIssuer: loginUrl,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (AerogelSolid.clientID) {
|
|
27
|
+
options.clientId = AerogelSolid.clientID.client_id;
|
|
28
|
+
options.clientName = AerogelSolid.clientID.client_name;
|
|
29
|
+
options.redirectUrl = AerogelSolid.clientID.redirect_uris[0];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await this._login(options);
|
|
33
|
+
|
|
34
|
+
// Browser should redirect, so just make it wait for a while.
|
|
35
|
+
await after({ seconds: 60 });
|
|
36
|
+
|
|
37
|
+
return fail('Browser should have redirected, but it didn\'t');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public async logout(): Promise<void> {
|
|
41
|
+
await this._logout();
|
|
42
|
+
await this.endSession();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
protected async restoreSession(): Promise<void> {
|
|
46
|
+
const { fetch, handleIncomingRedirect, login, logout } = await import('./InruptAuthenticator.lazy');
|
|
47
|
+
|
|
48
|
+
this._fetch = fetch;
|
|
49
|
+
this._login = login;
|
|
50
|
+
this._logout = logout;
|
|
51
|
+
this._handleIncomingRedirect = handleIncomingRedirect;
|
|
52
|
+
|
|
53
|
+
await this.loginFromRedirect();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
protected async loginFromRedirect(): Promise<void> {
|
|
57
|
+
if (!Storage.has(STORAGE_KEY)) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const session = await this._handleIncomingRedirect(window.location.href);
|
|
62
|
+
|
|
63
|
+
Storage.remove(STORAGE_KEY);
|
|
64
|
+
|
|
65
|
+
if (session?.isLoggedIn && session.webId) {
|
|
66
|
+
await this.initSession(session.webId);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
protected async initSession(webId: string): Promise<void> {
|
|
71
|
+
await this.initAuthenticatedFetch(this._fetch);
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const { default: Solid } = await import('@/services/Solid');
|
|
75
|
+
const user = await Solid.requireUserProfile(webId);
|
|
76
|
+
|
|
77
|
+
await this.startSession({ user, loginUrl: webId });
|
|
78
|
+
} catch (error) {
|
|
79
|
+
await this.failSession(webId, error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import InruptAuthenticator from './authenticators/InruptAuthenticator';
|
|
2
|
+
import type Authenticator from './Authenticator';
|
|
3
|
+
|
|
4
|
+
const _authenticators = {} as Authenticators;
|
|
5
|
+
|
|
6
|
+
type BaseAuthenticators = typeof authenticators;
|
|
7
|
+
|
|
8
|
+
export * from './Authenticator';
|
|
9
|
+
export { Authenticator };
|
|
10
|
+
|
|
11
|
+
export const authenticators = {
|
|
12
|
+
inrupt: new InruptAuthenticator(),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function setDefaultAuthenticator(authenticator: Authenticator): void {
|
|
16
|
+
_authenticators.default = authenticator;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getAuthenticator<T extends AuthenticatorName>(name: T): Authenticators[T] {
|
|
20
|
+
return _authenticators[name];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function registerAuthenticators(customAuthenticators: Record<string, Authenticator>): void {
|
|
24
|
+
Object.entries(customAuthenticators).forEach(([name, authenticator]) =>
|
|
25
|
+
registerAuthenticator(name as AuthenticatorName, authenticator));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function registerAuthenticator<T extends AuthenticatorName>(name: T, authenticator: Authenticators[T]): void {
|
|
29
|
+
_authenticators[name] = authenticator;
|
|
30
|
+
|
|
31
|
+
authenticator.name = authenticator.name ?? name;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type AuthenticatorName = keyof Authenticators;
|
|
35
|
+
|
|
36
|
+
export interface Authenticators extends BaseAuthenticators {
|
|
37
|
+
default: Authenticator;
|
|
38
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<slot v-if="$solid.loginStale" name="login-stale">
|
|
4
|
+
<AGMarkdown :text="$td('solid.loginStale', 'This is taking too long...')" />
|
|
5
|
+
</slot>
|
|
6
|
+
|
|
7
|
+
<slot v-if="$solid.loginOngoing" name="login-ongoing">
|
|
8
|
+
<AGMarkdown :text="$td('ui.loading', 'Loading...')" />
|
|
9
|
+
</slot>
|
|
10
|
+
|
|
11
|
+
<slot v-else-if="$solid.isLoggedIn()" name="logged-in" :session="$solid.session">
|
|
12
|
+
<div class="flex flex-col gap-3">
|
|
13
|
+
<AGMarkdown
|
|
14
|
+
:text="
|
|
15
|
+
$td(
|
|
16
|
+
'solid.loggedIn',
|
|
17
|
+
{
|
|
18
|
+
userName: $solid.user.name ?? $solid.user.webId,
|
|
19
|
+
userWebId: $solid.user.webId,
|
|
20
|
+
},
|
|
21
|
+
'You are logged in as [{userName}]({userWebId})'
|
|
22
|
+
)
|
|
23
|
+
"
|
|
24
|
+
/>
|
|
25
|
+
<AGButton @click="$solid.logout()">
|
|
26
|
+
{{ $td('solid.logout', 'Logout') }}
|
|
27
|
+
</AGButton>
|
|
28
|
+
</div>
|
|
29
|
+
</slot>
|
|
30
|
+
|
|
31
|
+
<slot v-else name="logged-out">
|
|
32
|
+
<div class="flex flex-col">
|
|
33
|
+
<AGForm :form="form" class="flex" @submit="$solid.login(form.url)">
|
|
34
|
+
<AGInput
|
|
35
|
+
name="url"
|
|
36
|
+
:aria-label="$td('solid.url', 'Login url')"
|
|
37
|
+
:placeholder="$td('solid.placeholder', 'https://...')"
|
|
38
|
+
/>
|
|
39
|
+
<AGButton submit>
|
|
40
|
+
{{ $td('solid.login', 'Login') }}
|
|
41
|
+
</AGButton>
|
|
42
|
+
</AGForm>
|
|
43
|
+
<AGButton v-if="$solid.wasLoggedIn()" class="mt-3" @click="$solid.reconnect({ force: true })">
|
|
44
|
+
{{ $td('solid.reconnect', 'Reconnect') }}
|
|
45
|
+
</AGButton>
|
|
46
|
+
<AGMarkdown
|
|
47
|
+
v-if="$solid.previousSession?.error"
|
|
48
|
+
class="mt-1 self-center text-sm text-red-800"
|
|
49
|
+
:text="$td('solid.previousLoginError', 'Previous login attempt failed')"
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
</slot>
|
|
53
|
+
</div>
|
|
54
|
+
</template>
|
|
55
|
+
|
|
56
|
+
<script setup lang="ts">
|
|
57
|
+
import { AGButton, AGForm, AGInput, AGMarkdown, requiredStringInput, useForm } from '@aerogel/core';
|
|
58
|
+
|
|
59
|
+
const form = useForm({ url: requiredStringInput() });
|
|
60
|
+
</script>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { JSError } from '@noeldemartin/utils';
|
|
2
|
+
import type { JSErrorOptions } from '@noeldemartin/utils';
|
|
3
|
+
import type { SolidContainer } from 'soukai-solid';
|
|
4
|
+
|
|
5
|
+
export default class ContainerAlreadyInUse extends JSError {
|
|
6
|
+
|
|
7
|
+
constructor(container: SolidContainer, options?: JSErrorOptions) {
|
|
8
|
+
super(`Container at ${container.url} is already in use`, options);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
}
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { bootServices } from '@aerogel/core';
|
|
2
|
+
import { bootSolidModels } from 'soukai-solid';
|
|
3
|
+
import type { Plugin } from '@aerogel/core';
|
|
4
|
+
|
|
5
|
+
import Solid from '@/services/Solid';
|
|
6
|
+
import {
|
|
7
|
+
authenticators as baseAuthenticators,
|
|
8
|
+
getAuthenticator,
|
|
9
|
+
registerAuthenticators,
|
|
10
|
+
setDefaultAuthenticator,
|
|
11
|
+
} from '@/auth';
|
|
12
|
+
import type Authenticator from '@/auth/Authenticator';
|
|
13
|
+
import type { AuthenticatorName } from '@/auth';
|
|
14
|
+
|
|
15
|
+
const services = { $solid: Solid };
|
|
16
|
+
|
|
17
|
+
export * from './auth';
|
|
18
|
+
export * from './components';
|
|
19
|
+
export * from './errors';
|
|
20
|
+
export * from './services/Solid';
|
|
21
|
+
export * from './services/Solid.mock';
|
|
22
|
+
export { Solid };
|
|
23
|
+
export { default as SolidMock } from './services/Solid.mock';
|
|
24
|
+
|
|
25
|
+
export interface Options {
|
|
26
|
+
autoReconnect?: boolean;
|
|
27
|
+
authenticators?: Record<string, Authenticator>;
|
|
28
|
+
defaultAuthenticator?: AuthenticatorName;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type SolidServices = typeof services;
|
|
32
|
+
|
|
33
|
+
export default function solid(options: Options = {}): Plugin {
|
|
34
|
+
return {
|
|
35
|
+
async install(app) {
|
|
36
|
+
bootSolidModels();
|
|
37
|
+
registerAuthenticators({ ...baseAuthenticators, ...(options.authenticators ?? {}) });
|
|
38
|
+
setDefaultAuthenticator(getAuthenticator(options.defaultAuthenticator ?? 'inrupt'));
|
|
39
|
+
|
|
40
|
+
if (!Solid.hasPersistedState()) {
|
|
41
|
+
Solid.autoReconnect = options.autoReconnect ?? Solid.autoReconnect;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await bootServices(app, services);
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
declare module '@vue/runtime-core' {
|
|
50
|
+
interface ComponentCustomProperties extends SolidServices {}
|
|
51
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { fakeContainerUrl, fakeDocumentUrl } from '@noeldemartin/solid-utils';
|
|
2
|
+
import { FakeServer, facade, mock } from '@noeldemartin/utils';
|
|
3
|
+
import { SolidACLAuthorization, SolidDocument, SolidEngine, SolidTypeIndex } from 'soukai-solid';
|
|
4
|
+
|
|
5
|
+
import type { AuthSession } from '@/auth/Authenticator';
|
|
6
|
+
|
|
7
|
+
import { SolidService } from './Solid';
|
|
8
|
+
|
|
9
|
+
export class SolidMockService extends SolidService {
|
|
10
|
+
|
|
11
|
+
public readonly server: FakeServer = new FakeServer();
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
super();
|
|
15
|
+
|
|
16
|
+
const engine = new SolidEngine(this.server.fetch);
|
|
17
|
+
|
|
18
|
+
this.setState('session', {
|
|
19
|
+
user: mock({
|
|
20
|
+
privateTypeIndexUrl: fakeDocumentUrl(),
|
|
21
|
+
storageUrls: [fakeContainerUrl()],
|
|
22
|
+
}),
|
|
23
|
+
authenticator: mock({
|
|
24
|
+
engine,
|
|
25
|
+
requireAuthenticatedFetch: () => this.server.fetch,
|
|
26
|
+
}),
|
|
27
|
+
} as unknown as AuthSession);
|
|
28
|
+
|
|
29
|
+
SolidACLAuthorization.setEngine(engine);
|
|
30
|
+
SolidTypeIndex.setEngine(engine);
|
|
31
|
+
SolidDocument.setEngine(engine);
|
|
32
|
+
|
|
33
|
+
this.booted.resolve();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default facade(SolidMockService);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { defineServiceState } from '@aerogel/core';
|
|
2
|
+
import type { ErrorSource } from '@aerogel/core';
|
|
3
|
+
import type { Fetch, SolidUserProfile } from '@noeldemartin/solid-utils';
|
|
4
|
+
|
|
5
|
+
import type { AuthSession } from '@/auth/Authenticator';
|
|
6
|
+
import type { AuthenticatorName } from '@/auth';
|
|
7
|
+
|
|
8
|
+
export default defineServiceState({
|
|
9
|
+
name: 'solid',
|
|
10
|
+
persist: ['autoReconnect', 'dismissed', 'previousSession', 'profiles', 'staleProfiles'],
|
|
11
|
+
initialState: {
|
|
12
|
+
autoReconnect: false,
|
|
13
|
+
dismissed: false,
|
|
14
|
+
ignorePreviousSessionError: false,
|
|
15
|
+
loginStartupError: null as ErrorSource | null,
|
|
16
|
+
loginOngoing: false,
|
|
17
|
+
preferredAuthenticator: null as AuthenticatorName | null,
|
|
18
|
+
previousSession: null as {
|
|
19
|
+
profile?: SolidUserProfile;
|
|
20
|
+
authenticator: AuthenticatorName;
|
|
21
|
+
loginUrl: string;
|
|
22
|
+
error: ErrorSource | null;
|
|
23
|
+
} | null,
|
|
24
|
+
profiles: {} as Record<string, SolidUserProfile>,
|
|
25
|
+
session: null as AuthSession | null,
|
|
26
|
+
loginStale: false,
|
|
27
|
+
staleProfiles: [] as string[],
|
|
28
|
+
},
|
|
29
|
+
serialize(state) {
|
|
30
|
+
if (state.previousSession?.error instanceof Error) {
|
|
31
|
+
state.previousSession.error = state.previousSession.error.message;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return state;
|
|
35
|
+
},
|
|
36
|
+
computed: {
|
|
37
|
+
user: (state) => state.session?.user ?? state.previousSession?.profile ?? null,
|
|
38
|
+
authenticator: (state) => state.session?.authenticator ?? null,
|
|
39
|
+
fetch(): Fetch {
|
|
40
|
+
return this.authenticator?.getAuthenticatedFetch() ?? globalThis.fetch.bind(globalThis);
|
|
41
|
+
},
|
|
42
|
+
error(state): ErrorSource | null {
|
|
43
|
+
if (state.loginStartupError) {
|
|
44
|
+
return state.loginStartupError ?? null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (state.ignorePreviousSessionError) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return state.previousSession?.error ?? null;
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
});
|