@strivacity/sdk-svelte 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 +160 -104
- package/package.json +5 -4
- package/src/client/components.svelte.ts +6 -5
- package/src/client/hooks.svelte.ts +17 -147
- package/src/client/types.ts +16 -102
- package/src/server/sdk.ts +52 -28
- package/testing/tests/components.spec.ts +1 -1
- package/testing/tests/hooks.spec.ts +1 -1
- package/testing/tests/index.spec.ts +10 -0
- package/testing/tests/server/sdk.spec.ts +59 -15
- package/dist/assets/rolldown-runtime.cjs +0 -1
- package/dist/client/components.svelte.cjs +0 -2
- package/dist/client/components.svelte.cjs.map +0 -1
- package/dist/client/components.svelte.d.ts +0 -11
- package/dist/client/components.svelte.mjs +0 -2
- package/dist/client/components.svelte.mjs.map +0 -1
- package/dist/client/hooks.svelte.cjs +0 -2
- package/dist/client/hooks.svelte.cjs.map +0 -1
- package/dist/client/hooks.svelte.d.ts +0 -27
- package/dist/client/hooks.svelte.mjs +0 -2
- package/dist/client/hooks.svelte.mjs.map +0 -1
- package/dist/client/index.d.ts +0 -8
- package/dist/client/index.mjs +0 -1
- package/dist/client/storages.cjs +0 -1
- package/dist/client/storages.d.ts +0 -1
- package/dist/client/storages.mjs +0 -1
- package/dist/client/types.cjs +0 -1
- package/dist/client/types.d.ts +0 -238
- package/dist/client/types.mjs +0 -1
- package/dist/client/utils.cjs +0 -1
- package/dist/client/utils.d.ts +0 -1
- package/dist/client/utils.mjs +0 -1
- package/dist/errors.cjs +0 -1
- package/dist/errors.d.ts +0 -1
- package/dist/errors.mjs +0 -1
- package/dist/server/index.cjs +0 -1
- package/dist/server/index.d.ts +0 -6
- package/dist/server/index.mjs +0 -1
- package/dist/server/sdk.cjs +0 -2
- package/dist/server/sdk.cjs.map +0 -1
- package/dist/server/sdk.d.ts +0 -8
- package/dist/server/sdk.mjs +0 -2
- package/dist/server/sdk.mjs.map +0 -1
- package/dist/server/storages.cjs +0 -2
- package/dist/server/storages.cjs.map +0 -1
- package/dist/server/storages.d.ts +0 -10
- package/dist/server/storages.mjs +0 -2
- package/dist/server/storages.mjs.map +0 -1
- package/dist/server/types.cjs +0 -1
- package/dist/server/types.d.ts +0 -35
- package/dist/server/types.mjs +0 -1
- package/dist/server/utils.cjs +0 -1
- package/dist/server/utils.d.ts +0 -1
- package/dist/server/utils.mjs +0 -1
- package/dist/types.cjs +0 -1
- package/dist/types.d.ts +0 -2
- package/dist/types.mjs +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strivacity/sdk-svelte",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.2",
|
|
4
4
|
"description": "Strivacity Svelte SDK client",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "strivacity <opensource@strivacity.com>",
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/Strivacity/sdk-js"
|
|
10
10
|
},
|
|
11
|
-
"
|
|
12
|
-
"
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"main": "./dist/client/index.cjs",
|
|
13
|
+
"types": "./dist/client/index.d.ts",
|
|
13
14
|
"exports": {
|
|
14
15
|
"./client": {
|
|
15
16
|
"types": "./dist/client/index.d.ts",
|
|
@@ -61,7 +62,7 @@
|
|
|
61
62
|
}
|
|
62
63
|
},
|
|
63
64
|
"dependencies": {
|
|
64
|
-
"@strivacity/sdk-core": "4.0.0-beta.
|
|
65
|
+
"@strivacity/sdk-core": "4.0.0-beta.2"
|
|
65
66
|
},
|
|
66
67
|
"peerDependencies": {
|
|
67
68
|
"@sveltejs/kit": ">=2",
|
|
@@ -4,13 +4,14 @@ import type { IdTokenClaims, RedirectFlow, PopupFlow, EmbeddedFlow, NativeFlow,
|
|
|
4
4
|
import { STRIVACITY_SDK } from './hooks.svelte';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Initializes the Strivacity SDK and stores its reactive state and methods in the Svelte context, so `useStrivacity()` can read them in every descendant component.
|
|
8
|
+
* Call it during the initialization of a root component (e.g. the root `+layout.svelte`). The SDK is only created in the browser: during SSR `sdk` is `undefined` and `loading` stays `true`.
|
|
8
9
|
*
|
|
9
|
-
* @param {StyAuthProviderProps} props - The
|
|
10
|
+
* @param {StyAuthProviderProps} props - The options and the optional session getter used to configure the SDK.
|
|
10
11
|
* @param {SDKInitConfig} props.options - The configuration options for initializing the Strivacity SDK.
|
|
11
|
-
* @param {() => SessionData | null | undefined} [props.session] - Optional getter for session data
|
|
12
|
+
* @param {() => SessionData | null | undefined} [props.session] - Optional getter for the session data the SDK is initialized and kept in sync with.
|
|
12
13
|
*
|
|
13
|
-
* @returns {SDKContext<RedirectFlow | PopupFlow | NativeFlow | EmbeddedFlow>}
|
|
14
|
+
* @returns {SDKContext<RedirectFlow | PopupFlow | NativeFlow | EmbeddedFlow>} The context value that was set, the same object `useStrivacity()` returns.
|
|
14
15
|
*/
|
|
15
16
|
export function createStyAuthProvider(props: StyAuthProviderProps): SDKContext<RedirectFlow | PopupFlow | NativeFlow | EmbeddedFlow> {
|
|
16
17
|
let sdk: SDKInstance;
|
|
@@ -111,7 +112,7 @@ export function createStyAuthProvider(props: StyAuthProviderProps): SDKContext<R
|
|
|
111
112
|
checkAuthentication: (...args: Parameters<typeof sdk.checkAuthentication>) => sdk.checkAuthentication(...args),
|
|
112
113
|
tokenExchange: (...args: Parameters<typeof sdk.tokenExchange>) => sdk.tokenExchange(...args),
|
|
113
114
|
handleCallback: (...args: Parameters<typeof sdk.handleCallback>) => sdk.handleCallback(...args),
|
|
114
|
-
refresh: () => sdk.refresh(),
|
|
115
|
+
refresh: (...args: Parameters<typeof sdk.refresh>) => sdk.refresh(...args),
|
|
115
116
|
revoke: () => sdk.revoke(),
|
|
116
117
|
logout: (...args: Parameters<typeof sdk.logout>) => sdk.logout(...args),
|
|
117
118
|
login: (...args: Parameters<typeof sdk.login>) => sdk.login(...args),
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { getContext, setContext, onMount, onDestroy } from 'svelte';
|
|
2
|
-
import {
|
|
3
|
-
import type { RedirectFlow, PopupFlow, EmbeddedFlow, NativeFlow, NativeParams, NativeFlowState, NativeFlowMessage } from '@strivacity/sdk-core/types';
|
|
2
|
+
import type { RedirectFlow, PopupFlow, EmbeddedFlow, NativeFlow } from '@strivacity/sdk-core/types';
|
|
4
3
|
import type { LoginContext, SDKContext, UseNativeLoginOptions } from '../types';
|
|
5
|
-
import {
|
|
6
|
-
import { unflattenObject } from '@strivacity/sdk-core/utils';
|
|
4
|
+
import { createNativeLoginController } from '@strivacity/sdk-core/utils';
|
|
7
5
|
|
|
8
6
|
export const STRIVACITY_SDK = Symbol('strivacity-sdk');
|
|
9
7
|
export const STRIVACITY_LOGIN_CONTEXT = Symbol('strivacity-login-context');
|
|
@@ -51,149 +49,21 @@ export const useNativeLoginContext = (): LoginContext => {
|
|
|
51
49
|
* @throws {Error} - If the Strivacity SDK context is not found.
|
|
52
50
|
*/
|
|
53
51
|
export function useNativeLogin(options: UseNativeLoginOptions = {}): LoginContext {
|
|
54
|
-
const abortController = new AbortController();
|
|
55
52
|
const ctx = useStrivacity<NativeFlow>();
|
|
53
|
+
const controller = createNativeLoginController(() => ctx.sdk, options);
|
|
54
|
+
let snapshot = $state.raw(controller.getSnapshot());
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
const loading = $derived(snapshot.loading);
|
|
57
|
+
const forms = $derived(snapshot.forms);
|
|
58
|
+
const messages = $derived(snapshot.messages);
|
|
59
|
+
const state = $derived(snapshot.state);
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
await startSession(options.params);
|
|
67
|
-
} catch (error) {
|
|
68
|
-
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
ctx.sdk.logging?.error('Error fetching authorization URI', error as Error);
|
|
73
|
-
void options.onError?.(error as Error);
|
|
74
|
-
loading = false;
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
onDestroy(() => {
|
|
79
|
-
abortController.abort();
|
|
61
|
+
const unsubscribe = controller.subscribe((next) => {
|
|
62
|
+
snapshot = next;
|
|
80
63
|
});
|
|
81
64
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
loading = true;
|
|
85
|
-
|
|
86
|
-
const nextState = await ctx.sdk.startSession(loginParams);
|
|
87
|
-
|
|
88
|
-
if (nextState) {
|
|
89
|
-
handleResponse(nextState);
|
|
90
|
-
}
|
|
91
|
-
} catch (error) {
|
|
92
|
-
if (error instanceof FallbackError) {
|
|
93
|
-
ctx.sdk.logging?.error('Fallback error occurred', error);
|
|
94
|
-
void options.onFallback?.(error);
|
|
95
|
-
} else {
|
|
96
|
-
ctx.sdk.logging?.error('Error starting session', error as Error);
|
|
97
|
-
void options.onError?.(error as Error);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function handleResponse(nextState: Partial<NativeFlowState>) {
|
|
103
|
-
if (ctx.sdk.session) {
|
|
104
|
-
return void options.onLogin?.(ctx.sdk.session);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const newState: NativeFlowState = {
|
|
108
|
-
hostedUrl: nextState?.hostedUrl ?? state.hostedUrl,
|
|
109
|
-
finalizeUrl: nextState?.finalizeUrl ?? state.finalizeUrl,
|
|
110
|
-
screen: nextState?.screen ?? state.screen,
|
|
111
|
-
forms: nextState?.forms ?? state.forms,
|
|
112
|
-
layout: nextState?.layout ?? state.layout,
|
|
113
|
-
messages: nextState?.messages ?? {},
|
|
114
|
-
branding: nextState?.branding ?? state.branding,
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
if (newState.screen !== state.screen) {
|
|
118
|
-
forms = {};
|
|
119
|
-
messages = {};
|
|
120
|
-
|
|
121
|
-
for (const form of newState.forms ?? []) {
|
|
122
|
-
forms[form.id] = {};
|
|
123
|
-
messages[form.id] = {};
|
|
124
|
-
}
|
|
125
|
-
} else {
|
|
126
|
-
ctx.sdk.logging?.info(`Updating screen: ${newState.screen}`);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
for (const formId of Object.keys(newState.messages ?? {})) {
|
|
130
|
-
if (formId === 'global') {
|
|
131
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
|
132
|
-
void options.onGlobalMessage?.(newState.messages?.global!);
|
|
133
|
-
} else {
|
|
134
|
-
messages[formId] = newState.messages![formId] ?? {};
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
state = newState;
|
|
139
|
-
|
|
140
|
-
if (!newState.finalizeUrl) {
|
|
141
|
-
loading = false;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function triggerFallback(message?: string) {
|
|
146
|
-
ctx.sdk.logging?.warn(message ? `Triggering fallback due to: ${message}` : 'Triggering fallback');
|
|
147
|
-
|
|
148
|
-
if (!state.hostedUrl) {
|
|
149
|
-
const error = new Error('No hosted URL provided');
|
|
150
|
-
ctx.sdk.logging?.error('Fallback error', error);
|
|
151
|
-
throw error;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
void options.onFallback?.(new FallbackError(new SvelteURL(state.hostedUrl)));
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function triggerClose() {
|
|
158
|
-
ctx.sdk.logging?.debug('Triggering close');
|
|
159
|
-
void options.onClose?.();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
async function submitForm(formId: string, customBody?: Record<string, unknown>) {
|
|
163
|
-
try {
|
|
164
|
-
loading = true;
|
|
165
|
-
|
|
166
|
-
const nextState = await ctx.sdk.submitForm(formId, customBody ?? unflattenObject(forms[formId] ?? {}));
|
|
167
|
-
|
|
168
|
-
if (nextState) {
|
|
169
|
-
handleResponse(nextState);
|
|
170
|
-
}
|
|
171
|
-
} catch (error) {
|
|
172
|
-
if (error instanceof FallbackError) {
|
|
173
|
-
ctx.sdk.logging?.error('Fallback error occurred', error);
|
|
174
|
-
void options.onFallback?.(error);
|
|
175
|
-
} else {
|
|
176
|
-
ctx.sdk.logging?.error('Error submitting form', error as Error);
|
|
177
|
-
void options.onError?.(error as Error);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function setFormValue(formId: string, widgetId: string, value: unknown) {
|
|
183
|
-
if (forms[formId] === undefined) {
|
|
184
|
-
forms[formId] = {};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
forms[formId][widgetId] = value === '' ? null : value;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function setMessage(formId: string, widgetId: string, value: NativeFlowMessage) {
|
|
191
|
-
if (messages[formId] === undefined) {
|
|
192
|
-
messages[formId] = {};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
messages[formId][widgetId] = value;
|
|
196
|
-
}
|
|
65
|
+
onMount(() => controller.start());
|
|
66
|
+
onDestroy(unsubscribe);
|
|
197
67
|
|
|
198
68
|
const context: LoginContext = {
|
|
199
69
|
get loading() {
|
|
@@ -208,11 +78,11 @@ export function useNativeLogin(options: UseNativeLoginOptions = {}): LoginContex
|
|
|
208
78
|
get state() {
|
|
209
79
|
return state;
|
|
210
80
|
},
|
|
211
|
-
triggerFallback,
|
|
212
|
-
triggerClose,
|
|
213
|
-
submitForm,
|
|
214
|
-
setFormValue,
|
|
215
|
-
setMessage,
|
|
81
|
+
triggerFallback: controller.triggerFallback,
|
|
82
|
+
triggerClose: controller.triggerClose,
|
|
83
|
+
submitForm: controller.submitForm,
|
|
84
|
+
setFormValue: controller.setFormValue,
|
|
85
|
+
setMessage: controller.setMessage,
|
|
216
86
|
};
|
|
217
87
|
|
|
218
88
|
setContext(STRIVACITY_LOGIN_CONTEXT, context);
|
package/src/client/types.ts
CHANGED
|
@@ -5,19 +5,19 @@ import type {
|
|
|
5
5
|
NativeFlow,
|
|
6
6
|
EmbeddedFlow,
|
|
7
7
|
initFlow,
|
|
8
|
-
NativeParams,
|
|
9
8
|
NativeFlowState,
|
|
10
9
|
SessionData,
|
|
11
10
|
NativeFlowMessage,
|
|
12
11
|
SDKInitConfig,
|
|
12
|
+
NativeLoginControllerOptions,
|
|
13
13
|
} from '@strivacity/sdk-core';
|
|
14
|
-
import type { FallbackError } from '@strivacity/sdk-core/utils';
|
|
15
14
|
|
|
16
15
|
export * from '@strivacity/sdk-core/types';
|
|
17
16
|
|
|
18
17
|
export type SDKContext<Flow extends RedirectFlow | PopupFlow | NativeFlow | EmbeddedFlow> = {
|
|
19
18
|
/**
|
|
20
19
|
* The underlying Strivacity SDK flow instance.
|
|
20
|
+
* It is created in the browser only, so during SSR it is `undefined` and the SDK methods of this context must not be called.
|
|
21
21
|
*/
|
|
22
22
|
readonly sdk: Flow;
|
|
23
23
|
|
|
@@ -32,7 +32,7 @@ export type SDKContext<Flow extends RedirectFlow | PopupFlow | NativeFlow | Embe
|
|
|
32
32
|
readonly language: string;
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
35
|
+
* Whether the user is authenticated.
|
|
36
36
|
*/
|
|
37
37
|
readonly isAuthenticated: boolean;
|
|
38
38
|
|
|
@@ -52,7 +52,7 @@ export type SDKContext<Flow extends RedirectFlow | PopupFlow | NativeFlow | Embe
|
|
|
52
52
|
readonly refreshToken: string | null;
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
*
|
|
55
|
+
* Whether the access token has expired.
|
|
56
56
|
*/
|
|
57
57
|
readonly accessTokenExpired: boolean;
|
|
58
58
|
|
|
@@ -155,10 +155,10 @@ export type LoginContext = {
|
|
|
155
155
|
submitForm: (formId: string, customBody?: Record<string, unknown>) => Promise<void>;
|
|
156
156
|
|
|
157
157
|
/**
|
|
158
|
-
*
|
|
158
|
+
* Hands the flow over to the hosted login UI: calls `onFallback` with a `FallbackError` carrying the hosted URL, so the app can redirect there.
|
|
159
159
|
*
|
|
160
|
-
* @param message - Optional message to log
|
|
161
|
-
* @
|
|
160
|
+
* @param message - Optional message to log as the fallback reason.
|
|
161
|
+
* @throws {Error} When the IdP has not provided a hosted URL yet.
|
|
162
162
|
*/
|
|
163
163
|
triggerFallback: (message?: string) => void;
|
|
164
164
|
|
|
@@ -181,19 +181,19 @@ export type LoginContext = {
|
|
|
181
181
|
*
|
|
182
182
|
* @param formId - The ID of the form containing the widget.
|
|
183
183
|
* @param widgetId - The ID of the widget to set the message for.
|
|
184
|
-
* @param value - The message to set,
|
|
184
|
+
* @param value - The message to set, e.g. `{ type: 'error', text: 'Required' }`.
|
|
185
185
|
*/
|
|
186
186
|
setMessage: (formId: string, widgetId: string, value: NativeFlowMessage) => void;
|
|
187
187
|
};
|
|
188
188
|
|
|
189
189
|
export type StyAuthProviderProps = {
|
|
190
190
|
/**
|
|
191
|
-
* The options to
|
|
191
|
+
* The options passed to `initFlow` when `createStyAuthProvider` creates the SDK.
|
|
192
192
|
*/
|
|
193
193
|
options: SDKInitConfig;
|
|
194
194
|
|
|
195
195
|
/**
|
|
196
|
-
* A getter returning the session data
|
|
196
|
+
* A getter returning the session data the SDK is initialized with, typically loaded on the server for server-managed sessions.
|
|
197
197
|
*
|
|
198
198
|
* Called reactively (inside a Svelte effect) whenever the returned value changes, so it should read from a
|
|
199
199
|
* reactive source (e.g. a `$props()` field) rather than a plain snapshot. If not provided, the AuthProvider
|
|
@@ -203,97 +203,11 @@ export type StyAuthProviderProps = {
|
|
|
203
203
|
};
|
|
204
204
|
|
|
205
205
|
/**
|
|
206
|
-
* The reactive context value exposed to native login flow widgets via `useNativeLoginContext()
|
|
206
|
+
* The reactive context value exposed to native login flow widgets via `useNativeLoginContext()`.
|
|
207
207
|
*/
|
|
208
|
-
export type NativeFlowContextValue =
|
|
209
|
-
/**
|
|
210
|
-
* Whether a form submission or session initialization is in progress.
|
|
211
|
-
*/
|
|
212
|
-
loading: boolean;
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Current form field values, keyed by form ID then widget ID.
|
|
216
|
-
*/
|
|
217
|
-
forms: Record<string, Record<string, unknown>>;
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Current validation and info messages, keyed by form ID then widget ID.
|
|
221
|
-
*/
|
|
222
|
-
messages: Record<string, Record<string, NativeFlowMessage>>;
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* The current login flow state returned by the backend.
|
|
226
|
-
*/
|
|
227
|
-
state: Partial<NativeFlowState>;
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Submits the form with the given ID and advances the login flow.
|
|
231
|
-
*
|
|
232
|
-
* @param formId - The ID of the form to submit.
|
|
233
|
-
* @param customBody - Optional custom body to send with the form submission. If not provided, the current form values will be used.
|
|
234
|
-
* @returns A promise that resolves when the form submission is complete.
|
|
235
|
-
*/
|
|
236
|
-
submitForm: (formId: string, customBody?: Record<string, unknown>) => Promise<void>;
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Redirects to the hosted login UI.
|
|
240
|
-
*
|
|
241
|
-
* @param message - Optional message to log before redirecting.
|
|
242
|
-
*/
|
|
243
|
-
triggerFallback: (message?: string) => void;
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Signals that the login flow was closed by the user.
|
|
247
|
-
*/
|
|
248
|
-
triggerClose: () => void;
|
|
208
|
+
export type NativeFlowContextValue = LoginContext;
|
|
249
209
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
* @param widgetId - The ID of the widget (field) to update.
|
|
255
|
-
* @param value - The new value to set for the field.
|
|
256
|
-
*/
|
|
257
|
-
setFormValue: (formId: string, widgetId: string, value: unknown) => void;
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Sets a validation or info message on a specific widget.
|
|
261
|
-
*
|
|
262
|
-
* @param formId - The ID of the form containing the widget.
|
|
263
|
-
* @param widgetId - The ID of the widget to set the message for.
|
|
264
|
-
* @param value - The message to set.
|
|
265
|
-
*/
|
|
266
|
-
setMessage: (formId: string, widgetId: string, value: NativeFlowMessage) => void;
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
export type UseNativeLoginOptions = {
|
|
270
|
-
/**
|
|
271
|
-
* Optional parameters to be passed to the login session request. These parameters will be sent to the `authorizationUri` endpoint.
|
|
272
|
-
*/
|
|
273
|
-
params?: NativeParams;
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Called when the user successfully completes the login flow.
|
|
277
|
-
*/
|
|
278
|
-
onLogin?: (session: SessionData) => void | Promise<void>;
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Called when the fallback flow is triggered, typically due to an error or unsupported environment. Receives a `FallbackError` object containing the hosted URL to redirect to.
|
|
282
|
-
*/
|
|
283
|
-
onFallback?: (error: FallbackError) => void | Promise<void>;
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Called when the login flow is closed by the user.
|
|
287
|
-
*/
|
|
288
|
-
onClose?: () => void | Promise<void>;
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Called when an error occurs during the login session initialization or flow.
|
|
292
|
-
*/
|
|
293
|
-
onError?: (error: Error) => void | Promise<void>;
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Called when a global message is received during the login flow.
|
|
297
|
-
*/
|
|
298
|
-
onGlobalMessage?: (message: NativeFlowMessage) => void | Promise<void>;
|
|
299
|
-
};
|
|
210
|
+
/**
|
|
211
|
+
* Callbacks and parameters of the native login flow. See `NativeLoginControllerOptions` in `@strivacity/sdk-core`.
|
|
212
|
+
*/
|
|
213
|
+
export type UseNativeLoginOptions = NativeLoginControllerOptions;
|
package/src/server/sdk.ts
CHANGED
|
@@ -2,9 +2,37 @@ import type { RequestEvent, Handle } from '@sveltejs/kit';
|
|
|
2
2
|
import type { SessionData } from '@strivacity/sdk-core/types';
|
|
3
3
|
import type { SvelteKitServerSDK, SvelteKitServerSDKInitConfig } from './types';
|
|
4
4
|
import { redirect } from '@sveltejs/kit';
|
|
5
|
-
import {
|
|
5
|
+
import { flushSetCookies } from '@strivacity/sdk-core/utils';
|
|
6
6
|
import { createBaseServerSDK } from '@strivacity/sdk-core/server';
|
|
7
7
|
|
|
8
|
+
const canonicalEvents = new WeakMap<object, RequestEvent>();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* SvelteKit hands shallow copies of the request event to `load`, `resolve()` and each `sequence()` handler, while the core tracks queued `Set-Cookie` headers per event object.
|
|
12
|
+
* Map every copy back to the first event seen for the request (keyed by the `locals` object, which all copies share) so cookies queued from `load` / `+server.ts` are flushed by `handle`.
|
|
13
|
+
*
|
|
14
|
+
* @template T - The type of the request event, typically `RequestEvent` or `undefined`.
|
|
15
|
+
* @param event - The request event to canonicalize.
|
|
16
|
+
* @returns The canonical request event, which may be the same as the input or a previously seen event.
|
|
17
|
+
*/
|
|
18
|
+
function canonical<T extends RequestEvent | undefined>(event: T): T {
|
|
19
|
+
const key = event?.locals;
|
|
20
|
+
|
|
21
|
+
if (!key) {
|
|
22
|
+
return event;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const known = canonicalEvents.get(key);
|
|
26
|
+
|
|
27
|
+
if (known) {
|
|
28
|
+
return known as T;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
canonicalEvents.set(key, event);
|
|
32
|
+
|
|
33
|
+
return event;
|
|
34
|
+
}
|
|
35
|
+
|
|
8
36
|
/**
|
|
9
37
|
* Creates the Strivacity server SDK for SvelteKit: session management, auth route handlers, and the `handle` hook used in `hooks.server.ts`.
|
|
10
38
|
*
|
|
@@ -19,16 +47,11 @@ export function createServerSDK(initConfig: SvelteKitServerSDKInitConfig): Svelt
|
|
|
19
47
|
initConfig,
|
|
20
48
|
);
|
|
21
49
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
session = await base.refreshSession(event);
|
|
28
|
-
} catch {
|
|
29
|
-
session = null;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
50
|
+
// NOTE: SvelteKit runs the layout and page `load` functions concurrently, each with its own event copy: the canonical event makes
|
|
51
|
+
// them share the core's once-per-request refresh (`getSession`), and `handle` writes the rotated cookie.
|
|
52
|
+
async function requireSession(requestEvent: RequestEvent, guardOpts: { returnTo?: string } = {}): Promise<SessionData> {
|
|
53
|
+
const event = canonical(requestEvent);
|
|
54
|
+
const session = await base.getSession(event);
|
|
32
55
|
|
|
33
56
|
if (!session) {
|
|
34
57
|
const loginUrl = ['embedded', 'native'].includes(base.options.mode) ? base.options.loginUri : `${base.options.authUrlPrefix}/login`;
|
|
@@ -46,9 +69,10 @@ export function createServerSDK(initConfig: SvelteKitServerSDKInitConfig): Svelt
|
|
|
46
69
|
}
|
|
47
70
|
|
|
48
71
|
const handle: Handle = async ({ event, resolve }) => {
|
|
49
|
-
const
|
|
72
|
+
const requestEvent = canonical(event);
|
|
73
|
+
const response = await base.handler(requestEvent);
|
|
50
74
|
|
|
51
|
-
return response ?? resolve(event);
|
|
75
|
+
return response ?? flushSetCookies(requestEvent, await resolve(event));
|
|
52
76
|
};
|
|
53
77
|
|
|
54
78
|
return {
|
|
@@ -57,21 +81,21 @@ export function createServerSDK(initConfig: SvelteKitServerSDKInitConfig): Svelt
|
|
|
57
81
|
},
|
|
58
82
|
handle,
|
|
59
83
|
requireSession,
|
|
60
|
-
getSession: (event) => base.getSession(event),
|
|
61
|
-
updateSession: (session, event) => base.updateSession(session, event),
|
|
62
|
-
refreshSession: (event) => base.refreshSession(event),
|
|
63
|
-
revokeSession: (event) => base.revokeSession(event),
|
|
84
|
+
getSession: (event, params) => base.getSession(canonical(event), params),
|
|
85
|
+
updateSession: (session, event) => base.updateSession(session, canonical(event)),
|
|
86
|
+
refreshSession: (event) => base.refreshSession(canonical(event)),
|
|
87
|
+
revokeSession: (event) => base.revokeSession(canonical(event)),
|
|
64
88
|
getEntrySession: (entryUrl) => base.getEntrySession(entryUrl),
|
|
65
|
-
completeLogin: (params, event) => base.completeLogin(params, event),
|
|
66
|
-
logout: (postLogoutRedirectUri, event) => base.logout(postLogoutRedirectUri, event),
|
|
67
|
-
handleLogin: (event) => base.handleLogin(event),
|
|
68
|
-
handleRegister: (event) => base.handleRegister(event),
|
|
69
|
-
handleCallback: (event) => base.handleCallback(event),
|
|
70
|
-
handleRefresh: (event) => base.handleRefresh(event),
|
|
71
|
-
handleRevoke: (event) => base.handleRevoke(event),
|
|
72
|
-
handleEntry: (event) => base.handleEntry(event),
|
|
73
|
-
handleLogout: (event) => base.handleLogout(event),
|
|
74
|
-
handleBackChannelLogout: (event) => base.handleBackChannelLogout(event),
|
|
75
|
-
handler: (event) => base.handler(event),
|
|
89
|
+
completeLogin: (params, event) => base.completeLogin(params, canonical(event)),
|
|
90
|
+
logout: (postLogoutRedirectUri, event) => base.logout(postLogoutRedirectUri, canonical(event)),
|
|
91
|
+
handleLogin: (event) => base.handleLogin(canonical(event)),
|
|
92
|
+
handleRegister: (event) => base.handleRegister(canonical(event)),
|
|
93
|
+
handleCallback: (event) => base.handleCallback(canonical(event)),
|
|
94
|
+
handleRefresh: (event) => base.handleRefresh(canonical(event)),
|
|
95
|
+
handleRevoke: (event) => base.handleRevoke(canonical(event)),
|
|
96
|
+
handleEntry: (event) => base.handleEntry(canonical(event)),
|
|
97
|
+
handleLogout: (event) => base.handleLogout(canonical(event)),
|
|
98
|
+
handleBackChannelLogout: (event) => base.handleBackChannelLogout(canonical(event)),
|
|
99
|
+
handler: (event) => base.handler(canonical(event)),
|
|
76
100
|
};
|
|
77
101
|
}
|
|
@@ -62,7 +62,7 @@ describe('createStyAuthProvider', () => {
|
|
|
62
62
|
['checkAuthentication', [{ autoRefresh: false }], true],
|
|
63
63
|
['tokenExchange', [{ code: 'abc' }], undefined],
|
|
64
64
|
['handleCallback', ['https://brandtegrity.io/callback?code=abc'], undefined],
|
|
65
|
-
['refresh', [], undefined],
|
|
65
|
+
['refresh', [{ returnTo: '/orders' }], undefined],
|
|
66
66
|
['revoke', [], undefined],
|
|
67
67
|
['logout', [{ postLogoutRedirectUri: 'https://brandtegrity.io' }], undefined],
|
|
68
68
|
['login', [{ scopes: ['openid'] }], undefined],
|
|
@@ -112,7 +112,7 @@ describe('useNativeLogin', () => {
|
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
test('calls onLogin and leaves the tracked state untouched when the sdk already has a session', async () => {
|
|
115
|
-
const session = { access_token: 'access-token' } as unknown as SessionData;
|
|
115
|
+
const session = { access_token: 'access-token', expires_at: Math.floor(Date.now() / 1000) + 3600 } as unknown as SessionData;
|
|
116
116
|
const sdk = createMockFlow<NativeFlow>({ session });
|
|
117
117
|
vi.mocked(sdk.startSession).mockResolvedValue({ screen: 'identifier' });
|
|
118
118
|
const onLogin = vi.fn();
|
|
@@ -35,6 +35,7 @@ describe('client', () => {
|
|
|
35
35
|
'buildEndSessionUrl',
|
|
36
36
|
'createDefaultLogging',
|
|
37
37
|
'createHttpClient',
|
|
38
|
+
'createNativeLoginController',
|
|
38
39
|
'createSession',
|
|
39
40
|
'createState',
|
|
40
41
|
'createWebAuthnCredential',
|
|
@@ -72,6 +73,7 @@ describe('client', () => {
|
|
|
72
73
|
'throwHttpError',
|
|
73
74
|
'throwTokenEndpointError',
|
|
74
75
|
'timestamp',
|
|
76
|
+
'toClientSession',
|
|
75
77
|
'toSafeRedirect',
|
|
76
78
|
'unflattenObject',
|
|
77
79
|
'verifyIdToken',
|
|
@@ -94,7 +96,10 @@ describe('client', () => {
|
|
|
94
96
|
'createSessionIdCookieStorage',
|
|
95
97
|
'createSessionStorage',
|
|
96
98
|
'createWorkerStorage',
|
|
99
|
+
'deleteEncryptedCookie',
|
|
97
100
|
'handleWorkerStorageRequests',
|
|
101
|
+
'readEncryptedCookie',
|
|
102
|
+
'writeEncryptedCookie',
|
|
98
103
|
'AlgorithmTypeList',
|
|
99
104
|
'GrantTypeList',
|
|
100
105
|
'ResponseModeList',
|
|
@@ -131,6 +136,7 @@ describe('server', () => {
|
|
|
131
136
|
'buildEndSessionUrl',
|
|
132
137
|
'createDefaultLogging',
|
|
133
138
|
'createHttpClient',
|
|
139
|
+
'createNativeLoginController',
|
|
134
140
|
'createSession',
|
|
135
141
|
'createState',
|
|
136
142
|
'createWebAuthnCredential',
|
|
@@ -168,6 +174,7 @@ describe('server', () => {
|
|
|
168
174
|
'throwHttpError',
|
|
169
175
|
'throwTokenEndpointError',
|
|
170
176
|
'timestamp',
|
|
177
|
+
'toClientSession',
|
|
171
178
|
'toSafeRedirect',
|
|
172
179
|
'unflattenObject',
|
|
173
180
|
'verifyIdToken',
|
|
@@ -185,7 +192,10 @@ describe('server', () => {
|
|
|
185
192
|
'createServerStateStorage',
|
|
186
193
|
'createSessionStorage',
|
|
187
194
|
'createWorkerStorage',
|
|
195
|
+
'deleteEncryptedCookie',
|
|
188
196
|
'handleWorkerStorageRequests',
|
|
197
|
+
'readEncryptedCookie',
|
|
198
|
+
'writeEncryptedCookie',
|
|
189
199
|
'AlgorithmTypeList',
|
|
190
200
|
'GrantTypeList',
|
|
191
201
|
'ResponseModeList',
|