@strivacity/sdk-angular 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 +108 -62
- package/dist/fesm2022/strivacity-sdk-angular.mjs +36 -131
- package/package.json +2 -2
- package/src/lib/services/auth.service.ts +2 -2
- package/src/lib/services/native-login.service.ts +20 -140
- package/src/server/sdk.ts +1 -1
- package/src/server/session.ts +17 -3
- package/src/server/types.ts +3 -3
- package/src/types/index.ts +5 -33
- package/testing/tests/auth.service.spec.ts +1 -1
- package/testing/tests/index.spec.ts +7 -0
- package/testing/tests/native-login.service.spec.ts +11 -15
- package/testing/tests/server/sdk.spec.ts +1 -1
- package/testing/tests/server/session.spec.ts +51 -6
- package/dist/README.md +0 -2217
- package/dist/fesm2022/strivacity-sdk-angular-src-server.mjs +0 -221
- package/dist/fesm2022/strivacity-sdk-angular-src-server.mjs.map +0 -1
- package/dist/fesm2022/strivacity-sdk-angular-src-types.mjs +0 -6
- package/dist/fesm2022/strivacity-sdk-angular-src-types.mjs.map +0 -1
- package/dist/fesm2022/strivacity-sdk-angular.mjs.map +0 -1
- package/dist/types/strivacity-sdk-angular-src-server.d.ts +0 -82
- package/dist/types/strivacity-sdk-angular-src-types.d.ts +0 -41
- package/dist/types/strivacity-sdk-angular.d.ts +0 -147
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
export * from '@strivacity/sdk-core/flows';
|
|
2
2
|
export * from '@strivacity/sdk-core/handlers';
|
|
3
|
-
import {
|
|
3
|
+
import { flushSetCookies, toClientSession, createNativeLoginController } from '@strivacity/sdk-core/utils';
|
|
4
4
|
export * from '@strivacity/sdk-core/utils';
|
|
5
5
|
import * as i0 from '@angular/core';
|
|
6
|
-
import { InjectionToken, CUSTOM_ELEMENTS_SCHEMA, NgModule, makeStateKey, provideAppInitializer, inject, REQUEST, TransferState, signal, PLATFORM_ID, DestroyRef, Injectable } from '@angular/core';
|
|
6
|
+
import { InjectionToken, CUSTOM_ELEMENTS_SCHEMA, NgModule, makeStateKey, provideAppInitializer, inject, REQUEST, TransferState, RESPONSE_INIT, signal, PLATFORM_ID, DestroyRef, Injectable, computed } from '@angular/core';
|
|
7
7
|
import { isPlatformBrowser } from '@angular/common';
|
|
8
8
|
import { initFlow } from '@strivacity/sdk-core';
|
|
9
|
-
import { FallbackError } from '@strivacity/sdk-core/utils/errors';
|
|
10
9
|
export { COOKIE_CHUNK_SIZE, COOKIE_CONTEXT, createCacheAPIStorage, createIndexedDBStorage, createLocalStorage, createMemoryStorage, createServerMemoryStorage, createSessionStorage, createWorkerStorage, handleWorkerStorageRequests } from '@strivacity/sdk-core/storages';
|
|
11
10
|
|
|
12
11
|
const STRIVACITY_SDK = new InjectionToken('strivacity-sdk');
|
|
@@ -51,7 +50,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
51
50
|
const SESSION_TRANSFER_KEY = makeStateKey('sty.session');
|
|
52
51
|
/**
|
|
53
52
|
* Loads the current session from the encrypted cookie storage (using Angular's `REQUEST` token) before the app
|
|
54
|
-
* renders, and hands it over to the client through `TransferState
|
|
53
|
+
* renders, and hands it over to the client through `TransferState` (without the refresh and ID tokens, which stay server-side), so `StrivacityAuthService` can hydrate without
|
|
55
54
|
* an extra round-trip or a loading flash.
|
|
56
55
|
*
|
|
57
56
|
* Add this to the server-only `ApplicationConfig` (e.g. `app.config.server.ts`), alongside `provideServerRendering()`.
|
|
@@ -66,8 +65,17 @@ function provideStrivacityServerSession(serverSdk) {
|
|
|
66
65
|
return;
|
|
67
66
|
}
|
|
68
67
|
const transferState = inject(TransferState);
|
|
68
|
+
const responseInit = inject(RESPONSE_INIT, { optional: true });
|
|
69
69
|
const session = await serverSdk.getSession(request);
|
|
70
|
-
|
|
70
|
+
const cookies = flushSetCookies(request, new Response(null)).headers.getSetCookie();
|
|
71
|
+
if (responseInit && cookies.length) {
|
|
72
|
+
const headers = new Headers(responseInit.headers);
|
|
73
|
+
for (const cookie of cookies) {
|
|
74
|
+
headers.append('set-cookie', cookie);
|
|
75
|
+
}
|
|
76
|
+
responseInit.headers = headers;
|
|
77
|
+
}
|
|
78
|
+
transferState.set(SESSION_TRANSFER_KEY, toClientSession(session));
|
|
71
79
|
});
|
|
72
80
|
}
|
|
73
81
|
|
|
@@ -136,8 +144,8 @@ class StrivacityAuthService {
|
|
|
136
144
|
handleCallback(...args) {
|
|
137
145
|
return this.sdk.handleCallback(...args);
|
|
138
146
|
}
|
|
139
|
-
refresh() {
|
|
140
|
-
return this.sdk.refresh();
|
|
147
|
+
refresh(...args) {
|
|
148
|
+
return this.sdk.refresh(...args);
|
|
141
149
|
}
|
|
142
150
|
revoke() {
|
|
143
151
|
return this.sdk.revoke();
|
|
@@ -191,22 +199,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
191
199
|
class StrivacityNativeLoginService {
|
|
192
200
|
constructor() {
|
|
193
201
|
this.authService = inject(StrivacityAuthService);
|
|
194
|
-
this.
|
|
195
|
-
this.
|
|
196
|
-
|
|
197
|
-
this.
|
|
198
|
-
...(ngDevMode ? [{ debugName: "
|
|
199
|
-
this.
|
|
200
|
-
...(ngDevMode ? [{ debugName: "
|
|
201
|
-
this.
|
|
202
|
-
...(ngDevMode ? [{ debugName: "
|
|
203
|
-
this.
|
|
204
|
-
...(ngDevMode ? [{ debugName: "
|
|
205
|
-
this.
|
|
206
|
-
|
|
207
|
-
this.messages = this.messagesSignal.asReadonly();
|
|
208
|
-
this.state = this.stateSignal.asReadonly();
|
|
209
|
-
inject(DestroyRef).onDestroy(() => this.abortController.abort());
|
|
202
|
+
this.controller = createNativeLoginController(() => this.authService.sdk);
|
|
203
|
+
this.snapshot = signal(this.controller.getSnapshot(), /* @ts-ignore */
|
|
204
|
+
...(ngDevMode ? [{ debugName: "snapshot" }] : /* istanbul ignore next */ []));
|
|
205
|
+
this.loading = computed(() => this.snapshot().loading, /* @ts-ignore */
|
|
206
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
207
|
+
this.forms = computed(() => this.snapshot().forms, /* @ts-ignore */
|
|
208
|
+
...(ngDevMode ? [{ debugName: "forms" }] : /* istanbul ignore next */ []));
|
|
209
|
+
this.messages = computed(() => this.snapshot().messages, /* @ts-ignore */
|
|
210
|
+
...(ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []));
|
|
211
|
+
this.state = computed(() => this.snapshot().state, /* @ts-ignore */
|
|
212
|
+
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
213
|
+
const unsubscribe = this.controller.subscribe((snapshot) => this.snapshot.set(snapshot));
|
|
214
|
+
inject(DestroyRef).onDestroy(unsubscribe);
|
|
210
215
|
}
|
|
211
216
|
/**
|
|
212
217
|
* Starts a native login flow session. Should be called once, from the page component that owns this service.
|
|
@@ -214,123 +219,23 @@ class StrivacityNativeLoginService {
|
|
|
214
219
|
* @param {NativeLoginOptions} [options] - Optional options for the login session.
|
|
215
220
|
*/
|
|
216
221
|
async start(options = {}) {
|
|
217
|
-
this.options
|
|
218
|
-
await this.
|
|
219
|
-
try {
|
|
220
|
-
await this.startSession(options.params);
|
|
221
|
-
}
|
|
222
|
-
catch (error) {
|
|
223
|
-
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
this.sdk.logging?.error('Error fetching authorization URI', error);
|
|
227
|
-
await this.options.onError?.(error);
|
|
228
|
-
this.loadingSignal.set(false);
|
|
229
|
-
}
|
|
222
|
+
this.controller.setOptions(options);
|
|
223
|
+
await this.controller.start();
|
|
230
224
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
this.loadingSignal.set(true);
|
|
234
|
-
const nextState = await this.sdk.submitForm(formId, customBody ?? unflattenObject(this.formsSignal()[formId] ?? {}));
|
|
235
|
-
if (nextState) {
|
|
236
|
-
await this.handleResponse(nextState);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
catch (error) {
|
|
240
|
-
if (error instanceof FallbackError) {
|
|
241
|
-
this.sdk.logging?.error('Fallback error occurred', error);
|
|
242
|
-
await this.options.onFallback?.(error);
|
|
243
|
-
}
|
|
244
|
-
else {
|
|
245
|
-
this.sdk.logging?.error('Error submitting form', error);
|
|
246
|
-
await this.options.onError?.(error);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
225
|
+
submitForm(formId, customBody) {
|
|
226
|
+
return this.controller.submitForm(formId, customBody);
|
|
249
227
|
}
|
|
250
228
|
setFormValue(formId, widgetId, value) {
|
|
251
|
-
|
|
252
|
-
forms[formId] = { ...(forms[formId] ?? {}), [widgetId]: value === '' ? null : value };
|
|
253
|
-
this.formsSignal.set(forms);
|
|
229
|
+
this.controller.setFormValue(formId, widgetId, value);
|
|
254
230
|
}
|
|
255
231
|
setMessage(formId, widgetId, value) {
|
|
256
|
-
|
|
257
|
-
messages[formId] = { ...(messages[formId] ?? {}), [widgetId]: value };
|
|
258
|
-
this.messagesSignal.set(messages);
|
|
232
|
+
this.controller.setMessage(formId, widgetId, value);
|
|
259
233
|
}
|
|
260
234
|
triggerFallback(message) {
|
|
261
|
-
this.
|
|
262
|
-
const hostedUrl = this.stateSignal().hostedUrl;
|
|
263
|
-
if (!hostedUrl) {
|
|
264
|
-
const error = new Error('No hosted URL provided');
|
|
265
|
-
this.sdk.logging?.error('Fallback error', error);
|
|
266
|
-
throw error;
|
|
267
|
-
}
|
|
268
|
-
void this.options.onFallback?.(new FallbackError(new URL(hostedUrl)));
|
|
235
|
+
this.controller.triggerFallback(message);
|
|
269
236
|
}
|
|
270
237
|
triggerClose() {
|
|
271
|
-
this.
|
|
272
|
-
void this.options.onClose?.();
|
|
273
|
-
}
|
|
274
|
-
async startSession(loginParams = {}) {
|
|
275
|
-
try {
|
|
276
|
-
this.loadingSignal.set(true);
|
|
277
|
-
const nextState = await this.sdk.startSession(loginParams);
|
|
278
|
-
if (nextState) {
|
|
279
|
-
await this.handleResponse(nextState);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
catch (error) {
|
|
283
|
-
if (error instanceof FallbackError) {
|
|
284
|
-
this.sdk.logging?.error('Fallback error occurred', error);
|
|
285
|
-
await this.options.onFallback?.(error);
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
this.sdk.logging?.error('Error starting session', error);
|
|
289
|
-
await this.options.onError?.(error);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
async handleResponse(nextState) {
|
|
294
|
-
if (this.sdk.session) {
|
|
295
|
-
return await this.options.onLogin?.(this.sdk.session);
|
|
296
|
-
}
|
|
297
|
-
const currentState = this.stateSignal();
|
|
298
|
-
const newState = {
|
|
299
|
-
hostedUrl: nextState.hostedUrl ?? currentState.hostedUrl,
|
|
300
|
-
finalizeUrl: nextState.finalizeUrl ?? currentState.finalizeUrl,
|
|
301
|
-
screen: nextState.screen ?? currentState.screen,
|
|
302
|
-
forms: nextState.forms ?? currentState.forms,
|
|
303
|
-
layout: nextState.layout ?? currentState.layout,
|
|
304
|
-
messages: nextState.messages ?? {},
|
|
305
|
-
branding: nextState.branding ?? currentState.branding,
|
|
306
|
-
};
|
|
307
|
-
if (newState.screen !== currentState.screen) {
|
|
308
|
-
const nextForms = {};
|
|
309
|
-
const nextMessages = {};
|
|
310
|
-
for (const form of newState.forms ?? []) {
|
|
311
|
-
nextForms[form.id] = {};
|
|
312
|
-
nextMessages[form.id] = {};
|
|
313
|
-
}
|
|
314
|
-
this.formsSignal.set(nextForms);
|
|
315
|
-
this.messagesSignal.set(nextMessages);
|
|
316
|
-
}
|
|
317
|
-
else {
|
|
318
|
-
this.sdk.logging?.info(`Updating screen: ${newState.screen}`);
|
|
319
|
-
}
|
|
320
|
-
const nextMessageMap = { ...this.messagesSignal() };
|
|
321
|
-
for (const formId of Object.keys(newState.messages ?? {})) {
|
|
322
|
-
if (formId === 'global') {
|
|
323
|
-
await this.options.onGlobalMessage?.(newState.messages.global);
|
|
324
|
-
}
|
|
325
|
-
else {
|
|
326
|
-
nextMessageMap[formId] = newState.messages[formId] ?? {};
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
this.messagesSignal.set(nextMessageMap);
|
|
330
|
-
this.stateSignal.set(newState);
|
|
331
|
-
if (!newState.finalizeUrl) {
|
|
332
|
-
this.loadingSignal.set(false);
|
|
333
|
-
}
|
|
238
|
+
this.controller.triggerClose();
|
|
334
239
|
}
|
|
335
240
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrivacityNativeLoginService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
336
241
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrivacityNativeLoginService }); }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strivacity/sdk-angular",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.2",
|
|
4
4
|
"description": "Strivacity Angular SDK client",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "strivacity <opensource@strivacity.com>",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@strivacity/sdk-core": "4.0.0-beta.
|
|
35
|
+
"@strivacity/sdk-core": "4.0.0-beta.2"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@angular/core": ">=20",
|
|
@@ -76,8 +76,8 @@ export class StrivacityAuthService {
|
|
|
76
76
|
return this.sdk.handleCallback(...args);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
refresh(): ReturnType<typeof this.sdk.refresh> {
|
|
80
|
-
return this.sdk.refresh();
|
|
79
|
+
refresh(...args: Parameters<typeof this.sdk.refresh>): ReturnType<typeof this.sdk.refresh> {
|
|
80
|
+
return this.sdk.refresh(...args);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
revoke(): ReturnType<typeof this.sdk.revoke> {
|
|
@@ -1,28 +1,23 @@
|
|
|
1
|
-
import type { NativeFlowMessage,
|
|
2
|
-
import { DestroyRef, Injectable, inject, signal } from '@angular/core';
|
|
3
|
-
import {
|
|
4
|
-
import { unflattenObject } from '@strivacity/sdk-core/utils';
|
|
1
|
+
import type { NativeFlowMessage, NativeLoginOptions } from '../../types';
|
|
2
|
+
import { DestroyRef, Injectable, computed, inject, signal } from '@angular/core';
|
|
3
|
+
import { createNativeLoginController } from '@strivacity/sdk-core/utils';
|
|
5
4
|
import { StrivacityAuthService } from './auth.service';
|
|
6
5
|
|
|
7
6
|
@Injectable()
|
|
8
7
|
export class StrivacityNativeLoginService {
|
|
9
8
|
private readonly authService = inject(StrivacityAuthService);
|
|
10
|
-
private readonly
|
|
11
|
-
private readonly
|
|
12
|
-
private options: NativeLoginOptions = {};
|
|
9
|
+
private readonly controller = createNativeLoginController(() => this.authService.sdk);
|
|
10
|
+
private readonly snapshot = signal(this.controller.getSnapshot());
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
readonly loading = this.loadingSignal.asReadonly();
|
|
20
|
-
readonly forms = this.formsSignal.asReadonly();
|
|
21
|
-
readonly messages = this.messagesSignal.asReadonly();
|
|
22
|
-
readonly state = this.stateSignal.asReadonly();
|
|
12
|
+
readonly loading = computed(() => this.snapshot().loading);
|
|
13
|
+
readonly forms = computed(() => this.snapshot().forms);
|
|
14
|
+
readonly messages = computed(() => this.snapshot().messages);
|
|
15
|
+
readonly state = computed(() => this.snapshot().state);
|
|
23
16
|
|
|
24
17
|
constructor() {
|
|
25
|
-
|
|
18
|
+
const unsubscribe = this.controller.subscribe((snapshot) => this.snapshot.set(snapshot));
|
|
19
|
+
|
|
20
|
+
inject(DestroyRef).onDestroy(unsubscribe);
|
|
26
21
|
}
|
|
27
22
|
|
|
28
23
|
/**
|
|
@@ -31,142 +26,27 @@ export class StrivacityNativeLoginService {
|
|
|
31
26
|
* @param {NativeLoginOptions} [options] - Optional options for the login session.
|
|
32
27
|
*/
|
|
33
28
|
async start(options: NativeLoginOptions = {}): Promise<void> {
|
|
34
|
-
this.options
|
|
35
|
-
|
|
36
|
-
await this.sdk.init();
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
await this.startSession(options.params);
|
|
40
|
-
} catch (error) {
|
|
41
|
-
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
this.sdk.logging?.error('Error fetching authorization URI', error as Error);
|
|
46
|
-
await this.options.onError?.(error as Error);
|
|
47
|
-
this.loadingSignal.set(false);
|
|
48
|
-
}
|
|
29
|
+
this.controller.setOptions(options);
|
|
30
|
+
await this.controller.start();
|
|
49
31
|
}
|
|
50
32
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
this.loadingSignal.set(true);
|
|
54
|
-
|
|
55
|
-
const nextState = await this.sdk.submitForm(formId, customBody ?? unflattenObject(this.formsSignal()[formId] ?? {}));
|
|
56
|
-
|
|
57
|
-
if (nextState) {
|
|
58
|
-
await this.handleResponse(nextState);
|
|
59
|
-
}
|
|
60
|
-
} catch (error) {
|
|
61
|
-
if (error instanceof FallbackError) {
|
|
62
|
-
this.sdk.logging?.error('Fallback error occurred', error);
|
|
63
|
-
await this.options.onFallback?.(error);
|
|
64
|
-
} else {
|
|
65
|
-
this.sdk.logging?.error('Error submitting form', error as Error);
|
|
66
|
-
await this.options.onError?.(error as Error);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
33
|
+
submitForm(formId: string, customBody?: Record<string, unknown>): Promise<void> {
|
|
34
|
+
return this.controller.submitForm(formId, customBody);
|
|
69
35
|
}
|
|
70
36
|
|
|
71
37
|
setFormValue(formId: string, widgetId: string, value: unknown): void {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
forms[formId] = { ...(forms[formId] ?? {}), [widgetId]: value === '' ? null : value };
|
|
75
|
-
this.formsSignal.set(forms);
|
|
38
|
+
this.controller.setFormValue(formId, widgetId, value);
|
|
76
39
|
}
|
|
77
40
|
|
|
78
41
|
setMessage(formId: string, widgetId: string, value: NativeFlowMessage): void {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
messages[formId] = { ...(messages[formId] ?? {}), [widgetId]: value };
|
|
82
|
-
this.messagesSignal.set(messages);
|
|
42
|
+
this.controller.setMessage(formId, widgetId, value);
|
|
83
43
|
}
|
|
84
44
|
|
|
85
45
|
triggerFallback(message?: string): void {
|
|
86
|
-
this.
|
|
87
|
-
|
|
88
|
-
const hostedUrl = this.stateSignal().hostedUrl;
|
|
89
|
-
|
|
90
|
-
if (!hostedUrl) {
|
|
91
|
-
const error = new Error('No hosted URL provided');
|
|
92
|
-
this.sdk.logging?.error('Fallback error', error);
|
|
93
|
-
throw error;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
void this.options.onFallback?.(new FallbackError(new URL(hostedUrl)));
|
|
46
|
+
this.controller.triggerFallback(message);
|
|
97
47
|
}
|
|
98
48
|
|
|
99
49
|
triggerClose(): void {
|
|
100
|
-
this.
|
|
101
|
-
void this.options.onClose?.();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
private async startSession(loginParams: NativeParams = {}): Promise<void> {
|
|
105
|
-
try {
|
|
106
|
-
this.loadingSignal.set(true);
|
|
107
|
-
|
|
108
|
-
const nextState = await this.sdk.startSession(loginParams);
|
|
109
|
-
|
|
110
|
-
if (nextState) {
|
|
111
|
-
await this.handleResponse(nextState);
|
|
112
|
-
}
|
|
113
|
-
} catch (error) {
|
|
114
|
-
if (error instanceof FallbackError) {
|
|
115
|
-
this.sdk.logging?.error('Fallback error occurred', error);
|
|
116
|
-
await this.options.onFallback?.(error);
|
|
117
|
-
} else {
|
|
118
|
-
this.sdk.logging?.error('Error starting session', error as Error);
|
|
119
|
-
await this.options.onError?.(error as Error);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
private async handleResponse(nextState: Partial<NativeFlowState>): Promise<void> {
|
|
125
|
-
if (this.sdk.session) {
|
|
126
|
-
return await this.options.onLogin?.(this.sdk.session);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const currentState = this.stateSignal();
|
|
130
|
-
const newState: NativeFlowState = {
|
|
131
|
-
hostedUrl: nextState.hostedUrl ?? currentState.hostedUrl,
|
|
132
|
-
finalizeUrl: nextState.finalizeUrl ?? currentState.finalizeUrl,
|
|
133
|
-
screen: nextState.screen ?? currentState.screen,
|
|
134
|
-
forms: nextState.forms ?? currentState.forms,
|
|
135
|
-
layout: nextState.layout ?? currentState.layout,
|
|
136
|
-
messages: nextState.messages ?? {},
|
|
137
|
-
branding: nextState.branding ?? currentState.branding,
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
if (newState.screen !== currentState.screen) {
|
|
141
|
-
const nextForms: Record<string, Record<string, unknown>> = {};
|
|
142
|
-
const nextMessages: Record<string, Record<string, NativeFlowMessage>> = {};
|
|
143
|
-
|
|
144
|
-
for (const form of newState.forms ?? []) {
|
|
145
|
-
nextForms[form.id] = {};
|
|
146
|
-
nextMessages[form.id] = {};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
this.formsSignal.set(nextForms);
|
|
150
|
-
this.messagesSignal.set(nextMessages);
|
|
151
|
-
} else {
|
|
152
|
-
this.sdk.logging?.info(`Updating screen: ${newState.screen}`);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const nextMessageMap = { ...this.messagesSignal() };
|
|
156
|
-
|
|
157
|
-
for (const formId of Object.keys(newState.messages ?? {})) {
|
|
158
|
-
if (formId === 'global') {
|
|
159
|
-
await this.options.onGlobalMessage?.(newState.messages!.global!);
|
|
160
|
-
} else {
|
|
161
|
-
nextMessageMap[formId] = newState.messages![formId] ?? {};
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
this.messagesSignal.set(nextMessageMap);
|
|
166
|
-
this.stateSignal.set(newState);
|
|
167
|
-
|
|
168
|
-
if (!newState.finalizeUrl) {
|
|
169
|
-
this.loadingSignal.set(false);
|
|
170
|
-
}
|
|
50
|
+
this.controller.triggerClose();
|
|
171
51
|
}
|
|
172
52
|
}
|
package/src/server/sdk.ts
CHANGED
|
@@ -93,7 +93,7 @@ export function createServerSDK(initConfig: AngularServerSDKInitConfig): Angular
|
|
|
93
93
|
return base.options;
|
|
94
94
|
},
|
|
95
95
|
handlers: getRouteHandlers(base),
|
|
96
|
-
getSession: (req) => base.getSession(req),
|
|
96
|
+
getSession: (req, params) => base.getSession(req, params),
|
|
97
97
|
updateSession: (session, req) => base.updateSession(session, req),
|
|
98
98
|
refreshSession: (req) => base.refreshSession(req),
|
|
99
99
|
revokeSession: (req) => base.revokeSession(req),
|
package/src/server/session.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { EnvironmentProviders } from '@angular/core';
|
|
2
2
|
import type { AngularServerSDK, SessionData } from './types';
|
|
3
|
-
import { REQUEST, TransferState, inject, makeStateKey, provideAppInitializer } from '@angular/core';
|
|
3
|
+
import { REQUEST, RESPONSE_INIT, TransferState, inject, makeStateKey, provideAppInitializer } from '@angular/core';
|
|
4
|
+
import { flushSetCookies, toClientSession } from '@strivacity/sdk-core/utils';
|
|
4
5
|
|
|
5
6
|
export const SESSION_TRANSFER_KEY = makeStateKey<SessionData | null>('sty.session');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Loads the current session from the encrypted cookie storage (using Angular's `REQUEST` token) before the app
|
|
9
|
-
* renders, and hands it over to the client through `TransferState
|
|
10
|
+
* renders, and hands it over to the client through `TransferState` (without the refresh and ID tokens, which stay server-side), so `StrivacityAuthService` can hydrate without
|
|
10
11
|
* an extra round-trip or a loading flash.
|
|
11
12
|
*
|
|
12
13
|
* Add this to the server-only `ApplicationConfig` (e.g. `app.config.server.ts`), alongside `provideServerRendering()`.
|
|
@@ -23,8 +24,21 @@ export function provideStrivacityServerSession(serverSdk: AngularServerSDK | und
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
const transferState = inject(TransferState);
|
|
27
|
+
const responseInit = inject(RESPONSE_INIT, { optional: true });
|
|
28
|
+
|
|
26
29
|
const session = await serverSdk.getSession(request);
|
|
30
|
+
const cookies = flushSetCookies(request, new Response(null)).headers.getSetCookie();
|
|
31
|
+
|
|
32
|
+
if (responseInit && cookies.length) {
|
|
33
|
+
const headers = new Headers(responseInit.headers);
|
|
34
|
+
|
|
35
|
+
for (const cookie of cookies) {
|
|
36
|
+
headers.append('set-cookie', cookie);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
responseInit.headers = headers;
|
|
40
|
+
}
|
|
27
41
|
|
|
28
|
-
transferState.set(SESSION_TRANSFER_KEY, session);
|
|
42
|
+
transferState.set(SESSION_TRANSFER_KEY, toClientSession(session));
|
|
29
43
|
});
|
|
30
44
|
}
|
package/src/server/types.ts
CHANGED
|
@@ -16,17 +16,17 @@ export type AngularServerStorage = SDKStorage<
|
|
|
16
16
|
export type AngularServerSDKOptions<
|
|
17
17
|
Storage extends AngularServerStorage = AngularServerStorage,
|
|
18
18
|
StateStorage extends SDKStorage = SDKStorage,
|
|
19
|
-
> = ServerSDKOptions<Storage, StateStorage>;
|
|
19
|
+
> = ServerSDKOptions<AngularServerRequest | undefined, Storage, StateStorage>;
|
|
20
20
|
|
|
21
21
|
export type AngularServerSDKInitConfig<
|
|
22
22
|
Storage extends AngularServerStorage = AngularServerStorage,
|
|
23
23
|
StateStorage extends SDKStorage = SDKStorage,
|
|
24
|
-
> = ServerSDKInitConfig & Partial<AngularServerSDKOptions<Storage, StateStorage>>;
|
|
24
|
+
> = ServerSDKInitConfig<AngularServerRequest | undefined> & Partial<AngularServerSDKOptions<Storage, StateStorage>>;
|
|
25
25
|
|
|
26
26
|
export type AngularServerSDK<TEvent extends AngularServerRequest | undefined = AngularServerRequest | undefined> = BaseServerSDK<TEvent> & {
|
|
27
27
|
/**
|
|
28
28
|
* The Express router exposing the authentication endpoints.
|
|
29
|
-
* Mount it under `authUrlPrefix`, e.g. `app.use('/auth', sdk.
|
|
29
|
+
* Mount it under `authUrlPrefix`, e.g. `app.use('/auth', sdk.handlers)`.
|
|
30
30
|
*/
|
|
31
31
|
readonly handlers: Router;
|
|
32
32
|
};
|
package/src/types/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { SDKInitConfig,
|
|
1
|
+
import type { SDKInitConfig, NativeLoginControllerOptions } from '@strivacity/sdk-core/types';
|
|
2
2
|
import type { initFlow } from '@strivacity/sdk-core';
|
|
3
|
-
import type { FallbackError } from '@strivacity/sdk-core/utils';
|
|
4
3
|
|
|
5
4
|
export * from '@strivacity/sdk-core/types';
|
|
6
5
|
|
|
@@ -14,34 +13,7 @@ export type AngularSDKInitConfig = SDKInitConfig;
|
|
|
14
13
|
*/
|
|
15
14
|
export type SDKInstance = ReturnType<typeof initFlow>;
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
params?: NativeParams;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Called when the user successfully completes the login flow.
|
|
25
|
-
*/
|
|
26
|
-
onLogin?: (session: SessionData) => void | Promise<void>;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Called when the fallback flow is triggered, typically due to an error or unsupported environment.
|
|
30
|
-
*/
|
|
31
|
-
onFallback?: (error: FallbackError) => void | Promise<void>;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Called when the login flow is closed by the user.
|
|
35
|
-
*/
|
|
36
|
-
onClose?: () => void | Promise<void>;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Called when an error occurs during the login session initialization or flow.
|
|
40
|
-
*/
|
|
41
|
-
onError?: (error: Error) => void | Promise<void>;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Called when a global message is received during the login flow.
|
|
45
|
-
*/
|
|
46
|
-
onGlobalMessage?: (message: NativeFlowMessage) => void | Promise<void>;
|
|
47
|
-
};
|
|
16
|
+
/**
|
|
17
|
+
* Callbacks and parameters of the native login flow. See `NativeLoginControllerOptions` in `@strivacity/sdk-core`.
|
|
18
|
+
*/
|
|
19
|
+
export type NativeLoginOptions = NativeLoginControllerOptions;
|
|
@@ -202,7 +202,7 @@ describe('StrivacityAuthService', () => {
|
|
|
202
202
|
['checkAuthentication', [{ autoRefresh: false }]],
|
|
203
203
|
['tokenExchange', [{ code: 'abc' }]],
|
|
204
204
|
['handleCallback', ['https://brandtegrity.io/callback?code=abc']],
|
|
205
|
-
['refresh', []],
|
|
205
|
+
['refresh', [{ returnTo: '/orders' }]],
|
|
206
206
|
['revoke', []],
|
|
207
207
|
['logout', [{ postLogoutRedirectUri: 'https://brandtegrity.io' }]],
|
|
208
208
|
['login', [{ scopes: ['openid'] }]],
|
|
@@ -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',
|
|
@@ -128,7 +130,10 @@ describe('server', () => {
|
|
|
128
130
|
'createServerStateStorage',
|
|
129
131
|
'createSessionStorage',
|
|
130
132
|
'createWorkerStorage',
|
|
133
|
+
'deleteEncryptedCookie',
|
|
131
134
|
'handleWorkerStorageRequests',
|
|
135
|
+
'readEncryptedCookie',
|
|
136
|
+
'writeEncryptedCookie',
|
|
132
137
|
'AlgorithmTypeList',
|
|
133
138
|
'GrantTypeList',
|
|
134
139
|
'ResponseModeList',
|
|
@@ -147,6 +152,7 @@ describe('server', () => {
|
|
|
147
152
|
'buildEndSessionUrl',
|
|
148
153
|
'createDefaultLogging',
|
|
149
154
|
'createHttpClient',
|
|
155
|
+
'createNativeLoginController',
|
|
150
156
|
'createSession',
|
|
151
157
|
'createState',
|
|
152
158
|
'createWebAuthnCredential',
|
|
@@ -182,6 +188,7 @@ describe('server', () => {
|
|
|
182
188
|
'serializeState',
|
|
183
189
|
'submitLoginForm',
|
|
184
190
|
'timestamp',
|
|
191
|
+
'toClientSession',
|
|
185
192
|
'toSafeRedirect',
|
|
186
193
|
'unflattenObject',
|
|
187
194
|
'verifyIdToken',
|