@tempoxyz/id 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/dist/app/server/.vite/manifest.json +65 -65
  2. package/dist/app/server/assets/Grant-D_tun0mF.js.map +1 -1
  3. package/dist/app/server/assets/authorize-DXYo-K2n-B-6vqhvc.js +56 -0
  4. package/dist/app/server/assets/authorize-DXYo-K2n-B-6vqhvc.js.map +1 -0
  5. package/dist/app/server/assets/core-FSS9-r35.js +56 -0
  6. package/dist/app/server/assets/core-FSS9-r35.js.map +1 -0
  7. package/dist/app/server/assets/default-u_dwuiYb-Bby14ucv.js +2 -0
  8. package/dist/app/server/assets/{default-u_dwuiYb-DOrMMcg9.js.map → default-u_dwuiYb-Bby14ucv.js.map} +1 -1
  9. package/dist/app/server/assets/{dist-CoH0hD1s.js → dist-B9KiBJYw.js} +2 -2
  10. package/dist/app/server/assets/{dist-CoH0hD1s.js.map → dist-B9KiBJYw.js.map} +1 -1
  11. package/dist/app/server/assets/{dist-DcICI3fd.js → dist-CU1NXSiz.js} +2 -2
  12. package/dist/app/server/assets/{dist-DcICI3fd.js.map → dist-CU1NXSiz.js.map} +1 -1
  13. package/dist/app/server/assets/dist-CetH4JSY.js +1 -0
  14. package/dist/app/server/assets/{effect-QlVUlMFu-mXPkuQTb.js → effect-QlVUlMFu-Cm-eTtaO.js} +2 -2
  15. package/dist/app/server/assets/{effect-QlVUlMFu-mXPkuQTb.js.map → effect-QlVUlMFu-Cm-eTtaO.js.map} +1 -1
  16. package/dist/app/server/assets/factory-g3i_Ffi5.js +5 -0
  17. package/dist/app/server/assets/{factory-dvoOlKer.js.map → factory-g3i_Ffi5.js.map} +1 -1
  18. package/dist/app/server/assets/get-id-field-BpOAoqqI.js +6 -0
  19. package/dist/app/server/assets/get-id-field-BpOAoqqI.js.map +1 -0
  20. package/dist/app/server/assets/{index-CLddUTqr-CC33CMvS.js → index-CLddUTqr-CmgkbWi9.js} +2 -2
  21. package/dist/app/server/assets/{index-CLddUTqr-CC33CMvS.js.map → index-CLddUTqr-CmgkbWi9.js.map} +1 -1
  22. package/dist/app/server/assets/{index-DZEfthgZ-Ec2MMQ_e.js → index-DZEfthgZ-BnPmps9O.js} +2 -2
  23. package/dist/app/server/assets/{index-DZEfthgZ-Ec2MMQ_e.js.map → index-DZEfthgZ-BnPmps9O.js.map} +1 -1
  24. package/dist/app/server/assets/kysely-adapter-B8VoPArC.js +1 -0
  25. package/dist/app/server/assets/{schema-check-oLqvHR8y.js → schema-check-Bgwl1v0_.js} +2 -2
  26. package/dist/app/server/assets/{schema-check-oLqvHR8y.js.map → schema-check-Bgwl1v0_.js.map} +1 -1
  27. package/dist/app/server/assets/{sury-CWZTCd75-CDaOS2oM.js → sury-CWZTCd75-Bd_PzqCV.js} +2 -2
  28. package/dist/app/server/assets/{sury-CWZTCd75-CDaOS2oM.js.map → sury-CWZTCd75-Bd_PzqCV.js.map} +1 -1
  29. package/dist/app/server/assets/{valibot--1zFm7rT-CIKoH3MS.js → valibot--1zFm7rT-Dd2aEhWG.js} +2 -2
  30. package/dist/app/server/assets/{valibot--1zFm7rT-CIKoH3MS.js.map → valibot--1zFm7rT-Dd2aEhWG.js.map} +1 -1
  31. package/dist/app/server/assets/{valibot-D_HTw1Gn-BdPyLkXt.js → valibot-D_HTw1Gn-C1l3va6F.js} +2 -2
  32. package/dist/app/server/assets/{valibot-D_HTw1Gn-BdPyLkXt.js.map → valibot-D_HTw1Gn-C1l3va6F.js.map} +1 -1
  33. package/dist/app/server/assets/{zod-Bwrt9trS-BezI5Gi1.js → zod-Bwrt9trS-Dry2w0in.js} +2 -2
  34. package/dist/app/server/assets/{zod-Bwrt9trS-BezI5Gi1.js.map → zod-Bwrt9trS-Dry2w0in.js.map} +1 -1
  35. package/dist/app/server/assets/{zod-DSgpEGAE-DZ7M_Q33.js → zod-DSgpEGAE-BZMP86UA.js} +2 -2
  36. package/dist/app/server/assets/{zod-DSgpEGAE-DZ7M_Q33.js.map → zod-DSgpEGAE-BZMP86UA.js.map} +1 -1
  37. package/dist/app/server/index.js +35 -35
  38. package/dist/app/server/index.js.map +1 -1
  39. package/dist/client/Auth.d.ts +1 -1
  40. package/dist/client/Auth.d.ts.map +1 -1
  41. package/dist/client/Auth.js.map +1 -1
  42. package/dist/client/Decorator.d.ts +3 -1
  43. package/dist/client/Decorator.d.ts.map +1 -1
  44. package/dist/client/Decorator.js +1 -0
  45. package/dist/client/Decorator.js.map +1 -1
  46. package/dist/client/actions/passkey.d.ts +63 -2
  47. package/dist/client/actions/passkey.d.ts.map +1 -1
  48. package/dist/client/actions/passkey.js +121 -3
  49. package/dist/client/actions/passkey.js.map +1 -1
  50. package/dist/internal/Apps.d.ts +14 -0
  51. package/dist/internal/Apps.d.ts.map +1 -1
  52. package/dist/internal/Apps.js +14 -2
  53. package/dist/internal/Apps.js.map +1 -1
  54. package/dist/internal/Auth.d.ts +7 -0
  55. package/dist/internal/Auth.d.ts.map +1 -1
  56. package/dist/internal/Auth.js +2 -1
  57. package/dist/internal/Auth.js.map +1 -1
  58. package/dist/internal/Authorize.d.ts.map +1 -1
  59. package/dist/internal/Authorize.js +13 -13
  60. package/dist/internal/Authorize.js.map +1 -1
  61. package/dist/internal/ErrorResponse.d.ts +5 -0
  62. package/dist/internal/ErrorResponse.d.ts.map +1 -1
  63. package/dist/internal/ErrorResponse.js +8 -0
  64. package/dist/internal/ErrorResponse.js.map +1 -1
  65. package/dist/internal/KeyAuthorization.d.ts.map +1 -1
  66. package/dist/internal/KeyAuthorization.js +28 -28
  67. package/dist/internal/KeyAuthorization.js.map +1 -1
  68. package/dist/internal/Owner.d.ts +16 -0
  69. package/dist/internal/Owner.d.ts.map +1 -1
  70. package/dist/internal/Owner.js.map +1 -1
  71. package/dist/internal/Passkey.d.ts +66 -4
  72. package/dist/internal/Passkey.d.ts.map +1 -1
  73. package/dist/internal/Passkey.js +66 -30
  74. package/dist/internal/Passkey.js.map +1 -1
  75. package/dist/internal/Privy.d.ts +27 -0
  76. package/dist/internal/Privy.d.ts.map +1 -1
  77. package/dist/internal/Privy.js +82 -0
  78. package/dist/internal/Privy.js.map +1 -1
  79. package/dist/internal/TempoAccount.d.ts +57 -0
  80. package/dist/internal/TempoAccount.d.ts.map +1 -1
  81. package/dist/internal/TempoAccount.js +118 -5
  82. package/dist/internal/TempoAccount.js.map +1 -1
  83. package/dist/internal/owners/Privy.d.ts.map +1 -1
  84. package/dist/internal/owners/Privy.js +11 -2
  85. package/dist/internal/owners/Privy.js.map +1 -1
  86. package/package.json +1 -1
  87. package/src/api/account.ts +140 -103
  88. package/src/api/authenticate.ts +48 -51
  89. package/src/api/authorize.ts +35 -36
  90. package/src/api/device.ts +20 -23
  91. package/src/api/health.ts +1 -2
  92. package/src/api/lib/AccountStep.ts +75 -0
  93. package/src/api/lib/Auth.ts +6 -0
  94. package/src/api/lib/Cors.ts +3 -0
  95. package/src/api/lib/KeySigner.ts +1 -1
  96. package/src/api/oauth.ts +23 -22
  97. package/src/client/Auth.ts +1 -0
  98. package/src/client/Decorator.ts +6 -1
  99. package/src/client/actions/passkey.ts +197 -3
  100. package/src/internal/Apps.ts +26 -2
  101. package/src/internal/Auth.ts +4 -0
  102. package/src/internal/Authorize.ts +13 -15
  103. package/src/internal/ErrorResponse.ts +10 -0
  104. package/src/internal/KeyAuthorization.ts +28 -31
  105. package/src/internal/OpenApi.ts +35 -0
  106. package/src/internal/Owner.ts +16 -0
  107. package/src/internal/Passkey.ts +99 -34
  108. package/src/internal/Privy.ts +128 -0
  109. package/src/internal/TempoAccount.ts +221 -7
  110. package/src/internal/owners/Privy.ts +19 -6
  111. package/dist/app/server/assets/authorize-DXYo-K2n-rmQQ42x1.js +0 -56
  112. package/dist/app/server/assets/authorize-DXYo-K2n-rmQQ42x1.js.map +0 -1
  113. package/dist/app/server/assets/core-BBrZXfrB.js +0 -56
  114. package/dist/app/server/assets/core-BBrZXfrB.js.map +0 -1
  115. package/dist/app/server/assets/default-u_dwuiYb-DOrMMcg9.js +0 -2
  116. package/dist/app/server/assets/dist-CR6FW-yS.js +0 -1
  117. package/dist/app/server/assets/factory-dvoOlKer.js +0 -5
  118. package/dist/app/server/assets/get-id-field-OwLpfjj2.js +0 -6
  119. package/dist/app/server/assets/get-id-field-OwLpfjj2.js.map +0 -1
  120. package/dist/app/server/assets/kysely-adapter-B8tQwTcu.js +0 -1
