@strivacity/sdk-nuxt 4.0.0-beta.0 → 4.0.0-beta.2
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/README.md +171 -101
- package/dist/module.mjs +5 -4
- package/package.json +2 -2
- package/project.json +3 -0
- package/src/module.ts +7 -3
- package/src/runtime/composables/use-native-login.ts +27 -159
- package/src/runtime/composables/use-strivacity.ts +217 -67
- package/src/runtime/middleware/auth.server.ts +10 -1
- package/src/runtime/plugins/strivacity.client.ts +17 -0
- package/src/runtime/server/plugins/auth.server.ts +66 -30
- package/src/runtime/storages/default.ts +22 -67
- package/src/runtime/types.ts +7 -36
- package/src/runtime/utils/core.ts +2 -0
- package/testing/stubs/app.ts +8 -0
- package/testing/tests/composables.spec.ts +105 -4
- package/testing/tests/module.spec.ts +46 -0
- package/testing/tests/server/plugin.spec.ts +48 -10
- package/testing/tests/server/routes.spec.ts +2 -2
- package/.nuxt/app-component.mjs +0 -1
- package/.nuxt/app.config.mjs +0 -21
- package/.nuxt/component-chunk.mjs +0 -1
- package/.nuxt/component-names.mjs +0 -1
- package/.nuxt/components.d.ts +0 -66
- package/.nuxt/components.islands.mjs +0 -8
- package/.nuxt/components.plugin.mjs +0 -5
- package/.nuxt/css.mjs +0 -0
- package/.nuxt/error-component.mjs +0 -1
- package/.nuxt/fetch.d.ts +0 -1
- package/.nuxt/fetch.mjs +0 -8
- package/.nuxt/global-polyfills.mjs +0 -4
- package/.nuxt/imports.d.ts +0 -41
- package/.nuxt/imports.mjs +0 -42
- package/.nuxt/island-renderer.mjs +0 -2
- package/.nuxt/layouts.mjs +0 -2
- package/.nuxt/manifest/meta/cb11df97-b0c9-4a91-9d4b-60c25c918599.json +0 -1
- package/.nuxt/middleware.mjs +0 -7
- package/.nuxt/nitro.client.mjs +0 -2
- package/.nuxt/nuxt.config.mjs +0 -103
- package/.nuxt/nuxt.d.ts +0 -19
- package/.nuxt/nuxt.node.d.ts +0 -13
- package/.nuxt/nuxt.shared.d.ts +0 -7
- package/.nuxt/pages.mjs +0 -2
- package/.nuxt/paths.mjs +0 -15
- package/.nuxt/plugins.client.mjs +0 -14
- package/.nuxt/plugins.server.mjs +0 -10
- package/.nuxt/root-component.mjs +0 -1
- package/.nuxt/route-rules.mjs +0 -22
- package/.nuxt/router.options.mjs +0 -2
- package/.nuxt/schema/nuxt.schema.d.ts +0 -17
- package/.nuxt/schema/nuxt.schema.json +0 -3
- package/.nuxt/test-component-wrapper.mjs +0 -1
- package/.nuxt/tsconfig.app.json +0 -211
- package/.nuxt/tsconfig.json +0 -212
- package/.nuxt/tsconfig.node.json +0 -117
- package/.nuxt/tsconfig.server.json +0 -161
- package/.nuxt/tsconfig.shared.json +0 -165
- package/.nuxt/types/app.config.d.ts +0 -35
- package/.nuxt/types/build.d.ts +0 -0
- package/.nuxt/types/builder-env.d.ts +0 -1
- package/.nuxt/types/components.d.ts +0 -71
- package/.nuxt/types/imports.d.ts +0 -2
- package/.nuxt/types/layouts.d.ts +0 -16
- package/.nuxt/types/middleware.d.ts +0 -11
- package/.nuxt/types/modules.d.ts +0 -59
- package/.nuxt/types/nitro-config.d.ts +0 -14
- package/.nuxt/types/nitro-imports.d.ts +0 -20
- package/.nuxt/types/nitro-layouts.d.ts +0 -17
- package/.nuxt/types/nitro-nuxt.d.ts +0 -75
- package/.nuxt/types/nitro-routes.d.ts +0 -38
- package/.nuxt/types/nitro.d.ts +0 -3
- package/.nuxt/types/plugins.d.ts +0 -30
- package/.nuxt/types/runtime-config.d.ts +0 -38
- package/.nuxt/types/shared-app.config.d.ts +0 -28
- package/.nuxt/types/shared-imports.d.ts +0 -2
- package/.nuxt/types/vue-shim.d.ts +0 -0
- package/.nuxt/unhead-options.mjs +0 -5
- package/.nuxt/unhead.config.mjs +0 -1
- package/dist/module.d.mts +0 -7
- package/dist/module.json +0 -9
- package/dist/module.mjs.map +0 -1
- package/dist/runtime/composables/use-native-login-context.d.ts +0 -10
- package/dist/runtime/composables/use-native-login-context.js +0 -9
- package/dist/runtime/composables/use-native-login.d.ts +0 -10
- package/dist/runtime/composables/use-native-login.js +0 -143
- package/dist/runtime/composables/use-session.d.ts +0 -8
- package/dist/runtime/composables/use-session.js +0 -2
- package/dist/runtime/composables/use-strivacity.d.ts +0 -9
- package/dist/runtime/composables/use-strivacity.js +0 -77
- package/dist/runtime/middleware/auth.server.d.ts +0 -2
- package/dist/runtime/middleware/auth.server.js +0 -12
- package/dist/runtime/server/api/auth/backchannel-logout.get.d.ts +0 -2
- package/dist/runtime/server/api/auth/backchannel-logout.get.js +0 -2
- package/dist/runtime/server/api/auth/callback.get.d.ts +0 -2
- package/dist/runtime/server/api/auth/callback.get.js +0 -2
- package/dist/runtime/server/api/auth/entry.get.d.ts +0 -2
- package/dist/runtime/server/api/auth/entry.get.js +0 -2
- package/dist/runtime/server/api/auth/login.get.d.ts +0 -2
- package/dist/runtime/server/api/auth/login.get.js +0 -2
- package/dist/runtime/server/api/auth/logout.get.d.ts +0 -2
- package/dist/runtime/server/api/auth/logout.get.js +0 -2
- package/dist/runtime/server/api/auth/refresh.get.d.ts +0 -2
- package/dist/runtime/server/api/auth/refresh.get.js +0 -2
- package/dist/runtime/server/api/auth/register.get.d.ts +0 -2
- package/dist/runtime/server/api/auth/register.get.js +0 -2
- package/dist/runtime/server/api/auth/revoke.get.d.ts +0 -2
- package/dist/runtime/server/api/auth/revoke.get.js +0 -2
- package/dist/runtime/server/composables/use-strivacity.d.ts +0 -2
- package/dist/runtime/server/composables/use-strivacity.js +0 -9
- package/dist/runtime/server/plugins/auth.server.d.ts +0 -10
- package/dist/runtime/server/plugins/auth.server.js +0 -66
- package/dist/runtime/server/utils/helpers.d.ts +0 -18
- package/dist/runtime/server/utils/helpers.js +0 -39
- package/dist/runtime/storages/core.d.ts +0 -1
- package/dist/runtime/storages/core.js +0 -13
- package/dist/runtime/storages/default.d.ts +0 -21
- package/dist/runtime/storages/default.js +0 -71
- package/dist/runtime/types.d.ts +0 -219
- package/dist/runtime/types.js +0 -1
- package/dist/runtime/utils/core.d.ts +0 -1
- package/dist/runtime/utils/core.js +0 -64
- package/dist/runtime/utils/noop.d.ts +0 -1
- package/dist/runtime/utils/noop.js +0 -3
- package/dist/types.d.mts +0 -9
- /package/src/runtime/server/api/auth/{backchannel-logout.get.ts → backchannel-logout.post.ts} +0 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import type { NativeFlow,
|
|
1
|
+
import type { NativeFlow, NativeFlowState, NativeFlowMessage } from '@strivacity/sdk-core/types';
|
|
2
2
|
import type { LoginContext, UseNativeLoginOptions } from '../types';
|
|
3
3
|
import { STRIVACITY_LOGIN_CONTEXT, type useNativeLoginContext } from './use-native-login-context';
|
|
4
4
|
import { provide, ref, onMounted, onUnmounted } from 'vue';
|
|
5
|
-
import {
|
|
6
|
-
import { unflattenObject } from '@strivacity/sdk-core/utils';
|
|
5
|
+
import { createNativeLoginController } from '@strivacity/sdk-core/utils';
|
|
7
6
|
import { useStrivacity } from './use-strivacity';
|
|
8
7
|
|
|
9
8
|
/**
|
|
@@ -14,169 +13,38 @@ import { useStrivacity } from './use-strivacity';
|
|
|
14
13
|
* @throws {Error} - If the Strivacity SDK context is not found.
|
|
15
14
|
*/
|
|
16
15
|
export function useNativeLogin(options: UseNativeLoginOptions = {}): ReturnType<typeof useNativeLoginContext> {
|
|
17
|
-
const abortController = new AbortController();
|
|
18
16
|
const { sdk } = useStrivacity<NativeFlow>();
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
sdk.logging?.error('Error fetching authorization URI', error as Error);
|
|
36
|
-
void options.onError?.(error as Error);
|
|
37
|
-
loading.value = false;
|
|
38
|
-
}
|
|
17
|
+
const controller = createNativeLoginController(() => sdk, options);
|
|
18
|
+
const snapshot = controller.getSnapshot();
|
|
19
|
+
|
|
20
|
+
const loading = ref<boolean>(snapshot.loading);
|
|
21
|
+
const forms = ref<Record<string, Record<string, unknown>>>(snapshot.forms);
|
|
22
|
+
const messages = ref<Record<string, Record<string, NativeFlowMessage>>>(snapshot.messages);
|
|
23
|
+
const state = ref<Partial<NativeFlowState>>(snapshot.state);
|
|
24
|
+
|
|
25
|
+
const unsubscribe = controller.subscribe((next) => {
|
|
26
|
+
loading.value = next.loading;
|
|
27
|
+
forms.value = next.forms;
|
|
28
|
+
messages.value = next.messages;
|
|
29
|
+
state.value = next.state;
|
|
39
30
|
});
|
|
40
31
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
function handleResponse(nextState: Partial<NativeFlowState>) {
|
|
44
|
-
if (sdk.session) {
|
|
45
|
-
return void options.onLogin?.(sdk.session);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const newState: NativeFlowState = {
|
|
49
|
-
hostedUrl: nextState?.hostedUrl ?? state.value.hostedUrl,
|
|
50
|
-
finalizeUrl: nextState?.finalizeUrl ?? state.value.finalizeUrl,
|
|
51
|
-
screen: nextState?.screen ?? state.value.screen,
|
|
52
|
-
forms: nextState?.forms ?? state.value.forms,
|
|
53
|
-
layout: nextState?.layout ?? state.value.layout,
|
|
54
|
-
messages: nextState?.messages ?? {},
|
|
55
|
-
branding: nextState?.branding ?? state.value.branding,
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
if (newState.screen !== state.value.screen) {
|
|
59
|
-
forms.value = {};
|
|
60
|
-
messages.value = {};
|
|
61
|
-
|
|
62
|
-
for (const form of newState.forms ?? []) {
|
|
63
|
-
forms.value[form.id] = {};
|
|
64
|
-
messages.value[form.id] = {};
|
|
65
|
-
}
|
|
66
|
-
} else {
|
|
67
|
-
sdk.logging?.info(`Updating screen: ${newState.screen}`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
for (const formId of Object.keys(newState.messages ?? {})) {
|
|
71
|
-
if (formId === 'global') {
|
|
72
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
|
73
|
-
void options.onGlobalMessage?.(newState.messages?.global!);
|
|
74
|
-
} else {
|
|
75
|
-
messages.value[formId] = newState.messages![formId] ?? {};
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
state.value = newState;
|
|
80
|
-
|
|
81
|
-
if (!newState.finalizeUrl) {
|
|
82
|
-
loading.value = false;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function startSession(loginParams: NativeParams = {}) {
|
|
87
|
-
try {
|
|
88
|
-
loading.value = true;
|
|
89
|
-
|
|
90
|
-
const nextState = await sdk.startSession(loginParams);
|
|
91
|
-
|
|
92
|
-
if (nextState) {
|
|
93
|
-
handleResponse(nextState);
|
|
94
|
-
}
|
|
95
|
-
} catch (error) {
|
|
96
|
-
if (error instanceof FallbackError) {
|
|
97
|
-
sdk.logging?.error('Fallback error occurred', error);
|
|
98
|
-
void options.onFallback?.(error);
|
|
99
|
-
} else {
|
|
100
|
-
sdk.logging?.error('Error starting session', error as Error);
|
|
101
|
-
void options.onError?.(error as Error);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function triggerFallback(message?: string) {
|
|
107
|
-
sdk.logging?.warn(message ? `Triggering fallback due to: ${message}` : 'Triggering fallback');
|
|
108
|
-
|
|
109
|
-
if (!state.value.hostedUrl) {
|
|
110
|
-
const error = new Error('No hosted URL provided');
|
|
111
|
-
sdk.logging?.error('Fallback error', error);
|
|
112
|
-
throw error;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
void options.onFallback?.(new FallbackError(new URL(state.value.hostedUrl)));
|
|
116
|
-
}
|
|
32
|
+
onMounted(() => controller.start());
|
|
33
|
+
onUnmounted(unsubscribe);
|
|
117
34
|
|
|
118
|
-
|
|
119
|
-
sdk.logging?.debug('Triggering close');
|
|
120
|
-
void options.onClose?.();
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async function submitForm(formId: string, customBody?: Record<string, unknown>) {
|
|
124
|
-
try {
|
|
125
|
-
loading.value = true;
|
|
126
|
-
|
|
127
|
-
const nextState = await sdk.submitForm(formId, customBody ?? unflattenObject(forms.value[formId] ?? {}));
|
|
128
|
-
|
|
129
|
-
if (nextState) {
|
|
130
|
-
handleResponse(nextState);
|
|
131
|
-
}
|
|
132
|
-
} catch (error) {
|
|
133
|
-
if (error instanceof FallbackError) {
|
|
134
|
-
sdk.logging?.error('Fallback error occurred', error);
|
|
135
|
-
void options.onFallback?.(error);
|
|
136
|
-
} else {
|
|
137
|
-
sdk.logging?.error('Error submitting form', error as Error);
|
|
138
|
-
void options.onError?.(error as Error);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function setFormValue(formId: string, widgetId: string, value: unknown) {
|
|
144
|
-
if (forms.value[formId] === undefined) {
|
|
145
|
-
forms.value[formId] = {};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
forms.value[formId][widgetId] = value === '' ? null : value;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function setMessage(formId: string, widgetId: string, value: NativeFlowMessage) {
|
|
152
|
-
if (messages.value[formId] === undefined) {
|
|
153
|
-
messages.value[formId] = {};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
messages.value[formId][widgetId] = value;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
provide<LoginContext>(STRIVACITY_LOGIN_CONTEXT, {
|
|
35
|
+
const context: LoginContext = {
|
|
160
36
|
loading,
|
|
161
37
|
forms,
|
|
162
38
|
messages,
|
|
163
39
|
state,
|
|
164
|
-
triggerFallback,
|
|
165
|
-
triggerClose,
|
|
166
|
-
submitForm,
|
|
167
|
-
setFormValue,
|
|
168
|
-
setMessage,
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
loading,
|
|
173
|
-
forms,
|
|
174
|
-
messages,
|
|
175
|
-
state,
|
|
176
|
-
triggerFallback,
|
|
177
|
-
triggerClose,
|
|
178
|
-
submitForm,
|
|
179
|
-
setFormValue,
|
|
180
|
-
setMessage,
|
|
40
|
+
triggerFallback: controller.triggerFallback,
|
|
41
|
+
triggerClose: controller.triggerClose,
|
|
42
|
+
submitForm: controller.submitForm,
|
|
43
|
+
setFormValue: controller.setFormValue,
|
|
44
|
+
setMessage: controller.setMessage,
|
|
181
45
|
};
|
|
46
|
+
|
|
47
|
+
provide<LoginContext>(STRIVACITY_LOGIN_CONTEXT, context);
|
|
48
|
+
|
|
49
|
+
return context;
|
|
182
50
|
}
|
|
@@ -1,95 +1,245 @@
|
|
|
1
|
-
import type { EmbeddedFlow, IdTokenClaims, NativeFlow, PopupFlow, RedirectFlow } from '@strivacity/sdk-core/types';
|
|
1
|
+
import type { EmbeddedFlow, IdTokenClaims, NativeFlow, PopupFlow, RedirectFlow, SessionData } from '@strivacity/sdk-core/types';
|
|
2
2
|
import type { SDKContext } from '../types';
|
|
3
|
-
import { ref } from 'vue';
|
|
3
|
+
import { computed, ref } from 'vue';
|
|
4
4
|
import { initFlow } from '@strivacity/sdk-core';
|
|
5
|
-
import {
|
|
5
|
+
import { isSessionExpired } from '@strivacity/sdk-core/utils';
|
|
6
|
+
import { tryUseNuxtApp, useRuntimeConfig } from '#app';
|
|
6
7
|
import { useSession } from './use-session';
|
|
7
8
|
import createLogging from '#strivacity-options-logging';
|
|
8
9
|
import createHttpClient from '#strivacity-options-httpClient';
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
let sdk: any;
|
|
11
|
+
type Flow = RedirectFlow | PopupFlow | NativeFlow | EmbeddedFlow;
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const idTokenClaimsRef = ref<IdTokenClaims | null>(null);
|
|
17
|
-
const accessTokenRef = ref<string | null>(null);
|
|
18
|
-
const refreshTokenRef = ref<string | null>(null);
|
|
19
|
-
const accessTokenExpiredRef = ref<boolean>(true);
|
|
20
|
-
const accessTokenExpirationDateRef = ref<number | null>(null);
|
|
13
|
+
// Browser-only singleton: there is exactly one user per page there. It is never created during SSR, where a module-level
|
|
14
|
+
// instance would be shared by every concurrent request (and leak one user's session into another user's HTML).
|
|
15
|
+
let clientContext: SDKContext<Flow> | undefined;
|
|
21
16
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (sdk.accessTokenExpired !== accessTokenExpiredRef.value) {
|
|
44
|
-
accessTokenExpiredRef.value = sdk.accessTokenExpired;
|
|
45
|
-
}
|
|
46
|
-
if (sdk.accessTokenExpirationDate !== accessTokenExpirationDateRef.value) {
|
|
47
|
-
accessTokenExpirationDateRef.value = sdk.accessTokenExpirationDate || null;
|
|
48
|
-
}
|
|
49
|
-
};
|
|
17
|
+
/**
|
|
18
|
+
* Derives the reactive state from a session. Used by the SSR context, and as the browser's initial state, so the first client
|
|
19
|
+
* render matches the server-rendered HTML (no hydration mismatch) until the browser SDK takes over.
|
|
20
|
+
*
|
|
21
|
+
* @param {SessionData | undefined} session - The current session (`useSession()`).
|
|
22
|
+
* @param {boolean} serverManaged - Whether server-managed sessions are enabled (`serverSessionUri`).
|
|
23
|
+
* @returns The derived state values.
|
|
24
|
+
*/
|
|
25
|
+
function deriveState(session: SessionData | undefined, serverManaged: boolean) {
|
|
26
|
+
return {
|
|
27
|
+
// With client-managed sessions the server cannot know the session (it lives in browser storage), so stay "loading"
|
|
28
|
+
// until the browser SDK has loaded it.
|
|
29
|
+
loading: !serverManaged,
|
|
30
|
+
language: 'en-US',
|
|
31
|
+
isAuthenticated: serverManaged && !isSessionExpired(session),
|
|
32
|
+
idTokenClaims: session?.claims ?? null,
|
|
33
|
+
accessToken: session?.access_token ?? null,
|
|
34
|
+
accessTokenExpired: isSessionExpired(session),
|
|
35
|
+
accessTokenExpirationDate: session?.expires_at ?? null,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
50
38
|
|
|
51
39
|
/**
|
|
52
|
-
*
|
|
40
|
+
* Creates the browser SDK instance and its reactive state. Called once, by the `strivacity` client plugin (or lazily by
|
|
41
|
+
* the first `useStrivacity()` call in the browser).
|
|
53
42
|
*
|
|
54
|
-
* @
|
|
55
|
-
* @returns {SDKContext<T>} The Strivacity SDK context.
|
|
43
|
+
* @returns {SDKContext} The browser SDK context.
|
|
56
44
|
*/
|
|
57
|
-
export
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (!sdk) {
|
|
61
|
-
sdk = initFlow({
|
|
62
|
-
...useRuntimeConfig().public.strivacity,
|
|
63
|
-
logging: createLogging ? createLogging() : undefined,
|
|
64
|
-
httpClient: createHttpClient ? createHttpClient() : undefined,
|
|
65
|
-
});
|
|
66
|
-
sdk.subscribeToAllEvents(updateSession);
|
|
67
|
-
|
|
68
|
-
sdk.session = useSession().value;
|
|
69
|
-
void updateSession();
|
|
45
|
+
export function getClientContext(): SDKContext<Flow> {
|
|
46
|
+
if (clientContext) {
|
|
47
|
+
return clientContext;
|
|
70
48
|
}
|
|
71
49
|
|
|
72
|
-
|
|
50
|
+
const options = useRuntimeConfig().public.strivacity;
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
52
|
+
const sdk: any = initFlow({
|
|
53
|
+
...options,
|
|
54
|
+
logging: createLogging ? createLogging() : undefined,
|
|
55
|
+
httpClient: createHttpClient ? createHttpClient() : undefined,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Start from the same values the server rendered with, then let the SDK update them.
|
|
59
|
+
const initial = deriveState(useSession().value, !!options.serverSessionUri);
|
|
60
|
+
const loading = ref<boolean>(initial.loading);
|
|
61
|
+
const language = ref<string>(initial.language);
|
|
62
|
+
const isAuthenticated = ref<boolean>(initial.isAuthenticated);
|
|
63
|
+
const idTokenClaims = ref<IdTokenClaims | null>(initial.idTokenClaims);
|
|
64
|
+
const accessToken = ref<string | null>(initial.accessToken);
|
|
65
|
+
const refreshToken = ref<string | null>(null);
|
|
66
|
+
const accessTokenExpired = ref<boolean>(initial.accessTokenExpired);
|
|
67
|
+
const accessTokenExpirationDate = ref<number | null>(initial.accessTokenExpirationDate);
|
|
68
|
+
|
|
69
|
+
const updateSession = async () => {
|
|
70
|
+
const authenticated = await sdk.isAuthenticated;
|
|
71
|
+
|
|
72
|
+
if (loading.value) {
|
|
73
|
+
loading.value = false;
|
|
74
|
+
}
|
|
75
|
+
if (sdk.language !== language.value) {
|
|
76
|
+
language.value = sdk.language;
|
|
77
|
+
}
|
|
78
|
+
if (authenticated !== isAuthenticated.value) {
|
|
79
|
+
isAuthenticated.value = authenticated;
|
|
80
|
+
}
|
|
81
|
+
if (sdk.idTokenClaims !== idTokenClaims.value) {
|
|
82
|
+
idTokenClaims.value = sdk.idTokenClaims || null;
|
|
83
|
+
}
|
|
84
|
+
if (sdk.accessToken !== accessToken.value) {
|
|
85
|
+
accessToken.value = sdk.accessToken || null;
|
|
86
|
+
}
|
|
87
|
+
if (sdk.refreshToken !== refreshToken.value) {
|
|
88
|
+
refreshToken.value = sdk.refreshToken || null;
|
|
89
|
+
}
|
|
90
|
+
if (sdk.accessTokenExpired !== accessTokenExpired.value) {
|
|
91
|
+
accessTokenExpired.value = sdk.accessTokenExpired;
|
|
92
|
+
}
|
|
93
|
+
if (sdk.accessTokenExpirationDate !== accessTokenExpirationDate.value) {
|
|
94
|
+
accessTokenExpirationDate.value = sdk.accessTokenExpirationDate || null;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
sdk.subscribeToAllEvents(updateSession);
|
|
99
|
+
sdk.session = useSession().value;
|
|
100
|
+
void updateSession();
|
|
101
|
+
|
|
102
|
+
clientContext = {
|
|
73
103
|
sdk,
|
|
74
|
-
loading
|
|
75
|
-
language
|
|
76
|
-
isAuthenticated
|
|
77
|
-
idTokenClaims
|
|
78
|
-
accessToken
|
|
79
|
-
refreshToken
|
|
80
|
-
accessTokenExpired
|
|
81
|
-
accessTokenExpirationDate
|
|
104
|
+
loading,
|
|
105
|
+
language,
|
|
106
|
+
isAuthenticated,
|
|
107
|
+
idTokenClaims,
|
|
108
|
+
accessToken,
|
|
109
|
+
refreshToken,
|
|
110
|
+
accessTokenExpired,
|
|
111
|
+
accessTokenExpirationDate,
|
|
82
112
|
init: () => sdk.init(),
|
|
83
113
|
subscribeToEvent: ((...args: Parameters<typeof sdk.subscribeToEvent>) => sdk.subscribeToEvent(...args)) as typeof sdk.subscribeToEvent,
|
|
84
114
|
subscribeToAllEvents: (...args: Parameters<typeof sdk.subscribeToAllEvents>) => sdk.subscribeToAllEvents(...args),
|
|
85
115
|
checkAuthentication: (...args: Parameters<typeof sdk.checkAuthentication>) => sdk.checkAuthentication(...args),
|
|
86
116
|
tokenExchange: (...args: Parameters<typeof sdk.tokenExchange>) => sdk.tokenExchange(...args),
|
|
87
117
|
handleCallback: (...args: Parameters<typeof sdk.handleCallback>) => sdk.handleCallback(...args),
|
|
88
|
-
refresh: () => sdk.refresh(),
|
|
118
|
+
refresh: (...args: Parameters<typeof sdk.refresh>) => sdk.refresh(...args),
|
|
89
119
|
revoke: () => sdk.revoke(),
|
|
90
120
|
logout: (...args: Parameters<typeof sdk.logout>) => sdk.logout(...args),
|
|
91
121
|
login: (...args: Parameters<typeof sdk.login>) => sdk.login(...args),
|
|
92
122
|
register: (...args: Parameters<typeof sdk.register>) => sdk.register(...args),
|
|
93
123
|
entry: (...args: Parameters<typeof sdk.entry>) => sdk.entry(...args),
|
|
94
124
|
};
|
|
125
|
+
|
|
126
|
+
return clientContext;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const SDK_METHODS = new Set([
|
|
130
|
+
'init',
|
|
131
|
+
'login',
|
|
132
|
+
'register',
|
|
133
|
+
'entry',
|
|
134
|
+
'handleCallback',
|
|
135
|
+
'tokenExchange',
|
|
136
|
+
'refresh',
|
|
137
|
+
'revoke',
|
|
138
|
+
'logout',
|
|
139
|
+
'updateSession',
|
|
140
|
+
'cleanupSession',
|
|
141
|
+
'startSession',
|
|
142
|
+
'submitForm',
|
|
143
|
+
'finalizeSession',
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
function browserOnly(name: string): () => never {
|
|
147
|
+
return () => {
|
|
148
|
+
throw new Error(`[@strivacity/sdk-nuxt] ${name}() is only available in the browser. During SSR, useStrivacity() is read-only.`);
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Builds a read-only context for server-side rendering from the current request's session (`useSession()`, filled by the
|
|
154
|
+
* module's server middleware when server-managed sessions are enabled). No SDK instance is created on the server.
|
|
155
|
+
*
|
|
156
|
+
* @returns {SDKContext} The read-only SSR context.
|
|
157
|
+
*/
|
|
158
|
+
function createServerContext(): SDKContext<Flow> {
|
|
159
|
+
const options = useRuntimeConfig().public.strivacity;
|
|
160
|
+
const session = useSession();
|
|
161
|
+
const serverManaged = !!options.serverSessionUri;
|
|
162
|
+
|
|
163
|
+
const state = computed(() => deriveState(session.value, serverManaged));
|
|
164
|
+
const isAuthenticated = computed(() => state.value.isAuthenticated);
|
|
165
|
+
const idTokenClaims = computed(() => state.value.idTokenClaims);
|
|
166
|
+
const accessToken = computed(() => state.value.accessToken);
|
|
167
|
+
const accessTokenExpired = computed(() => state.value.accessTokenExpired);
|
|
168
|
+
const accessTokenExpirationDate = computed(() => state.value.accessTokenExpirationDate);
|
|
169
|
+
const noopSubscription = () => ({ dispose: () => undefined });
|
|
170
|
+
|
|
171
|
+
const values: Record<string, () => unknown> = {
|
|
172
|
+
options: () => options,
|
|
173
|
+
session: () => session.value ?? null,
|
|
174
|
+
isAuthenticated: async () => Promise.resolve(isAuthenticated.value),
|
|
175
|
+
isAuthenticatedSync: () => isAuthenticated.value,
|
|
176
|
+
idTokenClaims: () => idTokenClaims.value,
|
|
177
|
+
accessToken: () => accessToken.value,
|
|
178
|
+
accessTokenExpired: () => accessTokenExpired.value,
|
|
179
|
+
accessTokenExpirationDate: () => accessTokenExpirationDate.value,
|
|
180
|
+
subscribeToEvent: () => noopSubscription,
|
|
181
|
+
subscribeToAllEvents: () => noopSubscription,
|
|
182
|
+
checkAuthentication: () => async () => Promise.resolve(isAuthenticated.value),
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const sdk = new Proxy(
|
|
186
|
+
{},
|
|
187
|
+
{
|
|
188
|
+
get(_target, property) {
|
|
189
|
+
if (typeof property !== 'string') {
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
if (property in values) {
|
|
193
|
+
return values[property]!();
|
|
194
|
+
}
|
|
195
|
+
if (SDK_METHODS.has(property)) {
|
|
196
|
+
return browserOnly(`sdk.${property}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return undefined;
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
) as Flow;
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
sdk,
|
|
206
|
+
loading: ref<boolean>(state.value.loading),
|
|
207
|
+
language: ref<string>(state.value.language),
|
|
208
|
+
isAuthenticated,
|
|
209
|
+
idTokenClaims,
|
|
210
|
+
accessToken,
|
|
211
|
+
refreshToken: ref<string | null>(null),
|
|
212
|
+
accessTokenExpired,
|
|
213
|
+
accessTokenExpirationDate,
|
|
214
|
+
init: async () => Promise.resolve(),
|
|
215
|
+
subscribeToEvent: noopSubscription,
|
|
216
|
+
subscribeToAllEvents: noopSubscription,
|
|
217
|
+
checkAuthentication: async () => Promise.resolve(isAuthenticated.value),
|
|
218
|
+
tokenExchange: browserOnly('tokenExchange'),
|
|
219
|
+
handleCallback: browserOnly('handleCallback'),
|
|
220
|
+
refresh: browserOnly('refresh'),
|
|
221
|
+
revoke: browserOnly('revoke'),
|
|
222
|
+
logout: browserOnly('logout'),
|
|
223
|
+
login: browserOnly('login'),
|
|
224
|
+
register: browserOnly('register'),
|
|
225
|
+
entry: browserOnly('entry'),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* A composable to access the Strivacity SDK context.
|
|
231
|
+
*
|
|
232
|
+
* In the browser it returns the live SDK instance and its reactive state. During server-side rendering it returns a
|
|
233
|
+
* read-only context derived from the current request's session: the refs and `sdk.options` / `sdk.session` can be read,
|
|
234
|
+
* while SDK methods such as `login()` throw, because authentication flows only run in the browser.
|
|
235
|
+
*
|
|
236
|
+
* @template T - The type of the Strivacity SDK flow (RedirectFlow, PopupFlow, NativeFlow, or EmbeddedFlow).
|
|
237
|
+
* @returns {SDKContext<T>} The Strivacity SDK context.
|
|
238
|
+
*/
|
|
239
|
+
export const useStrivacity = <T extends Flow = Flow>(): SDKContext<T> => {
|
|
240
|
+
if (import.meta.server || tryUseNuxtApp()?.ssrContext) {
|
|
241
|
+
return createServerContext() as unknown as SDKContext<T>;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return getClientContext() as unknown as SDKContext<T>;
|
|
95
245
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { appendResponseHeader } from 'h3';
|
|
1
2
|
import { defineNuxtRouteMiddleware, useNuxtApp } from '#imports';
|
|
3
|
+
import { flushSetCookies, toClientSession } from '@strivacity/sdk-core/utils';
|
|
2
4
|
import { useSession } from '../composables/use-session';
|
|
3
5
|
|
|
4
6
|
export default defineNuxtRouteMiddleware(async () => {
|
|
@@ -12,5 +14,12 @@ export default defineNuxtRouteMiddleware(async () => {
|
|
|
12
14
|
const { useStrivacity } = await import('../server/composables/use-strivacity');
|
|
13
15
|
const sdk = useStrivacity(h3Event);
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
const session = await sdk.getSession();
|
|
18
|
+
const cookies = flushSetCookies(h3Event, new Response(null)).headers.getSetCookie();
|
|
19
|
+
|
|
20
|
+
for (const cookie of cookies) {
|
|
21
|
+
appendResponseHeader(h3Event, 'set-cookie', cookie);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
useSession().value = toClientSession(session) ?? undefined;
|
|
16
25
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineNuxtPlugin } from '#app';
|
|
2
|
+
import { getClientContext } from '../composables/use-strivacity';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates the browser SDK instance as soon as the Nuxt app starts on the client (it is never created during SSR), and
|
|
6
|
+
* exposes it as `useNuxtApp().$strivacity`. `useStrivacity()` returns the same context.
|
|
7
|
+
*/
|
|
8
|
+
export default defineNuxtPlugin({
|
|
9
|
+
name: 'strivacity',
|
|
10
|
+
setup() {
|
|
11
|
+
return {
|
|
12
|
+
provide: {
|
|
13
|
+
strivacity: getClientContext(),
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { H3Event } from 'h3';
|
|
2
2
|
import type { NitroApp } from 'nitropack/types';
|
|
3
3
|
import type { NuxtServerSDK, NuxtServerSDKInitConfig } from '../../types';
|
|
4
|
-
import { toWebRequest } from 'h3';
|
|
4
|
+
import { toWebRequest, createError } from 'h3';
|
|
5
5
|
import { useRuntimeConfig } from '#imports';
|
|
6
6
|
import { defineNitroPlugin } from 'nitropack/dist/runtime/plugin';
|
|
7
7
|
import { createBaseServerSDK } from '@strivacity/sdk-core/server';
|
|
@@ -16,11 +16,72 @@ declare module 'h3' {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
type BaseSDK = ReturnType<typeof createBaseServerSDK<H3Event>>;
|
|
20
|
+
|
|
21
|
+
export default defineNitroPlugin((nitroApp: NitroApp) => {
|
|
22
|
+
let base: BaseSDK | undefined;
|
|
23
|
+
let setupError: unknown;
|
|
24
|
+
|
|
25
|
+
const ready = setupAuthPlugin()
|
|
26
|
+
.then((result) => {
|
|
27
|
+
base = result;
|
|
28
|
+
})
|
|
29
|
+
.catch((error: unknown) => {
|
|
30
|
+
// NOTE: Nitro calls plugins synchronously and never awaits or catches their result, so a rejection
|
|
31
|
+
// here would otherwise be a silent unhandled rejection. Logging it and falling back to a stub sdk
|
|
32
|
+
// below turns every request into a clear 500 instead of an unrelated "sdk is undefined" error.
|
|
33
|
+
// eslint-disable-next-line no-console
|
|
34
|
+
console.error('[@strivacity/sdk-nuxt] Failed to initialize the Strivacity server SDK:', error);
|
|
35
|
+
setupError = error;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// NOTE: Registered synchronously, before setupAuthPlugin's awaited dynamic imports resolve, so even the
|
|
39
|
+
// very first request right after startup is covered by it - the hook itself just awaits `ready` first.
|
|
40
|
+
nitroApp.hooks.hook('request', async (event: H3Event) => {
|
|
41
|
+
await ready;
|
|
42
|
+
|
|
43
|
+
event.context.strivacity ??= {} as never;
|
|
44
|
+
|
|
45
|
+
if (event.context.strivacity.sdk) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!base) {
|
|
50
|
+
event.context.strivacity.sdk = new Proxy({} as NuxtServerSDK, {
|
|
51
|
+
get: () => {
|
|
52
|
+
throw createError({ statusCode: 500, statusMessage: 'Strivacity SDK failed to initialize', cause: setupError });
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const sdk = base;
|
|
59
|
+
|
|
60
|
+
event.context.strivacity.sdk = {
|
|
61
|
+
get options() {
|
|
62
|
+
return sdk.options;
|
|
63
|
+
},
|
|
64
|
+
getSession: (_event, params) => sdk.getSession(event, params),
|
|
65
|
+
updateSession: (session) => sdk.updateSession(session, event),
|
|
66
|
+
refreshSession: () => sdk.refreshSession(event),
|
|
67
|
+
revokeSession: () => sdk.revokeSession(event),
|
|
68
|
+
getEntrySession: (entryUrl) => sdk.getEntrySession(entryUrl),
|
|
69
|
+
completeLogin: (params) => sdk.completeLogin(params, event),
|
|
70
|
+
logout: (postLogoutRedirectUri) => sdk.logout(postLogoutRedirectUri, event),
|
|
71
|
+
handleLogin: (evt) => sdk.handleLogin(evt),
|
|
72
|
+
handleRegister: (evt) => sdk.handleRegister(evt),
|
|
73
|
+
handleCallback: (evt) => sdk.handleCallback(evt),
|
|
74
|
+
handleRefresh: (evt) => sdk.handleRefresh(evt),
|
|
75
|
+
handleRevoke: (evt) => sdk.handleRevoke(evt),
|
|
76
|
+
handleEntry: (evt) => sdk.handleEntry(evt),
|
|
77
|
+
handleLogout: (evt) => sdk.handleLogout(evt),
|
|
78
|
+
handleBackChannelLogout: (evt) => sdk.handleBackChannelLogout(evt),
|
|
79
|
+
handler: (evt) => sdk.handler(evt),
|
|
80
|
+
};
|
|
81
|
+
});
|
|
21
82
|
});
|
|
22
83
|
|
|
23
|
-
async function setupAuthPlugin(
|
|
84
|
+
async function setupAuthPlugin(): Promise<BaseSDK> {
|
|
24
85
|
const config = useRuntimeConfig();
|
|
25
86
|
const initConfig = { ...config.strivacity } as NuxtServerSDKInitConfig;
|
|
26
87
|
const customStorage = (await import('#strivacity-options-storage')).default();
|
|
@@ -50,35 +111,10 @@ async function setupAuthPlugin(nitroApp: NitroApp) {
|
|
|
50
111
|
initConfig.storage = createEncryptedCookieStorage(initConfig.secret, { defaultCookieOptions: { maxAge: initConfig.cookieMaxAge } });
|
|
51
112
|
}
|
|
52
113
|
|
|
53
|
-
|
|
114
|
+
return createBaseServerSDK<H3Event>(
|
|
54
115
|
{
|
|
55
116
|
toRequest: (event) => toWebRequest(event),
|
|
56
117
|
},
|
|
57
118
|
initConfig,
|
|
58
119
|
);
|
|
59
|
-
|
|
60
|
-
nitroApp.hooks.hook('request', (event: H3Event) => {
|
|
61
|
-
event.context.strivacity ??= {} as never;
|
|
62
|
-
event.context.strivacity.sdk ??= {
|
|
63
|
-
get options() {
|
|
64
|
-
return base.options;
|
|
65
|
-
},
|
|
66
|
-
getSession: () => base.getSession(event),
|
|
67
|
-
updateSession: (session) => base.updateSession(session, event),
|
|
68
|
-
refreshSession: () => base.refreshSession(event),
|
|
69
|
-
revokeSession: () => base.revokeSession(event),
|
|
70
|
-
getEntrySession: (entryUrl) => base.getEntrySession(entryUrl),
|
|
71
|
-
completeLogin: (params) => base.completeLogin(params, event),
|
|
72
|
-
logout: (postLogoutRedirectUri) => base.logout(postLogoutRedirectUri, event),
|
|
73
|
-
handleLogin: (evt) => base.handleLogin(evt),
|
|
74
|
-
handleRegister: (evt) => base.handleRegister(evt),
|
|
75
|
-
handleCallback: (evt) => base.handleCallback(evt),
|
|
76
|
-
handleRefresh: (evt) => base.handleRefresh(evt),
|
|
77
|
-
handleRevoke: (evt) => base.handleRevoke(evt),
|
|
78
|
-
handleEntry: (evt) => base.handleEntry(evt),
|
|
79
|
-
handleLogout: (evt) => base.handleLogout(evt),
|
|
80
|
-
handleBackChannelLogout: (evt) => base.handleBackChannelLogout(evt),
|
|
81
|
-
handler: (evt) => base.handler(evt),
|
|
82
|
-
};
|
|
83
|
-
});
|
|
84
120
|
}
|