@strivacity/sdk-angular 3.0.2 → 4.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +1991 -609
  3. package/dist/README.md +1991 -609
  4. package/dist/fesm2022/strivacity-sdk-angular-src-server.mjs +221 -0
  5. package/dist/fesm2022/strivacity-sdk-angular-src-server.mjs.map +1 -0
  6. package/dist/fesm2022/strivacity-sdk-angular-src-types.mjs +6 -0
  7. package/dist/fesm2022/strivacity-sdk-angular-src-types.mjs.map +1 -0
  8. package/dist/fesm2022/strivacity-sdk-angular.mjs +284 -498
  9. package/dist/fesm2022/strivacity-sdk-angular.mjs.map +1 -1
  10. package/dist/types/strivacity-sdk-angular-src-server.d.ts +82 -0
  11. package/dist/types/strivacity-sdk-angular-src-types.d.ts +41 -0
  12. package/dist/types/strivacity-sdk-angular.d.ts +147 -0
  13. package/eslint.config.mjs +31 -0
  14. package/ng-package.json +3 -3
  15. package/package.json +29 -11
  16. package/project.json +33 -0
  17. package/src/index.ts +8 -0
  18. package/src/lib/services/auth.service.ts +131 -0
  19. package/src/lib/services/index.ts +2 -0
  20. package/src/lib/services/native-login.service.ts +172 -0
  21. package/src/lib/storages.ts +12 -0
  22. package/src/lib/utils.ts +39 -0
  23. package/src/server/errors.ts +1 -0
  24. package/src/server/index.ts +6 -0
  25. package/src/server/ng-package.json +6 -0
  26. package/src/server/sdk.ts +113 -0
  27. package/src/server/session.ts +30 -0
  28. package/src/server/storages.ts +25 -0
  29. package/src/server/types.ts +32 -0
  30. package/src/server/utils.ts +74 -0
  31. package/src/types/index.ts +47 -0
  32. package/src/types/ng-package.json +6 -0
  33. package/testing/setup.ts +10 -0
  34. package/testing/tests/auth.service.spec.ts +236 -0
  35. package/testing/tests/index.spec.ts +193 -0
  36. package/testing/tests/native-login.service.spec.ts +311 -0
  37. package/testing/tests/server/errors.spec.ts +14 -0
  38. package/testing/tests/server/sdk.spec.ts +197 -0
  39. package/testing/tests/server/session.spec.ts +52 -0
  40. package/testing/tests/server/storages.spec.ts +58 -0
  41. package/testing/tests/server/utils.spec.ts +112 -0
  42. package/testing/tests/storages.spec.ts +31 -0
  43. package/testing/tests/utils.spec.ts +24 -0
  44. package/testing/utils/testbed.ts +26 -0
  45. package/tsconfig.lib.json +13 -0
  46. package/tsconfig.lib.prod.json +9 -0
  47. package/tsconfig.spec.json +8 -0
  48. package/vite.config.mts +11 -0
  49. package/dist/index.d.ts +0 -5
  50. package/dist/lib/components/login-renderer.component.d.ts +0 -38
  51. package/dist/lib/components/widget-renderer.component.d.ts +0 -16
  52. package/dist/lib/services/auth.service.d.ts +0 -93
  53. package/dist/lib/services/widget.service.d.ts +0 -25
  54. package/dist/lib/strivacity-auth.module.d.ts +0 -10
  55. package/dist/lib/utils/helpers.d.ts +0 -16
  56. package/dist/lib/utils/types.d.ts +0 -41
  57. package/dist/public-api.d.ts +0 -16
