@serialsubscriptions/platform-integration 0.0.79

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 (60) hide show
  1. package/README.md +1 -0
  2. package/lib/SSIProject.d.ts +343 -0
  3. package/lib/SSIProject.js +429 -0
  4. package/lib/SSIProjectApi.d.ts +384 -0
  5. package/lib/SSIProjectApi.js +534 -0
  6. package/lib/SSISubscribedFeatureApi.d.ts +387 -0
  7. package/lib/SSISubscribedFeatureApi.js +511 -0
  8. package/lib/SSISubscribedLimitApi.d.ts +384 -0
  9. package/lib/SSISubscribedLimitApi.js +534 -0
  10. package/lib/SSISubscribedPlanApi.d.ts +384 -0
  11. package/lib/SSISubscribedPlanApi.js +537 -0
  12. package/lib/SubscribedPlanManager.d.ts +380 -0
  13. package/lib/SubscribedPlanManager.js +288 -0
  14. package/lib/UsageApi.d.ts +128 -0
  15. package/lib/UsageApi.js +224 -0
  16. package/lib/auth.server.d.ts +192 -0
  17. package/lib/auth.server.js +579 -0
  18. package/lib/cache/SSICache.d.ts +40 -0
  19. package/lib/cache/SSICache.js +134 -0
  20. package/lib/cache/backends/MemoryCacheBackend.d.ts +15 -0
  21. package/lib/cache/backends/MemoryCacheBackend.js +46 -0
  22. package/lib/cache/backends/RedisCacheBackend.d.ts +27 -0
  23. package/lib/cache/backends/RedisCacheBackend.js +95 -0
  24. package/lib/cache/constants.d.ts +7 -0
  25. package/lib/cache/constants.js +10 -0
  26. package/lib/cache/types.d.ts +27 -0
  27. package/lib/cache/types.js +2 -0
  28. package/lib/frontend/index.d.ts +1 -0
  29. package/lib/frontend/index.js +6 -0
  30. package/lib/frontend/session/SessionClient.d.ts +24 -0
  31. package/lib/frontend/session/SessionClient.js +145 -0
  32. package/lib/index.d.ts +15 -0
  33. package/lib/index.js +38 -0
  34. package/lib/lib/session/SessionClient.d.ts +11 -0
  35. package/lib/lib/session/SessionClient.js +47 -0
  36. package/lib/lib/session/index.d.ts +3 -0
  37. package/lib/lib/session/index.js +3 -0
  38. package/lib/lib/session/stores/MemoryStore.d.ts +7 -0
  39. package/lib/lib/session/stores/MemoryStore.js +23 -0
  40. package/lib/lib/session/stores/index.d.ts +1 -0
  41. package/lib/lib/session/stores/index.js +1 -0
  42. package/lib/lib/session/types.d.ts +37 -0
  43. package/lib/lib/session/types.js +1 -0
  44. package/lib/session/SessionClient.d.ts +19 -0
  45. package/lib/session/SessionClient.js +132 -0
  46. package/lib/session/SessionManager.d.ts +139 -0
  47. package/lib/session/SessionManager.js +443 -0
  48. package/lib/stateStore.d.ts +5 -0
  49. package/lib/stateStore.js +9 -0
  50. package/lib/storage/SSIStorage.d.ts +24 -0
  51. package/lib/storage/SSIStorage.js +117 -0
  52. package/lib/storage/backends/MemoryBackend.d.ts +10 -0
  53. package/lib/storage/backends/MemoryBackend.js +44 -0
  54. package/lib/storage/backends/PostgresBackend.d.ts +24 -0
  55. package/lib/storage/backends/PostgresBackend.js +106 -0
  56. package/lib/storage/backends/RedisBackend.d.ts +19 -0
  57. package/lib/storage/backends/RedisBackend.js +78 -0
  58. package/lib/storage/types.d.ts +27 -0
  59. package/lib/storage/types.js +2 -0
  60. package/package.json +71 -0
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Usage Reporting API Client
3
+ *
4
+ * Sends usage events to the subscription usage API endpoint.
5
+ * Requires a base URL and Bearer token for authentication.
6
+ */
7
+ /**
8
+ * Usage event data structure
9
+ */
10
+ export interface UsageEvent {
11
+ /** The internal ID of the Limit entity this usage event applies to (required) */
12
+ limit_id: number;
13
+ /** Positive usage amount for this event (optional, default: 1.0) */
14
+ amount?: number;
15
+ /** Project entity ID when mode supports projects (optional) */
16
+ project_id?: number;
17
+ /** Aggregate/grouping identifier (e.g. campaign/job ID) (optional) */
18
+ aggregate_id?: number;
19
+ /** Human-readable name for the aggregate (not indexed) (optional) */
20
+ aggregate_name?: string;
21
+ /** Arbitrary JSON metadata from the client (optional) */
22
+ metadata?: Record<string, any> | string;
23
+ /** Unix timestamp (UTC seconds since epoch) for when the usage event occurred (optional) */
24
+ event_timestamp?: number;
25
+ }
26
+ /**
27
+ * API response structure
28
+ */
29
+ export interface UsageApiResponse {
30
+ /** Status of the response (always "recorded" on success) */
31
+ status: string;
32
+ /** Number of events successfully processed */
33
+ count: number;
34
+ }
35
+ /**
36
+ * API error response structure
37
+ */
38
+ export interface UsageApiError {
39
+ message: string;
40
+ status?: number;
41
+ statusText?: string;
42
+ }
43
+ /**
44
+ * Usage information structure
45
+ */
46
+ export interface UsageInfo {
47
+ /** The internal ID of the Limit entity */
48
+ limit_id: number;
49
+ /** Total usage amount for this limit */
50
+ total_usage: number;
51
+ }
52
+ /**
53
+ * Project usage information structure
54
+ */
55
+ export interface ProjectUsageInfo {
56
+ /** The internal ID of the Limit entity */
57
+ limit_id: number;
58
+ /** Total usage amount for this limit */
59
+ total_usage: number;
60
+ /** The project entity ID */
61
+ project_id: number;
62
+ }
63
+ /**
64
+ * Usage API client for reporting subscription usage events
65
+ */
66
+ export declare class UsageApi {
67
+ private baseUrl;
68
+ private bearerToken;
69
+ private endpoint;
70
+ /**
71
+ * Creates a new UsageApi instance
72
+ *
73
+ * @param baseUrl - The base URL of the API (e.g., "https://account.serialsubscriptionsdev.com/")
74
+ * @param bearerToken - The Bearer token for authentication
75
+ */
76
+ constructor(baseUrl: string, bearerToken: string);
77
+ /**
78
+ * Reports a single usage event
79
+ *
80
+ * @param event - The usage event to report
81
+ * @returns Promise resolving to the API response
82
+ * @throws {UsageApiError} If the request fails
83
+ */
84
+ reportEvent(event: UsageEvent): Promise<UsageApiResponse>;
85
+ /**
86
+ * Reports multiple usage events in a single batch request
87
+ *
88
+ * All events in a batch are processed within a single database transaction.
89
+ * If any event fails validation, the entire batch is rolled back.
90
+ *
91
+ * @param events - Array of usage events to report
92
+ * @returns Promise resolving to the API response
93
+ * @throws {UsageApiError} If the request fails
94
+ */
95
+ reportEvents(events: UsageEvent[]): Promise<UsageApiResponse>;
96
+ /**
97
+ * Gets usage information for all limits
98
+ *
99
+ * @returns Promise resolving to an array of usage information
100
+ * @throws {UsageApiError} If the request fails
101
+ */
102
+ getUsageAll(): Promise<UsageInfo[]>;
103
+ /**
104
+ * Gets usage information for a specific limit
105
+ *
106
+ * @param limit_id - The limit ID to get usage for
107
+ * @returns Promise resolving to the total usage value, or undefined if not found
108
+ * @throws {UsageApiError} If the request fails
109
+ */
110
+ getUsage(limit_id: number): Promise<number | undefined>;
111
+ /**
112
+ * Gets usage information for all limits for a specific project
113
+ *
114
+ * @param project_id - The project ID to get usage for
115
+ * @returns Promise resolving to an array of project usage information
116
+ * @throws {UsageApiError} If the request fails
117
+ */
118
+ getProjectUsageAll(project_id: number): Promise<ProjectUsageInfo[]>;
119
+ /**
120
+ * Gets usage information for a specific limit within a project
121
+ *
122
+ * @param project_id - The project ID to get usage for
123
+ * @param limit_id - The limit ID to get usage for
124
+ * @returns Promise resolving to the total usage value, or undefined if not found
125
+ * @throws {UsageApiError} If the request fails
126
+ */
127
+ getProjectUsage(project_id: number, limit_id: number): Promise<number | undefined>;
128
+ }
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ /**
3
+ * Usage Reporting API Client
4
+ *
5
+ * Sends usage events to the subscription usage API endpoint.
6
+ * Requires a base URL and Bearer token for authentication.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.UsageApi = void 0;
10
+ /**
11
+ * Usage API client for reporting subscription usage events
12
+ */
13
+ class UsageApi {
14
+ /**
15
+ * Creates a new UsageApi instance
16
+ *
17
+ * @param baseUrl - The base URL of the API (e.g., "https://account.serialsubscriptionsdev.com/")
18
+ * @param bearerToken - The Bearer token for authentication
19
+ */
20
+ constructor(baseUrl, bearerToken) {
21
+ this.endpoint = '/api/v1/usage/report';
22
+ // Normalize base URL: ensure it ends with a slash
23
+ this.baseUrl = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
24
+ this.bearerToken = bearerToken;
25
+ }
26
+ /**
27
+ * Reports a single usage event
28
+ *
29
+ * @param event - The usage event to report
30
+ * @returns Promise resolving to the API response
31
+ * @throws {UsageApiError} If the request fails
32
+ */
33
+ async reportEvent(event) {
34
+ return this.reportEvents([event]);
35
+ }
36
+ /**
37
+ * Reports multiple usage events in a single batch request
38
+ *
39
+ * All events in a batch are processed within a single database transaction.
40
+ * If any event fails validation, the entire batch is rolled back.
41
+ *
42
+ * @param events - Array of usage events to report
43
+ * @returns Promise resolving to the API response
44
+ * @throws {UsageApiError} If the request fails
45
+ */
46
+ async reportEvents(events) {
47
+ if (events.length === 0) {
48
+ throw {
49
+ message: 'At least one event is required',
50
+ };
51
+ }
52
+ // Normalize: if single event, send as object; if multiple, send as array
53
+ const payload = events.length === 1 ? events[0] : events;
54
+ const url = `${this.baseUrl}${this.endpoint.replace(/^\//, '')}`;
55
+ try {
56
+ const response = await fetch(url, {
57
+ method: 'POST',
58
+ headers: {
59
+ 'Authorization': `Bearer ${this.bearerToken}`,
60
+ 'Content-Type': 'application/json',
61
+ 'Accept': 'application/json',
62
+ },
63
+ body: JSON.stringify(payload),
64
+ });
65
+ if (!response.ok) {
66
+ let errorMessage = `API request failed with status ${response.status}`;
67
+ try {
68
+ const errorData = await response.json();
69
+ if (errorData.message) {
70
+ errorMessage = errorData.message;
71
+ }
72
+ }
73
+ catch {
74
+ // If response is not JSON, use the status text
75
+ errorMessage = response.statusText || errorMessage;
76
+ }
77
+ throw {
78
+ message: errorMessage,
79
+ status: response.status,
80
+ statusText: response.statusText,
81
+ };
82
+ }
83
+ const data = await response.json();
84
+ return data;
85
+ }
86
+ catch (error) {
87
+ if (error && typeof error === 'object' && 'status' in error) {
88
+ // Re-throw UsageApiError
89
+ throw error;
90
+ }
91
+ // Handle network errors or other exceptions
92
+ throw {
93
+ message: error instanceof Error ? error.message : 'Unknown error occurred',
94
+ };
95
+ }
96
+ }
97
+ /**
98
+ * Gets usage information for all limits
99
+ *
100
+ * @returns Promise resolving to an array of usage information
101
+ * @throws {UsageApiError} If the request fails
102
+ */
103
+ async getUsageAll() {
104
+ const endpoint = '/api/v1/usage/get';
105
+ const url = `${this.baseUrl}${endpoint.replace(/^\//, '')}`;
106
+ try {
107
+ const response = await fetch(url, {
108
+ method: 'GET',
109
+ headers: {
110
+ 'Authorization': `Bearer ${this.bearerToken}`,
111
+ 'Accept': 'application/json',
112
+ },
113
+ });
114
+ if (!response.ok) {
115
+ let errorMessage = `API request failed with status ${response.status}`;
116
+ try {
117
+ const errorData = await response.json();
118
+ if (errorData.message) {
119
+ errorMessage = errorData.message;
120
+ }
121
+ }
122
+ catch {
123
+ // If response is not JSON, use the status text
124
+ errorMessage = response.statusText || errorMessage;
125
+ }
126
+ throw {
127
+ message: errorMessage,
128
+ status: response.status,
129
+ statusText: response.statusText,
130
+ };
131
+ }
132
+ const data = await response.json();
133
+ return data;
134
+ }
135
+ catch (error) {
136
+ if (error && typeof error === 'object' && 'status' in error) {
137
+ // Re-throw UsageApiError
138
+ throw error;
139
+ }
140
+ // Handle network errors or other exceptions
141
+ throw {
142
+ message: error instanceof Error ? error.message : 'Unknown error occurred',
143
+ };
144
+ }
145
+ }
146
+ /**
147
+ * Gets usage information for a specific limit
148
+ *
149
+ * @param limit_id - The limit ID to get usage for
150
+ * @returns Promise resolving to the total usage value, or undefined if not found
151
+ * @throws {UsageApiError} If the request fails
152
+ */
153
+ async getUsage(limit_id) {
154
+ const allUsage = await this.getUsageAll();
155
+ const usageInfo = allUsage.find((info) => info.limit_id === limit_id);
156
+ return usageInfo?.total_usage;
157
+ }
158
+ /**
159
+ * Gets usage information for all limits for a specific project
160
+ *
161
+ * @param project_id - The project ID to get usage for
162
+ * @returns Promise resolving to an array of project usage information
163
+ * @throws {UsageApiError} If the request fails
164
+ */
165
+ async getProjectUsageAll(project_id) {
166
+ const endpoint = '/api/v1/usage/get';
167
+ const url = `${this.baseUrl}${endpoint.replace(/^\//, '')}`;
168
+ try {
169
+ const response = await fetch(url, {
170
+ method: 'POST',
171
+ headers: {
172
+ 'Authorization': `Bearer ${this.bearerToken}`,
173
+ 'Content-Type': 'application/json',
174
+ 'Accept': 'application/json',
175
+ },
176
+ body: JSON.stringify({ project_id }),
177
+ });
178
+ if (!response.ok) {
179
+ let errorMessage = `API request failed with status ${response.status}`;
180
+ try {
181
+ const errorData = await response.json();
182
+ if (errorData.message) {
183
+ errorMessage = errorData.message;
184
+ }
185
+ }
186
+ catch {
187
+ // If response is not JSON, use the status text
188
+ errorMessage = response.statusText || errorMessage;
189
+ }
190
+ throw {
191
+ message: errorMessage,
192
+ status: response.status,
193
+ statusText: response.statusText,
194
+ };
195
+ }
196
+ const data = await response.json();
197
+ return data;
198
+ }
199
+ catch (error) {
200
+ if (error && typeof error === 'object' && 'status' in error) {
201
+ // Re-throw UsageApiError
202
+ throw error;
203
+ }
204
+ // Handle network errors or other exceptions
205
+ throw {
206
+ message: error instanceof Error ? error.message : 'Unknown error occurred',
207
+ };
208
+ }
209
+ }
210
+ /**
211
+ * Gets usage information for a specific limit within a project
212
+ *
213
+ * @param project_id - The project ID to get usage for
214
+ * @param limit_id - The limit ID to get usage for
215
+ * @returns Promise resolving to the total usage value, or undefined if not found
216
+ * @throws {UsageApiError} If the request fails
217
+ */
218
+ async getProjectUsage(project_id, limit_id) {
219
+ const allUsage = await this.getProjectUsageAll(project_id);
220
+ const usageInfo = allUsage.find((info) => info.limit_id === limit_id);
221
+ return usageInfo?.total_usage;
222
+ }
223
+ }
224
+ exports.UsageApi = UsageApi;
@@ -0,0 +1,192 @@
1
+ import { SSIStorage } from "./storage/SSIStorage";
2
+ export declare const scopes: {
3
+ readonly defaultScopes: "openid profile email view_project create_project delete_project update_project view_subscribed_plan view_subscribed_feature view_subscribed_limit access_subscription_usage";
4
+ };
5
+ export type AuthConfig = {
6
+ issuerBaseUrl?: string;
7
+ authorizationEndpoint?: string;
8
+ tokenEndpoint?: string;
9
+ logoutEndpoint?: string;
10
+ jwksUri?: string;
11
+ jwksPath?: string;
12
+ jwks?: {
13
+ keys: Array<{
14
+ kty: string;
15
+ n: string;
16
+ e: string;
17
+ kid?: string;
18
+ }>;
19
+ };
20
+ clientId?: string;
21
+ clientSecret?: string;
22
+ redirectUri?: string;
23
+ scopes?: string | string[];
24
+ audience?: string;
25
+ storage?: SSIStorage;
26
+ };
27
+ export type LoginUrlOptions = {
28
+ stateKey?: string;
29
+ extraParams?: Record<string, string>;
30
+ stateTtlSeconds?: number;
31
+ };
32
+ export type CallbackParams = {
33
+ code?: string | null;
34
+ state?: string | null;
35
+ };
36
+ export type TokenResponse = {
37
+ access_token?: string;
38
+ id_token?: string;
39
+ token_type?: string;
40
+ refresh_token?: string;
41
+ expires_in?: number;
42
+ scope?: string;
43
+ raw: any;
44
+ id_claims?: Record<string, unknown>;
45
+ access_claims?: Record<string, unknown>;
46
+ };
47
+ export declare class AuthServer {
48
+ private cfg;
49
+ private stateStorage;
50
+ private jwksCache;
51
+ private lastTokens?;
52
+ constructor(config?: AuthConfig);
53
+ /**
54
+ * Build the authorization URL and persist a CSRF state for the callback.
55
+ * Returns: { url, stateKey, stateValue }
56
+ */
57
+ getLoginUrl(opts?: LoginUrlOptions): Promise<{
58
+ url: string;
59
+ stateKey: string;
60
+ stateValue: `${string}-${string}-${string}-${string}-${string}`;
61
+ }>;
62
+ /**
63
+ * Build the logout URL for RP-initiated logout.
64
+ * Common params: id_token_hint, post_logout_redirect_uri
65
+ */
66
+ getLogoutUrl(opts?: {
67
+ idTokenHint?: string;
68
+ postLogoutRedirectUri?: string;
69
+ extraParams?: Record<string, string>;
70
+ }): {
71
+ url: string;
72
+ };
73
+ /**
74
+ * Handle the OAuth callback: validates state and exchanges the code for tokens.
75
+ * Returns TokenResponse (and includes raw for debugging).
76
+ */
77
+ handleCallback(params: CallbackParams): Promise<TokenResponse>;
78
+ /**
79
+ * Exchange a refresh_token for new tokens.
80
+ * Verifies any returned JWTs and returns a TokenResponse.
81
+ *
82
+ * @param refreshToken - The refresh token to exchange for new tokens
83
+ * @param options - Optional parameters for the refresh request
84
+ * @param options.scope - Optional scope parameter (cannot exceed originally granted scope)
85
+ * @param options.useAuthHeader - Use Authorization header instead of client_secret in body
86
+ * @returns Promise<TokenResponse> - New tokens with verified claims
87
+ */
88
+ refreshTokens(refreshToken: string, options?: {
89
+ scope?: string;
90
+ useAuthHeader?: boolean;
91
+ }): Promise<TokenResponse>;
92
+ /**
93
+ * Fetch and cache JWKS from the remote endpoint.
94
+ * Uses SSICache to cache the JWKS response.
95
+ * @private
96
+ */
97
+ private fetchAndCacheJWKS;
98
+ /**
99
+ * Verify JWT signature and claims using jose library.
100
+ * Handles JWKS fetching, caching, kid selection, and algorithm validation.
101
+ * @private
102
+ */
103
+ private verifyWithIssuer;
104
+ /**
105
+ * Public method to verify a JWT.
106
+ * Use this in middleware or anywhere you need to validate tokens.
107
+ */
108
+ verifyJwt<T = unknown>(jwt: string): Promise<T>;
109
+ /**
110
+ * Verify and decode a JWT before returning its claims.
111
+ * Throws if the JWT is invalid, expired, or fails signature verification.
112
+ *
113
+ * This method is safe to use in middleware or debugging contexts when you
114
+ * simply need the decoded, verified claims from a JWT string.
115
+ */
116
+ verifyAndDecodeJwt<T = unknown>(jwt?: string): Promise<T>;
117
+ /**
118
+ * Automatically refresh tokens if they are expired or about to expire.
119
+ * This is a convenience method that handles the refresh logic automatically.
120
+ *
121
+ * @param refreshToken - The refresh token to use for refreshing
122
+ * @param options - Optional parameters for the refresh request
123
+ * @param options.bufferSeconds - How many seconds before expiry to consider tokens as "about to expire" (default: 60)
124
+ * @param options.scope - Optional scope parameter
125
+ * @param options.useAuthHeader - Use Authorization header instead of client_secret in body
126
+ * @returns Promise<TokenResponse | null> - New tokens if refreshed, null if not needed
127
+ */
128
+ autoRefreshTokens(refreshToken: string, options?: {
129
+ bufferSeconds?: number;
130
+ scope?: string;
131
+ useAuthHeader?: boolean;
132
+ }): Promise<TokenResponse | null>;
133
+ /**
134
+ * Check if the current tokens are expired or about to expire.
135
+ *
136
+ * @param bufferSeconds - How many seconds before expiry to consider tokens as "about to expire" (default: 60)
137
+ * @returns boolean - true if tokens need refreshing, false otherwise
138
+ */
139
+ needsTokenRefresh(bufferSeconds?: number): boolean;
140
+ /**
141
+ * Get the time until the current access token expires.
142
+ *
143
+ * @returns number - Seconds until expiry, or 0 if no token or expired
144
+ */
145
+ getTokenExpirySeconds(): number;
146
+ /** Returns the last TokenResponse produced by handleCallback/refreshTokens in this instance */
147
+ getLastTokenResponse(): TokenResponse | undefined;
148
+ }
149
+ /**
150
+ * USAGE EXAMPLES
151
+ *
152
+ * // Basic token refresh
153
+ * const auth = new AuthServer(config);
154
+ * const newTokens = await auth.refreshTokens(refreshToken);
155
+ *
156
+ * // Refresh with Authorization header instead of client_secret in body
157
+ * const newTokens = await auth.refreshTokens(refreshToken, {
158
+ * useAuthHeader: true
159
+ * });
160
+ *
161
+ * // Refresh with specific scope (cannot exceed originally granted scope)
162
+ * const newTokens = await auth.refreshTokens(refreshToken, {
163
+ * scope: "openid profile email offline_access"
164
+ * });
165
+ *
166
+ * // Automatic refresh - only refreshes if tokens are expired or about to expire
167
+ * const newTokens = await auth.autoRefreshTokens(refreshToken, {
168
+ * bufferSeconds: 120, // Refresh if expires within 2 minutes
169
+ * useAuthHeader: true
170
+ * });
171
+ *
172
+ * // Check if tokens need refreshing
173
+ * if (auth.needsTokenRefresh(60)) {
174
+ * const newTokens = await auth.refreshTokens(refreshToken);
175
+ * }
176
+ *
177
+ * // Get time until token expires
178
+ * const secondsUntilExpiry = auth.getTokenExpirySeconds();
179
+ * console.log(`Token expires in ${secondsUntilExpiry} seconds`);
180
+ *
181
+ * // Error handling
182
+ * try {
183
+ * const newTokens = await auth.refreshTokens(refreshToken);
184
+ * // Use new tokens...
185
+ * } catch (error) {
186
+ * if (error.message.includes('invalid_grant')) {
187
+ * // Refresh token is invalid/expired - redirect to login
188
+ * return Response.redirect('/login');
189
+ * }
190
+ * // Handle other errors...
191
+ * }
192
+ */