@aerogel/plugin-solid 0.0.1-next.0d1cf82f9dfbe1d3e7ec5644f85f97a43c8c2334

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.
Files changed (32) hide show
  1. package/dist/InruptAuthenticator.lazy-BvpQLGoj.js +9 -0
  2. package/dist/InruptAuthenticator.lazy-BvpQLGoj.js.map +1 -0
  3. package/dist/LegacyAuthenticator.lazy-ChFFqP14.js +5 -0
  4. package/dist/LegacyAuthenticator.lazy-ChFFqP14.js.map +1 -0
  5. package/dist/aerogel-plugin-solid.d.ts +309 -0
  6. package/dist/aerogel-plugin-solid.js +683 -0
  7. package/dist/aerogel-plugin-solid.js.map +1 -0
  8. package/package.json +49 -0
  9. package/src/auth/Authenticator.ts +123 -0
  10. package/src/auth/authenticators/InruptAuthenticator.lazy.ts +1 -0
  11. package/src/auth/authenticators/InruptAuthenticator.ts +103 -0
  12. package/src/auth/authenticators/LegacyAuthenticator.lazy.ts +1 -0
  13. package/src/auth/authenticators/LegacyAuthenticator.ts +80 -0
  14. package/src/auth/index.ts +40 -0
  15. package/src/components/AGSolidImage.vue +33 -0
  16. package/src/components/AGSolidStatus.ts +5 -0
  17. package/src/components/AGSolidStatus.vue +68 -0
  18. package/src/components/AGSolidUserAvatar.vue +35 -0
  19. package/src/components/index.ts +4 -0
  20. package/src/errors/AuthenticationCancelledError.ts +3 -0
  21. package/src/errors/AuthenticationFailedError.ts +22 -0
  22. package/src/errors/ContainerAlreadyInUse.ts +11 -0
  23. package/src/errors/index.ts +3 -0
  24. package/src/forms/validation.ts +11 -0
  25. package/src/index.test.ts +24 -0
  26. package/src/index.ts +65 -0
  27. package/src/services/Solid.state.ts +56 -0
  28. package/src/services/Solid.ts +549 -0
  29. package/src/testing/setup.ts +11 -0
  30. package/src/types/env.d.ts +1 -0
  31. package/src/types/shims.d.ts +7 -0
  32. package/src/types/solid-auth-client.d.ts +21 -0