@@ -0,0 +1,131 @@
1
+ import type { IdTokenClaims, SDKInstance } from '../../types';
2
+ import { DestroyRef, Injectable, PLATFORM_ID, TransferState, inject, signal } from '@angular/core';
3
+ import { isPlatformBrowser } from '@angular/common';
4
+ import { initFlow } from '@strivacity/sdk-core';
5
+ import { STRIVACITY_SDK } from '../utils';
6
+ import { SESSION_TRANSFER_KEY } from '../../server/session';
7
+
8
+ /**
9
+ * Signal-based service exposing the Strivacity SDK state and actions to Angular components.
10
+ *
11
+ * Provided via `provideStrivacity()` (standalone) or `StrivacityAuthModule.forRoot()` (NgModule).
12
+ */
13
+ @Injectable()
14
+ export class StrivacityAuthService {
15
+ private readonly loadingSignal = signal(true);
16
+ private readonly languageSignal = signal(globalThis.navigator?.language ?? 'en-US');
17
+ private readonly isAuthenticatedSignal = signal(false);
18
+ private readonly idTokenClaimsSignal = signal<IdTokenClaims | null>(null);
19
+ private readonly accessTokenSignal = signal<string | null>(null);
20
+ private readonly refreshTokenSignal = signal<string | null>(null);
21
+ private readonly accessTokenExpiredSignal = signal(true);
22
+ private readonly accessTokenExpirationDateSignal = signal<number | null>(null);
23
+
24
+ readonly options = inject(STRIVACITY_SDK);
25
+ readonly sdk: SDKInstance = initFlow(this.options as never);
26
+ readonly loading = this.loadingSignal.asReadonly();
27
+ readonly language = this.languageSignal.asReadonly();
28
+ readonly isAuthenticated = this.isAuthenticatedSignal.asReadonly();
29
+ readonly idTokenClaims = this.idTokenClaimsSignal.asReadonly();
30
+ readonly accessToken = this.accessTokenSignal.asReadonly();
31
+ readonly refreshToken = this.refreshTokenSignal.asReadonly();
32
+ readonly accessTokenExpired = this.accessTokenExpiredSignal.asReadonly();
33
+ readonly accessTokenExpirationDate = this.accessTokenExpirationDateSignal.asReadonly();
34
+
35
+ constructor() {
36
+ if (this.options.serverSessionUri) {
37
+ const transferState = inject(TransferState);
38
+ const hydratedSession = transferState.get(SESSION_TRANSFER_KEY, undefined);
39
+
40
+ if (hydratedSession) {
41
+ this.sdk.session = hydratedSession;
42
+
43
+ if (isPlatformBrowser(inject(PLATFORM_ID))) {
44
+ transferState.remove(SESSION_TRANSFER_KEY);
45
+ }
46
+ }
47
+ }
48
+
49
+ void this.updateSession();
50
+
51
+ const subscription = this.sdk.subscribeToAllEvents(() => this.updateSession());
52
+ inject(DestroyRef).onDestroy(() => subscription.dispose());
53
+ }
54
+
55
+ init(...args: Parameters<typeof this.sdk.init>): ReturnType<typeof this.sdk.init> {
56
+ return this.sdk.init(...args);
57
+ }
58
+
59
+ subscribeToEvent(...args: Parameters<typeof this.sdk.subscribeToEvent>): ReturnType<typeof this.sdk.subscribeToEvent> {
60
+ return this.sdk.subscribeToEvent(...args);
61
+ }
62
+
63
+ subscribeToAllEvents(...args: Parameters<typeof this.sdk.subscribeToAllEvents>): ReturnType<typeof this.sdk.subscribeToAllEvents> {
64
+ return this.sdk.subscribeToAllEvents(...args);
65
+ }
66
+
67
+ checkAuthentication(...args: Parameters<typeof this.sdk.checkAuthentication>): ReturnType<typeof this.sdk.checkAuthentication> {
68
+ return this.sdk.checkAuthentication(...args);
69
+ }
70
+
71
+ tokenExchange(...args: Parameters<typeof this.sdk.tokenExchange>): ReturnType<typeof this.sdk.tokenExchange> {
72
+ return this.sdk.tokenExchange(...args);
73
+ }
74
+
75
+ handleCallback(...args: Parameters<typeof this.sdk.handleCallback>): ReturnType<typeof this.sdk.handleCallback> {
76
+ return this.sdk.handleCallback(...args);
77
+ }
78
+
79
+ refresh(): ReturnType<typeof this.sdk.refresh> {
80
+ return this.sdk.refresh();
81
+ }
82
+
83
+ revoke(): ReturnType<typeof this.sdk.revoke> {
84
+ return this.sdk.revoke();
85
+ }
86
+
87
+ logout(...args: Parameters<typeof this.sdk.logout>): ReturnType<typeof this.sdk.logout> {
88
+ return this.sdk.logout(...args);
89
+ }
90
+
91
+ login(...args: Parameters<typeof this.sdk.login>): ReturnType<typeof this.sdk.login> {
92
+ return this.sdk.login(...args);
93
+ }
94
+
95
+ register(...args: Parameters<typeof this.sdk.register>): ReturnType<typeof this.sdk.register> {
96
+ return this.sdk.register(...args);
97
+ }
98
+
99
+ entry(...args: Parameters<typeof this.sdk.entry>): ReturnType<typeof this.sdk.entry> {
100
+ return this.sdk.entry(...args);
101
+ }
102
+
103
+ private async updateSession(): Promise<void> {
104
+ const authenticated = await this.sdk.isAuthenticated;
105
+
106
+ if (this.loadingSignal()) {
107
+ this.loadingSignal.set(false);
108
+ }
109
+ if (this.sdk.language !== this.languageSignal()) {
110
+ this.languageSignal.set(this.sdk.language);
111
+ }
112
+ if (authenticated !== this.isAuthenticatedSignal()) {
113
+ this.isAuthenticatedSignal.set(authenticated);
114
+ }
115
+ if (this.sdk.idTokenClaims !== this.idTokenClaimsSignal()) {
116
+ this.idTokenClaimsSignal.set(this.sdk.idTokenClaims ?? null);
117
+ }
118
+ if (this.sdk.accessToken !== this.accessTokenSignal()) {
119
+ this.accessTokenSignal.set(this.sdk.accessToken ?? null);
120
+ }
121
+ if (this.sdk.refreshToken !== this.refreshTokenSignal()) {
122
+ this.refreshTokenSignal.set(this.sdk.refreshToken ?? null);
123
+ }
124
+ if (this.sdk.accessTokenExpired !== this.accessTokenExpiredSignal()) {
125
+ this.accessTokenExpiredSignal.set(this.sdk.accessTokenExpired);
126
+ }
127
+ if (this.sdk.accessTokenExpirationDate !== this.accessTokenExpirationDateSignal()) {
128
+ this.accessTokenExpirationDateSignal.set(this.sdk.accessTokenExpirationDate ?? null);
129
+ }
130
+ }
131
+ }
@@ -0,0 +1,2 @@
1
+ export { StrivacityAuthService } from './auth.service';
2
+ export { StrivacityNativeLoginService } from './native-login.service';
@@ -0,0 +1,172 @@
1
+ import type { NativeFlowMessage, NativeFlowState, NativeParams, NativeLoginOptions } from '../../types';
2
+ import { DestroyRef, Injectable, inject, signal } from '@angular/core';
3
+ import { FallbackError } from '@strivacity/sdk-core/utils/errors';
4
+ import { unflattenObject } from '@strivacity/sdk-core/utils';
5
+ import { StrivacityAuthService } from './auth.service';
6
+
7
+ @Injectable()
8
+ export class StrivacityNativeLoginService {
9
+ private readonly authService = inject(StrivacityAuthService);
10
+ private readonly sdk = this.authService.sdk;
11
+ private readonly abortController = new AbortController();
12
+ private options: NativeLoginOptions = {};
13
+
14
+ private readonly loadingSignal = signal(true);
15
+ private readonly formsSignal = signal<Record<string, Record<string, unknown>>>({});
16
+ private readonly messagesSignal = signal<Record<string, Record<string, NativeFlowMessage>>>({});
17
+ private readonly stateSignal = signal<Partial<NativeFlowState>>({});
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();
23
+
24
+ constructor() {
25
+ inject(DestroyRef).onDestroy(() => this.abortController.abort());
26
+ }
27
+
28
+ /**
29
+ * Starts a native login flow session. Should be called once, from the page component that owns this service.
30
+ *
31
+ * @param {NativeLoginOptions} [options] - Optional options for the login session.
32
+ */
33
+ async start(options: NativeLoginOptions = {}): Promise<void> {
34
+ this.options = 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
+ }
49
+ }
50
+
51
+ async submitForm(formId: string, customBody?: Record<string, unknown>): Promise<void> {
52
+ try {
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
+ }
69
+ }
70
+
71
+ setFormValue(formId: string, widgetId: string, value: unknown): void {
72
+ const forms = { ...this.formsSignal() };
73
+
74
+ forms[formId] = { ...(forms[formId] ?? {}), [widgetId]: value === '' ? null : value };
75
+ this.formsSignal.set(forms);
76
+ }
77
+
78
+ setMessage(formId: string, widgetId: string, value: NativeFlowMessage): void {
79
+ const messages = { ...this.messagesSignal() };
80
+
81
+ messages[formId] = { ...(messages[formId] ?? {}), [widgetId]: value };
82
+ this.messagesSignal.set(messages);
83
+ }
84
+
85
+ triggerFallback(message?: string): void {
86
+ this.sdk.logging?.warn(message ? `Triggering fallback due to: ${message}` : 'Triggering fallback');
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)));
97
+ }
98
+
99
+ triggerClose(): void {
100
+ this.sdk.logging?.debug('Triggering close');
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
+ }
171
+ }
172
+ }
@@ -0,0 +1,12 @@
1
+ export {
2
+ COOKIE_CONTEXT,
3
+ COOKIE_CHUNK_SIZE,
4
+ createCacheAPIStorage,
5
+ createIndexedDBStorage,
6
+ createLocalStorage,
7
+ createMemoryStorage,
8
+ createServerMemoryStorage,
9
+ createSessionStorage,
10
+ createWorkerStorage,
11
+ handleWorkerStorageRequests,
12
+ } from '@strivacity/sdk-core/storages';
@@ -0,0 +1,39 @@
1
+ import type { Provider, ModuleWithProviders } from '@angular/core';
2
+ import type { AngularSDKInitConfig } from '../types';
3
+ import { CUSTOM_ELEMENTS_SCHEMA, NgModule, InjectionToken } from '@angular/core';
4
+ import { StrivacityAuthService } from './services/auth.service';
5
+
6
+ export const STRIVACITY_SDK = new InjectionToken<AngularSDKInitConfig>('strivacity-sdk');
7
+
8
+ /**
9
+ * Provides the Strivacity SDK configuration and the `StrivacityAuthService` as dependency injection providers.
10
+ *
11
+ * This function is used to supply the Strivacity SDK configuration to the application
12
+ * by binding it to the `STRIVACITY_SDK` token, and to register `StrivacityAuthService` as an injectable.
13
+ *
14
+ * @param {AngularSDKInitConfig} config The SDK configuration options.
15
+ * @returns {Provider[]} The providers to add to the application (or component) injector.
16
+ */
17
+ export function provideStrivacity(config: AngularSDKInitConfig): Provider[] {
18
+ return [{ provide: STRIVACITY_SDK, useValue: config }, StrivacityAuthService];
19
+ }
20
+
21
+ @NgModule({
22
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
23
+ })
24
+ export class StrivacityAuthModule {
25
+ /**
26
+ * Registers the Strivacity SDK configuration and the `StrivacityAuthService` as dependency injection providers.
27
+ *
28
+ * This is the `NgModule`-based equivalent of `provideStrivacity()`, for applications that still bootstrap via `NgModule`.
29
+ *
30
+ * @param {AngularSDKInitConfig} options The SDK configuration options.
31
+ * @returns {ModuleWithProviders<StrivacityAuthModule>} The module along with its providers, to be imported once at the root of the application.
32
+ */
33
+ static forRoot(options: AngularSDKInitConfig): ModuleWithProviders<StrivacityAuthModule> {
34
+ return {
35
+ ngModule: StrivacityAuthModule,
36
+ providers: [provideStrivacity(options)],
37
+ };
38
+ }
39
+ }
@@ -0,0 +1 @@
1
+ export * from '@strivacity/sdk-core/utils/errors';
@@ -0,0 +1,6 @@
1
+ export * from './errors';
2
+ export * from './sdk';
3
+ export * from './session';
4
+ export * from './storages';
5
+ export * from './types';
6
+ export * from './utils';
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
3
+ "lib": {
4
+ "entryFile": "./index.ts"
5
+ }
6
+ }
@@ -0,0 +1,113 @@
1
+ import type { AngularServerRequest, AngularServerSDK, AngularServerSDKInitConfig } from './types';
2
+ import { Router } from 'express';
3
+ import { createBaseServerSDK } from '@strivacity/sdk-core/server';
4
+ import { applyResponse, toWebRequest } from './utils';
5
+
6
+ function getRouteHandlers(base: ReturnType<typeof createBaseServerSDK<AngularServerRequest | undefined>>): Router {
7
+ const router = Router();
8
+
9
+ router.get('/login', async (req, res, next) => {
10
+ try {
11
+ const response = await base.handleLogin(req);
12
+ await applyResponse(response, res);
13
+ } catch (error) {
14
+ next(error);
15
+ }
16
+ });
17
+ router.get('/register', async (req, res, next) => {
18
+ try {
19
+ const response = await base.handleRegister(req);
20
+ await applyResponse(response, res);
21
+ } catch (error) {
22
+ next(error);
23
+ }
24
+ });
25
+ router.get('/callback', async (req, res, next) => {
26
+ try {
27
+ const response = await base.handleCallback(req);
28
+ await applyResponse(response, res);
29
+ } catch (error) {
30
+ next(error);
31
+ }
32
+ });
33
+ router.get('/refresh', async (req, res, next) => {
34
+ try {
35
+ const response = await base.handleRefresh(req);
36
+ await applyResponse(response, res);
37
+ } catch (error) {
38
+ next(error);
39
+ }
40
+ });
41
+ router.get('/revoke', async (req, res, next) => {
42
+ try {
43
+ const response = await base.handleRevoke(req);
44
+ await applyResponse(response, res);
45
+ } catch (error) {
46
+ next(error);
47
+ }
48
+ });
49
+ router.get('/entry', async (req, res, next) => {
50
+ try {
51
+ const response = await base.handleEntry(req);
52
+ await applyResponse(response, res);
53
+ } catch (error) {
54
+ next(error);
55
+ }
56
+ });
57
+ router.get('/logout', async (req, res, next) => {
58
+ try {
59
+ const response = await base.handleLogout(req);
60
+ await applyResponse(response, res);
61
+ } catch (error) {
62
+ next(error);
63
+ }
64
+ });
65
+ router.post('/backchannel-logout', async (req, res, next) => {
66
+ try {
67
+ const response = await base.handleBackChannelLogout(req);
68
+ await applyResponse(response, res);
69
+ } catch (error) {
70
+ next(error);
71
+ }
72
+ });
73
+
74
+ return router;
75
+ }
76
+
77
+ /**
78
+ * Creates the server-side Strivacity SDK: an Express router exposing the auth endpoints and session helpers that can be reused elsewhere on the server.
79
+ *
80
+ * @param {AngularServerSDKInitConfig} initConfig - The SDK configuration options.
81
+ * @returns {AngularServerSDK} The server-side SDK instance.
82
+ */
83
+ export function createServerSDK(initConfig: AngularServerSDKInitConfig): AngularServerSDK {
84
+ const base = createBaseServerSDK<AngularServerRequest | undefined>(
85
+ {
86
+ toRequest: (req) => toWebRequest(req as AngularServerRequest),
87
+ },
88
+ initConfig,
89
+ );
90
+
91
+ return {
92
+ get options() {
93
+ return base.options;
94
+ },
95
+ handlers: getRouteHandlers(base),
96
+ getSession: (req) => base.getSession(req),
97
+ updateSession: (session, req) => base.updateSession(session, req),
98
+ refreshSession: (req) => base.refreshSession(req),
99
+ revokeSession: (req) => base.revokeSession(req),
100
+ getEntrySession: (entryUrl) => base.getEntrySession(entryUrl),
101
+ completeLogin: (params, req) => base.completeLogin(params, req),
102
+ logout: (postLogoutRedirectUri, req) => base.logout(postLogoutRedirectUri, req),
103
+ handleLogin: (req) => base.handleLogin(req),
104
+ handleRegister: (req) => base.handleRegister(req),
105
+ handleCallback: (req) => base.handleCallback(req),
106
+ handleRefresh: (req) => base.handleRefresh(req),
107
+ handleRevoke: (req) => base.handleRevoke(req),
108
+ handleEntry: (req) => base.handleEntry(req),
109
+ handleLogout: (req) => base.handleLogout(req),
110
+ handleBackChannelLogout: (req) => base.handleBackChannelLogout(req),
111
+ handler: (req) => base.handler(req),
112
+ };
113
+ }
@@ -0,0 +1,30 @@
1
+ import type { EnvironmentProviders } from '@angular/core';
2
+ import type { AngularServerSDK, SessionData } from './types';
3
+ import { REQUEST, TransferState, inject, makeStateKey, provideAppInitializer } from '@angular/core';
4
+
5
+ export const SESSION_TRANSFER_KEY = makeStateKey<SessionData | null>('sty.session');
6
+
7
+ /**
8
+ * 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`, so `StrivacityAuthService` can hydrate without
10
+ * an extra round-trip or a loading flash.
11
+ *
12
+ * Add this to the server-only `ApplicationConfig` (e.g. `app.config.server.ts`), alongside `provideServerRendering()`.
13
+ *
14
+ * @param {AngularServerSDK | undefined} serverSdk - The server SDK instance created via `createServerSDK()`, or `undefined` when server-side sessions aren't configured (a no-op in that case).
15
+ * @returns {EnvironmentProviders} The providers to add to the server `ApplicationConfig`.
16
+ */
17
+ export function provideStrivacityServerSession(serverSdk: AngularServerSDK | undefined): EnvironmentProviders {
18
+ return provideAppInitializer(async () => {
19
+ const request = inject(REQUEST);
20
+
21
+ if (!request || !serverSdk) {
22
+ return;
23
+ }
24
+
25
+ const transferState = inject(TransferState);
26
+ const session = await serverSdk.getSession(request);
27
+
28
+ transferState.set(SESSION_TRANSFER_KEY, session);
29
+ });
30
+ }
@@ -0,0 +1,25 @@
1
+ import type { ServerStorage, AngularServerRequest } from './types';
2
+ import type { SDKStorage, SessionIdCookieStorageOptions } from '@strivacity/sdk-core/types';
3
+ import { createSessionIdCookieStorage as createSessionIdCookieStorageBase } from '@strivacity/sdk-core/storages/server';
4
+ import { toWebRequest } from './utils';
5
+
6
+ export * from '@strivacity/sdk-core/storages';
7
+
8
+ /**
9
+ * Creates a session storage that puts a random unique id value cookie on the client and keeping the actual session data in the given `storage`.
10
+ *
11
+ * @param {SDKStorage} storage - The storage used to persist session data, keyed by a randomly generated session id.
12
+ * @param {SessionIdCookieStorageOptions} [options] - Options for the session ID cookie, including default cookie attributes and a UUID generator function.
13
+ * @param {Partial<ServerCookieOptions>} [options.defaultCookieOptions] - Default cookie attributes for the session ID cookie.
14
+ * @param {() => string} [options.uuidGenerator] - Function to generate a new UUID for the session ID. Defaults to `() => crypto.randomUUID()`.
15
+ * @returns {ServerStorage} An object implementing the ServerStorage interface for managing sessions indexed by a session-id cookie.
16
+ */
17
+ export function createSessionIdCookieStorage(storage: SDKStorage, options?: SessionIdCookieStorageOptions): ServerStorage {
18
+ return createSessionIdCookieStorageBase<AngularServerRequest>(
19
+ {
20
+ toRequest: (req) => toWebRequest(req),
21
+ },
22
+ storage,
23
+ options,
24
+ );
25
+ }
@@ -0,0 +1,32 @@
1
+ import type { Request as ExpressRequest, Response as ExpressResponse, Router } from 'express';
2
+ import type { BaseServerSDK, CookieOptions, LogoutTokenClaims, ServerSDKInitConfig, ServerSDKOptions, SDKStorage } from '@strivacity/sdk-core/types';
3
+
4
+ export * from '@strivacity/sdk-core/types';
5
+
6
+ export type AngularServerRequest = ExpressRequest | Request;
7
+
8
+ export type AngularServerStorage = SDKStorage<
9
+ [req?: AngularServerRequest],
10
+ [req?: ExpressRequest, res?: ExpressResponse, cookieOptions?: CookieOptions],
11
+ [req?: ExpressRequest, res?: ExpressResponse, cookieOptions?: CookieOptions]
12
+ > & {
13
+ deleteByLogoutToken?(token: LogoutTokenClaims): Promise<void>;
14
+ };
15
+
16
+ export type AngularServerSDKOptions<
17
+ Storage extends AngularServerStorage = AngularServerStorage,
18
+ StateStorage extends SDKStorage = SDKStorage,
19
+ > = ServerSDKOptions<Storage, StateStorage>;
20
+
21
+ export type AngularServerSDKInitConfig<
22
+ Storage extends AngularServerStorage = AngularServerStorage,
23
+ StateStorage extends SDKStorage = SDKStorage,
24
+ > = ServerSDKInitConfig & Partial<AngularServerSDKOptions<Storage, StateStorage>>;
25
+
26
+ export type AngularServerSDK<TEvent extends AngularServerRequest | undefined = AngularServerRequest | undefined> = BaseServerSDK<TEvent> & {
27
+ /**
28
+ * The Express router exposing the authentication endpoints.
29
+ * Mount it under `authUrlPrefix`, e.g. `app.use('/auth', sdk.router)`.
30
+ */
31
+ readonly handlers: Router;
32
+ };
@@ -0,0 +1,74 @@
1
+ import type { Response as ExpressResponse } from 'express';
2
+ import type { AngularServerRequest } from './types';
3
+ import { Readable } from 'node:stream';
4
+
5
+ export * from '@strivacity/sdk-core/utils';
6
+
7
+ /**
8
+ * Converts an incoming Express request (or an already-standard `Request`, e.g. Angular's SSR `REQUEST` token) into a standard Web `Request`.
9
+ *
10
+ * @param {AngularServerRequest} req - The incoming request.
11
+ * @returns {Request} The equivalent Web `Request` object.
12
+ */
13
+ export function toWebRequest(req: AngularServerRequest): Request {
14
+ if (req instanceof Request) {
15
+ return req;
16
+ }
17
+
18
+ const url = new URL(req.originalUrl, `${req.protocol}://${req.get('host')}`);
19
+ const headers = new Headers();
20
+
21
+ for (const [key, value] of Object.entries(req.headers)) {
22
+ if (value === undefined) {
23
+ continue;
24
+ }
25
+
26
+ for (const v of Array.isArray(value) ? value : [value]) {
27
+ headers.append(key, v);
28
+ }
29
+ }
30
+
31
+ const hasBody = req.method !== 'GET' && req.method !== 'HEAD';
32
+
33
+ return new Request(url, {
34
+ method: req.method,
35
+ headers,
36
+ ...(hasBody ? { body: Readable.toWeb(req), duplex: 'half' } : {}),
37
+ } as RequestInit);
38
+ }
39
+
40
+ /**
41
+ * Writes a standard Web `Response` (built by the shared server SDK core) onto the Express response.
42
+ *
43
+ * @param {Response} response - The `Response` to write.
44
+ * @param {ExpressResponse} res - The Express response to write to.
45
+ */
46
+ export async function applyResponse(response: Response, res: ExpressResponse): Promise<void> {
47
+ res.status(response.status);
48
+
49
+ for (const [key, value] of response.headers.entries()) {
50
+ if (key.toLowerCase() === 'set-cookie') {
51
+ continue;
52
+ }
53
+
54
+ res.setHeader(key, value);
55
+ }
56
+
57
+ const setCookies = response.headers.getSetCookie();
58
+
59
+ if (setCookies.length) {
60
+ res.setHeader('set-cookie', setCookies);
61
+ }
62
+
63
+ if (!response.body) {
64
+ res.end();
65
+ return;
66
+ }
67
+
68
+ await new Promise<void>((resolve, reject) => {
69
+ Readable.fromWeb(response.body as Parameters<typeof Readable.fromWeb>[0])
70
+ .pipe(res)
71
+ .on('finish', resolve)
72
+ .on('error', reject);
73
+ });
74
+ }