@aerogel/plugin-solid 0.0.1-next.129b164cda8e791661ba16ad7bf51aaeae6be2eb

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.
@@ -0,0 +1,3 @@
1
+ export { default as AuthenticationCancelledError } from '@/errors/AuthenticationCancelledError';
2
+ export { default as AuthenticationFailedError } from '@/errors/AuthenticationFailedError';
3
+ export { default as ContainerAlreadyInUse } from '@/errors/ContainerAlreadyInUse';
@@ -0,0 +1,11 @@
1
+ import { defineFormValidationRule } from '@aerogel/core';
2
+
3
+ export function defineFormValidationRules(): void {
4
+ defineFormValidationRule<string>('container_url', (value) => {
5
+ if (value.endsWith('/')) {
6
+ return;
7
+ }
8
+
9
+ return 'containerEndingSlashMissing';
10
+ });
11
+ }
package/src/main.ts ADDED
@@ -0,0 +1,70 @@
1
+ import { Events, bootServices, registerErrorHandler } from '@aerogel/core';
2
+ import { bootSolidModels } from 'soukai-solid';
3
+ import type { Plugin } from '@aerogel/core';
4
+ import type { SolidStore, SolidUserProfile } from '@noeldemartin/solid-utils';
5
+
6
+ import Solid from '@/services/Solid';
7
+ import { DEFAULT_STATE } from '@/services/Solid.state';
8
+ import { defineFormValidationRules } from '@/forms/validation';
9
+ import {
10
+ authenticators as baseAuthenticators,
11
+ getAuthenticator,
12
+ registerAuthenticators,
13
+ setDefaultAuthenticator,
14
+ } from '@/auth';
15
+ import { AuthenticationFailedError } from '@/errors';
16
+ import type Authenticator from '@/auth/Authenticator';
17
+ import type { AuthenticatorName } from '@/auth';
18
+
19
+ const services = { $solid: Solid };
20
+
21
+ export * from './auth';
22
+ export * from './components';
23
+ export * from './errors';
24
+ export * from './services/Solid';
25
+ export { Solid };
26
+
27
+ export interface Options {
28
+ autoReconnect?: boolean;
29
+ authenticators?: Record<string, Authenticator>;
30
+ defaultAuthenticator?: AuthenticatorName;
31
+ onUserProfileLoaded?(user: SolidUserProfile, store: SolidStore): Promise<unknown> | unknown;
32
+ }
33
+
34
+ export type SolidServices = typeof services;
35
+
36
+ export default function solid(options: Options = {}): Plugin {
37
+ return {
38
+ async install(app) {
39
+ bootSolidModels();
40
+ registerAuthenticators({ ...baseAuthenticators, ...(options.authenticators ?? {}) });
41
+ defineFormValidationRules();
42
+ setDefaultAuthenticator(getAuthenticator(options.defaultAuthenticator ?? 'inrupt'));
43
+ registerErrorHandler((error) => {
44
+ if (!(error instanceof AuthenticationFailedError)) {
45
+ return;
46
+ }
47
+
48
+ return error.description ? `${error.message} (${error.description})` : error.message;
49
+ });
50
+
51
+ if (typeof options.autoReconnect === 'boolean') {
52
+ DEFAULT_STATE.autoReconnect = options.autoReconnect;
53
+
54
+ Solid.hasPersistedState() || (Solid.autoReconnect = options.autoReconnect);
55
+ }
56
+
57
+ if (options.onUserProfileLoaded) {
58
+ Events.on('solid:user-profile-loaded', ([user, store]) => {
59
+ options.onUserProfileLoaded?.(user, store);
60
+ });
61
+ }
62
+
63
+ await bootServices(app, services);
64
+ },
65
+ };
66
+ }
67
+
68
+ declare module '@aerogel/core' {
69
+ interface Services extends SolidServices {}
70
+ }
@@ -0,0 +1,56 @@
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 const DEFAULT_STATE = { autoReconnect: true };
9
+
10
+ export default defineServiceState({
11
+ name: 'solid',
12
+ persist: ['autoReconnect', 'dismissed', 'previousSession', 'profiles', 'staleProfiles'],
13
+ initialState: {
14
+ autoReconnect: DEFAULT_STATE.autoReconnect,
15
+ dismissed: false,
16
+ ignorePreviousSessionError: false,
17
+ loginStartupError: null as ErrorSource | null,
18
+ loginOngoing: false,
19
+ preferredAuthenticator: null as AuthenticatorName | null,
20
+ previousSession: null as {
21
+ profile?: SolidUserProfile;
22
+ authenticator: AuthenticatorName;
23
+ loginUrl: string;
24
+ error: ErrorSource | null;
25
+ } | null,
26
+ profiles: {} as Record<string, SolidUserProfile>,
27
+ session: null as AuthSession | null,
28
+ loginStale: false,
29
+ staleProfiles: [] as string[],
30
+ },
31
+ serialize(state) {
32
+ if (state.previousSession?.error instanceof Error) {
33
+ state.previousSession.error = state.previousSession.error.message;
34
+ }
35
+
36
+ return state;
37
+ },
38
+ computed: {
39
+ user: (state) => state.session?.user ?? state.previousSession?.profile ?? null,
40
+ authenticator: (state) => state.session?.authenticator ?? null,
41
+ fetch(): Fetch {
42
+ return this.authenticator?.getAuthenticatedFetch() ?? globalThis.fetch.bind(globalThis);
43
+ },
44
+ error(state): ErrorSource | null {
45
+ if (state.loginStartupError) {
46
+ return state.loginStartupError ?? null;
47
+ }
48
+
49
+ if (state.ignorePreviousSessionError) {
50
+ return null;
51
+ }
52
+
53
+ return state.previousSession?.error ?? null;
54
+ },
55
+ },
56
+ });
@@ -0,0 +1,529 @@
1
+ import {
2
+ after,
3
+ arrayFilter,
4
+ arrayUnique,
5
+ arrayWithout,
6
+ asyncMemo,
7
+ facade,
8
+ fail,
9
+ getLocationQueryParameter,
10
+ hasLocationQueryParameter,
11
+ objectWithout,
12
+ objectWithoutEmpty,
13
+ parseBoolean,
14
+ requireUrlParse,
15
+ setAsyncMemo,
16
+ tap,
17
+ urlRoot,
18
+ } from '@noeldemartin/utils';
19
+ import { App, Colors, Errors, Events, UI, translateWithDefault } from '@aerogel/core';
20
+ import { fetchLoginUserProfile } from '@noeldemartin/solid-utils';
21
+ import { setEngine } from 'soukai';
22
+ import { SolidACLAuthorization, SolidContainer, SolidDocument, SolidTypeIndex } from 'soukai-solid';
23
+ import type { ErrorSource } from '@aerogel/core';
24
+ import type { Fetch, SolidModelConstructor } from 'soukai-solid';
25
+ import type { SolidStore, SolidUserProfile } from '@noeldemartin/solid-utils';
26
+
27
+ import AuthenticationCancelledError from '@/errors/AuthenticationCancelledError';
28
+ import { ContainerAlreadyInUse } from '@/errors';
29
+ import { getAuthenticator } from '@/auth';
30
+ import type Authenticator from '@/auth/Authenticator';
31
+ import type { AuthenticatorName } from '@/auth';
32
+ import type { AuthSession } from '@/auth/Authenticator';
33
+
34
+ import Service, { DEFAULT_STATE } from './Solid.state';
35
+
36
+ export type LoginOptions = {
37
+ authenticator?: AuthenticatorName;
38
+ onError?(error: ErrorSource): unknown;
39
+ fallbackUrl?: string;
40
+ loading?: boolean;
41
+ };
42
+
43
+ export type ReconnectOptions = Omit<LoginOptions, 'authenticator'> & {
44
+ force?: boolean;
45
+ };
46
+
47
+ export class SolidService extends Service {
48
+
49
+ public isLoggedIn(): this is { session: AuthSession; user: SolidUserProfile; authenticator: Authenticator } {
50
+ return !!this.session;
51
+ }
52
+
53
+ public wasLoggedIn(): this is { user: SolidUserProfile } {
54
+ return !!this.previousSession;
55
+ }
56
+
57
+ public hasLoggedIn(): this is { user: SolidUserProfile } {
58
+ return !!(this.session || this.previousSession);
59
+ }
60
+
61
+ public requireAuthenticator(): Authenticator {
62
+ return this.authenticator ?? fail('Could not get authenticator');
63
+ }
64
+
65
+ public requireUser(): SolidUserProfile {
66
+ return this.user ?? fail('Could not get user profile (are you logged in?)');
67
+ }
68
+
69
+ public requireUserProfile(url: string): Promise<SolidUserProfile> {
70
+ return this.getUserProfile(url, true);
71
+ }
72
+
73
+ public async getUserProfile(url: string, required: true): Promise<SolidUserProfile>;
74
+ public async getUserProfile(url: string, required?: false): Promise<SolidUserProfile | null>;
75
+ public async getUserProfile(url: string, required: boolean = false): Promise<SolidUserProfile | null> {
76
+ let profileStore: SolidStore | null = null;
77
+ const usingAuthenticatedFetch = !!this.authenticator?.getAuthenticatedFetch();
78
+
79
+ return (
80
+ this.profiles[url] ??
81
+ tap(
82
+ await fetchLoginUserProfile(url, {
83
+ fetch: this.fetch,
84
+ required,
85
+ onLoaded(store) {
86
+ profileStore = store;
87
+ },
88
+ }),
89
+ async (profile) => {
90
+ if (!profile) {
91
+ return;
92
+ }
93
+
94
+ if (profileStore) {
95
+ await Events.emit('solid:user-profile-loaded', [profile, profileStore]);
96
+ }
97
+
98
+ this.rememberProfile(profile);
99
+
100
+ if (!usingAuthenticatedFetch) {
101
+ this.setState({ staleProfiles: arrayUnique(this.staleProfiles.concat([profile.webId])) });
102
+ }
103
+ },
104
+ )
105
+ );
106
+ }
107
+
108
+ public async refreshUserProfile(): Promise<void> {
109
+ if (!this.isLoggedIn()) {
110
+ return;
111
+ }
112
+
113
+ this.setState({ profiles: objectWithout(this.profiles, [this.user.webId]) });
114
+
115
+ const user = await this.requireUserProfile(this.user.webId);
116
+
117
+ this.setState({
118
+ session: {
119
+ ...this.session,
120
+ user,
121
+ },
122
+ });
123
+ }
124
+
125
+ public async login(loginUrl: string, options: LoginOptions = {}): Promise<boolean> {
126
+ if (App.development && loginUrl === 'dev') {
127
+ loginUrl = 'http://localhost:3000';
128
+ }
129
+
130
+ if (!/^https?:\/\//.test(loginUrl)) {
131
+ options.fallbackUrl ??= `http://${loginUrl}`;
132
+ loginUrl = `https://${loginUrl}`;
133
+ }
134
+
135
+ if (this.isLoggedIn()) {
136
+ return true;
137
+ }
138
+
139
+ if (this.loginOngoing) {
140
+ throw new Error('Authentication already in progress');
141
+ }
142
+
143
+ if (!options.loading) {
144
+ return this.attemptLogin(loginUrl, options);
145
+ }
146
+
147
+ return UI.loading(translateWithDefault('solid.loggingIn', 'Logging in...'), () => {
148
+ return this.attemptLogin(loginUrl, options);
149
+ });
150
+ }
151
+
152
+ public async reconnect(options: ReconnectOptions = {}): Promise<void> {
153
+ const { force, ...loginOptions } = options;
154
+ if (this.isLoggedIn() || !this.previousSession || (this.previousSession.error !== null && !force)) {
155
+ return;
156
+ }
157
+
158
+ await this.login(this.previousSession.loginUrl, {
159
+ authenticator: this.previousSession.authenticator,
160
+ ...loginOptions,
161
+ loading: loginOptions.loading ?? false,
162
+ });
163
+ }
164
+
165
+ public async logout(force: boolean = false): Promise<void> {
166
+ const isCloudReady = !App.plugin('@aerogel/offline-first') || !!App.service('$cloud')?.ready;
167
+ const hasLocalChanges = !App.plugin('@aerogel/offline-first') || !!App.service('$cloud')?.dirty;
168
+ const [confirmLogout, options] = force
169
+ ? [true, { wipeLocalData: isCloudReady }]
170
+ : await UI.confirm(
171
+ isCloudReady
172
+ ? translateWithDefault('solid.logoutConfirmTitle', 'Log out from this device?')
173
+ : translateWithDefault('solid.disconnectConfirmTitle', 'Disconnect account?'),
174
+ isCloudReady
175
+ ? hasLocalChanges
176
+ ? translateWithDefault(
177
+ 'solid.logoutConfirmMessageWithLocalChanges',
178
+ 'There are some changes that haven\'t been synchronized and will be lost, ' +
179
+ 'but the rest of the data will remain in your Solid POD.',
180
+ )
181
+ : translateWithDefault(
182
+ 'solid.logoutConfirmMessage',
183
+ 'Logging out will remove all the data from this device, ' +
184
+ 'but you\'ll still have it in your Solid POD.',
185
+ )
186
+ : translateWithDefault(
187
+ 'solid.disconnectConfirmMessage',
188
+ 'You\'ll need to introduce logging details again to connect to your Solid POD.',
189
+ ),
190
+ {
191
+ acceptText: translateWithDefault('solid.logoutConfirmAccept', 'Log out'),
192
+ acceptColor: Colors.Danger,
193
+ cancelText: translateWithDefault(
194
+ 'solid.logoutConfirmCancel',
195
+ translateWithDefault('ui.cancel', 'Cancel'),
196
+ ),
197
+ checkboxes: isCloudReady
198
+ ? hasLocalChanges
199
+ ? {
200
+ wipeLocalData: {
201
+ label: translateWithDefault(
202
+ 'solid.logoutConfirmWipeNotice',
203
+ 'I understand that changes that haven\'t been synchronized will be lost.',
204
+ ),
205
+ default: false,
206
+ required: true,
207
+ },
208
+ }
209
+ : undefined
210
+ : {
211
+ wipeLocalData: {
212
+ label: translateWithDefault(
213
+ 'solid.logoutConfirmWipe',
214
+ 'Also remove all existing data (tasks, lists, etc.)',
215
+ ),
216
+ default: false,
217
+ },
218
+ },
219
+ },
220
+ );
221
+
222
+ if (!confirmLogout) {
223
+ return;
224
+ }
225
+
226
+ if (!this.previousSession) {
227
+ return;
228
+ }
229
+
230
+ await UI.loading(translateWithDefault('solid.logoutOngoing', 'Logging out...'), async () => {
231
+ this.setState({
232
+ dismissed: false,
233
+ ignorePreviousSessionError: false,
234
+ loginStartupError: null,
235
+ previousSession: null,
236
+ ...DEFAULT_STATE,
237
+ });
238
+
239
+ this.isLoggedIn() && (await this.authenticator.logout());
240
+
241
+ await Events.emit('auth:logout', { wipeLocalData: options.wipeLocalData ?? true });
242
+ await Events.emit('auth:after-logout');
243
+ });
244
+ }
245
+
246
+ public async whenLoggedIn<T>(callback: (session: AuthSession) => T): Promise<T> {
247
+ if (this.isLoggedIn()) {
248
+ return callback(this.session);
249
+ }
250
+
251
+ return new Promise((resolve) => {
252
+ const onLogin = (session: AuthSession) => resolve(callback(session));
253
+
254
+ Events.once('auth:login', onLogin);
255
+ });
256
+ }
257
+
258
+ public dismiss(): void {
259
+ this.setState({ dismissed: true });
260
+ }
261
+
262
+ public async findOrCreatePrivateTypeIndex(): Promise<SolidTypeIndex> {
263
+ return (await this.findPrivateTypeIndex()) ?? (await this.createPrivateTypeIndex());
264
+ }
265
+
266
+ public async findPrivateTypeIndex(): Promise<SolidTypeIndex | null> {
267
+ const { webId, privateTypeIndexUrl } = this.requireUser();
268
+ const engine = this.requireAuthenticator().engine;
269
+
270
+ if (!privateTypeIndexUrl) {
271
+ return null;
272
+ }
273
+
274
+ return asyncMemo(`${webId}-privateTypeIndex`, () =>
275
+ SolidTypeIndex.withEngine(engine).find(privateTypeIndexUrl));
276
+ }
277
+
278
+ public async findOrCreatePublicTypeIndex(): Promise<SolidTypeIndex> {
279
+ return (await this.findPublicTypeIndex()) ?? (await this.createPublicTypeIndex());
280
+ }
281
+
282
+ public async findPublicTypeIndex(): Promise<SolidTypeIndex | null> {
283
+ const { webId, publicTypeIndexUrl } = this.requireUser();
284
+ const engine = this.requireAuthenticator().engine;
285
+
286
+ if (!publicTypeIndexUrl) {
287
+ return null;
288
+ }
289
+
290
+ return asyncMemo(`${webId}-publicTypeIndex`, () => SolidTypeIndex.withEngine(engine).find(publicTypeIndexUrl));
291
+ }
292
+
293
+ public async createPrivateContainer(options: {
294
+ url: string;
295
+ name?: string;
296
+ registerFor?: SolidModelConstructor;
297
+ reuseExisting?: boolean;
298
+ }): Promise<SolidContainer> {
299
+ const engine = this.requireAuthenticator().engine;
300
+
301
+ return SolidContainer.withEngine(engine, async () => {
302
+ const existingContainer = await SolidContainer.find(options.url);
303
+
304
+ if (existingContainer && !options.reuseExisting) {
305
+ throw new ContainerAlreadyInUse(existingContainer);
306
+ }
307
+
308
+ const container = existingContainer ?? new SolidContainer({ url: options.url, name: options.name });
309
+
310
+ await container.save();
311
+
312
+ if (options.registerFor) {
313
+ const typeIndex = await this.findOrCreatePrivateTypeIndex();
314
+
315
+ await container.register(typeIndex, options.registerFor);
316
+ }
317
+
318
+ return container;
319
+ });
320
+ }
321
+
322
+ protected async boot(): Promise<void> {
323
+ await Errors.booted;
324
+
325
+ if (hasLocationQueryParameter('authenticator')) {
326
+ this.setState({
327
+ preferredAuthenticator: getLocationQueryParameter('authenticator') as AuthenticatorName,
328
+ });
329
+ }
330
+
331
+ if (hasLocationQueryParameter('refreshProfile')) {
332
+ this.setState({
333
+ staleProfiles: arrayUnique(
334
+ this.staleProfiles.concat(arrayFilter([this.session?.user.webId, this.previousSession?.loginUrl])),
335
+ ),
336
+ });
337
+ }
338
+
339
+ await this.restorePreviousSession();
340
+ await this.startupReconnect();
341
+ }
342
+
343
+ protected async createPrivateTypeIndex(): Promise<SolidTypeIndex> {
344
+ const user = this.requireUser();
345
+ const authenticator = this.requireAuthenticator();
346
+ const typeIndex = await SolidTypeIndex.withEngine(authenticator.engine).createPrivate(user);
347
+
348
+ return tap(typeIndex, () => {
349
+ user.privateTypeIndexUrl = typeIndex.url;
350
+
351
+ setAsyncMemo(`${user.webId}-privateTypeIndex`, typeIndex);
352
+
353
+ this.rememberProfile(user);
354
+ });
355
+ }
356
+
357
+ public async createPublicTypeIndex(): Promise<SolidTypeIndex> {
358
+ const user = this.requireUser();
359
+ const authenticator = this.requireAuthenticator();
360
+ const typeIndex = await SolidTypeIndex.withEngine(authenticator.engine).createPublic(user);
361
+
362
+ return tap(typeIndex, () => {
363
+ user.privateTypeIndexUrl = typeIndex.url;
364
+
365
+ setAsyncMemo(`${user.webId}-publicTypeIndex`, typeIndex);
366
+
367
+ this.rememberProfile(user);
368
+ });
369
+ }
370
+
371
+ protected async attemptLogin(loginUrl: string, options: LoginOptions): Promise<boolean> {
372
+ const authenticatorName = options.authenticator ?? this.preferredAuthenticator ?? 'default';
373
+ const handleLoginError =
374
+ options.onError ??
375
+ ((error: ErrorSource) =>
376
+ App.isMounted() ? Errors.report(error) : this.setState({ loginStartupError: error }));
377
+ const staleTimeout = setTimeout(() => (this.loginStale = true), 10000);
378
+ const initialState = {
379
+ dismissed: this.dismissed,
380
+ ignorePreviousSessionError: this.ignorePreviousSessionError,
381
+ previousSession: this.previousSession,
382
+ };
383
+ this.loginOngoing = true;
384
+
385
+ try {
386
+ const profile = await this.getUserProfile(loginUrl);
387
+ const oidcIssuerUrl = profile?.oidcIssuerUrl ?? urlRoot(profile?.webId ?? loginUrl);
388
+ const authenticator = await this.bootAuthenticator(authenticatorName);
389
+ const { domain: loginDomain } = requireUrlParse(loginUrl);
390
+
391
+ this.setState({
392
+ dismissed: false,
393
+ ignorePreviousSessionError: true,
394
+ previousSession: objectWithoutEmpty({
395
+ profile,
396
+ loginUrl,
397
+ authenticator: authenticatorName,
398
+ error: translateWithDefault(
399
+ 'auth.stuckConnecting',
400
+ `We didn't hear back from the identity provider at \`${loginDomain}\`, maybe try reconnecting?`,
401
+ ),
402
+ }),
403
+ });
404
+
405
+ await Events.emit('auth:before-login');
406
+
407
+ // This should redirect away from the app, so in most cases
408
+ // the rest of the code won't be reached.
409
+ await authenticator.login(oidcIssuerUrl, profile);
410
+
411
+ return true;
412
+ } catch (error) {
413
+ this.setState(initialState);
414
+
415
+ if (error instanceof AuthenticationCancelledError) {
416
+ return false;
417
+ }
418
+
419
+ if (options.fallbackUrl) {
420
+ return after().then(() => this.login(options.fallbackUrl ?? '', objectWithout(options, 'fallbackUrl')));
421
+ }
422
+
423
+ handleLoginError(error);
424
+
425
+ return false;
426
+ } finally {
427
+ clearTimeout(staleTimeout);
428
+
429
+ this.setState({
430
+ loginOngoing: false,
431
+ loginStale: false,
432
+ });
433
+ }
434
+ }
435
+
436
+ private async restorePreviousSession(): Promise<void> {
437
+ if (!this.previousSession) {
438
+ return;
439
+ }
440
+
441
+ await this.bootAuthenticator(this.previousSession.authenticator);
442
+ }
443
+
444
+ private async startupReconnect(): Promise<void> {
445
+ if (!this.reconnectOnStartup()) {
446
+ return;
447
+ }
448
+
449
+ await this.reconnect();
450
+ }
451
+
452
+ private reconnectOnStartup(): boolean {
453
+ if (hasLocationQueryParameter('autoReconnect')) {
454
+ return parseBoolean(getLocationQueryParameter('autoReconnect'));
455
+ }
456
+
457
+ return this.autoReconnect;
458
+ }
459
+
460
+ private async bootAuthenticator(name: AuthenticatorName): Promise<Authenticator> {
461
+ const authenticator = getAuthenticator(name);
462
+
463
+ authenticator.addListener({
464
+ onSessionStarted: async (session) => {
465
+ this.setState({
466
+ session,
467
+ previousSession: {
468
+ authenticator: authenticator.name,
469
+ loginUrl: session.loginUrl,
470
+ profile: session.user,
471
+ error: null,
472
+ },
473
+ });
474
+
475
+ if (this.staleProfiles.includes(session.user.webId)) {
476
+ await this.refreshUserProfile();
477
+ }
478
+
479
+ App.plugin('@aerogel/offline-first') || setEngine(session.authenticator.engine);
480
+
481
+ SolidACLAuthorization.setEngine(session.authenticator.engine);
482
+ SolidTypeIndex.setEngine(session.authenticator.engine);
483
+ SolidDocument.setEngine(session.authenticator.engine);
484
+
485
+ await Events.emit('auth:login', session);
486
+ },
487
+ onSessionFailed: async (loginUrl, error) => {
488
+ this.setState({
489
+ session: null,
490
+ previousSession: {
491
+ authenticator: authenticator.name,
492
+ loginUrl,
493
+ error,
494
+ },
495
+ });
496
+ },
497
+ onSessionEnded: () => this.setState({ session: null }),
498
+ onAuthenticatedFetchReady: (fetch) => Events.emit('auth:fetch-ready', fetch),
499
+ });
500
+
501
+ await authenticator.boot();
502
+
503
+ return authenticator;
504
+ }
505
+
506
+ private rememberProfile(profile: SolidUserProfile): void {
507
+ this.setState({
508
+ profiles: {
509
+ ...this.profiles,
510
+ [profile.webId]: profile,
511
+ },
512
+ staleProfiles: arrayWithout(this.staleProfiles, profile.webId),
513
+ });
514
+ }
515
+
516
+ }
517
+
518
+ export default facade(SolidService);
519
+
520
+ declare module '@aerogel/core' {
521
+ export interface EventsPayload {
522
+ 'auth:fetch-ready': Fetch;
523
+ 'auth:before-login': void;
524
+ 'auth:login': AuthSession;
525
+ 'auth:logout': { wipeLocalData: boolean };
526
+ 'auth:after-logout': void;
527
+ 'solid:user-profile-loaded': [SolidUserProfile, SolidStore];
528
+ }
529
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="@aerogel/plugin-offline-first" />
@@ -0,0 +1,7 @@
1
+ declare module '*.vue' {
2
+ import type { ComponentOptions } from 'vue';
3
+
4
+ const component: ComponentOptions;
5
+
6
+ export default component;
7
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "types": ["unplugin-icons/types/vue3", "@aerogel/vite/dist/virtual"],
6
+ "paths": {
7
+ "@/*": ["./src/*"]
8
+ }
9
+ },
10
+ "include": ["src/**/*.ts", "src/**/*.vue"]
11
+ }