@softwarepatterns/am 0.0.2 → 0.1.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
@@ -80,6 +80,7 @@ type AccountStatus = "active" | "trial" | "past_due" | "suspended" | "closed";
80
80
  */
81
81
  type AccountResource = {
82
82
  id: AccountId;
83
+ parentId: AccountId | null;
83
84
  /** Display name chosen by the account owner */
84
85
  name: string | null;
85
86
  /** URL to the account's avatar image */
@@ -98,7 +99,7 @@ type AccountResource = {
98
99
  * - suspended: Access restricted due to billing or policy
99
100
  * - closed: Permanently closed
100
101
  */
101
- type UserStatus = "active" | "trial" | "past_due" | "suspended" | "closed";
102
+ type UserStatus = "active" | "disabled" | "suspended" | "deleted";
102
103
  /**
103
104
  * A reference to a user. Will not be deleted even due to GDPR requests or account closures,
104
105
  * to maintain referential integrity in audit logs and historical records.
@@ -174,7 +175,9 @@ type SessionTokens = {
174
175
  type SessionProfile = UserResource & {
175
176
  identity: UserIdentity | null;
176
177
  emailCredentials: EmailCredential[];
177
- memberships: Membership[];
178
+ memberships: (Membership & {
179
+ account: AccountResource;
180
+ })[];
178
181
  /** Currently active membership in the accessToken (determined by preferredMembershipId or context) */
179
182
  activeMembership: Membership | null;
180
183
  lastUpdatedAt: number;
@@ -184,7 +187,7 @@ type SessionProfile = UserResource & {
184
187
  */
185
188
  type Authentication = {
186
189
  tokens: SessionTokens;
187
- profile: SessionProfile | null;
190
+ profile: SessionProfile;
188
191
  };
189
192
  /**
190
193
  * Result of checkEmail() indicating whether the email is registered.
@@ -213,34 +216,21 @@ type Config = {
213
216
  baseUrl: string;
214
217
  earlyRefreshMs: number;
215
218
  fetchFn: FetchFn;
216
- onRefresh?: (tokens: SessionTokens) => void | Promise<void>;
217
- onProfileRefetch?: (profile: SessionProfile) => void | Promise<void>;
218
- onUnauthenticated?: (e: AuthError) => void | Promise<void>;
219
219
  profileStorageKey: string;
220
220
  storage: StorageConfig;
221
221
  tokensStorageKey: string;
222
222
  };
223
+ type AuthEventMap = {
224
+ refresh: SessionTokens;
225
+ profileChange: SessionProfile;
226
+ unauthenticated: AuthError;
227
+ sessionChange: AuthSession | null;
228
+ };
223
229
  /**
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.
230
+ * AuthError represents structured authentication failures from Accountmaker endpoints.
231
231
  *
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)
232
+ * AuthError wraps RFC 7807 Problem Details. invalidParams may be present for field-level validation.
233
+ * Network failures throw other error types.
244
234
  *
245
235
  * Also note that the `type` field often contains a URI that points to documentation about the
246
236
  * specific error type, including how to resolve it, code samples, and links to the RFCs or other
@@ -263,6 +253,16 @@ type Config = {
263
253
  * }
264
254
  * }
265
255
  * ```
256
+ *
257
+ * Note that HTTP error codes are distinctly:
258
+ * - 400: Client error (bad request, invalid input, etc.)
259
+ * - 401: Unauthenticated (we don't know who you are)
260
+ * - 402: Payment required (e.g. billing issue)
261
+ * - 403: Unauthorized (we know who you are, but you don't have permission)
262
+ * - 404: Not found
263
+ * - 409: Conflict (email already registered, user already invited, etc.)
264
+ * - 429: Too many requests (rate limiting)
265
+ * - 500: Internal server error (server's fault)
266
266
  */
267
267
  declare class AuthError extends Error {
268
268
  readonly problem: ProblemDetails;
@@ -275,45 +275,20 @@ declare class AuthError extends Error {
275
275
  get invalidParams(): ProblemDetails["invalidParams"] | undefined;
276
276
  }
277
277
  /**
278
- * You receive an AuthSession after successful sign-in/register/etc.
278
+ * AuthSession represents an authenticated user with automatic token refresh and persisted state.
279
279
  *
280
- * It contains the current access token, user profile, and methods to perform authorized
281
- * requests.
282
- *
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)
280
+ * AuthSession owns tokens, profile data, refresh logic, and authenticated requests.
287
281
  */
288
282
  declare class AuthSession {
289
283
  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
284
  /**
306
285
  * 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
286
+ * refreshes of token and profile data. Does NOT clear current token or profile data from the
308
287
  * session memory, but effectively deactivates the session for future use.
309
288
  */
310
289
  clear(): void;
311
- accessToken(): string;
312
- refreshToken(): string;
313
- idToken(): string | undefined;
314
- expiresIn(): number;
315
- expiresAt(): Date;
316
- profile(): SessionProfile | null;
290
+ get tokens(): SessionTokens;
291
+ get profile(): SessionProfile;
317
292
  toJSON(): Authentication;
318
293
  /**
319
294
  * Creates an AuthSession from existing authentication data. Useful for restoring
@@ -326,38 +301,31 @@ declare class AuthSession {
326
301
  */
327
302
  isExpired(): boolean;
328
303
  /**
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
337
- *
338
- * Assumes the response is JSON and parses it. Throws AuthError on non-2xx responses.
304
+ * Performs standard fetch() with a Bearer token and automatic refresh.
339
305
  *
340
- * If the error is a RFC 7807 Problem Details response, the AuthError.problem
341
- * will contain the full details.
306
+ * Returns Response, does not parse the body, does not throw for HTTP status codes.
307
+ * Throws AuthError when refresh fails, trows runtime errors on network failure.
342
308
  *
343
309
  * @example
344
310
  * ```ts
345
- * const res = await session.fetch('/api/projects');
311
+ * const res = await session.fetch("/api/projects");
346
312
  * const projects = await res.json();
347
313
  * ```
348
314
  *
349
- * Throws AuthError on network errors, etc.
350
- * @throws AuthError
315
+ * Any service can validate tokens using:
316
+ * https://api.accountmaker.com/.well-known/jwks.json?client_id={clientId}
351
317
  */
352
- fetch(url: string | URL, init?: RequestInit): Promise<any>;
318
+ fetch(url: string | URL, init?: RequestInit): Promise<Response>;
353
319
  /**
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.
320
+ * Replaces the access token using the refresh token.
321
+ *
322
+ * It is called automatically by all other methods when the access token is expired or near expiry.
357
323
  */
358
324
  refresh(): Promise<void>;
359
325
  /**
360
- * Refetches the user's profile from the server and updates the stored profile.
326
+ * refetchProfile() replaces the cached profile with server state.
327
+ *
328
+ * Concurrent calls are deduplicated.
361
329
  *
362
330
  * @example
363
331
  * ```ts
@@ -370,29 +338,19 @@ declare class AuthSession {
370
338
  */
371
339
  refetchProfile(): Promise<void>;
372
340
  /**
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.
341
+ * Requests a verification email for the current user.
375
342
  *
376
343
  * Throws AuthError on network errors, etc.
377
344
  * @throws AuthError
378
345
  */
379
346
  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
347
  }
388
348
  /**
389
- * Use `Am` to perform initial sign-in, registration, password reset flows, magic links,
390
- * invite acceptance, and other unauthenticated actions.
349
+ * Am runs authentication flows and produces AuthSession.
391
350
  *
392
- * Once authentication succeeds, these methods return an `AuthSession` that you use
393
- * for all subsequent authenticated requests.
351
+ * Use Am before a session exists (sign-in, sign-up, magic link, invites, password reset, CSRF).
394
352
  *
395
- * Example:
353
+ * @example
396
354
  * ```ts
397
355
  * const am = new Am();
398
356
  * const session = await am.signIn({ email: 'user@example.com', password: 'secret' });
@@ -400,68 +358,55 @@ declare class AuthSession {
400
358
  * ```
401
359
  */
402
360
  declare class Am {
403
- private options;
404
361
  constructor(config?: Partial<Config>);
405
362
  /**
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.
363
+ * session returns the current AuthSession or null.
364
+ *
365
+ * Use restoreSession() to load from storage.
366
+ *
367
+ * @example
368
+ * ```ts
369
+ * const session = am.session;
370
+ * if (!session) throw new Error("Not authenticated");
371
+ * ```
408
372
  */
409
- static createAuthSession(initial: Authentication, config?: Partial<Config>): AuthSession;
373
+ get session(): AuthSession | null;
410
374
  /**
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.
375
+ * Constructs AuthSession from existing tokens and profile.
376
+ *
377
+ * Use this after a server-side auth handshake or custom persistence.
413
378
  */
414
- createAuthSession(initial: Authentication): AuthSession;
379
+ createSession(initial: Authentication): AuthSession;
415
380
  /**
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).
381
+ * restoreSession loads AuthSession from storage or returns null.
419
382
  *
420
- * Throws AuthError on invalid or expired token, etc.
421
- * @throws AuthError
383
+ * Invalid or partial stored data is cleared.
384
+ *
385
+ * @example
386
+ * ```ts
387
+ * const am = new Am({ storage: 'localStorage' });
388
+ * const session = am.restoreSession();
389
+ * if (session) session.fetch("/api/me");
390
+ * ```
422
391
  */
423
- static acceptInvite(query: {
424
- clientId: ClientId;
425
- token: string;
426
- }, config?: Partial<Config>): Promise<AuthSession>;
392
+ restoreSession(): AuthSession | null;
427
393
  /**
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).
394
+ * Subscribes to auth events and returns an unsubscribe function.
395
+ */
396
+ on<K extends keyof AuthEventMap>(event: K, fn: (v: AuthEventMap[K]) => void): () => boolean;
397
+ /**
398
+ * acceptInvite exchanges an invite token for a fresh AuthSession.
431
399
  *
432
- * Throws AuthError on invalid or expired token, etc.
433
- * @throws AuthError
400
+ * Throws AuthError on invalid, expired, or already-used tokens.
434
401
  */
435
402
  acceptInvite(query: {
436
403
  clientId: ClientId;
437
404
  token: string;
438
405
  }): Promise<AuthSession>;
439
406
  /**
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.
407
+ * checkEmail returns how an email should authenticate for this client.
462
408
  *
463
- * Throws AuthError on invalid client ID, network errors, etc.
464
- * @throws AuthError
409
+ * Use this to choose password vs magic link vs SSO before rendering a login form.
465
410
  */
466
411
  checkEmail(body: {
467
412
  clientId: ClientId;
@@ -473,66 +418,25 @@ declare class Am {
473
418
  available: LoginMethod[];
474
419
  }>;
475
420
  /**
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.
421
+ * Sets the httpOnly CSRF cookie.
499
422
  *
500
- * @throws AuthError
423
+ * Call csrfToken() next to fetch a signed token for form posts.
501
424
  */
502
- static csrfToken(config?: Partial<Config>): Promise<{
425
+ csrfSession(): Promise<{
503
426
  csrfToken: string;
504
427
  }>;
505
428
  /**
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.
429
+ * Returns a signed CSRF token for form posts.
511
430
  *
512
- * @throws AuthError
431
+ * Call csrfSession() first to set the CSRF cookie.
513
432
  */
514
433
  csrfToken(): Promise<{
515
434
  csrfToken: string;
516
435
  }>;
517
436
  /**
518
- * On success, returns an AuthSession containing fresh tokens and profile.
519
- * Tokens are automatically persisted (if storage is enabled).
520
- *
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).
437
+ * Authenticates with email and password and returns a new AuthSession.
533
438
  *
534
- * Throws AuthError on invalid credentials, unverified email, etc.
535
- * @throws AuthError
439
+ * Tokens and profile are persisted when storage is configured.
536
440
  */
537
441
  signIn(body: {
538
442
  clientId: ClientId;
@@ -541,62 +445,15 @@ declare class Am {
541
445
  csrfToken?: string;
542
446
  }): Promise<AuthSession>;
543
447
  /**
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).
448
+ * Authenticates with a one-time token and returns a new AuthSession.
555
449
  *
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
450
+ * Use this for magic links and similar one-time login flows.
561
451
  */
562
452
  signInWithToken(token: string): Promise<AuthSession>;
563
453
  /**
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.
567
- *
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).
454
+ * Creates a new user and returns a new AuthSession.
597
455
  *
598
- * Throws AuthError on invalid data, existing email, etc.
599
- * @throws AuthError
456
+ * Tokens and profile are persisted when storage is configured.
600
457
  */
601
458
  signUp(body: {
602
459
  clientId: ClientId;
@@ -605,45 +462,14 @@ declare class Am {
605
462
  csrfToken?: string;
606
463
  }): Promise<AuthSession>;
607
464
  /**
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
465
+ * Sets a new password using a one-time reset token.
624
466
  */
625
467
  resetPassword(body: {
626
468
  token: string;
627
469
  newPassword: string;
628
470
  }): Promise<void>;
629
471
  /**
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
472
+ * Sends a one-time sign-in link to an email address.
647
473
  */
648
474
  sendMagicLink(body: {
649
475
  clientId: ClientId;
@@ -651,21 +477,7 @@ declare class Am {
651
477
  csrfToken?: string;
652
478
  }): Promise<void>;
653
479
  /**
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
480
+ * Sends a password reset link to an email address.
669
481
  */
670
482
  sendPasswordReset(body: {
671
483
  clientId: ClientId;
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=l(r)?r:null;null===r||s||a(t,e),s&&s.expiresAt>=n.expiresAt||i(t,e,n)}function u(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 l(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.accountId&&"string"==typeof t.status&&"number"==typeof t.lastUpdatedAt&&("object"==typeof t.identity||null===t.identity)}function h(e){return e[t]}function p(t){return t[n]}function y(t,n){t[e]=n;const s=h(n);!function(t,e){t[r]=(t,n)=>{const r=p(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);if(401!==r.status)return r;if(t.cleared)return r;try{return await x(t),r=await P(t,e,n),r}catch(e){throw!t.cleared&&K(e)&&g(t,"unauthenticated",e),e}},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}))}),o=v(await j(r));t.tokens=o;const i=s(t.config.storage);c(i,t.config.tokensStorageKey,o),g(t,"refresh",o)}(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;u(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&&u(i,r.profileStorageKey,o.profile)}clear(){const t=h(this);!function(t){const e=s(t.storage);a(e,t.profileStorageKey),a(e,t.tokensStorageKey)}(t.config),t.cleared=!0}get tokens(){return h(this).tokens}get profile(){return h(this).profile}toJSON(){const t=h(this);return{tokens:t.tokens,profile:t.profile}}static fromJSON(t,e){return new J(t,e)}isExpired(){return U(h(this))}async fetch(t,e={}){return A(h(this),t,e)}async refresh(){return x(h(this))}async refetchProfile(){const t=h(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))})})(h(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,p(this).config);return y(this,e),e}restoreSession(){const t=p(this).config,e=s(t.storage);if(!e)return null;const n=o(e,t.tokensStorageKey),r=l(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 u=new J({tokens:r,profile:c},t);return y(this,u),u}on(t,e){const n=p(this).listeners,r=n[t]??(n[t]=new Set);return r.add(e),()=>r.delete(e)}async acceptInvite(t){const e=p(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 $(p(this).config,"/auth/check-email",t)}async csrfSession(){return O(p(this).config,"/auth/csrf-session")}async csrfToken(){return O(p(this).config,"/auth/csrf-token")}async signIn(t){const e=p(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=p(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=p(this).config,n=E(await $(e,"/auth/sign-up",t)),r=new J(n,e);return y(this,r),r}async resetPassword(t){return $(p(this).config,"/auth/reset-password",t)}async sendMagicLink(t){return $(p(this).config,"/auth/send-magic-link",t)}async sendPasswordReset(t){return $(p(this).config,"/auth/send-password-reset",t)}}export{N as Am,k as AuthError,J as AuthSession};
2
2
  //# sourceMappingURL=index.mjs.map