@softwarepatterns/am 0.0.2 → 0.2.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.
package/dist/index.d.ts CHANGED
@@ -57,6 +57,8 @@ type UserId = `uid${string}`;
57
57
  type AccountId = `acc${string}`;
58
58
  /** Identifier for a membership linking a user to an account. */
59
59
  type MembershipId = `mbr${string}`;
60
+ /** Identifier for an application (namespace for users and clients). */
61
+ type ApplicationId = `app${string}`;
60
62
  /**
61
63
  * Possible statuses for an account.
62
64
  *
@@ -80,6 +82,8 @@ type AccountStatus = "active" | "trial" | "past_due" | "suspended" | "closed";
80
82
  */
81
83
  type AccountResource = {
82
84
  id: AccountId;
85
+ /** Parent application ID - accounts always belong to an application */
86
+ parentId: ApplicationId;
83
87
  /** Display name chosen by the account owner */
84
88
  name: string | null;
85
89
  /** URL to the account's avatar image */
@@ -98,14 +102,17 @@ type AccountResource = {
98
102
  * - suspended: Access restricted due to billing or policy
99
103
  * - closed: Permanently closed
100
104
  */
101
- type UserStatus = "active" | "trial" | "past_due" | "suspended" | "closed";
105
+ type UserStatus = "active" | "disabled" | "suspended" | "deleted";
102
106
  /**
103
107
  * A reference to a user. Will not be deleted even due to GDPR requests or account closures,
104
108
  * to maintain referential integrity in audit logs and historical records.
109
+ *
110
+ * Users belong to an Application. They access Accounts via Memberships.
105
111
  */
106
112
  type UserResource = {
107
113
  id: UserId;
108
- accountId: AccountId;
114
+ /** The application this user belongs to */
115
+ applicationId: ApplicationId;
109
116
  status: UserStatus;
110
117
  preferredMembershipId: MembershipId | null;
111
118
  };
@@ -174,17 +181,21 @@ type SessionTokens = {
174
181
  type SessionProfile = UserResource & {
175
182
  identity: UserIdentity | null;
176
183
  emailCredentials: EmailCredential[];
177
- memberships: Membership[];
178
- /** Currently active membership in the accessToken (determined by preferredMembershipId or context) */
184
+ memberships: (Membership & {
185
+ account: AccountResource;
186
+ })[];
187
+ /** Currently active membership in the accessToken */
179
188
  activeMembership: Membership | null;
180
189
  lastUpdatedAt: number;
190
+ /** Convenience: the account ID from the active membership (for navigation) */
191
+ accountId: AccountId | null;
181
192
  };
182
193
  /**
183
194
  * Combined authentication state containing tokens and optional profile.
184
195
  */
185
196
  type Authentication = {
186
197
  tokens: SessionTokens;
187
- profile: SessionProfile | null;
198
+ profile: SessionProfile;
188
199
  };
189
200
  /**
190
201
  * Result of checkEmail() indicating whether the email is registered.
@@ -213,34 +224,21 @@ type Config = {
213
224
  baseUrl: string;
214
225
  earlyRefreshMs: number;
215
226
  fetchFn: FetchFn;
216
- onRefresh?: (tokens: SessionTokens) => void | Promise<void>;
217
- onProfileRefetch?: (profile: SessionProfile) => void | Promise<void>;
218
- onUnauthenticated?: (e: AuthError) => void | Promise<void>;
219
227
  profileStorageKey: string;
220
228
  storage: StorageConfig;
221
229
  tokensStorageKey: string;
222
230
  };
231
+ type AuthEventMap = {
232
+ refresh: SessionTokens;
233
+ profileChange: SessionProfile;
234
+ unauthenticated: AuthError;
235
+ sessionChange: AuthSession | null;
236
+ };
223
237
  /**
224
- * Error type for authentication-related failures.
225
- *
226
- * Always thrown on non-2xx responses from auth endpoints. Contains structured ProblemDetails
227
- * from the server when available.
228
- *
229
- * Most 400 errors will also contain `invalidParams` for parameters that caused
230
- * the error, which can be used to display field-level validation messages.
238
+ * AuthError represents structured authentication failures from Accountmaker endpoints.
231
239
  *
232
- * Note that network errors, timeouts, etc. will throw other Error types (e.g. TypeError) unrelated
233
- * to AuthError.
234
- *
235
- * Note that HTTP error codes are distinctly:
236
- * - 400: Client error (bad request, invalid input, etc.)
237
- * - 401: Unauthenticated (we don't know who you are)
238
- * - 402: Payment required (e.g. billing issue)
239
- * - 403: Unauthorized (we know who you are, but you don't have permission)
240
- * - 404: Not found
241
- * - 409: Conflict (email already registered, user already invited, etc.)
242
- * - 429: Too many requests (rate limiting)
243
- * - 500: Internal server error (server's fault)
240
+ * AuthError wraps RFC 7807 Problem Details. invalidParams may be present for field-level validation.
241
+ * Network failures throw other error types.
244
242
  *
245
243
  * Also note that the `type` field often contains a URI that points to documentation about the
246
244
  * specific error type, including how to resolve it, code samples, and links to the RFCs or other
@@ -263,6 +261,16 @@ type Config = {
263
261
  * }
264
262
  * }
265
263
  * ```
264
+ *
265
+ * Note that HTTP error codes are distinctly:
266
+ * - 400: Client error (bad request, invalid input, etc.)
267
+ * - 401: Unauthenticated (we don't know who you are)
268
+ * - 402: Payment required (e.g. billing issue)
269
+ * - 403: Unauthorized (we know who you are, but you don't have permission)
270
+ * - 404: Not found
271
+ * - 409: Conflict (email already registered, user already invited, etc.)
272
+ * - 429: Too many requests (rate limiting)
273
+ * - 500: Internal server error (server's fault)
266
274
  */
267
275
  declare class AuthError extends Error {
268
276
  readonly problem: ProblemDetails;
@@ -275,45 +283,20 @@ declare class AuthError extends Error {
275
283
  get invalidParams(): ProblemDetails["invalidParams"] | undefined;
276
284
  }
277
285
  /**
278
- * You receive an AuthSession after successful sign-in/register/etc.
279
- *
280
- * It contains the current access token, user profile, and methods to perform authorized
281
- * requests.
286
+ * AuthSession represents an authenticated user with automatic token refresh and persisted state.
282
287
  *
283
- * Features:
284
- * - session.fetch() will always use a valid access token (refreshing automatically)
285
- * - All non-2xx responses throw AuthError
286
- * - Tokens and profile are automatically persisted to storage (if configured)
288
+ * AuthSession owns tokens, profile data, refresh logic, and authenticated requests.
287
289
  */
288
290
  declare class AuthSession {
289
291
  constructor(initial: Authentication, config: Partial<Config>);
290
- /**
291
- * Restores an AuthSession from persisted storage. Returns null if no valid session
292
- * is found.
293
- *
294
- * @example
295
- * ```ts
296
- * const session = AuthSession.restoreSession();
297
- * if (session) {
298
- * console.log("Restored session for user:", session.profile);
299
- * } else {
300
- * console.log("No valid session found.");
301
- * }
302
- * ```
303
- */
304
- static restoreSession(config?: Partial<Config>): AuthSession | null;
305
292
  /**
306
293
  * Removes all persisted data (tokens, profile) from storage, and prevents future
307
- * refreshs of token and profile data. Does NOT clear current token or profile data from the
294
+ * refreshes of token and profile data. Does NOT clear current token or profile data from the
308
295
  * session memory, but effectively deactivates the session for future use.
309
296
  */
310
297
  clear(): void;
311
- accessToken(): string;
312
- refreshToken(): string;
313
- idToken(): string | undefined;
314
- expiresIn(): number;
315
- expiresAt(): Date;
316
- profile(): SessionProfile | null;
298
+ get tokens(): SessionTokens;
299
+ get profile(): SessionProfile;
317
300
  toJSON(): Authentication;
318
301
  /**
319
302
  * Creates an AuthSession from existing authentication data. Useful for restoring
@@ -326,38 +309,31 @@ declare class AuthSession {
326
309
  */
327
310
  isExpired(): boolean;
328
311
  /**
329
- * Perform an authenticated fetch. Used to call your own APIs with the current
330
- * session's access token. Any service can validate the token against Am's public
331
- * keys at https://api.accountmaker.com/.well-known/jwks.json?client_id={clientId}
332
- *
333
- * Automatically:
334
- * - Adds Authorization header
335
- * - Refreshes token if expired
336
- * - Retries once on 401 if refresh succeeds
312
+ * Performs standard fetch() with a Bearer token and automatic refresh.
337
313
  *
338
- * Assumes the response is JSON and parses it. Throws AuthError on non-2xx responses.
339
- *
340
- * If the error is a RFC 7807 Problem Details response, the AuthError.problem
341
- * will contain the full details.
314
+ * Returns Response, does not parse the body, does not throw for HTTP status codes.
315
+ * Throws AuthError when refresh fails, trows runtime errors on network failure.
342
316
  *
343
317
  * @example
344
318
  * ```ts
345
- * const res = await session.fetch('/api/projects');
319
+ * const res = await session.fetch("/api/projects");
346
320
  * const projects = await res.json();
347
321
  * ```
348
322
  *
349
- * Throws AuthError on network errors, etc.
350
- * @throws AuthError
323
+ * Any service can validate tokens using:
324
+ * https://api.accountmaker.com/.well-known/jwks.json?client_id={clientId}
351
325
  */
352
- fetch(url: string | URL, init?: RequestInit): Promise<any>;
326
+ fetch(url: string | URL, init?: RequestInit): Promise<Response>;
353
327
  /**
354
- * Refreshes the access token using the refresh token. Updates the stored tokens on
355
- * success. This is called automatically by fetch() if the token is expired or close to
356
- * expiring.
328
+ * Replaces the access token using the refresh token.
329
+ *
330
+ * It is called automatically by all other methods when the access token is expired or near expiry.
357
331
  */
358
332
  refresh(): Promise<void>;
359
333
  /**
360
- * Refetches the user's profile from the server and updates the stored profile.
334
+ * refetchProfile() replaces the cached profile with server state.
335
+ *
336
+ * Concurrent calls are deduplicated.
361
337
  *
362
338
  * @example
363
339
  * ```ts
@@ -370,29 +346,19 @@ declare class AuthSession {
370
346
  */
371
347
  refetchProfile(): Promise<void>;
372
348
  /**
373
- * Sends a verification email to the user's primary email address. This only succeeds if
374
- * called by the currently authenticated user or an account admin.
349
+ * Requests a verification email for the current user.
375
350
  *
376
351
  * Throws AuthError on network errors, etc.
377
352
  * @throws AuthError
378
353
  */
379
354
  sendVerificationEmail(): Promise<void>;
380
- /**
381
- * Fetches a user by ID.
382
- *
383
- * Throws AuthError on invalid user ID, network errors, etc.
384
- * @throws AuthError
385
- */
386
- user(id: UserId): Promise<UserResource>;
387
355
  }
388
356
  /**
389
- * Use `Am` to perform initial sign-in, registration, password reset flows, magic links,
390
- * invite acceptance, and other unauthenticated actions.
357
+ * Am runs authentication flows and produces AuthSession.
391
358
  *
392
- * Once authentication succeeds, these methods return an `AuthSession` that you use
393
- * for all subsequent authenticated requests.
359
+ * Use Am before a session exists (sign-in, sign-up, magic link, invites, password reset, CSRF).
394
360
  *
395
- * Example:
361
+ * @example
396
362
  * ```ts
397
363
  * const am = new Am();
398
364
  * const session = await am.signIn({ email: 'user@example.com', password: 'secret' });
@@ -400,68 +366,55 @@ declare class AuthSession {
400
366
  * ```
401
367
  */
402
368
  declare class Am {
403
- private options;
404
369
  constructor(config?: Partial<Config>);
405
370
  /**
406
- * Creates an AuthSession from existing authentication data. Useful for restoring
407
- * a session from custom storage or creating a session from custom server-provided data.
371
+ * session returns the current AuthSession or null.
372
+ *
373
+ * Use restoreSession() to load from storage.
374
+ *
375
+ * @example
376
+ * ```ts
377
+ * const session = am.session;
378
+ * if (!session) throw new Error("Not authenticated");
379
+ * ```
408
380
  */
409
- static createAuthSession(initial: Authentication, config?: Partial<Config>): AuthSession;
381
+ get session(): AuthSession | null;
410
382
  /**
411
- * Creates an AuthSession from existing authentication data. Useful for restoring
412
- * a session from custom storage or creating a session from custom server-provided data.
383
+ * Constructs AuthSession from existing tokens and profile.
384
+ *
385
+ * Use this after a server-side auth handshake or custom persistence.
413
386
  */
414
- createAuthSession(initial: Authentication): AuthSession;
387
+ createSession(initial: Authentication): AuthSession;
415
388
  /**
416
- * Accepts an invitation to join an account. On success, returns an AuthSession
417
- * containing fresh tokens and profile. Tokens are automatically persisted (if
418
- * storage is enabled).
389
+ * restoreSession loads AuthSession from storage or returns null.
419
390
  *
420
- * Throws AuthError on invalid or expired token, etc.
421
- * @throws AuthError
391
+ * Invalid or partial stored data is cleared.
392
+ *
393
+ * @example
394
+ * ```ts
395
+ * const am = new Am({ storage: 'localStorage' });
396
+ * const session = am.restoreSession();
397
+ * if (session) session.fetch("/api/me");
398
+ * ```
422
399
  */
423
- static acceptInvite(query: {
424
- clientId: ClientId;
425
- token: string;
426
- }, config?: Partial<Config>): Promise<AuthSession>;
400
+ restoreSession(): AuthSession | null;
427
401
  /**
428
- * Accepts an invitation to join an account. On success, returns an AuthSession
429
- * containing fresh tokens and profile. Tokens are automatically persisted (if
430
- * storage is enabled).
402
+ * Subscribes to auth events and returns an unsubscribe function.
403
+ */
404
+ on<K extends keyof AuthEventMap>(event: K, fn: (v: AuthEventMap[K]) => void): () => boolean;
405
+ /**
406
+ * acceptInvite exchanges an invite token for a fresh AuthSession.
431
407
  *
432
- * Throws AuthError on invalid or expired token, etc.
433
- * @throws AuthError
408
+ * Throws AuthError on invalid, expired, or already-used tokens.
434
409
  */
435
410
  acceptInvite(query: {
436
411
  clientId: ClientId;
437
412
  token: string;
438
413
  }): Promise<AuthSession>;
439
414
  /**
440
- * Checks the status of an email address for authentication purposes. Indicates whether the
441
- * email is associated with an active account, and what login methods are preferred and
442
- * available for that user. This can be used to enforce login expierences for enterprise SSO or
443
- * to prefer passwordless login methods.
444
- *
445
- * Throws AuthError on invalid client ID, network errors, etc.
446
- * @throws AuthError
447
- */
448
- static checkEmail(body: {
449
- clientId: ClientId;
450
- email: string;
451
- csrfToken?: string;
452
- }, config?: Partial<Config>): Promise<{
453
- status: EmailCheckStatus;
454
- preferred: LoginMethod[];
455
- available: LoginMethod[];
456
- }>;
457
- /**
458
- * Checks the status of an email address for authentication purposes. Indicates whether the
459
- * email is associated with an active account, and what login methods are preferred and
460
- * available for that user. This can be used to enforce login expierences for enterprise SSO or
461
- * to prefer passwordless login methods.
415
+ * checkEmail returns how an email should authenticate for this client.
462
416
  *
463
- * Throws AuthError on invalid client ID, network errors, etc.
464
- * @throws AuthError
417
+ * Use this to choose password vs magic link vs SSO before rendering a login form.
465
418
  */
466
419
  checkEmail(body: {
467
420
  clientId: ClientId;
@@ -473,66 +426,25 @@ declare class Am {
473
426
  available: LoginMethod[];
474
427
  }>;
475
428
  /**
476
- * When called, sets a httpOnly CSRF session cookie. Then when rendering a form, call csrfToken()
477
- * to get the signed token to include in the form. When the form is submitted, the server
478
- * will verify the signed token against the session cookie. This prevents a certain class
479
- * of CSRF attacks that rely on being able to read values from the target site.
480
- */
481
- static csrfSession(config?: Partial<Config>): Promise<{
482
- csrfToken: string;
483
- }>;
484
- /**
485
- * When called, sets a httpOnly CSRF session cookie. Then when rendering a form, call csrfToken()
486
- * to get the signed token to include in the form. When the form is submitted, the server
487
- * will verify the signed token against the session cookie. This prevents a certain class
488
- * of CSRF attacks that rely on being able to read values from the target site.
489
- */
490
- csrfSession(): Promise<{
491
- csrfToken: string;
492
- }>;
493
- /**
494
- * Fetches a signed CSRF token for use in forms. Call csrfSession() first to set a httpOnly CSRF
495
- * session cookie, then call this method to get the signed token, then include the token in your
496
- * form submissions. When the form is submitted, the server will verify the signed token against
497
- * the httpOnly session cookie. This prevents a certain class of CSRF attacks that rely on being
498
- * able to read values from the target site.
429
+ * Sets the httpOnly CSRF cookie.
499
430
  *
500
- * @throws AuthError
431
+ * Call csrfToken() next to fetch a signed token for form posts.
501
432
  */
502
- static csrfToken(config?: Partial<Config>): Promise<{
433
+ csrfSession(): Promise<{
503
434
  csrfToken: string;
504
435
  }>;
505
436
  /**
506
- * Fetches a signed CSRF token for use in forms. Call csrfSession() first to set a httpOnly CSRF
507
- * session cookie, then call this method to get the signed token, then include the token in your
508
- * form submissions. When the form is submitted, the server will verify the signed token against
509
- * the httpOnly session cookie. This prevents a certain class of CSRF attacks that rely on being
510
- * able to read values from the target site.
437
+ * Returns a signed CSRF token for form posts.
511
438
  *
512
- * @throws AuthError
439
+ * Call csrfSession() first to set the CSRF cookie.
513
440
  */
514
441
  csrfToken(): Promise<{
515
442
  csrfToken: string;
516
443
  }>;
517
444
  /**
518
- * On success, returns an AuthSession containing fresh tokens and profile.
519
- * Tokens are automatically persisted (if storage is enabled).
445
+ * Authenticates with email and password and returns a new AuthSession.
520
446
  *
521
- * Throws AuthError on invalid credentials, unverified email, etc.
522
- * @throws AuthError
523
- */
524
- static signIn(body: {
525
- clientId: ClientId;
526
- email: string;
527
- password: string;
528
- csrfToken?: string;
529
- }, config?: Partial<Config>): Promise<AuthSession>;
530
- /**
531
- * On success, returns an AuthSession containing fresh tokens and profile.
532
- * Tokens are automatically persisted (if storage is enabled).
533
- *
534
- * Throws AuthError on invalid credentials, unverified email, etc.
535
- * @throws AuthError
447
+ * Tokens and profile are persisted when storage is configured.
536
448
  */
537
449
  signIn(body: {
538
450
  clientId: ClientId;
@@ -541,62 +453,15 @@ declare class Am {
541
453
  csrfToken?: string;
542
454
  }): Promise<AuthSession>;
543
455
  /**
544
- * Authenticates using a one-time token (e.g., magic link or invite token).
545
- *
546
- * On success, returns an AuthSession containing fresh tokens and profile.
547
- * Tokens are automatically persisted (if storage is enabled).
548
- *
549
- * Throws AuthError on invalid or expired token, etc.
550
- * @throws AuthError
551
- */
552
- static signInWithToken(token: string, config?: Partial<Config>): Promise<AuthSession>;
553
- /**
554
- * Authenticates using a one-time token (e.g., magic link or invite token).
456
+ * Authenticates with a one-time token and returns a new AuthSession.
555
457
  *
556
- * On success, returns an AuthSession containing fresh tokens and profile.
557
- * Tokens are automatically persisted (if storage is enabled).
558
- *
559
- * Throws AuthError on invalid or expired token, etc.
560
- * @throws AuthError
458
+ * Use this for magic links and similar one-time login flows.
561
459
  */
562
460
  signInWithToken(token: string): Promise<AuthSession>;
563
461
  /**
564
- * Manually refreshes session tokens using the provided refresh token. Does NOT
565
- * persist tokens. Use AuthSession.refresh() to refresh and persist tokens in an
566
- * existing session.
462
+ * Creates a new user and returns a new AuthSession.
567
463
  *
568
- * Throws AuthError on invalid or expired refresh token, etc.
569
- * @throws AuthError
570
- */
571
- static refresh(refreshToken: string, config?: Partial<Config>): Promise<SessionTokens>;
572
- /**
573
- * Manually refreshes session tokens using the provided refresh token. Does NOT
574
- * persist tokens. Use AuthSession.refresh() to refresh and persist tokens in an
575
- * existing session.
576
- *
577
- * Throws AuthError on invalid or expired refresh token, etc.
578
- * @throws AuthError
579
- */
580
- refresh(refreshToken: string): Promise<SessionTokens>;
581
- /**
582
- * Successful registration immediately authenticates the user and returns an
583
- * AuthSession. Tokens are automatically persisted (if storage is enabled).
584
- *
585
- * Throws AuthError on invalid data, existing email, etc.
586
- * @throws AuthError
587
- */
588
- static signUp(body: {
589
- clientId: ClientId;
590
- email: string;
591
- password: string;
592
- csrfToken?: string;
593
- }, config?: Partial<Config>): Promise<AuthSession>;
594
- /**
595
- * Successful registration immediately authenticates the user and returns an
596
- * AuthSession. Tokens are automatically persisted (if storage is enabled).
597
- *
598
- * Throws AuthError on invalid data, existing email, etc.
599
- * @throws AuthError
464
+ * Tokens and profile are persisted when storage is configured.
600
465
  */
601
466
  signUp(body: {
602
467
  clientId: ClientId;
@@ -605,45 +470,14 @@ declare class Am {
605
470
  csrfToken?: string;
606
471
  }): Promise<AuthSession>;
607
472
  /**
608
- * Completes a password reset using the token received via email. On success,
609
- * the user's password is updated.
610
- *
611
- * Throws AuthError on invalid or expired token, weak password, etc.
612
- * @throws AuthError
613
- */
614
- static resetPassword(body: {
615
- token: string;
616
- newPassword: string;
617
- }, config?: Partial<Config>): Promise<void>;
618
- /**
619
- * Completes a password reset using the token received via email. On success,
620
- * the user's password is updated.
621
- *
622
- * Throws AuthError on invalid or expired token, weak password, etc.
623
- * @throws AuthError
473
+ * Sets a new password using a one-time reset token.
624
474
  */
625
475
  resetPassword(body: {
626
476
  token: string;
627
477
  newPassword: string;
628
478
  }): Promise<void>;
629
479
  /**
630
- * Sends a magic link email to the specified address. A magic link allows
631
- * passwordless authentication.
632
- *
633
- * Throws AuthError on invalid email format, etc.
634
- * @throws AuthError
635
- */
636
- static sendMagicLink(body: {
637
- clientId: ClientId;
638
- email: string;
639
- csrfToken?: string;
640
- }, config?: Partial<Config>): Promise<void>;
641
- /**
642
- * Sends a magic link email to the specified address. A magic link allows
643
- * passwordless authentication.
644
- *
645
- * Throws AuthError on invalid email format, etc.
646
- * @throws AuthError
480
+ * Sends a one-time sign-in link to an email address.
647
481
  */
648
482
  sendMagicLink(body: {
649
483
  clientId: ClientId;
@@ -651,21 +485,7 @@ declare class Am {
651
485
  csrfToken?: string;
652
486
  }): Promise<void>;
653
487
  /**
654
- * Sends a password reset email to the specified address.
655
- *
656
- * Throws AuthError on invalid email format, etc.
657
- * @throws AuthError
658
- */
659
- static sendPasswordReset(body: {
660
- clientId: ClientId;
661
- email: string;
662
- csrfToken?: string;
663
- }, config?: Partial<Config>): Promise<void>;
664
- /**
665
- * Sends a password reset email to the specified address.
666
- *
667
- * Throws AuthError on invalid email format, etc.
668
- * @throws AuthError
488
+ * Sends a password reset link to an email address.
669
489
  */
670
490
  sendPasswordReset(body: {
671
491
  clientId: ClientId;
@@ -675,4 +495,4 @@ declare class Am {
675
495
  }
676
496
 
677
497
  export { Am, AuthError, AuthSession };
678
- export type { AccountId, AccountResource, AccountStatus, Authentication, ClientId, EmailCheckStatus, EmailCredential, LoginMethod, Membership, MembershipId, MembershipRole, ProblemDetails, SessionProfile, SessionTokens, StorageLike, UserId, UserIdentity, UserResource, UserStatus };
498
+ export type { AccountId, AccountResource, AccountStatus, ApplicationId, Authentication, ClientId, EmailCheckStatus, EmailCredential, LoginMethod, Membership, MembershipId, MembershipRole, ProblemDetails, SessionProfile, SessionTokens, StorageLike, UserId, UserIdentity, UserResource, UserStatus };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- const t=Symbol("state");function e(t){return t?"localStorage"===t?function(){try{return"undefined"==typeof window?null:window.localStorage?window.localStorage:null}catch{return null}}():t:null}function n(t,e){return t?function(t){if(!t)return null;try{return JSON.parse(t)}catch{return null}}(t.getItem(e)):null}function r(t,e,n){if(t)try{t.setItem(e,JSON.stringify(n))}catch{}}function s(t,e){if(t)try{t.removeItem(e)}catch{}}function o(t,e,o){if(!t)return;const i=n(t,e),c=a(i)?i:null;null===i||c||s(t,e),c&&c.expiresAt>=o.expiresAt||r(t,e,o)}function i(t,e,o){if(!t)return;const i=n(t,e),a=c(i)?i:null;null===i||a||s(t,e),a&&a.lastUpdatedAt>=o.lastUpdatedAt||r(t,e,o)}function a(t){return!!t&&"string"==typeof t.accessToken&&"string"==typeof t.refreshToken&&"number"==typeof t.expiresAt&&"number"==typeof t.expiresIn&&"Bearer"===t.tokenType}function c(t){return!!t&&"string"==typeof t.id&&"string"==typeof t.accountId&&"string"==typeof t.status&&"number"==typeof t.lastUpdatedAt&&("object"==typeof t.identity||null===t.identity)}function u(e){return e[t]}function l(t){return t.replace(/_([a-z])/g,(t,e)=>e.toUpperCase())}function f(t){if(null===t||"object"!=typeof t)return t;if(Array.isArray(t))return t.map(t=>f(t));const e=t,n={};for(const t of Object.keys(e))Object.prototype.hasOwnProperty.call(e,t)&&(n[l(t)]=f(e[t]));return n}function p(t){return t.replace(/[A-Z]/g,t=>`_${t.toLowerCase()}`)}function h(t){if(null===t||"object"!=typeof t)return t;if(Array.isArray(t))return t.map(t=>h(t));const e=t,n={};for(const t of Object.keys(e))Object.prototype.hasOwnProperty.call(e,t)&&(n[p(t)]=h(e[t]));return n}class y extends Error{constructor(t){super(t.title),this.name="AuthError",this.problem=Object.freeze(t)}get type(){return this.problem.type}get title(){return this.problem.title}get status(){return this.problem.status}get code(){return this.problem.code}get detail(){return this.problem.detail}get invalidParams(){return this.problem.invalidParams}}const g={fetchFn:function(){const t=globalThis.fetch;return"function"==typeof t?t.bind(globalThis):async()=>{throw new Error("Missing fetch implementation. Provide config.fetchFn or use a runtime with global fetch.")}}(),baseUrl:"https://api.accountmaker.com",earlyRefreshMs:6e4,storage:null,tokensStorageKey:"am_tokens",profileStorageKey:"am_profile"};const d=async t=>{if(204===t.status)return;const e=f(await async function(t){const e=t.headers.get("Content-Type")||"";if(!e.includes("application/json")&&!e.includes("+json"))return null;try{return await t.json()}catch{return null}}(t));if(!t.ok)throw new y(function(t,e){return function(t){return(t.headers.get("Content-Type")||"").includes("application/problem+json")}(t)&&e&&"object"==typeof e&&"string"==typeof e.type&&"string"==typeof e.title&&"number"==typeof e.status?e:function(t,e){return{type:"about:blank",title:t.statusText||"Request failed",status:t.status,detail:"string"==typeof e?e:void 0}}(t,e)}(t,e));return e},w=async({fetchFn:t,baseUrl:e},n,r={})=>{const s=new URLSearchParams(h(r)).toString(),o=s?`${e}${n}?${s}`:`${e}${n}`,i=await t(o,{method:"GET",headers:{Accept:"application/json"}});return d(i)},m=async({fetchFn:t,baseUrl:e},n,r)=>{const s=await t(`${e}${n}`,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(h(r))});return d(s)},k=async(t,e,n={})=>{const r=await t.config.fetchFn(e,{...n,headers:{...n.headers||{},Authorization:`Bearer ${t.tokens.accessToken}`}});return d(r)},b=t=>{const e=Math.min(Math.max(t.config.earlyRefreshMs,0),3e5);return Date.now()>=t.tokens.expiresAt-e},S=async t=>{if(!t.cleared){t.refreshPromise||(t.refreshPromise=async function(t){const{fetchFn:n,baseUrl:r}=t.config,s=await n(`${r}/auth/refresh`,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(h({refreshToken:t.tokens.refreshToken}))}),i=j(await d(s));t.tokens=i;const a=e(t.config.storage);o(a,t.config.tokensStorageKey,i),await(t.config.onRefresh?.(i))}(t));try{await t.refreshPromise}finally{t.refreshPromise=null}}},T=t=>t instanceof y&&401===t.status,P=async(t,e,n={})=>{b(t)&&await S(t);try{return await k(t,e,n)}catch(r){if(!T(r))throw r;if(t.cleared)throw r;try{return await S(t),await k(t,e,n)}catch(e){if(t.config.onUnauthenticated&&!t.cleared&&T(e))try{await t.config.onUnauthenticated(e)}catch{}throw e}}},A=async(t,e,n={})=>{const r=new URLSearchParams(h(n)).toString(),s=t.config.baseUrl,o=r?`${s}${e}?${r}`:`${s}${e}`;return await P(t,o,{method:"GET",headers:{Accept:"application/json"}})},j=t=>{const e="number"==typeof t.expiresIn?t.expiresIn:0;return{...t,expiresAt:Date.now()+1e3*e}},I=t=>({...t,lastUpdatedAt:Date.now()});const U=t=>({tokens:j(t.tokens),profile:I(t.profile)});class ${constructor(n,r){const s={...g,...r},a={...n,config:s,refreshPromise:null,profilePromise:null,cleared:!1};!function(e,n){e[t]=n}(this,a);const c=e(s.storage);o(c,s.tokensStorageKey,a.tokens),a.profile&&i(c,s.profileStorageKey,a.profile)}static restoreSession(t={}){const r={...g,...t},o=e(r.storage);if(!o)return null;const i=n(o,r.tokensStorageKey),u=a(i)?i:null;if(!u)return null!==i&&s(o,r.tokensStorageKey),s(o,r.profileStorageKey),null;const l=n(o,r.profileStorageKey),f=c(l)?l:null;return null===l||f||s(o,r.profileStorageKey),new $({tokens:u,profile:f},r)}clear(){const t=u(this);!function(t){const n=e(t.storage);s(n,t.profileStorageKey),s(n,t.tokensStorageKey)}(t.config),t.cleared=!0}accessToken(){return u(this).tokens.accessToken}refreshToken(){return u(this).tokens.refreshToken}idToken(){return u(this).tokens.idToken}expiresIn(){return u(this).tokens.expiresIn}expiresAt(){return new Date(u(this).tokens.expiresAt)}profile(){return u(this).profile}toJSON(){const t=u(this);return{tokens:t.tokens,profile:t.profile}}static fromJSON(t,e){return new $(t,e)}isExpired(){return b(u(this))}async fetch(t,e={}){return P(u(this),t,e)}async refresh(){return S(u(this))}async refetchProfile(){const t=u(this);if(!t.cleared){t.profilePromise||(t.profilePromise=async function(t){const n=I(await A(t,"/auth/me",{}));t.profile=n,i(e(t.config.storage),t.config.profileStorageKey,n),await(t.config.onProfileRefetch?.(n))}(t));try{await t.profilePromise}finally{t.profilePromise=null}}}async sendVerificationEmail(){await(async(t,e,n)=>{const r=t.config.baseUrl;return await P(t,`${r}${e}`,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(h(n))})})(u(this),"/auth/send-verification-email",{})}async user(t){return A(u(this),`/auth/user/${t}`,{})}}class x{constructor(t){this.options={...g,...t}}static createAuthSession(t,e){return new x(e).createAuthSession(t)}createAuthSession(t){return new $(t,this.options)}static async acceptInvite(t,e){return new x(e).acceptInvite(t)}async acceptInvite(t){const e=U(await w(this.options,"/auth/accept-invite",t));return new $(e,this.options)}static async checkEmail(t,e){return new x(e).checkEmail(t)}async checkEmail(t){return m(this.options,"/auth/check-email",t)}static async csrfSession(t){return new x(t).csrfSession()}async csrfSession(){return w(this.options,"/auth/csrf-session")}static async csrfToken(t){return new x(t).csrfToken()}async csrfToken(){return w(this.options,"/auth/csrf-token")}static async signIn(t,e){return new x(e).signIn(t)}async signIn(t){const e=U(await m(this.options,"/auth/sign-in",t));return new $(e,this.options)}static async signInWithToken(t,e){return new x(e).signInWithToken(t)}async signInWithToken(t){const e=this.options,n=U(await w(e,"/auth/sign-in-with-token",{token:t}));return new $(n,e)}static async refresh(t,e){return new x(e).refresh(t)}async refresh(t){return j(await m(this.options,"/auth/refresh",{refreshToken:t}))}static async signUp(t,e){return new x(e).signUp(t)}async signUp(t){const e=U(await m(this.options,"/auth/sign-up",t));return new $(e,this.options)}static async resetPassword(t,e){return new x(e).resetPassword(t)}async resetPassword(t){return m(this.options,"/auth/reset-password",t)}static async sendMagicLink(t,e){return new x(e).sendMagicLink(t)}async sendMagicLink(t){return m(this.options,"/auth/send-magic-link",t)}static async sendPasswordReset(t,e){return new x(e).sendPasswordReset(t)}async sendPasswordReset(t){return m(this.options,"/auth/send-password-reset",t)}}export{x as Am,y as AuthError,$ as AuthSession};
1
+ const t=Symbol("session_state"),e=Symbol("auth_session"),n=Symbol("auth_state"),r=Symbol("emitter");function s(t){return t?"localStorage"===t?function(){try{return"undefined"==typeof window?null:window.localStorage?window.localStorage:null}catch{return null}}():t:null}function o(t,e){return t?function(t){if(!t)return null;try{return JSON.parse(t)}catch{return null}}(t.getItem(e)):null}function i(t,e,n){if(t)try{t.setItem(e,JSON.stringify(n))}catch{}}function a(t,e){if(t)try{t.removeItem(e)}catch{}}function c(t,e,n){if(!t)return;const r=o(t,e),s=u(r)?r:null;null===r||s||a(t,e),s&&s.expiresAt>=n.expiresAt||i(t,e,n)}function l(t,e,n){if(!t)return;const r=o(t,e),s=f(r)?r:null;null===r||s||a(t,e),s&&s.lastUpdatedAt>=n.lastUpdatedAt||i(t,e,n)}function u(t){return!!t&&"string"==typeof t.accessToken&&"string"==typeof t.refreshToken&&"number"==typeof t.expiresAt&&"number"==typeof t.expiresIn&&"Bearer"===t.tokenType}function f(t){return!!t&&"string"==typeof t.id&&"string"==typeof t.applicationId&&"string"==typeof t.status&&"number"==typeof t.lastUpdatedAt&&("object"==typeof t.identity||null===t.identity)}function p(e){return e[t]}function h(t){return t[n]}function y(t,n){t[e]=n;const s=p(n);!function(t,e){t[r]=(t,n)=>{const r=h(e).listeners[t];if(r)for(const e of r)try{e(n)}catch{console.warn("Unhandled error in AuthEvent listener for event",t)}}}(s,t),g(s,"sessionChange",n)}function g(t,e,n){(0,t[r])(e,n)}function d(t){return t.replace(/_([a-z])/g,(t,e)=>e.toUpperCase())}function m(t){if(null===t||"object"!=typeof t)return t;if(Array.isArray(t))return t.map(t=>m(t));const e=t,n={};for(const t of Object.keys(e))Object.prototype.hasOwnProperty.call(e,t)&&(n[d(t)]=m(e[t]));return n}function w(t){return t.replace(/[A-Z]/g,t=>`_${t.toLowerCase()}`)}function b(t){if(null===t||"object"!=typeof t)return t;if(Array.isArray(t))return t.map(t=>b(t));const e=t,n={};for(const t of Object.keys(e))Object.prototype.hasOwnProperty.call(e,t)&&(n[w(t)]=b(e[t]));return n}class k extends Error{constructor(t){super(t.title),this.name="AuthError",this.problem=Object.freeze(t)}get type(){return this.problem.type}get title(){return this.problem.title}get status(){return this.problem.status}get code(){return this.problem.code}get detail(){return this.problem.detail}get invalidParams(){return this.problem.invalidParams}}const S={fetchFn:function(){const t=globalThis.fetch;return"function"==typeof t?t.bind(globalThis):async()=>{throw new Error("Missing fetch implementation. Provide config.fetchFn or use a runtime with global fetch.")}}(),baseUrl:"https://api.accountmaker.com",earlyRefreshMs:6e4,storage:null,tokensStorageKey:"am_tokens",profileStorageKey:"am_profile"};const P=async(t,e,n={})=>t.config.fetchFn(e,function(t,e){const n=new Headers(t.headers);return n.set("Authorization",`Bearer ${e}`),{...t,headers:n}}(n,t.tokens.accessToken)),A=async(t,e,n={})=>{U(t)&&await x(t);let r=await P(t,e,n);return 401!==r.status||t.cleared||(await x(t),r=await P(t,e,n)),r},j=async t=>{if(204===t.status)return;const e=m(await async function(t){const e=t.headers.get("Content-Type")||"";if(!e.includes("application/json")&&!e.includes("+json"))return null;try{return await t.json()}catch{return null}}(t));if(!t.ok)throw new k(function(t,e){return function(t){return(t.headers.get("Content-Type")||"").includes("application/problem+json")}(t)&&e&&"object"==typeof e&&"string"==typeof e.type&&"string"==typeof e.title&&"number"==typeof e.status?e:function(t,e){return{type:"about:blank",title:t.statusText||"Request failed",status:t.status,detail:"string"==typeof e?e:void 0}}(t,e)}(t,e));return e},T=async(t,e,n={})=>{const r=await A(t,e,n);return j(r)},O=async({fetchFn:t,baseUrl:e},n,r={})=>{const s=new URLSearchParams(b(r)).toString(),o=s?`${e}${n}?${s}`:`${e}${n}`,i=await t(o,{method:"GET",headers:{Accept:"application/json"}});return j(i)},$=async({fetchFn:t,baseUrl:e},n,r)=>{const s=await t(`${e}${n}`,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(b(r))});return j(s)},U=t=>{const e=Math.min(Math.max(t.config.earlyRefreshMs,0),3e5);return Date.now()>=t.tokens.expiresAt-e},x=async t=>{if(!t.cleared){t.refreshPromise||(t.refreshPromise=async function(t){const{fetchFn:e,baseUrl:n}=t.config,r=await e(`${n}/auth/refresh`,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(b({refreshToken:t.tokens.refreshToken}))});let o;try{o=await j(r)}catch(e){throw!t.cleared&&K(e)&&g(t,"unauthenticated",e),e}const i=v(o);t.tokens=i;const a=s(t.config.storage);c(a,t.config.tokensStorageKey,i),g(t,"refresh",i)}(t));try{await t.refreshPromise}finally{t.refreshPromise=null}}},K=t=>t instanceof k&&401===t.status,v=t=>{const e="number"==typeof t.expiresIn?t.expiresIn:0;return{...t,expiresAt:Date.now()+1e3*e}},I=t=>({...t,lastUpdatedAt:Date.now()});async function C(t){const e=I(await(async(t,e,n={})=>{const r=new URLSearchParams(b(n)).toString(),s=t.config.baseUrl,o=r?`${s}${e}?${r}`:`${s}${e}`;return await T(t,o,{method:"GET",headers:{Accept:"application/json"}})})(t,"/auth/me",{}));t.profile=e;l(s(t.config.storage),t.config.profileStorageKey,e),g(t,"profileChange",e)}const E=t=>({tokens:v(t.tokens),profile:I(t.profile)});class J{constructor(e,n){const r={...S,...n},o={...e,config:r,refreshPromise:null,profilePromise:null,cleared:!1};!function(e,n){e[t]=n}(this,o);const i=s(r.storage);c(i,r.tokensStorageKey,o.tokens),o.profile&&l(i,r.profileStorageKey,o.profile)}clear(){const t=p(this);!function(t){const e=s(t.storage);a(e,t.profileStorageKey),a(e,t.tokensStorageKey)}(t.config),t.cleared=!0}get tokens(){return p(this).tokens}get profile(){return p(this).profile}toJSON(){const t=p(this);return{tokens:t.tokens,profile:t.profile}}static fromJSON(t,e){return new J(t,e)}isExpired(){return U(p(this))}async fetch(t,e={}){return A(p(this),t,e)}async refresh(){return x(p(this))}async refetchProfile(){const t=p(this);if(!t.cleared){t.profilePromise||(t.profilePromise=C(t));try{await t.profilePromise}finally{t.profilePromise=null}}}async sendVerificationEmail(){await(async(t,e,n)=>{const r=t.config.baseUrl;return await T(t,`${r}${e}`,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(b(n))})})(p(this),"/auth/send-verification-email",{})}}class N{constructor(t){var e,r;e=this,r={config:{...S,...t},listeners:{}},e[n]=r}get session(){return this[e]||null}createSession(t){const e=new J(t,h(this).config);return y(this,e),e}restoreSession(){const t=h(this).config,e=s(t.storage);if(!e)return null;const n=o(e,t.tokensStorageKey),r=u(n)?n:null,i=o(e,t.profileStorageKey),c=f(i)?i:null;if(!r||!c)return a(e,t.tokensStorageKey),a(e,t.profileStorageKey),null;const l=new J({tokens:r,profile:c},t);return y(this,l),l}on(t,e){const n=h(this).listeners,r=n[t]??(n[t]=new Set);return r.add(e),()=>r.delete(e)}async acceptInvite(t){const e=h(this).config,n=E(await O(e,"/auth/accept-invite",t)),r=new J(n,e);return y(this,r),r}async checkEmail(t){return $(h(this).config,"/auth/check-email",t)}async csrfSession(){return O(h(this).config,"/auth/csrf-session")}async csrfToken(){return O(h(this).config,"/auth/csrf-token")}async signIn(t){const e=h(this).config,n=E(await $(e,"/auth/sign-in",t)),r=new J(n,e);return y(this,r),r}async signInWithToken(t){const e=h(this).config,n=E(await O(e,"/auth/sign-in-with-token",{token:t})),r=new J(n,e);return y(this,r),r}async signUp(t){const e=h(this).config,n=E(await $(e,"/auth/sign-up",t)),r=new J(n,e);return y(this,r),r}async resetPassword(t){return $(h(this).config,"/auth/reset-password",t)}async sendMagicLink(t){return $(h(this).config,"/auth/send-magic-link",t)}async sendPasswordReset(t){return $(h(this).config,"/auth/send-password-reset",t)}}export{N as Am,k as AuthError,J as AuthSession};
2
2
  //# sourceMappingURL=index.mjs.map