@@ -0,0 +1,68 @@
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('solid.loggedIn', 'You are logged in as [{userName}]({userWebId})', {
16
+ userName: $solid.user.name ?? $solid.user.webId,
17
+ userWebId: $solid.user.webId,
18
+ })
19
+ "
20
+ />
21
+ <AGButton @click="$solid.logout()">
22
+ {{ $td('solid.logout', 'Logout') }}
23
+ </AGButton>
24
+ </div>
25
+ </slot>
26
+
27
+ <slot v-else name="logged-out">
28
+ <div class="flex flex-col">
29
+ <AGForm :form="form" class="flex" @submit="$solid.login(form.url)">
30
+ <AGInput
31
+ name="url"
32
+ :aria-label="$td('solid.url', 'Login url')"
33
+ :placeholder="$td('solid.placeholder', 'https://...')"
34
+ />
35
+ <AGButton v-if="showDevLogin" submit @click="form.url = 'dev'">
36
+ {{ $td('solid.devLogin', 'Dev login') }}
37
+ </AGButton>
38
+ <AGButton v-else submit>
39
+ {{ $td('solid.login', 'Login') }}
40
+ </AGButton>
41
+ </AGForm>
42
+ <AGButton v-if="$solid.wasLoggedIn()" class="mt-3" @click="$solid.reconnect({ force: true })">
43
+ {{ $td('solid.reconnect', 'Reconnect') }}
44
+ </AGButton>
45
+ <p
46
+ v-if="$solid.previousSession?.error || $solid.loginStartupError"
47
+ class="mt-1 self-center text-sm text-red-800"
48
+ >
49
+ <AGMarkdown :text="$td('solid.previousLoginError', 'Previous login attempt failed')" inline />
50
+ <AGLink @click="$errors.inspect($solid.previousSession?.error || $solid.loginStartupError)">
51
+ ({{ $td('errors.viewDetails', 'View details') }})
52
+ </AGLink>
53
+ </p>
54
+ </div>
55
+ </slot>
56
+ </div>
57
+ </template>
58
+
59
+ <script setup lang="ts">
60
+ import { AGButton, AGForm, AGInput, AGLink, AGMarkdown, requiredStringInput, useForm } from '@aerogel/core';
61
+ import { isDevelopment } from '@noeldemartin/utils';
62
+ import { computed } from 'vue';
63
+
64
+ const form = useForm({ url: requiredStringInput() });
65
+ const showDevLogin = computed(
66
+ () => isDevelopment() && (!form.url || form.url === 'dev' || form.url.trim().length === 0),
67
+ );
68
+ </script>
@@ -0,0 +1,35 @@
1
+ <template>
2
+ <div class="relative flex items-center justify-center rounded-full bg-gray-200">
3
+ <AGSolidImage
4
+ v-if="$solid.user?.avatarUrl"
5
+ :src="$solid.user?.avatarUrl"
6
+ alt=""
7
+ class="absolute inset-0 h-full w-full rounded-full"
8
+ />
9
+ <span v-else-if="userInitials" class="text-sm font-semibold tracking-wider text-gray-500" aria-hidden="true">
10
+ {{ userInitials }}
11
+ </span>
12
+ <IconAnonymous v-else class="h-6 w-6 text-gray-500" />
13
+ </div>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ import IconAnonymous from '~icons/ooui/user-anonymous';
18
+
19
+ import { computed } from 'vue';
20
+
21
+ import Solid from '@aerogel/plugin-solid/services/Solid';
22
+
23
+ import AGSolidImage from './AGSolidImage.vue';
24
+
25
+ const userInitials = computed(() => {
26
+ const name = Solid.user?.name ?? '';
27
+
28
+ return name
29
+ .trim()
30
+ .split(' ')
31
+ .map((part) => part[0]?.toUpperCase())
32
+ .join('')
33
+ .slice(0, 3);
34
+ });
35
+ </script>
@@ -0,0 +1,4 @@
1
+ export { default as AGSolidImage } from './AGSolidImage.vue';
2
+ export { default as AGSolidStatus } from './AGSolidStatus.vue';
3
+ export { default as AGSolidUserAvatar } from './AGSolidUserAvatar.vue';
4
+ export type { IAGSolidStatusLoggedInSlotProps } from './AGSolidStatus';
@@ -0,0 +1,3 @@
1
+ import { JSError } from '@noeldemartin/utils';
2
+
3
+ export default class AuthenticationCancelledError extends JSError {}
@@ -0,0 +1,22 @@
1
+ import { JSError } from '@noeldemartin/utils';
2
+ import type { JSErrorOptions } from '@noeldemartin/utils';
3
+
4
+ function getErrorMessage(message: string, description?: string | null): string {
5
+ if (!description) {
6
+ return message;
7
+ }
8
+
9
+ return `${message} (${description})`;
10
+ }
11
+
12
+ export default class AuthenticationFailedError extends JSError {
13
+
14
+ public readonly description?: string | null;
15
+
16
+ constructor(message: string, description?: string | null, options?: JSErrorOptions) {
17
+ super(getErrorMessage(message), options);
18
+
19
+ this.description = description;
20
+ }
21
+
22
+ }
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ export { default as AuthenticationCancelledError } from '@aerogel/plugin-solid/errors/AuthenticationCancelledError';
2
+ export { default as AuthenticationFailedError } from '@aerogel/plugin-solid/errors/AuthenticationFailedError';
3
+ export { default as ContainerAlreadyInUse } from '@aerogel/plugin-solid/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
+ }
@@ -0,0 +1,24 @@
1
+ import { createApp } from 'vue';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { Errors, bootServices } from '@aerogel/core';
4
+ import { Metadata } from 'soukai-solid';
5
+ import { requireBootedModel } from 'soukai';
6
+
7
+ import solid from './index';
8
+
9
+ describe('Solid plugin', () => {
10
+
11
+ it('Initializes models', async () => {
12
+ // Arrange
13
+ const app = createApp({});
14
+
15
+ await bootServices(app, { $errors: Errors });
16
+
17
+ // Act
18
+ await solid().install(app, {});
19
+
20
+ // Assert
21
+ expect(requireBootedModel('Metadata')).toEqual(Metadata);
22
+ });
23
+
24
+ });
package/src/index.ts ADDED
@@ -0,0 +1,65 @@
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 '@aerogel/plugin-solid/services/Solid';
7
+ import { DEFAULT_STATE } from '@aerogel/plugin-solid/services/Solid.state';
8
+ import { defineFormValidationRules } from '@aerogel/plugin-solid/forms/validation';
9
+ import {
10
+ authenticators as baseAuthenticators,
11
+ getAuthenticator,
12
+ registerAuthenticators,
13
+ setDefaultAuthenticator,
14
+ } from '@aerogel/plugin-solid/auth';
15
+ import { AuthenticationFailedError } from '@aerogel/plugin-solid/errors';
16
+ import type Authenticator from '@aerogel/plugin-solid/auth/Authenticator';
17
+ import type { AuthenticatorName } from '@aerogel/plugin-solid/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
+ return error.description ? `${error.message} (${error.description})` : error.message;
48
+ });
49
+ if (typeof options.autoReconnect === 'boolean') {
50
+ DEFAULT_STATE.autoReconnect = options.autoReconnect;
51
+ Solid.hasPersistedState() || (Solid.autoReconnect = options.autoReconnect);
52
+ }
53
+ if (options.onUserProfileLoaded) {
54
+ Events.on('solid:user-profile-loaded', ([user, store]) => {
55
+ options.onUserProfileLoaded?.(user, store);
56
+ });
57
+ }
58
+ await bootServices(app, services);
59
+ },
60
+ };
61
+ }
62
+
63
+ declare module '@aerogel/core' {
64
+ interface Services extends SolidServices {}
65
+ }
@@ -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 '@aerogel/plugin-solid/auth/Authenticator';
6
+ import type { AuthenticatorName } from '@aerogel/plugin-solid/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
+ });