@@ -9,12 +9,14 @@
9
9
  */
10
10
  import { and, eq, inArray, notExists, sql } from 'drizzle-orm'
11
11
  import { createMiddleware } from 'hono/factory'
12
+ import { Address } from 'ox'
12
13
  import { AccountConfig } from 'viem/tempo'
13
14
  import { z } from 'zod'
14
15
  import * as Schema from '../app/db/schema.js'
15
16
  import * as ConfigurableAccount from './ConfigurableAccount.js'
16
17
  import type { Env } from './Env.js'
17
18
  import * as ErrorResponse from './ErrorResponse.js'
19
+ import type * as Log from './Log.js'
18
20
  import * as Owner from './Owner.js'
19
21
  import * as Passkey from './owners/Passkey.js'
20
22
  import * as Privy from './owners/Privy.js'
@@ -46,6 +48,22 @@ export declare namespace ForeignAccountError {
46
48
  }
47
49
  }
48
50
 
51
+ /** A kind of owner key did not list within the deadline that {@link addresses} received. */
52
+ export class TimeoutError extends Error {
53
+ override readonly name = 'TempoAccount.TimeoutError'
54
+
55
+ constructor(options: TimeoutError.Options) {
56
+ super(`The owner keys did not list within ${options.timeout} ms.`)
57
+ }
58
+ }
59
+
60
+ export declare namespace TimeoutError {
61
+ type Options = {
62
+ /** The deadline, in milliseconds. */
63
+ timeout: number
64
+ }
65
+ }
66
+
49
67
  /**
50
68
  * The kinds of owner keys, in the order that breaks a tie in creation time: a passkey comes before a
51
69
  * Privy wallet created in the same second, and a Privy wallet before a secp256k1 key. The first kind
@@ -105,7 +123,7 @@ export namespace schema {
105
123
  })
106
124
  .meta({
107
125
  description:
108
- 'A realm, which holds Tempo Accounts. An app reaches only the accounts of its realms.',
126
+ 'A realm, which holds Tempo Accounts. An app reaches only the accounts in its realms.',
109
127
  id: 'Realm',
110
128
  })
111
129
 
@@ -113,8 +131,7 @@ export namespace schema {
113
131
  export const tempoAccount = z
114
132
  .object({
115
133
  address: address.meta({
116
- description:
117
- "The address on the request's chain: the owner key's address, or the initial configuration's address on a chain with configurable accounts. A linked wallet.tempo passkey keeps its own address, also after its upgrade.",
134
+ description: "The account's address on the request's chain.",
118
135
  examples: ['0x8C3b5d0e2A4f6B1c9D7e3F5a0B2c4D6e8F1a3B5c'],
119
136
  }),
120
137
  config: z
@@ -125,7 +142,7 @@ export namespace schema {
125
142
  .optional()
126
143
  .meta({
127
144
  description:
128
- 'The ID of the chain where the configuration applies, which the request named. Absent on a chain whose accounts are not configurable, and for a wallet.tempo passkey that no upgrade made configurable.',
145
+ 'The ID of the chain where the configuration applies. Absent if the account is not configurable on the chain.',
129
146
  examples: [31318],
130
147
  }),
131
148
  owners: z
@@ -138,7 +155,7 @@ export namespace schema {
138
155
  }),
139
156
  type: z.enum(kinds.map((kind) => kind.type)).meta({
140
157
  description:
141
- 'Who holds the owner key: a Privy wallet (`privy`), a key that Tempo ID holds in development (`secp256k1`), or a passkey (`webAuthn`), which signs only in the browser.',
158
+ "The owner key's holder: a Privy wallet (`privy`), a development key (`secp256k1`), or a passkey (`webAuthn`).",
142
159
  examples: ['webAuthn'],
143
160
  }),
144
161
  weight: z.number().meta({
@@ -157,8 +174,7 @@ export namespace schema {
157
174
  examples: [`0x${'0'.repeat(64)}`],
158
175
  }),
159
176
  threshold: z.number().meta({
160
- description:
161
- 'The total owner weight that a signature needs to act for the account.',
177
+ description: 'The total owner weight that a signature needs.',
162
178
  examples: [1],
163
179
  }),
164
180
  version: z.templateLiteral(['0x', z.string()]).meta({
@@ -390,6 +406,163 @@ export namespace list {
390
406
  export const errors = create.errors
391
407
  }
392
408
 
409
+ /**
410
+ * Returns every address of the user's Tempo Accounts in `realms`, de-duplicated and in no meaningful
411
+ * order: each account's address on a chain whose accounts are not configurable, and its address on
412
+ * each chain that `CONFIGURABLE_ACCOUNTS` names. It creates no account. Each kind lists on its own,
413
+ * so a kind that reads an upstream service and fails, such as Privy during an outage, leaves out its
414
+ * addresses and returns its error in `errors` instead of throwing. A failure of the database throws.
415
+ */
416
+ export async function addresses(
417
+ c: Context,
418
+ options: addresses.Options,
419
+ ): Promise<addresses.ReturnType> {
420
+ const {
421
+ db,
422
+ env,
423
+ maxAge,
424
+ timeout,
425
+ upstream = true,
426
+ userId,
427
+ waitUntil,
428
+ warn,
429
+ } = options
430
+
431
+ // A request that already listed the user's keys in these realms, such as a headless sign-in, reads
432
+ // them again from its lists. A failed list fails as a whole, so each kind lists again below, and a
433
+ // failure of the database throws there. A list that misses the deadline already spent the upstream
434
+ // kinds' time, so the upstream kinds list no more.
435
+ const listed = await (async () => {
436
+ const reading = c.var.ownerLists?.get(listKey(options))
437
+ if (!reading) return undefined
438
+ try {
439
+ const lists = await Timing.measure(
440
+ c,
441
+ within(reading, { timeout, waitUntil }),
442
+ 'TempoAccount.owners',
443
+ )
444
+ return { lists }
445
+ } catch (error) {
446
+ if (error instanceof TimeoutError) return { error }
447
+ return undefined
448
+ }
449
+ })()
450
+ const errors: unknown[] = listed && 'error' in listed ? [listed.error] : []
451
+ const remoteAllowed = upstream && errors.length === 0
452
+ const lists =
453
+ (listed && 'lists' in listed ? listed.lists : undefined) ??
454
+ (await Promise.all(
455
+ kinds.map(async (kind): Promise<readonly Owner.Owner<Type>[]> => {
456
+ // Only a kind that reads an upstream service, such as Privy, declares refusals. The deadline
457
+ // applies to that kind alone, so a slow database leaves out no key that it holds.
458
+ const remote = kind.errors.length > 0
459
+ if (remote && !remoteAllowed) return []
460
+ // `load` reads only the database, such as a realm's Privy app, so its failure fails the call,
461
+ // as any other read of the database does.
462
+ const loaded = await Timing.measure(
463
+ c,
464
+ kind.load(c, options),
465
+ 'TempoAccount.load',
466
+ )
467
+ try {
468
+ return await Timing.measure(
469
+ c,
470
+ within<readonly Owner.Owner<Type>[]>(
471
+ loaded.list(c, { db, maxAge, waitUntil, warn }),
472
+ { timeout: remote ? timeout : undefined, waitUntil },
473
+ ),
474
+ 'TempoAccount.owners',
475
+ )
476
+ } catch (error) {
477
+ if (!remote) throw error
478
+ errors.push(error)
479
+ return []
480
+ }
481
+ }),
482
+ ))
483
+ const merged = Owner.merge(lists)
484
+
485
+ const chains = ConfigurableAccount.chains({
486
+ rpcUrl: env.TEMPO_RPC_URL,
487
+ rpcUrls: env.RPC_URLS,
488
+ value: env.CONFIGURABLE_ACCOUNTS,
489
+ })
490
+ const views = await Promise.all(
491
+ chains.map(async (chain) => {
492
+ const configs = await Timing.measure(
493
+ c,
494
+ ConfigurableAccount.list(db, { chainId: chain.chainId, userId }),
495
+ 'db.accountConfig.list',
496
+ )
497
+ const applied = ConfigurableAccount.apply(merged, {
498
+ chainId: chain.chainId,
499
+ configs,
500
+ })
501
+ // `apply` lists a configuration through its key's owner, which a kind that failed leaves out.
502
+ // A configuration that names a listed key among its owners is the user's through that key.
503
+ const owned = configs.filter((config) =>
504
+ config.owners.some((member) =>
505
+ merged.some((entry) =>
506
+ Address.isEqual(Owner.key(entry), member.address),
507
+ ),
508
+ ),
509
+ )
510
+ return [
511
+ ...applied.map((owner) => Owner.address(owner)),
512
+ ...owned.map((config) => config.address),
513
+ ]
514
+ }),
515
+ )
516
+
517
+ const unique = new Map<string, Address.Address>()
518
+ for (const entry of [
519
+ ...merged.map((owner) => Owner.address(owner)),
520
+ ...views.flat(),
521
+ ]) {
522
+ const address = Address.checksum(entry)
523
+ unique.set(address.toLowerCase(), address)
524
+ }
525
+ return { addresses: [...unique.values()], errors }
526
+ }
527
+
528
+ export declare namespace addresses {
529
+ type Options = Omit<list.Options, 'chainId'> & {
530
+ /**
531
+ * How old, in seconds, a cached read of an upstream service, such as Privy's wallets, may be.
532
+ * Absent, each kind reads its upstream service.
533
+ */
534
+ maxAge?: number | undefined
535
+ /**
536
+ * How long each kind that reads an upstream service, such as Privy, may list, in milliseconds.
537
+ * Such a kind that takes longer leaves out its addresses, and returns a {@link TimeoutError} in
538
+ * `errors`. The kinds that read only the database have no deadline. Absent, no deadline applies.
539
+ */
540
+ timeout?: number | undefined
541
+ /**
542
+ * Whether the kinds that read an upstream service, such as Privy, list. Defaults to `true`.
543
+ * `false` leaves out their addresses without an error.
544
+ */
545
+ upstream?: boolean | undefined
546
+ /**
547
+ * Keeps a read that missed `timeout` alive after the response, such as
548
+ * `c.executionCtx.waitUntil`, so it still fills its cache. Absent, the runtime may end it.
549
+ */
550
+ waitUntil?: ((promise: Promise<unknown>) => void) | undefined
551
+ /**
552
+ * Logs a recoverable problem with the request's details, such as a failure of the cache of
553
+ * Privy's wallets. Absent, the kinds log without them.
554
+ */
555
+ warn?: ((options: Log.Options) => void) | undefined
556
+ }
557
+
558
+ type ReturnType = {
559
+ /** The addresses, checksummed, each once. */
560
+ addresses: readonly Address.Address[]
561
+ /** The failures of the kinds whose addresses are left out. Empty when every kind listed. */
562
+ errors: readonly unknown[]
563
+ }
564
+ }
565
+
393
566
  /**
394
567
  * Empties the owner keys that the request listed. Call it after a write that changes a user's owner
395
568
  * keys outside {@link create}, such as a passkey's link or registration, so a later list in the same
@@ -543,3 +716,44 @@ export function serialize(owner: Owner.Owner): TempoAccount {
543
716
  realm: { id: owner.realm.id, name: owner.realm.name },
544
717
  })
545
718
  }
719
+
720
+ /**
721
+ * Returns the value of `promise`, or rejects with a {@link TimeoutError} once `timeout` milliseconds
722
+ * pass. Without `timeout`, it waits for `promise`.
723
+ */
724
+ async function within<value>(
725
+ promise: Promise<value>,
726
+ options: within.Options,
727
+ ): Promise<value> {
728
+ const { timeout, waitUntil } = options
729
+ if (timeout === undefined) return await promise
730
+
731
+ // A read that rejects after the deadline has no caller left, so its rejection is dropped.
732
+ const settled = promise.then(
733
+ () => undefined,
734
+ () => undefined,
735
+ )
736
+ let timer: ReturnType<typeof setTimeout> | undefined
737
+ const expired = new Promise<never>((_, reject) => {
738
+ timer = setTimeout(() => {
739
+ // The read continues past the response, so a late answer still fills its cache, such as the
740
+ // Privy wallets that the next grant reads.
741
+ waitUntil?.(settled)
742
+ reject(new TimeoutError({ timeout }))
743
+ }, timeout)
744
+ })
745
+ try {
746
+ return await Promise.race([promise, expired])
747
+ } finally {
748
+ clearTimeout(timer)
749
+ }
750
+ }
751
+
752
+ declare namespace within {
753
+ type Options = {
754
+ /** The deadline, in milliseconds. Absent, none applies. */
755
+ timeout?: number | undefined
756
+ /** Keeps the read alive after the response when the deadline passes. Absent, the runtime may end it. */
757
+ waitUntil?: ((promise: Promise<unknown>) => void) | undefined
758
+ }
759
+ }
@@ -61,16 +61,29 @@ export const owner = Owner.kind({
61
61
  return toOwner(app, realm, wallet)
62
62
  }
63
63
  },
64
- async list(c) {
64
+ async list(c, listOptions) {
65
+ const { maxAge, waitUntil, warn } = listOptions
65
66
  const lists = await Promise.all(
66
67
  realms.map(async (realm) => {
67
68
  const app = apps.get(realm.id)
68
69
  if (!app) return []
69
- const wallets = await Timing.measure(
70
- c,
71
- Privy.listWallets(c, { app, userId }),
72
- 'Privy.listWallets',
73
- )
70
+ const wallets = await (maxAge === undefined
71
+ ? Timing.measure(
72
+ c,
73
+ Privy.listWallets(c, { app, userId }),
74
+ 'Privy.listWallets',
75
+ )
76
+ : Timing.measure(
77
+ c,
78
+ Privy.cachedWallets(c, {
79
+ app,
80
+ maxAge,
81
+ userId,
82
+ waitUntil,
83
+ warn,
84
+ }),
85
+ 'Privy.cachedWallets',
86
+ ))
74
87
  return wallets.map((wallet) => toOwner(app, realm, wallet))
75
88
  }),
76
89
  )