@tempoxyz/id 0.0.0-stage → 0.0.1
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/README.md +121 -2
- package/dist/App.d.ts +121 -0
- package/dist/App.d.ts.map +1 -0
- package/dist/App.js +132 -0
- package/dist/App.js.map +1 -0
- package/dist/app/client/.assetsignore +2 -0
- package/dist/app/client/_headers +4 -0
- package/dist/app/client/assets/Account-BYLpNFf3.css +1 -0
- package/dist/app/client/assets/Account-BllJah07.js +1 -0
- package/dist/app/client/assets/Amount-E_7snI0-.js +1 -0
- package/dist/app/client/assets/Amount-ZdtluGf-.css +1 -0
- package/dist/app/client/assets/Approval-CgV5JHlb.css +1 -0
- package/dist/app/client/assets/Approval-yKflypU2.js +1 -0
- package/dist/app/client/assets/ArrowUp-CRvqfMNU.js +1 -0
- package/dist/app/client/assets/Authentication-BAXhOh_4.js +2 -0
- package/dist/app/client/assets/BRLA-CZvVDNYr.png +0 -0
- package/dist/app/client/assets/BRZ-Cn3jEi9t.svg +1 -0
- package/dist/app/client/assets/CHFAU-DCedc4aH.png +0 -0
- package/dist/app/client/assets/Callout-B736759d.js +1 -0
- package/dist/app/client/assets/Callout-DBfEmP7q.css +1 -0
- package/dist/app/client/assets/Check-DVgD8tDG.js +1 -0
- package/dist/app/client/assets/ChevronRight-CsXtIZCB.js +1 -0
- package/dist/app/client/assets/ChooseAccount-BjxfQjPK.js +1 -0
- package/dist/app/client/assets/ChooseAccount-DL3wgJhE.css +1 -0
- package/dist/app/client/assets/Client-JrABNovb.js +1 -0
- package/dist/app/client/assets/Close-CRvWlhqc.js +1 -0
- package/dist/app/client/assets/Cloud-DgJsrqpQ.js +1 -0
- package/dist/app/client/assets/CodeInput-79Ctx8ZA.js +1 -0
- package/dist/app/client/assets/CodeInput-BqRteSwD.css +1 -0
- package/dist/app/client/assets/DLUSD-BnRXm3EI.png +0 -0
- package/dist/app/client/assets/Deposit-CclrAoXL.css +1 -0
- package/dist/app/client/assets/Deposit-c7uYn2Eb.js +1 -0
- package/dist/app/client/assets/DevTools-BYkmtuV5.css +1 -0
- package/dist/app/client/assets/DevTools-BgnzDGo2.js +1 -0
- package/dist/app/client/assets/DeviceApproval-BBUVv58e.js +1 -0
- package/dist/app/client/assets/DeviceApproval-BffFLD8F.css +1 -0
- package/dist/app/client/assets/EURAU-sjieTTB9.svg +1 -0
- package/dist/app/client/assets/EURC.e-CxYiiwkz.svg +1 -0
- package/dist/app/client/assets/Fingerprint-BNCA6qOJ.js +1 -0
- package/dist/app/client/assets/GBPA-DI_25xSG.svg +4 -0
- package/dist/app/client/assets/GUSD-BDEP2Qb-.svg +1 -0
- package/dist/app/client/assets/GeistMono-Variable-BRXCGrxA.woff2 +0 -0
- package/dist/app/client/assets/Globe-f0eWzcEI.js +1 -0
- package/dist/app/client/assets/Grant-5a-n1gJH.css +1 -0
- package/dist/app/client/assets/Grant-Dp7mpcEi.js +1 -0
- package/dist/app/client/assets/Hex-C9KeV7_9.css +1 -0
- package/dist/app/client/assets/Hex-CSp2U46W.js +10 -0
- package/dist/app/client/assets/IconChip-DpZHa1JW.js +1 -0
- package/dist/app/client/assets/IconChip-yqaQ2y_d.css +1 -0
- package/dist/app/client/assets/InputGroup-BlZvt9In.css +1 -0
- package/dist/app/client/assets/InputGroup-CsOeBnpB.js +1 -0
- package/dist/app/client/assets/Inter-cyrillic-e3OmmzmJ.woff2 +0 -0
- package/dist/app/client/assets/Inter-cyrillic-ext-DOWZeO4Q.woff2 +0 -0
- package/dist/app/client/assets/Inter-greek-BD3ujQlX.woff2 +0 -0
- package/dist/app/client/assets/Inter-greek-ext-CXEwbus4.woff2 +0 -0
- package/dist/app/client/assets/Inter-latin-BDFm4vTb.woff2 +0 -0
- package/dist/app/client/assets/Inter-latin-ext-E7a9JEPe.woff2 +0 -0
- package/dist/app/client/assets/Inter-symbols-DEch7Rxr.woff2 +0 -0
- package/dist/app/client/assets/Inter-vietnamese-Bg9723vR.woff2 +0 -0
- package/dist/app/client/assets/Loader-Cm9YJNNJ.js +1 -0
- package/dist/app/client/assets/MACH-sK0r79kQ.svg +4 -0
- package/dist/app/client/assets/OUSD-BFXgVo-Z.svg +5 -0
- package/dist/app/client/assets/OUSD.dark-BbbjMvA1.svg +17 -0
- package/dist/app/client/assets/OUSD.light-B_zpSz_c.svg +17 -0
- package/dist/app/client/assets/PRIME-CiFp2-3e.svg +14 -0
- package/dist/app/client/assets/PopoverPopup-DprRXRQe.js +1 -0
- package/dist/app/client/assets/Rows-DPGTUdA8.css +1 -0
- package/dist/app/client/assets/Rows-Qqo3evtk.js +1 -0
- package/dist/app/client/assets/SBC-BTYkbkiD.svg +1 -0
- package/dist/app/client/assets/SEKAU-CB5Fc6o8.svg +5 -0
- package/dist/app/client/assets/Separator-CZUjVQOL.js +1 -0
- package/dist/app/client/assets/SignIn-HdaDzFmq.js +1 -0
- package/dist/app/client/assets/SignIn-mlBTwUq4.css +1 -0
- package/dist/app/client/assets/Stack-CkkvkOzj.js +1 -0
- package/dist/app/client/assets/Stack-D7Yt22Ed.css +1 -0
- package/dist/app/client/assets/Tag-BMh6ajrW.css +1 -0
- package/dist/app/client/assets/Tag-CLOVANTX.js +1 -0
- package/dist/app/client/assets/ToolbarRootContext-Zty_Dh3J.js +1 -0
- package/dist/app/client/assets/Tooltip-CLATV6YU.js +1 -0
- package/dist/app/client/assets/Tooltip-d3y9ESUJ.css +1 -0
- package/dist/app/client/assets/USD1-COVcO_PB.svg +30 -0
- package/dist/app/client/assets/USDB-Ckwb6UQw.svg +11 -0
- package/dist/app/client/assets/USDC.e-3nMsKAMC.svg +1 -0
- package/dist/app/client/assets/USDT0-ia60Dw0e.svg +1 -0
- package/dist/app/client/assets/USDY-CB4iet2q.svg +16 -0
- package/dist/app/client/assets/USDe-BXHQCd92.svg +1 -0
- package/dist/app/client/assets/Upgrade-D0_aO5AP.js +1 -0
- package/dist/app/client/assets/WalletUpgrade-BPsPNQsd.js +1 -0
- package/dist/app/client/assets/WalletUpgrade-C04kW0Jl.css +1 -0
- package/dist/app/client/assets/YLDS-CRh8Zw0g.svg +3 -0
- package/dist/app/client/assets/___-I98bns4o.css +1 -0
- package/dist/app/client/assets/___.flows-XOlLwMIy.js +5 -0
- package/dist/app/client/assets/alphaUSD-CpCFoVuh.svg +1 -0
- package/dist/app/client/assets/authorize-C-MlCyaM.js +1 -0
- package/dist/app/client/assets/betaUSD-9A8AjgYg.svg +1 -0
- package/dist/app/client/assets/cUSD-WsQfa-iC.svg +1 -0
- package/dist/app/client/assets/cbBTC-DvUDqrDi.svg +1 -0
- package/dist/app/client/assets/choose-account-nXR-SKa2.js +1 -0
- package/dist/app/client/assets/deposit-G8tiLPN4.js +1 -0
- package/dist/app/client/assets/device-BojBF7hZ.js +1 -0
- package/dist/app/client/assets/error-KiOpX7GD.js +1 -0
- package/dist/app/client/assets/frxUSD-n9pTpinR.svg +1 -0
- package/dist/app/client/assets/getPseudoElementBounds-BsLy4zf_.js +1 -0
- package/dist/app/client/assets/goUSD-D9kUvHsF.svg +5 -0
- package/dist/app/client/assets/iUSD-CWCJAq9X.svg +1 -0
- package/dist/app/client/assets/index-BZH0M7Nf.js +13 -0
- package/dist/app/client/assets/index-CIYxHmYA.css +1 -0
- package/dist/app/client/assets/login-DUjpk60p.js +1 -0
- package/dist/app/client/assets/pathUSD-BvaO6ooS.svg +25 -0
- package/dist/app/client/assets/preload-helper-Bs9KvdYl.js +1 -0
- package/dist/app/client/assets/rUSD-B-xuFS7T.svg +1 -0
- package/dist/app/client/assets/reUSD-CkIUs0Lx.svg +1 -0
- package/dist/app/client/assets/removable-DMiA3t_o.js +1 -0
- package/dist/app/client/assets/reset-co-CHrff.css +1 -0
- package/dist/app/client/assets/rolldown-runtime-WkwZ5CU-.js +1 -0
- package/dist/app/client/assets/routes-CK8cthzA.js +1 -0
- package/dist/app/client/assets/sUSDe-j9GOnH7I.svg +1 -0
- package/dist/app/client/assets/siUSD-C10QUNYS.svg +1 -0
- package/dist/app/client/assets/stcUSD-DIntDJeh.svg +1 -0
- package/dist/app/client/assets/syrupUSDC-DhYwZqdM.svg +5 -0
- package/dist/app/client/assets/thetaUSD-Dgmksf6G.svg +1 -0
- package/dist/app/client/assets/useNavigate-CB6S2u4J.js +1 -0
- package/dist/app/client/assets/useQuery-BpHfPhTT.js +1 -0
- package/dist/app/client/assets/useRegisterFieldControl-DsslqY-V.js +1 -0
- package/dist/app/client/assets/wYLDS-CaCKDrO-.svg +10 -0
- package/dist/app/client/assets/wsrUSD-B2Ey0bDQ.svg +1 -0
- package/dist/app/client/assets/zyzz-DYC-X0_B.css +1 -0
- package/dist/app/client/assets/zyzz.config-DX_D_7j3.js +2 -0
- package/dist/app/db/auth.d.ts +3412 -0
- package/dist/app/db/auth.d.ts.map +1 -0
- package/dist/app/db/auth.js +349 -0
- package/dist/app/db/auth.js.map +1 -0
- package/dist/app/db/schema.d.ts +1091 -0
- package/dist/app/db/schema.d.ts.map +1 -0
- package/dist/app/db/schema.js +183 -0
- package/dist/app/db/schema.js.map +1 -0
- package/dist/app/db/table.d.ts +8 -0
- package/dist/app/db/table.d.ts.map +1 -0
- package/dist/app/db/table.js +28 -0
- package/dist/app/db/table.js.map +1 -0
- package/dist/app/server/.vite/manifest.json +2093 -0
- package/dist/app/server/assets/Account-BYLpNFf3.css +1 -0
- package/dist/app/server/assets/Account-G_SUeRU3.js +2 -0
- package/dist/app/server/assets/Account-G_SUeRU3.js.map +1 -0
- package/dist/app/server/assets/Amount-Cgwdr3Xt.js +2 -0
- package/dist/app/server/assets/Amount-Cgwdr3Xt.js.map +1 -0
- package/dist/app/server/assets/Amount-ZdtluGf-.css +1 -0
- package/dist/app/server/assets/Approval-CgV5JHlb.css +1 -0
- package/dist/app/server/assets/Approval-DvkB9tWA.js +2 -0
- package/dist/app/server/assets/Approval-DvkB9tWA.js.map +1 -0
- package/dist/app/server/assets/ArrowUp-DpK_zNgZ.js +2 -0
- package/dist/app/server/assets/ArrowUp-DpK_zNgZ.js.map +1 -0
- package/dist/app/server/assets/Authentication-BOkhlNQu.js +2 -0
- package/dist/app/server/assets/Authentication-BOkhlNQu.js.map +1 -0
- package/dist/app/server/assets/Authentication-C3oNZAVX.js +2 -0
- package/dist/app/server/assets/Authentication-C3oNZAVX.js.map +1 -0
- package/dist/app/server/assets/BRLA-CZvVDNYr.png +0 -0
- package/dist/app/server/assets/BRZ-Cn3jEi9t.svg +1 -0
- package/dist/app/server/assets/Bytes-C7CRVS3J.js +4 -0
- package/dist/app/server/assets/Bytes-C7CRVS3J.js.map +1 -0
- package/dist/app/server/assets/CHFAU-DCedc4aH.png +0 -0
- package/dist/app/server/assets/Callout-DBfEmP7q.css +1 -0
- package/dist/app/server/assets/Callout-tQoAitWq.js +2 -0
- package/dist/app/server/assets/Callout-tQoAitWq.js.map +1 -0
- package/dist/app/server/assets/ChainList-C4A_7e-9.js +6 -0
- package/dist/app/server/assets/ChainList-C4A_7e-9.js.map +1 -0
- package/dist/app/server/assets/ChainName-BMnJyJrd.js +2 -0
- package/dist/app/server/assets/ChainName-BMnJyJrd.js.map +1 -0
- package/dist/app/server/assets/Check-DU_oR3Ev.js +2 -0
- package/dist/app/server/assets/Check-DU_oR3Ev.js.map +1 -0
- package/dist/app/server/assets/ChevronRight-DTBnw-W6.js +2 -0
- package/dist/app/server/assets/ChevronRight-DTBnw-W6.js.map +1 -0
- package/dist/app/server/assets/ChooseAccount-BizJvMrY.js +2 -0
- package/dist/app/server/assets/ChooseAccount-BizJvMrY.js.map +1 -0
- package/dist/app/server/assets/ChooseAccount-DL3wgJhE.css +1 -0
- package/dist/app/server/assets/Client-Dx3KrpJZ.js +2 -0
- package/dist/app/server/assets/Client-Dx3KrpJZ.js.map +1 -0
- package/dist/app/server/assets/ClientOnly-CKLiu67O.js +2 -0
- package/dist/app/server/assets/ClientOnly-CKLiu67O.js.map +1 -0
- package/dist/app/server/assets/Close-DUeE37ev.js +2 -0
- package/dist/app/server/assets/Close-DUeE37ev.js.map +1 -0
- package/dist/app/server/assets/Cloud-CLB3rTTy.js +2 -0
- package/dist/app/server/assets/Cloud-CLB3rTTy.js.map +1 -0
- package/dist/app/server/assets/CodeInput-BqRteSwD.css +1 -0
- package/dist/app/server/assets/CodeInput-FsPzVuLU.js +2 -0
- package/dist/app/server/assets/CodeInput-FsPzVuLU.js.map +1 -0
- package/dist/app/server/assets/Credential-DFjcrdyI.js +2 -0
- package/dist/app/server/assets/Credential-DFjcrdyI.js.map +1 -0
- package/dist/app/server/assets/DLUSD-BnRXm3EI.png +0 -0
- package/dist/app/server/assets/Deposit-CclrAoXL.css +1 -0
- package/dist/app/server/assets/Deposit-ChYGRfW2.js +2 -0
- package/dist/app/server/assets/Deposit-ChYGRfW2.js.map +1 -0
- package/dist/app/server/assets/DevTools-BYkmtuV5.css +1 -0
- package/dist/app/server/assets/DevTools-ZJVXqrIK.js +2 -0
- package/dist/app/server/assets/DevTools-ZJVXqrIK.js.map +1 -0
- package/dist/app/server/assets/DeviceApproval-BffFLD8F.css +1 -0
- package/dist/app/server/assets/DeviceApproval-Bqe3Mi78.js +2 -0
- package/dist/app/server/assets/DeviceApproval-Bqe3Mi78.js.map +1 -0
- package/dist/app/server/assets/EURAU-sjieTTB9.svg +1 -0
- package/dist/app/server/assets/EURC.e-CxYiiwkz.svg +1 -0
- package/dist/app/server/assets/ExecutionError-CDszGSF1.js +9 -0
- package/dist/app/server/assets/ExecutionError-CDszGSF1.js.map +1 -0
- package/dist/app/server/assets/Fingerprint-DhOPOdRr.js +2 -0
- package/dist/app/server/assets/Fingerprint-DhOPOdRr.js.map +1 -0
- package/dist/app/server/assets/GBPA-DI_25xSG.svg +4 -0
- package/dist/app/server/assets/GUSD-BDEP2Qb-.svg +1 -0
- package/dist/app/server/assets/GeistMono-Variable-BRXCGrxA.woff2 +0 -0
- package/dist/app/server/assets/Globe-2CxqxU-7.js +2 -0
- package/dist/app/server/assets/Globe-2CxqxU-7.js.map +1 -0
- package/dist/app/server/assets/Grant-5a-n1gJH.css +1 -0
- package/dist/app/server/assets/Grant-D_tun0mF.js +2 -0
- package/dist/app/server/assets/Grant-D_tun0mF.js.map +1 -0
- package/dist/app/server/assets/Hex-C9KeV7_9.css +1 -0
- package/dist/app/server/assets/Hex-Z6m_0Y0m.js +11 -0
- package/dist/app/server/assets/Hex-Z6m_0Y0m.js.map +1 -0
- package/dist/app/server/assets/IconChip-C9ZrmGB5.js +2 -0
- package/dist/app/server/assets/IconChip-C9ZrmGB5.js.map +1 -0
- package/dist/app/server/assets/IconChip-yqaQ2y_d.css +1 -0
- package/dist/app/server/assets/InputGroup-BlZvt9In.css +1 -0
- package/dist/app/server/assets/InputGroup-hpjuLuR7.js +2 -0
- package/dist/app/server/assets/InputGroup-hpjuLuR7.js.map +1 -0
- package/dist/app/server/assets/Inter-cyrillic-e3OmmzmJ.woff2 +0 -0
- package/dist/app/server/assets/Inter-cyrillic-ext-DOWZeO4Q.woff2 +0 -0
- package/dist/app/server/assets/Inter-greek-BD3ujQlX.woff2 +0 -0
- package/dist/app/server/assets/Inter-greek-ext-CXEwbus4.woff2 +0 -0
- package/dist/app/server/assets/Inter-latin-BDFm4vTb.woff2 +0 -0
- package/dist/app/server/assets/Inter-latin-ext-E7a9JEPe.woff2 +0 -0
- package/dist/app/server/assets/Inter-symbols-DEch7Rxr.woff2 +0 -0
- package/dist/app/server/assets/Inter-vietnamese-Bg9723vR.woff2 +0 -0
- package/dist/app/server/assets/Loader-DnKC9P5p.js +2 -0
- package/dist/app/server/assets/Loader-DnKC9P5p.js.map +1 -0
- package/dist/app/server/assets/MACH-sK0r79kQ.svg +4 -0
- package/dist/app/server/assets/Match-CyVdYcSf.js +2 -0
- package/dist/app/server/assets/Match-CyVdYcSf.js.map +1 -0
- package/dist/app/server/assets/OUSD-BFXgVo-Z.svg +5 -0
- package/dist/app/server/assets/OUSD.dark-BbbjMvA1.svg +17 -0
- package/dist/app/server/assets/OUSD.light-B_zpSz_c.svg +17 -0
- package/dist/app/server/assets/PRIME-CiFp2-3e.svg +14 -0
- package/dist/app/server/assets/PopoverPopup-CwqeQelI.js +2 -0
- package/dist/app/server/assets/PopoverPopup-CwqeQelI.js.map +1 -0
- package/dist/app/server/assets/Rows-CQKQwTI1.js +2 -0
- package/dist/app/server/assets/Rows-CQKQwTI1.js.map +1 -0
- package/dist/app/server/assets/Rows-DPGTUdA8.css +1 -0
- package/dist/app/server/assets/SBC-BTYkbkiD.svg +1 -0
- package/dist/app/server/assets/SEKAU-CB5Fc6o8.svg +5 -0
- package/dist/app/server/assets/Separator-DrI7KRAs.js +2 -0
- package/dist/app/server/assets/Separator-DrI7KRAs.js.map +1 -0
- package/dist/app/server/assets/SignIn-DfeeLEmN.js +2 -0
- package/dist/app/server/assets/SignIn-DfeeLEmN.js.map +1 -0
- package/dist/app/server/assets/SignIn-mlBTwUq4.css +1 -0
- package/dist/app/server/assets/Stack-D7Yt22Ed.css +1 -0
- package/dist/app/server/assets/Stack-DGktaLZc.js +2 -0
- package/dist/app/server/assets/Stack-DGktaLZc.js.map +1 -0
- package/dist/app/server/assets/Tag-9vSRBpph.js +2 -0
- package/dist/app/server/assets/Tag-9vSRBpph.js.map +1 -0
- package/dist/app/server/assets/Tag-BMh6ajrW.css +1 -0
- package/dist/app/server/assets/ToolbarRootContext-BH_CFSWd.js +2 -0
- package/dist/app/server/assets/ToolbarRootContext-BH_CFSWd.js.map +1 -0
- package/dist/app/server/assets/Tooltip-DLX41peb.js +2 -0
- package/dist/app/server/assets/Tooltip-DLX41peb.js.map +1 -0
- package/dist/app/server/assets/Tooltip-d3y9ESUJ.css +1 -0
- package/dist/app/server/assets/USD1-COVcO_PB.svg +30 -0
- package/dist/app/server/assets/USDB-Ckwb6UQw.svg +11 -0
- package/dist/app/server/assets/USDC.e-3nMsKAMC.svg +1 -0
- package/dist/app/server/assets/USDT0-ia60Dw0e.svg +1 -0
- package/dist/app/server/assets/USDY-CB4iet2q.svg +16 -0
- package/dist/app/server/assets/USDe-BXHQCd92.svg +1 -0
- package/dist/app/server/assets/Upgrade-Dbfm2nm0.js +2 -0
- package/dist/app/server/assets/Upgrade-Dbfm2nm0.js.map +1 -0
- package/dist/app/server/assets/WalletUpgrade-C04kW0Jl.css +1 -0
- package/dist/app/server/assets/WalletUpgrade-CWKc5fYt.js +2 -0
- package/dist/app/server/assets/WalletUpgrade-CWKc5fYt.js.map +1 -0
- package/dist/app/server/assets/YLDS-CRh8Zw0g.svg +3 -0
- package/dist/app/server/assets/___-I98bns4o.css +1 -0
- package/dist/app/server/assets/___.flows-Cc-awUoC.js +6 -0
- package/dist/app/server/assets/___.flows-Cc-awUoC.js.map +1 -0
- package/dist/app/server/assets/_tanstack-start-manifest_v-U7TrtUSA.js +1 -0
- package/dist/app/server/assets/_zyzz__2Fhome_2Frunner_2Fwork_2Fid_2Fid_2Fsrc_2Fapp_2Froutes_2F_5B__5D.flows-B1pPOqsM.css +1 -0
- package/dist/app/server/assets/_zyzz__2Fhome_2Frunner_2Fwork_2Fid_2Fid_2Fsrc_2Fapp_2Froutes_2F_5B__5D.flows.tsx-CKALi9oD.js +2 -0
- package/dist/app/server/assets/_zyzz__2Fhome_2Frunner_2Fwork_2Fid_2Fid_2Fsrc_2Fapp_2Froutes_2F_5B__5D.flows.tsx-CKALi9oD.js.map +1 -0
- package/dist/app/server/assets/_zyzz_shared-BbDIEd_j.css +1 -0
- package/dist/app/server/assets/alphaUSD-CpCFoVuh.svg +1 -0
- package/dist/app/server/assets/arktype-aI7TBD0R-D1R3Byo5.js +2 -0
- package/dist/app/server/assets/arktype-aI7TBD0R-D1R3Byo5.js.map +1 -0
- package/dist/app/server/assets/async-local-storage-DlOvwxXd.js +2 -0
- package/dist/app/server/assets/async-local-storage-DlOvwxXd.js.map +1 -0
- package/dist/app/server/assets/authorize-BXzTo2Pb.js +2 -0
- package/dist/app/server/assets/authorize-BXzTo2Pb.js.map +1 -0
- package/dist/app/server/assets/authorize-BzoUqOdA.js +2 -0
- package/dist/app/server/assets/authorize-BzoUqOdA.js.map +1 -0
- package/dist/app/server/assets/authorize-DXYo-K2n-rmQQ42x1.js +56 -0
- package/dist/app/server/assets/authorize-DXYo-K2n-rmQQ42x1.js.map +1 -0
- package/dist/app/server/assets/authorize-DfjPYJqS.css +1 -0
- package/dist/app/server/assets/betaUSD-9A8AjgYg.svg +1 -0
- package/dist/app/server/assets/bun-sqlite-dialect-DoOVo4ja-CtyBkVMb.js +2 -0
- package/dist/app/server/assets/bun-sqlite-dialect-DoOVo4ja-CtyBkVMb.js.map +1 -0
- package/dist/app/server/assets/cUSD-WsQfa-iC.svg +1 -0
- package/dist/app/server/assets/call-dzi0xjZd.js +31 -0
- package/dist/app/server/assets/call-dzi0xjZd.js.map +1 -0
- package/dist/app/server/assets/cbBTC-DvUDqrDi.svg +1 -0
- package/dist/app/server/assets/ccip-DjD8z_3B.js +2 -0
- package/dist/app/server/assets/ccip-DjD8z_3B.js.map +1 -0
- package/dist/app/server/assets/choose-account-_X7xq5am.js +2 -0
- package/dist/app/server/assets/choose-account-_X7xq5am.js.map +1 -0
- package/dist/app/server/assets/choose-account-lqA5SLx9.css +1 -0
- package/dist/app/server/assets/choose-account-zm-5Qs3e.js +2 -0
- package/dist/app/server/assets/choose-account-zm-5Qs3e.js.map +1 -0
- package/dist/app/server/assets/compiled-query-Ds2NVcFT.js +2 -0
- package/dist/app/server/assets/compiled-query-Ds2NVcFT.js.map +1 -0
- package/dist/app/server/assets/convert-BIC8n32x.js +2 -0
- package/dist/app/server/assets/convert-BIC8n32x.js.map +1 -0
- package/dist/app/server/assets/cookie-DxViaO10.js +2 -0
- package/dist/app/server/assets/cookie-DxViaO10.js.map +1 -0
- package/dist/app/server/assets/core-BBrZXfrB.js +56 -0
- package/dist/app/server/assets/core-BBrZXfrB.js.map +1 -0
- package/dist/app/server/assets/d1-sqlite-dialect-m0f01-4z-Cv-csE1u.js +2 -0
- package/dist/app/server/assets/d1-sqlite-dialect-m0f01-4z-Cv-csE1u.js.map +1 -0
- package/dist/app/server/assets/decodeFunctionData-bP6V2ai4.js +2 -0
- package/dist/app/server/assets/decodeFunctionData-bP6V2ai4.js.map +1 -0
- package/dist/app/server/assets/default-u_dwuiYb-DOrMMcg9.js +2 -0
- package/dist/app/server/assets/default-u_dwuiYb-DOrMMcg9.js.map +1 -0
- package/dist/app/server/assets/deposit-BBaNI01u.js +2 -0
- package/dist/app/server/assets/deposit-BBaNI01u.js.map +1 -0
- package/dist/app/server/assets/deposit-LWS6ZTuf.js +2 -0
- package/dist/app/server/assets/deposit-LWS6ZTuf.js.map +1 -0
- package/dist/app/server/assets/deposit-oFx15XCA.css +1 -0
- package/dist/app/server/assets/device-CrXlusjr.js +2 -0
- package/dist/app/server/assets/device-CrXlusjr.js.map +1 -0
- package/dist/app/server/assets/device-DdFMgYe2.js +2 -0
- package/dist/app/server/assets/device-DdFMgYe2.js.map +1 -0
- package/dist/app/server/assets/device-tMCpu5zk.css +1 -0
- package/dist/app/server/assets/dist-CR6FW-yS.js +1 -0
- package/dist/app/server/assets/dist-CoH0hD1s.js +15 -0
- package/dist/app/server/assets/dist-CoH0hD1s.js.map +1 -0
- package/dist/app/server/assets/dist-DcICI3fd.js +2 -0
- package/dist/app/server/assets/dist-DcICI3fd.js.map +1 -0
- package/dist/app/server/assets/effect-QlVUlMFu-mXPkuQTb.js +2 -0
- package/dist/app/server/assets/effect-QlVUlMFu-mXPkuQTb.js.map +1 -0
- package/dist/app/server/assets/empty-plugin-adapters-BaGcmrjC.js +2 -0
- package/dist/app/server/assets/empty-plugin-adapters-BaGcmrjC.js.map +1 -0
- package/dist/app/server/assets/error-CXy8gqdE.css +1 -0
- package/dist/app/server/assets/error-DXDT2AHy.js +2 -0
- package/dist/app/server/assets/error-DXDT2AHy.js.map +1 -0
- package/dist/app/server/assets/error-bnVzy9tx.js +2 -0
- package/dist/app/server/assets/error-bnVzy9tx.js.map +1 -0
- package/dist/app/server/assets/esm-C6SW--yt.js +4 -0
- package/dist/app/server/assets/esm-C6SW--yt.js.map +1 -0
- package/dist/app/server/assets/factory-dvoOlKer.js +5 -0
- package/dist/app/server/assets/factory-dvoOlKer.js.map +1 -0
- package/dist/app/server/assets/frxUSD-n9pTpinR.svg +1 -0
- package/dist/app/server/assets/get-id-field-OwLpfjj2.js +6 -0
- package/dist/app/server/assets/get-id-field-OwLpfjj2.js.map +1 -0
- package/dist/app/server/assets/getPseudoElementBounds-PkWU72kn.js +2 -0
- package/dist/app/server/assets/getPseudoElementBounds-PkWU72kn.js.map +1 -0
- package/dist/app/server/assets/goUSD-D9kUvHsF.svg +5 -0
- package/dist/app/server/assets/iUSD-CWCJAq9X.svg +1 -0
- package/dist/app/server/assets/index-CLddUTqr-CC33CMvS.js +2 -0
- package/dist/app/server/assets/index-CLddUTqr-CC33CMvS.js.map +1 -0
- package/dist/app/server/assets/index-DZEfthgZ-Ec2MMQ_e.js +2 -0
- package/dist/app/server/assets/index-DZEfthgZ-Ec2MMQ_e.js.map +1 -0
- package/dist/app/server/assets/kysely-adapter-B8tQwTcu.js +1 -0
- package/dist/app/server/assets/lazyRouteComponent-DPOOPZw_.js +2 -0
- package/dist/app/server/assets/lazyRouteComponent-DPOOPZw_.js.map +1 -0
- package/dist/app/server/assets/load-server-Dr0IlySo.js +3 -0
- package/dist/app/server/assets/load-server-Dr0IlySo.js.map +1 -0
- package/dist/app/server/assets/login-BLq3tEEr.js +2 -0
- package/dist/app/server/assets/login-BLq3tEEr.js.map +1 -0
- package/dist/app/server/assets/login-DCIrAbED.js +2 -0
- package/dist/app/server/assets/login-DCIrAbED.js.map +1 -0
- package/dist/app/server/assets/login-msDnt3yT.css +1 -0
- package/dist/app/server/assets/matchContext-ByrIUj4L.js +2 -0
- package/dist/app/server/assets/matchContext-ByrIUj4L.js.map +1 -0
- package/dist/app/server/assets/node-sqlite-dialect-BowWLXvK.js +2 -0
- package/dist/app/server/assets/node-sqlite-dialect-BowWLXvK.js.map +1 -0
- package/dist/app/server/assets/pathUSD-BvaO6ooS.svg +25 -0
- package/dist/app/server/assets/rUSD-B-xuFS7T.svg +1 -0
- package/dist/app/server/assets/reUSD-CkIUs0Lx.svg +1 -0
- package/dist/app/server/assets/react-dom-DYzj0HXX.js +2 -0
- package/dist/app/server/assets/react-dom-DYzj0HXX.js.map +1 -0
- package/dist/app/server/assets/removable-BCLTvPOx.js +2 -0
- package/dist/app/server/assets/removable-BCLTvPOx.js.map +1 -0
- package/dist/app/server/assets/reset-DZWeTZeQ.js +2 -0
- package/dist/app/server/assets/reset-DZWeTZeQ.js.map +1 -0
- package/dist/app/server/assets/reset-co-CHrff.css +1 -0
- package/dist/app/server/assets/resolveError-CZXXvCJR.js +2 -0
- package/dist/app/server/assets/resolveError-CZXXvCJR.js.map +1 -0
- package/dist/app/server/assets/rolldown-runtime-BWaJ3C7D.js +1 -0
- package/dist/app/server/assets/router-Bqz5X4Qw.js +2 -0
- package/dist/app/server/assets/router-Bqz5X4Qw.js.map +1 -0
- package/dist/app/server/assets/router-CclzitkY.css +1 -0
- package/dist/app/server/assets/routes-BLQdKqcZ.js +2 -0
- package/dist/app/server/assets/routes-BLQdKqcZ.js.map +1 -0
- package/dist/app/server/assets/routes-BgiIzrOt.css +1 -0
- package/dist/app/server/assets/routes-Bv6WzsSR.js +2 -0
- package/dist/app/server/assets/routes-Bv6WzsSR.js.map +1 -0
- package/dist/app/server/assets/sUSDe-j9GOnH7I.svg +1 -0
- package/dist/app/server/assets/schema-check-oLqvHR8y.js +9 -0
- package/dist/app/server/assets/schema-check-oLqvHR8y.js.map +1 -0
- package/dist/app/server/assets/secp256k1-yg8eeIYU.js +2 -0
- package/dist/app/server/assets/secp256k1-yg8eeIYU.js.map +1 -0
- package/dist/app/server/assets/server-RRUiOef2.js +76 -0
- package/dist/app/server/assets/server-RRUiOef2.js.map +1 -0
- package/dist/app/server/assets/siUSD-C10QUNYS.svg +1 -0
- package/dist/app/server/assets/sqlite-adapter-C_LAV4e3.js +2 -0
- package/dist/app/server/assets/sqlite-adapter-C_LAV4e3.js.map +1 -0
- package/dist/app/server/assets/sqlite-introspector-DSCAP82F-Crx7Ny14.js +6 -0
- package/dist/app/server/assets/sqlite-introspector-DSCAP82F-Crx7Ny14.js.map +1 -0
- package/dist/app/server/assets/standard-json-DY77efq0.js +2 -0
- package/dist/app/server/assets/standard-json-DY77efq0.js.map +1 -0
- package/dist/app/server/assets/standard-json-ig2t0wRt.js +2 -0
- package/dist/app/server/assets/standard-json-ig2t0wRt.js.map +1 -0
- package/dist/app/server/assets/standard-json-t-LNckA6.js +2 -0
- package/dist/app/server/assets/standard-json-t-LNckA6.js.map +1 -0
- package/dist/app/server/assets/standard-openapi-CONmVRl8.js +2 -0
- package/dist/app/server/assets/standard-openapi-CONmVRl8.js.map +1 -0
- package/dist/app/server/assets/start-C63VetBp.js +2 -0
- package/dist/app/server/assets/start-C63VetBp.js.map +1 -0
- package/dist/app/server/assets/stcUSD-DIntDJeh.svg +1 -0
- package/dist/app/server/assets/sury-CWZTCd75-CDaOS2oM.js +2 -0
- package/dist/app/server/assets/sury-CWZTCd75-CDaOS2oM.js.map +1 -0
- package/dist/app/server/assets/syrupUSDC-DhYwZqdM.svg +5 -0
- package/dist/app/server/assets/thetaUSD-Dgmksf6G.svg +1 -0
- package/dist/app/server/assets/typebox-Dei93FPO-D_lCVdAz.js +2 -0
- package/dist/app/server/assets/typebox-Dei93FPO-D_lCVdAz.js.map +1 -0
- package/dist/app/server/assets/useNavigate-D6d1w7KE.js +2 -0
- package/dist/app/server/assets/useNavigate-D6d1w7KE.js.map +1 -0
- package/dist/app/server/assets/useQuery-BRpCr5vj.js +2 -0
- package/dist/app/server/assets/useQuery-BRpCr5vj.js.map +1 -0
- package/dist/app/server/assets/useRegisterFieldControl-cJesrOWK.js +2 -0
- package/dist/app/server/assets/useRegisterFieldControl-cJesrOWK.js.map +1 -0
- package/dist/app/server/assets/utils-C4ZXZIZW.js +2 -0
- package/dist/app/server/assets/utils-C4ZXZIZW.js.map +1 -0
- package/dist/app/server/assets/utils-D57zfcg-.js +2 -0
- package/dist/app/server/assets/utils-D57zfcg-.js.map +1 -0
- package/dist/app/server/assets/valibot--1zFm7rT-CIKoH3MS.js +2 -0
- package/dist/app/server/assets/valibot--1zFm7rT-CIKoH3MS.js.map +1 -0
- package/dist/app/server/assets/valibot-D_HTw1Gn-BdPyLkXt.js +2 -0
- package/dist/app/server/assets/valibot-D_HTw1Gn-BdPyLkXt.js.map +1 -0
- package/dist/app/server/assets/wYLDS-CaCKDrO-.svg +10 -0
- package/dist/app/server/assets/wsrUSD-B2Ey0bDQ.svg +1 -0
- package/dist/app/server/assets/zod-Bwrt9trS-BezI5Gi1.js +2 -0
- package/dist/app/server/assets/zod-Bwrt9trS-BezI5Gi1.js.map +1 -0
- package/dist/app/server/assets/zod-DSgpEGAE-DZ7M_Q33.js +2 -0
- package/dist/app/server/assets/zod-DSgpEGAE-DZ7M_Q33.js.map +1 -0
- package/dist/app/server/assets/zyzz-6R2K52xP.css +1 -0
- package/dist/app/server/assets/zyzz.config-CJRpjn0E.js +3 -0
- package/dist/app/server/assets/zyzz.config-CJRpjn0E.js.map +1 -0
- package/dist/app/server/index.js +532 -0
- package/dist/app/server/index.js.map +1 -0
- package/dist/app/server/wrangler.json +1 -0
- package/dist/cli/Admin.d.ts +34 -0
- package/dist/cli/Admin.d.ts.map +1 -0
- package/dist/cli/Admin.js +61 -0
- package/dist/cli/Admin.js.map +1 -0
- package/dist/cli/Apps.d.ts +9 -0
- package/dist/cli/Apps.d.ts.map +1 -0
- package/dist/cli/Apps.js +34 -0
- package/dist/cli/Apps.js.map +1 -0
- package/dist/cli/Cli.d.ts +13 -0
- package/dist/cli/Cli.d.ts.map +1 -0
- package/dist/cli/Cli.js +26 -0
- package/dist/cli/Cli.js.map +1 -0
- package/dist/cli/Project.d.ts +17 -0
- package/dist/cli/Project.d.ts.map +1 -0
- package/dist/cli/Project.js +90 -0
- package/dist/cli/Project.js.map +1 -0
- package/dist/cli/Realms.d.ts +24 -0
- package/dist/cli/Realms.d.ts.map +1 -0
- package/dist/cli/Realms.js +48 -0
- package/dist/cli/Realms.js.map +1 -0
- package/dist/cli/Session.d.ts +79 -0
- package/dist/cli/Session.d.ts.map +1 -0
- package/dist/cli/Session.js +229 -0
- package/dist/cli/Session.js.map +1 -0
- package/dist/cli/User.d.ts +24 -0
- package/dist/cli/User.d.ts.map +1 -0
- package/dist/cli/User.js +34 -0
- package/dist/cli/User.js.map +1 -0
- package/dist/cli/V1.d.ts +32 -0
- package/dist/cli/V1.d.ts.map +1 -0
- package/dist/cli/V1.js +38 -0
- package/dist/cli/V1.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +46 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/commands/admin/apps/create.d.ts +23 -0
- package/dist/cli/commands/admin/apps/create.d.ts.map +1 -0
- package/dist/cli/commands/admin/apps/create.js +84 -0
- package/dist/cli/commands/admin/apps/create.js.map +1 -0
- package/dist/cli/commands/admin/apps/delete.d.ts +15 -0
- package/dist/cli/commands/admin/apps/delete.d.ts.map +1 -0
- package/dist/cli/commands/admin/apps/delete.js +41 -0
- package/dist/cli/commands/admin/apps/delete.js.map +1 -0
- package/dist/cli/commands/admin/apps/index.d.ts +5 -0
- package/dist/cli/commands/admin/apps/index.d.ts.map +1 -0
- package/dist/cli/commands/admin/apps/index.js +14 -0
- package/dist/cli/commands/admin/apps/index.js.map +1 -0
- package/dist/cli/commands/admin/apps/list.d.ts +10 -0
- package/dist/cli/commands/admin/apps/list.d.ts.map +1 -0
- package/dist/cli/commands/admin/apps/list.js +26 -0
- package/dist/cli/commands/admin/apps/list.js.map +1 -0
- package/dist/cli/commands/admin/apps/rotate-secret.d.ts +17 -0
- package/dist/cli/commands/admin/apps/rotate-secret.d.ts.map +1 -0
- package/dist/cli/commands/admin/apps/rotate-secret.js +47 -0
- package/dist/cli/commands/admin/apps/rotate-secret.js.map +1 -0
- package/dist/cli/commands/admin/apps/update.d.ts +20 -0
- package/dist/cli/commands/admin/apps/update.d.ts.map +1 -0
- package/dist/cli/commands/admin/apps/update.js +71 -0
- package/dist/cli/commands/admin/apps/update.js.map +1 -0
- package/dist/cli/commands/admin/index.d.ts +5 -0
- package/dist/cli/commands/admin/index.d.ts.map +1 -0
- package/dist/cli/commands/admin/index.js +13 -0
- package/dist/cli/commands/admin/index.js.map +1 -0
- package/dist/cli/commands/admin/migrate.d.ts +10 -0
- package/dist/cli/commands/admin/migrate.d.ts.map +1 -0
- package/dist/cli/commands/admin/migrate.js +20 -0
- package/dist/cli/commands/admin/migrate.js.map +1 -0
- package/dist/cli/commands/admin/realms/add.d.ts +13 -0
- package/dist/cli/commands/admin/realms/add.d.ts.map +1 -0
- package/dist/cli/commands/admin/realms/add.js +42 -0
- package/dist/cli/commands/admin/realms/add.js.map +1 -0
- package/dist/cli/commands/admin/realms/index.d.ts +5 -0
- package/dist/cli/commands/admin/realms/index.d.ts.map +1 -0
- package/dist/cli/commands/admin/realms/index.js +13 -0
- package/dist/cli/commands/admin/realms/index.js.map +1 -0
- package/dist/cli/commands/admin/realms/list.d.ts +14 -0
- package/dist/cli/commands/admin/realms/list.d.ts.map +1 -0
- package/dist/cli/commands/admin/realms/list.js +36 -0
- package/dist/cli/commands/admin/realms/list.js.map +1 -0
- package/dist/cli/commands/admin/realms/passkeys/index.d.ts +5 -0
- package/dist/cli/commands/admin/realms/passkeys/index.d.ts.map +1 -0
- package/dist/cli/commands/admin/realms/passkeys/index.js +13 -0
- package/dist/cli/commands/admin/realms/passkeys/index.js.map +1 -0
- package/dist/cli/commands/admin/realms/passkeys/set.d.ts +17 -0
- package/dist/cli/commands/admin/realms/passkeys/set.d.ts.map +1 -0
- package/dist/cli/commands/admin/realms/passkeys/set.js +46 -0
- package/dist/cli/commands/admin/realms/passkeys/set.js.map +1 -0
- package/dist/cli/commands/admin/realms/privy/index.d.ts +5 -0
- package/dist/cli/commands/admin/realms/privy/index.d.ts.map +1 -0
- package/dist/cli/commands/admin/realms/privy/index.js +13 -0
- package/dist/cli/commands/admin/realms/privy/index.js.map +1 -0
- package/dist/cli/commands/admin/realms/privy/set.d.ts +19 -0
- package/dist/cli/commands/admin/realms/privy/set.d.ts.map +1 -0
- package/dist/cli/commands/admin/realms/privy/set.js +66 -0
- package/dist/cli/commands/admin/realms/privy/set.js.map +1 -0
- package/dist/cli/commands/admin/realms/remove.d.ts +12 -0
- package/dist/cli/commands/admin/realms/remove.d.ts.map +1 -0
- package/dist/cli/commands/admin/realms/remove.js +35 -0
- package/dist/cli/commands/admin/realms/remove.js.map +1 -0
- package/dist/cli/commands/admin/realms/secp256k1/disable.d.ts +16 -0
- package/dist/cli/commands/admin/realms/secp256k1/disable.d.ts.map +1 -0
- package/dist/cli/commands/admin/realms/secp256k1/disable.js +42 -0
- package/dist/cli/commands/admin/realms/secp256k1/disable.js.map +1 -0
- package/dist/cli/commands/admin/realms/secp256k1/enable.d.ts +16 -0
- package/dist/cli/commands/admin/realms/secp256k1/enable.d.ts.map +1 -0
- package/dist/cli/commands/admin/realms/secp256k1/enable.js +42 -0
- package/dist/cli/commands/admin/realms/secp256k1/enable.js.map +1 -0
- package/dist/cli/commands/admin/realms/secp256k1/index.d.ts +9 -0
- package/dist/cli/commands/admin/realms/secp256k1/index.d.ts.map +1 -0
- package/dist/cli/commands/admin/realms/secp256k1/index.js +17 -0
- package/dist/cli/commands/admin/realms/secp256k1/index.js.map +1 -0
- package/dist/cli/commands/admin/realms/set.d.ts +17 -0
- package/dist/cli/commands/admin/realms/set.d.ts.map +1 -0
- package/dist/cli/commands/admin/realms/set.js +46 -0
- package/dist/cli/commands/admin/realms/set.js.map +1 -0
- package/dist/cli/commands/admin/rotate-key.d.ts +16 -0
- package/dist/cli/commands/admin/rotate-key.d.ts.map +1 -0
- package/dist/cli/commands/admin/rotate-key.js +46 -0
- package/dist/cli/commands/admin/rotate-key.js.map +1 -0
- package/dist/cli/commands/init.d.ts +11 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +34 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/login.d.ts +12 -0
- package/dist/cli/commands/login.d.ts.map +1 -0
- package/dist/cli/commands/login.js +29 -0
- package/dist/cli/commands/login.js.map +1 -0
- package/dist/cli/commands/logout.d.ts +8 -0
- package/dist/cli/commands/logout.d.ts.map +1 -0
- package/dist/cli/commands/logout.js +19 -0
- package/dist/cli/commands/logout.js.map +1 -0
- package/dist/cli/commands/whoami.d.ts +12 -0
- package/dist/cli/commands/whoami.d.ts.map +1 -0
- package/dist/cli/commands/whoami.js +24 -0
- package/dist/cli/commands/whoami.js.map +1 -0
- package/dist/client/Actions.d.ts +108 -0
- package/dist/client/Actions.d.ts.map +1 -0
- package/dist/client/Actions.js +94 -0
- package/dist/client/Actions.js.map +1 -0
- package/dist/client/Auth.d.ts +143 -0
- package/dist/client/Auth.d.ts.map +1 -0
- package/dist/client/Auth.js +106 -0
- package/dist/client/Auth.js.map +1 -0
- package/dist/client/Authorization.d.ts +29 -0
- package/dist/client/Authorization.d.ts.map +1 -0
- package/dist/client/Authorization.js +12 -0
- package/dist/client/Authorization.js.map +1 -0
- package/dist/client/Decorator.d.ts +172 -0
- package/dist/client/Decorator.d.ts.map +1 -0
- package/dist/client/Decorator.js +241 -0
- package/dist/client/Decorator.js.map +1 -0
- package/dist/client/Key.d.ts +82 -0
- package/dist/client/Key.d.ts.map +1 -0
- package/dist/client/Key.js +81 -0
- package/dist/client/Key.js.map +1 -0
- package/dist/client/Pending.d.ts +38 -0
- package/dist/client/Pending.d.ts.map +1 -0
- package/dist/client/Pending.js +12 -0
- package/dist/client/Pending.js.map +1 -0
- package/dist/client/Permissions.d.ts +36 -0
- package/dist/client/Permissions.d.ts.map +1 -0
- package/dist/client/Permissions.js +16 -0
- package/dist/client/Permissions.js.map +1 -0
- package/dist/client/Storage.d.ts +27 -0
- package/dist/client/Storage.d.ts.map +1 -0
- package/dist/client/Storage.js +16 -0
- package/dist/client/Storage.js.map +1 -0
- package/dist/client/TempoAccount.d.ts +52 -0
- package/dist/client/TempoAccount.d.ts.map +1 -0
- package/dist/client/TempoAccount.js +17 -0
- package/dist/client/TempoAccount.js.map +1 -0
- package/dist/client/Tokens.d.ts +25 -0
- package/dist/client/Tokens.d.ts.map +1 -0
- package/dist/client/Tokens.js +12 -0
- package/dist/client/Tokens.js.map +1 -0
- package/dist/client/User.d.ts +21 -0
- package/dist/client/User.d.ts.map +1 -0
- package/dist/client/User.js +12 -0
- package/dist/client/User.js.map +1 -0
- package/dist/client/actions/account.d.ts +75 -0
- package/dist/client/actions/account.d.ts.map +1 -0
- package/dist/client/actions/account.js +102 -0
- package/dist/client/actions/account.js.map +1 -0
- package/dist/client/actions/device.d.ts +52 -0
- package/dist/client/actions/device.d.ts.map +1 -0
- package/dist/client/actions/device.js +103 -0
- package/dist/client/actions/device.js.map +1 -0
- package/dist/client/actions/email.d.ts +89 -0
- package/dist/client/actions/email.d.ts.map +1 -0
- package/dist/client/actions/email.js +86 -0
- package/dist/client/actions/email.js.map +1 -0
- package/dist/client/actions/oauth.d.ts +112 -0
- package/dist/client/actions/oauth.d.ts.map +1 -0
- package/dist/client/actions/oauth.js +103 -0
- package/dist/client/actions/oauth.js.map +1 -0
- package/dist/client/actions/passkey.d.ts +140 -0
- package/dist/client/actions/passkey.d.ts.map +1 -0
- package/dist/client/actions/passkey.js +170 -0
- package/dist/client/actions/passkey.js.map +1 -0
- package/dist/client/actions/realm.d.ts +25 -0
- package/dist/client/actions/realm.d.ts.map +1 -0
- package/dist/client/actions/realm.js +16 -0
- package/dist/client/actions/realm.js.map +1 -0
- package/dist/client/actions/redirect.d.ts +152 -0
- package/dist/client/actions/redirect.d.ts.map +1 -0
- package/dist/client/actions/redirect.js +311 -0
- package/dist/client/actions/redirect.js.map +1 -0
- package/dist/client/internal/accessKey.d.ts +21 -0
- package/dist/client/internal/accessKey.d.ts.map +1 -0
- package/dist/client/internal/accessKey.js +38 -0
- package/dist/client/internal/accessKey.js.map +1 -0
- package/dist/client/internal/account.d.ts +17 -0
- package/dist/client/internal/account.d.ts.map +1 -0
- package/dist/client/internal/account.js +3 -0
- package/dist/client/internal/account.js.map +1 -0
- package/dist/client/internal/api.d.ts +110 -0
- package/dist/client/internal/api.d.ts.map +1 -0
- package/dist/client/internal/api.js +136 -0
- package/dist/client/internal/api.js.map +1 -0
- package/dist/client/internal/headless.d.ts +159 -0
- package/dist/client/internal/headless.d.ts.map +1 -0
- package/dist/client/internal/headless.js +196 -0
- package/dist/client/internal/headless.js.map +1 -0
- package/dist/client/internal/indexedDb.d.ts +8 -0
- package/dist/client/internal/indexedDb.d.ts.map +1 -0
- package/dist/client/internal/indexedDb.js +32 -0
- package/dist/client/internal/indexedDb.js.map +1 -0
- package/dist/client/internal/request.d.ts +56 -0
- package/dist/client/internal/request.d.ts.map +1 -0
- package/dist/client/internal/request.js +44 -0
- package/dist/client/internal/request.js.map +1 -0
- package/dist/client/internal/store.d.ts +12 -0
- package/dist/client/internal/store.d.ts.map +1 -0
- package/dist/client/internal/store.js +15 -0
- package/dist/client/internal/store.js.map +1 -0
- package/dist/client/internal/verifier.d.ts +25 -0
- package/dist/client/internal/verifier.d.ts.map +1 -0
- package/dist/client/internal/verifier.js +52 -0
- package/dist/client/internal/verifier.js.map +1 -0
- package/dist/client.d.ts +18 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +18 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/Apps.d.ts +3169 -0
- package/dist/internal/Apps.d.ts.map +1 -0
- package/dist/internal/Apps.js +948 -0
- package/dist/internal/Apps.js.map +1 -0
- package/dist/internal/Auth.d.ts +4287 -0
- package/dist/internal/Auth.d.ts.map +1 -0
- package/dist/internal/Auth.js +185 -0
- package/dist/internal/Auth.js.map +1 -0
- package/dist/internal/Authorize.d.ts +526 -0
- package/dist/internal/Authorize.d.ts.map +1 -0
- package/dist/internal/Authorize.js +330 -0
- package/dist/internal/Authorize.js.map +1 -0
- package/dist/internal/Cache.d.ts +56 -0
- package/dist/internal/Cache.d.ts.map +1 -0
- package/dist/internal/Cache.js +65 -0
- package/dist/internal/Cache.js.map +1 -0
- package/dist/internal/Chain.d.ts +738 -0
- package/dist/internal/Chain.d.ts.map +1 -0
- package/dist/internal/Chain.js +65 -0
- package/dist/internal/Chain.js.map +1 -0
- package/dist/internal/ChainList.d.ts +117 -0
- package/dist/internal/ChainList.d.ts.map +1 -0
- package/dist/internal/ChainList.js +148 -0
- package/dist/internal/ChainList.js.map +1 -0
- package/dist/internal/ChainName.d.ts +11 -0
- package/dist/internal/ChainName.d.ts.map +1 -0
- package/dist/internal/ChainName.js +13 -0
- package/dist/internal/ChainName.js.map +1 -0
- package/dist/internal/CodeEmail.d.ts +38 -0
- package/dist/internal/CodeEmail.d.ts.map +1 -0
- package/dist/internal/CodeEmail.js +85 -0
- package/dist/internal/CodeEmail.js.map +1 -0
- package/dist/internal/ConfigurableAccount.d.ts +600 -0
- package/dist/internal/ConfigurableAccount.d.ts.map +1 -0
- package/dist/internal/ConfigurableAccount.js +795 -0
- package/dist/internal/ConfigurableAccount.js.map +1 -0
- package/dist/internal/Device.d.ts +246 -0
- package/dist/internal/Device.d.ts.map +1 -0
- package/dist/internal/Device.js +257 -0
- package/dist/internal/Device.js.map +1 -0
- package/dist/internal/Env.d.ts +97 -0
- package/dist/internal/Env.d.ts.map +1 -0
- package/dist/internal/Env.js +2 -0
- package/dist/internal/Env.js.map +1 -0
- package/dist/internal/ErrorResponse.d.ts +608 -0
- package/dist/internal/ErrorResponse.d.ts.map +1 -0
- package/dist/internal/ErrorResponse.js +701 -0
- package/dist/internal/ErrorResponse.js.map +1 -0
- package/dist/internal/Id.d.ts +10 -0
- package/dist/internal/Id.d.ts.map +1 -0
- package/dist/internal/Id.js +34 -0
- package/dist/internal/Id.js.map +1 -0
- package/dist/internal/KeyAuthorization.d.ts +1650 -0
- package/dist/internal/KeyAuthorization.d.ts.map +1 -0
- package/dist/internal/KeyAuthorization.js +1187 -0
- package/dist/internal/KeyAuthorization.js.map +1 -0
- package/dist/internal/Limits.d.ts +70 -0
- package/dist/internal/Limits.d.ts.map +1 -0
- package/dist/internal/Limits.js +36 -0
- package/dist/internal/Limits.js.map +1 -0
- package/dist/internal/Log.d.ts +34 -0
- package/dist/internal/Log.d.ts.map +1 -0
- package/dist/internal/Log.js +99 -0
- package/dist/internal/Log.js.map +1 -0
- package/dist/internal/LogLine.d.ts +29 -0
- package/dist/internal/LogLine.d.ts.map +1 -0
- package/dist/internal/LogLine.js +50 -0
- package/dist/internal/LogLine.js.map +1 -0
- package/dist/internal/Migrations.d.ts +28 -0
- package/dist/internal/Migrations.d.ts.map +1 -0
- package/dist/internal/Migrations.js +82 -0
- package/dist/internal/Migrations.js.map +1 -0
- package/dist/internal/Owner.d.ts +230 -0
- package/dist/internal/Owner.d.ts.map +1 -0
- package/dist/internal/Owner.js +101 -0
- package/dist/internal/Owner.js.map +1 -0
- package/dist/internal/Passkey.d.ts +901 -0
- package/dist/internal/Passkey.d.ts.map +1 -0
- package/dist/internal/Passkey.js +1185 -0
- package/dist/internal/Passkey.js.map +1 -0
- package/dist/internal/PendingKey.d.ts +49 -0
- package/dist/internal/PendingKey.d.ts.map +1 -0
- package/dist/internal/PendingKey.js +58 -0
- package/dist/internal/PendingKey.js.map +1 -0
- package/dist/internal/Privy.d.ts +138 -0
- package/dist/internal/Privy.d.ts.map +1 -0
- package/dist/internal/Privy.js +285 -0
- package/dist/internal/Privy.js.map +1 -0
- package/dist/internal/RateLimit.d.ts +218 -0
- package/dist/internal/RateLimit.d.ts.map +1 -0
- package/dist/internal/RateLimit.js +209 -0
- package/dist/internal/RateLimit.js.map +1 -0
- package/dist/internal/Realm.d.ts +290 -0
- package/dist/internal/Realm.d.ts.map +1 -0
- package/dist/internal/Realm.js +502 -0
- package/dist/internal/Realm.js.map +1 -0
- package/dist/internal/RequestId.d.ts +16 -0
- package/dist/internal/RequestId.d.ts.map +1 -0
- package/dist/internal/RequestId.js +78 -0
- package/dist/internal/RequestId.js.map +1 -0
- package/dist/internal/SigningKey.d.ts +126 -0
- package/dist/internal/SigningKey.d.ts.map +1 -0
- package/dist/internal/SigningKey.js +221 -0
- package/dist/internal/SigningKey.js.map +1 -0
- package/dist/internal/TempoAccount.d.ts +181 -0
- package/dist/internal/TempoAccount.d.ts.map +1 -0
- package/dist/internal/TempoAccount.js +369 -0
- package/dist/internal/TempoAccount.js.map +1 -0
- package/dist/internal/TestInbox.d.ts +42 -0
- package/dist/internal/TestInbox.d.ts.map +1 -0
- package/dist/internal/TestInbox.js +20 -0
- package/dist/internal/TestInbox.js.map +1 -0
- package/dist/internal/Timing.d.ts +52 -0
- package/dist/internal/Timing.d.ts.map +1 -0
- package/dist/internal/Timing.js +116 -0
- package/dist/internal/Timing.js.map +1 -0
- package/dist/internal/Validation.d.ts +52 -0
- package/dist/internal/Validation.d.ts.map +1 -0
- package/dist/internal/Validation.js +22 -0
- package/dist/internal/Validation.js.map +1 -0
- package/dist/internal/Verification.d.ts +24 -0
- package/dist/internal/Verification.d.ts.map +1 -0
- package/dist/internal/Verification.js +37 -0
- package/dist/internal/Verification.js.map +1 -0
- package/dist/internal/Wallet.d.ts +66 -0
- package/dist/internal/Wallet.d.ts.map +1 -0
- package/dist/internal/Wallet.js +59 -0
- package/dist/internal/Wallet.js.map +1 -0
- package/dist/internal/owners/Passkey.d.ts +9 -0
- package/dist/internal/owners/Passkey.d.ts.map +1 -0
- package/dist/internal/owners/Passkey.js +58 -0
- package/dist/internal/owners/Passkey.js.map +1 -0
- package/dist/internal/owners/Privy.d.ts +13 -0
- package/dist/internal/owners/Privy.d.ts.map +1 -0
- package/dist/internal/owners/Privy.js +59 -0
- package/dist/internal/owners/Privy.js.map +1 -0
- package/dist/internal/owners/Secp256k1.d.ts +12 -0
- package/dist/internal/owners/Secp256k1.d.ts.map +1 -0
- package/dist/internal/owners/Secp256k1.js +99 -0
- package/dist/internal/owners/Secp256k1.js.map +1 -0
- package/dist/worker.d.ts +18 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +18 -0
- package/dist/worker.js.map +1 -0
- package/package.json +83 -3
- package/src/Api.ts +157 -0
- package/src/App.ts +316 -0
- package/src/api/account.ts +2266 -0
- package/src/api/authenticate.ts +1662 -0
- package/src/api/authorize.ts +1086 -0
- package/src/api/device.ts +516 -0
- package/src/api/health.ts +72 -0
- package/src/api/lib/AccountStep.ts +485 -0
- package/src/api/lib/Auth.ts +164 -0
- package/src/api/lib/Configure.ts +921 -0
- package/src/api/lib/Consent.ts +388 -0
- package/src/api/lib/Cors.ts +160 -0
- package/src/api/lib/Database.ts +10 -0
- package/src/api/lib/DeviceRequest.ts +244 -0
- package/src/api/lib/FirstAccount.ts +136 -0
- package/src/api/lib/Headless.ts +616 -0
- package/src/api/lib/IsolateCache.ts +28 -0
- package/src/api/lib/KeySigner.ts +690 -0
- package/src/api/lib/PasskeyLink.ts +162 -0
- package/src/api/lib/Session.ts +64 -0
- package/src/api/lib/SignInConsent.ts +393 -0
- package/src/api/oauth.ts +1796 -0
- package/src/app/components/CodeInput.tsx +153 -0
- package/src/app/components/DevTools.tsx +1071 -0
- package/src/app/components/Grant.tsx +476 -0
- package/src/app/components/icons/BRLA.png +0 -0
- package/src/app/components/icons/BRZ.svg +1 -0
- package/src/app/components/icons/CHFAU.png +0 -0
- package/src/app/components/icons/DLUSD.png +0 -0
- package/src/app/components/icons/EURAU.svg +1 -0
- package/src/app/components/icons/EURC.e.svg +1 -0
- package/src/app/components/icons/GBPA.svg +4 -0
- package/src/app/components/icons/GUSD.svg +1 -0
- package/src/app/components/icons/MACH.svg +4 -0
- package/src/app/components/icons/OUSD.svg +5 -0
- package/src/app/components/icons/PRIME.svg +14 -0
- package/src/app/components/icons/SBC.svg +1 -0
- package/src/app/components/icons/SEKAU.svg +5 -0
- package/src/app/components/icons/USD1.svg +30 -0
- package/src/app/components/icons/USDB.svg +11 -0
- package/src/app/components/icons/USDC.e.svg +1 -0
- package/src/app/components/icons/USDT0.svg +1 -0
- package/src/app/components/icons/USDY.svg +16 -0
- package/src/app/components/icons/USDe.svg +1 -0
- package/src/app/components/icons/YLDS.svg +3 -0
- package/src/app/components/icons/alphaUSD.svg +1 -0
- package/src/app/components/icons/betaUSD.svg +1 -0
- package/src/app/components/icons/cUSD.svg +1 -0
- package/src/app/components/icons/cbBTC.svg +1 -0
- package/src/app/components/icons/frxUSD.svg +1 -0
- package/src/app/components/icons/goUSD.svg +5 -0
- package/src/app/components/icons/iUSD.svg +1 -0
- package/src/app/components/icons/pathUSD.svg +25 -0
- package/src/app/components/icons/rUSD.svg +1 -0
- package/src/app/components/icons/reUSD.svg +1 -0
- package/src/app/components/icons/sUSDe.svg +1 -0
- package/src/app/components/icons/siUSD.svg +1 -0
- package/src/app/components/icons/stcUSD.svg +1 -0
- package/src/app/components/icons/syrupUSDC.svg +5 -0
- package/src/app/components/icons/thetaUSD.svg +1 -0
- package/src/app/components/icons/wYLDS.svg +10 -0
- package/src/app/components/icons/wsrUSD.svg +1 -0
- package/src/app/components/logos/OUSD.dark.svg +17 -0
- package/src/app/components/logos/OUSD.light.svg +17 -0
- package/src/app/coverage.server.ts +73 -0
- package/src/app/coverage.ts +69 -0
- package/src/app/crons/cleanup.ts +104 -0
- package/src/app/db/auth.ts +440 -0
- package/src/app/db/migrations/0000_better_auth.sql +53 -0
- package/src/app/db/migrations/0001_oauth_provider.sql +162 -0
- package/src/app/db/migrations/0002_device_code.sql +17 -0
- package/src/app/db/migrations/0003_realms.sql +27 -0
- package/src/app/db/migrations/0004_default_realm.sql +2 -0
- package/src/app/db/migrations/0005_passkeys.sql +30 -0
- package/src/app/db/migrations/0006_first_accounts.sql +9 -0
- package/src/app/db/migrations/0007_pending_keys.sql +13 -0
- package/src/app/db/migrations/0008_secp256k1_owners.sql +18 -0
- package/src/app/db/migrations/0009_device_requests.sql +8 -0
- package/src/app/db/migrations/0010_device_keys.sql +4 -0
- package/src/app/db/migrations/0011_registered_passkeys.sql +2 -0
- package/src/app/db/migrations/0012_passkey_accounts.sql +7 -0
- package/src/app/db/migrations/0013_first_party_apps.sql +4 -0
- package/src/app/db/migrations/0014_logout_revokes_tokens.sql +17 -0
- package/src/app/db/migrations/0015_expiry_indexes.sql +9 -0
- package/src/app/db/migrations/0016_configurable_accounts.sql +15 -0
- package/src/app/db/migrations/0017_account_config_keys.sql +3 -0
- package/src/app/db/migrations/0018_better_auth_1_8.sql +9 -0
- package/src/app/db/migrations/0019_drop_provider_account_index.sql +1 -0
- package/src/app/db/migrations/meta/0000_snapshot.json +362 -0
- package/src/app/db/migrations/meta/0001_snapshot.json +1487 -0
- package/src/app/db/migrations/meta/0002_snapshot.json +1603 -0
- package/src/app/db/migrations/meta/0003_snapshot.json +1760 -0
- package/src/app/db/migrations/meta/0004_snapshot.json +1760 -0
- package/src/app/db/migrations/meta/0005_snapshot.json +1911 -0
- package/src/app/db/migrations/meta/0006_snapshot.json +1967 -0
- package/src/app/db/migrations/meta/0007_snapshot.json +2047 -0
- package/src/app/db/migrations/meta/0008_snapshot.json +2157 -0
- package/src/app/db/migrations/meta/0009_snapshot.json +2207 -0
- package/src/app/db/migrations/meta/0010_snapshot.json +2231 -0
- package/src/app/db/migrations/meta/0011_snapshot.json +2231 -0
- package/src/app/db/migrations/meta/0012_snapshot.json +2231 -0
- package/src/app/db/migrations/meta/0013_snapshot.json +2231 -0
- package/src/app/db/migrations/meta/0014_snapshot.json +2231 -0
- package/src/app/db/migrations/meta/0015_snapshot.json +2375 -0
- package/src/app/db/migrations/meta/0016_snapshot.json +2475 -0
- package/src/app/db/migrations/meta/0017_snapshot.json +2481 -0
- package/src/app/db/migrations/meta/0018_snapshot.json +2535 -0
- package/src/app/db/migrations/meta/0019_snapshot.json +2514 -0
- package/src/app/db/migrations/meta/_journal.json +146 -0
- package/src/app/db/schema.ts +241 -0
- package/src/app/db/table.ts +31 -0
- package/src/app/env.ts +57 -0
- package/src/app/inbox.ts +190 -0
- package/src/app/lib/Client.ts +82 -0
- package/src/app/lib/Inbox.ts +11 -0
- package/src/app/lib/Loader.ts +21 -0
- package/src/app/lib/Upgrade.ts +221 -0
- package/src/app/package.json +8 -0
- package/src/app/public/_headers +4 -0
- package/src/app/routeTree.gen.ts +219 -0
- package/src/app/router.tsx +47 -0
- package/src/app/routes/[_].flows.tsx +4254 -0
- package/src/app/routes/__root.tsx +172 -0
- package/src/app/routes/authorize.tsx +379 -0
- package/src/app/routes/choose-account.tsx +144 -0
- package/src/app/routes/deposit.tsx +107 -0
- package/src/app/routes/device.tsx +273 -0
- package/src/app/routes/error.tsx +62 -0
- package/src/app/routes/index.tsx +511 -0
- package/src/app/routes/login.tsx +577 -0
- package/src/app/screens/Account.tsx +559 -0
- package/src/app/screens/Approval.tsx +444 -0
- package/src/app/screens/ChooseAccount.tsx +206 -0
- package/src/app/screens/Deposit.tsx +523 -0
- package/src/app/screens/DeviceApproval.tsx +500 -0
- package/src/app/screens/NotFound.tsx +62 -0
- package/src/app/screens/SignIn.tsx +372 -0
- package/src/app/screens/WalletUpgrade.tsx +121 -0
- package/src/app/screens/chains/base.svg +3 -0
- package/src/app/screens/chains/ethereum.svg +11 -0
- package/src/app/screens/chains/solana.svg +4 -0
- package/src/app/screens/chains/tempo.svg +4 -0
- package/src/app/server.ts +342 -0
- package/src/app/vite.config.ts +128 -0
- package/src/app/void.config.ts +36 -0
- package/src/app/wallet.ts +69 -0
- package/src/app/zyzz.config.ts +52 -0
- package/src/cli/Admin.ts +74 -0
- package/src/cli/Apps.ts +27 -0
- package/src/cli/Cli.ts +41 -0
- package/src/cli/Project.ts +119 -0
- package/src/cli/Realms.ts +49 -0
- package/src/cli/Session.ts +290 -0
- package/src/cli/User.ts +40 -0
- package/src/cli/V1.ts +54 -0
- package/src/cli/bin.ts +44 -0
- package/src/cli/commands/admin/apps/create.ts +96 -0
- package/src/cli/commands/admin/apps/delete.ts +38 -0
- package/src/cli/commands/admin/apps/index.ts +15 -0
- package/src/cli/commands/admin/apps/list.ts +25 -0
- package/src/cli/commands/admin/apps/rotate-secret.ts +47 -0
- package/src/cli/commands/admin/apps/update.ts +79 -0
- package/src/cli/commands/admin/index.ts +14 -0
- package/src/cli/commands/admin/migrate.ts +19 -0
- package/src/cli/commands/admin/realms/add.ts +40 -0
- package/src/cli/commands/admin/realms/index.ts +14 -0
- package/src/cli/commands/admin/realms/list.ts +38 -0
- package/src/cli/commands/admin/realms/passkeys/index.ts +15 -0
- package/src/cli/commands/admin/realms/passkeys/set.ts +44 -0
- package/src/cli/commands/admin/realms/privy/index.ts +14 -0
- package/src/cli/commands/admin/realms/privy/set.ts +69 -0
- package/src/cli/commands/admin/realms/remove.ts +33 -0
- package/src/cli/commands/admin/realms/secp256k1/disable.ts +40 -0
- package/src/cli/commands/admin/realms/secp256k1/enable.ts +40 -0
- package/src/cli/commands/admin/realms/secp256k1/index.ts +19 -0
- package/src/cli/commands/admin/realms/set.ts +46 -0
- package/src/cli/commands/admin/rotate-key.ts +47 -0
- package/src/cli/commands/init.ts +42 -0
- package/src/cli/commands/login.ts +32 -0
- package/src/cli/commands/logout.ts +19 -0
- package/src/cli/commands/whoami.ts +23 -0
- package/src/client/Actions.ts +180 -0
- package/src/client/Auth.ts +267 -0
- package/src/client/Authorization.ts +34 -0
- package/src/client/Decorator.ts +444 -0
- package/src/client/Key.ts +137 -0
- package/src/client/Pending.ts +45 -0
- package/src/client/Permissions.ts +41 -0
- package/src/client/Storage.ts +29 -0
- package/src/client/TempoAccount.ts +55 -0
- package/src/client/Tokens.ts +27 -0
- package/src/client/User.ts +23 -0
- package/src/client/actions/account.ts +171 -0
- package/src/client/actions/device.ts +166 -0
- package/src/client/actions/email.ts +164 -0
- package/src/client/actions/oauth.ts +179 -0
- package/src/client/actions/passkey.ts +333 -0
- package/src/client/actions/realm.ts +37 -0
- package/src/client/actions/redirect.ts +502 -0
- package/src/client/internal/accessKey.ts +54 -0
- package/src/client/internal/account.ts +18 -0
- package/src/client/internal/api.ts +242 -0
- package/src/client/internal/headless.ts +308 -0
- package/src/client/internal/indexedDb.ts +44 -0
- package/src/client/internal/request.ts +70 -0
- package/src/client/internal/store.ts +20 -0
- package/src/client/internal/verifier.ts +75 -0
- package/src/client.ts +17 -0
- package/src/index.ts +5 -0
- package/src/internal/Apps.ts +1283 -0
- package/src/internal/Auth.ts +273 -0
- package/src/internal/Authorize.ts +433 -0
- package/src/internal/Balance.ts +144 -0
- package/src/internal/BodyLimit.ts +31 -0
- package/src/internal/Cache.ts +119 -0
- package/src/internal/Chain.ts +99 -0
- package/src/internal/ChainList.ts +270 -0
- package/src/internal/ChainName.ts +14 -0
- package/src/internal/ChainStatus.ts +65 -0
- package/src/internal/CodeEmail.ts +138 -0
- package/src/internal/ConfigurableAccount.ts +1315 -0
- package/src/internal/Device.ts +471 -0
- package/src/internal/Env.ts +92 -0
- package/src/internal/ErrorResponse.ts +896 -0
- package/src/internal/Faucet.ts +43 -0
- package/src/internal/Id.ts +41 -0
- package/src/internal/KeyAuthorization.ts +1536 -0
- package/src/internal/Limits.ts +40 -0
- package/src/internal/Log.ts +166 -0
- package/src/internal/LogLine.ts +81 -0
- package/src/internal/Logout.ts +356 -0
- package/src/internal/Migrations.ts +120 -0
- package/src/internal/OpenApi.ts +300 -0
- package/src/internal/Owner.ts +310 -0
- package/src/internal/Passkey.ts +1904 -0
- package/src/internal/PendingKey.ts +104 -0
- package/src/internal/Privy.ts +536 -0
- package/src/internal/RateLimit.ts +346 -0
- package/src/internal/Realm.ts +814 -0
- package/src/internal/RequestId.ts +118 -0
- package/src/internal/SignInGrant.ts +175 -0
- package/src/internal/SigningKey.ts +319 -0
- package/src/internal/TempoAccount.ts +545 -0
- package/src/internal/TestInbox.ts +51 -0
- package/src/internal/Timing.ts +172 -0
- package/src/internal/Validation.ts +52 -0
- package/src/internal/Verification.ts +58 -0
- package/src/internal/Wallet.ts +101 -0
- package/src/internal/owners/Passkey.ts +76 -0
- package/src/internal/owners/Privy.ts +82 -0
- package/src/internal/owners/Secp256k1.ts +139 -0
- package/src/types/app.d.ts +39 -0
- package/src/worker.ts +17 -0
|
@@ -0,0 +1,4254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The flows canvas: each flow's entry point, sample screens, requests, and exit. It pans on scroll,
|
|
3
|
+
* and zooms with a pinch, keys, or its controls. @module
|
|
4
|
+
*/
|
|
5
|
+
/* istanbul ignore file -- With `ssr: false`, it renders only in a browser. */
|
|
6
|
+
import { createFileRoute, Link } from '@tanstack/react-router'
|
|
7
|
+
import { Accordion, Button, Tooltip } from '@tempoxyz/ds'
|
|
8
|
+
import { ChevronRight, ZoomIn, ZoomOut } from '@tempoxyz/ds/icons'
|
|
9
|
+
import {
|
|
10
|
+
type FormEvent,
|
|
11
|
+
type ReactElement,
|
|
12
|
+
type ReactNode,
|
|
13
|
+
useCallback,
|
|
14
|
+
useEffect,
|
|
15
|
+
useLayoutEffect,
|
|
16
|
+
useMemo,
|
|
17
|
+
useRef,
|
|
18
|
+
useState,
|
|
19
|
+
} from 'react'
|
|
20
|
+
import { CodeInput } from '../components/CodeInput.js'
|
|
21
|
+
import { Account } from '../screens/Account.js'
|
|
22
|
+
import { Approval, InvalidRequest } from '../screens/Approval.js'
|
|
23
|
+
import { ChooseAccount } from '../screens/ChooseAccount.js'
|
|
24
|
+
import { Deposit } from '../screens/Deposit.js'
|
|
25
|
+
import { DeviceApproval } from '../screens/DeviceApproval.js'
|
|
26
|
+
import { NotFound } from '../screens/NotFound.js'
|
|
27
|
+
import { SignIn } from '../screens/SignIn.js'
|
|
28
|
+
import { WalletUpgrade } from '../screens/WalletUpgrade.js'
|
|
29
|
+
import { style, variants, vars } from '../zyzz.config.js'
|
|
30
|
+
|
|
31
|
+
/** An entry point, a Tempo ID screen, or an external step. */
|
|
32
|
+
type Node = {
|
|
33
|
+
/** A secondary line, such as the entry request. */
|
|
34
|
+
detail?: string | undefined
|
|
35
|
+
/** The request or event on the incoming arrow. Absent on the first node. */
|
|
36
|
+
edge?: string | undefined
|
|
37
|
+
/** The name of the screen or step. */
|
|
38
|
+
name: string
|
|
39
|
+
/** `app` for an app's own screen. Absent for Tempo ID. */
|
|
40
|
+
owner?: 'app' | undefined
|
|
41
|
+
/** Absent for an external step. */
|
|
42
|
+
screen?: ReactNode | undefined
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** One path from an entry point to an exit. */
|
|
46
|
+
type Flow = {
|
|
47
|
+
/** What the flow does, with code spans in backticks. */
|
|
48
|
+
description: string
|
|
49
|
+
/** The entry point, the steps, and the exit, in order. */
|
|
50
|
+
nodes: readonly Node[]
|
|
51
|
+
/** The flow's name. */
|
|
52
|
+
title: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** A canvas point that stays under a viewport point during a zoom. */
|
|
56
|
+
type Anchor = {
|
|
57
|
+
/** Unzoomed pixels from the canvas's top left. */
|
|
58
|
+
canvas: { x: number; y: number }
|
|
59
|
+
/** Pixels from the viewport's top left. */
|
|
60
|
+
viewport: { x: number; y: number }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A titled group of flows. */
|
|
64
|
+
type Section = {
|
|
65
|
+
/** In reading order. */
|
|
66
|
+
flows: readonly Flow[]
|
|
67
|
+
/** The group's name. */
|
|
68
|
+
title: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const Route = createFileRoute('/_/flows')({
|
|
72
|
+
component: Page,
|
|
73
|
+
// The canvas renders in the browser alone, since its sample balances format in the browser's
|
|
74
|
+
// locale and its sample expiries read the clock.
|
|
75
|
+
ssr: false,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
/** The zoom limits, and the factor of one step. */
|
|
79
|
+
const zoom = { max: 2, min: 0.25, step: 1.25 } as const
|
|
80
|
+
|
|
81
|
+
/** Renders the canvas. */
|
|
82
|
+
function Page(): ReactNode {
|
|
83
|
+
// The screens stay the same elements between zoom renders, so React skips them.
|
|
84
|
+
const all = useMemo(sections, [])
|
|
85
|
+
const [scale, setScale] = useState(1)
|
|
86
|
+
// The wheel and gesture listeners read the scale between renders, before the state updates.
|
|
87
|
+
const current = useRef(1)
|
|
88
|
+
const anchor = useRef<Anchor | undefined>(undefined)
|
|
89
|
+
const main = useRef<HTMLElement>(null)
|
|
90
|
+
const probe = useRef<HTMLDivElement>(null)
|
|
91
|
+
|
|
92
|
+
// The zoomed content starts below the canvas's top space, which the zoom does not scale. Engines
|
|
93
|
+
// with the legacy `zoom` geometry divide a zoomed element's rect by its zoom, so the origin comes
|
|
94
|
+
// from the canvas, outside the zoom.
|
|
95
|
+
const origin = () => {
|
|
96
|
+
if (!main.current) return window.scrollY
|
|
97
|
+
return (
|
|
98
|
+
main.current.getBoundingClientRect().top +
|
|
99
|
+
Number.parseFloat(getComputedStyle(main.current).paddingTop) +
|
|
100
|
+
window.scrollY
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Returns the rect pixels per CSS pixel inside the zoom: the scale in engines with the standard
|
|
105
|
+
// `zoom` geometry, and 1 in engines with the legacy one.
|
|
106
|
+
const unit = useCallback(
|
|
107
|
+
() => (probe.current?.getBoundingClientRect().height ?? 100) / 100,
|
|
108
|
+
[],
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
// Sets the zoom, and keeps the canvas point under `viewport` in place, or the viewport's center.
|
|
112
|
+
const zoomTo = (next: number, viewport?: Anchor['viewport']) => {
|
|
113
|
+
const clamped = Math.min(zoom.max, Math.max(zoom.min, next))
|
|
114
|
+
// An unchanged scale renders nothing, so the layout effect would never spend the anchor.
|
|
115
|
+
if (clamped === current.current) return
|
|
116
|
+
|
|
117
|
+
const point = viewport ?? {
|
|
118
|
+
x: window.innerWidth / 2,
|
|
119
|
+
y: window.innerHeight / 2,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// The scroll position updates only after the render, so a pending anchor keeps its canvas point.
|
|
123
|
+
anchor.current = {
|
|
124
|
+
canvas: anchor.current?.canvas ?? {
|
|
125
|
+
x: (window.scrollX + point.x) / current.current,
|
|
126
|
+
y: (window.scrollY + point.y - origin()) / current.current,
|
|
127
|
+
},
|
|
128
|
+
viewport: point,
|
|
129
|
+
}
|
|
130
|
+
current.current = clamped
|
|
131
|
+
setScale(clamped)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
useLayoutEffect(() => {
|
|
135
|
+
const pending = anchor.current
|
|
136
|
+
if (!pending) return
|
|
137
|
+
|
|
138
|
+
anchor.current = undefined
|
|
139
|
+
window.scrollTo(
|
|
140
|
+
pending.canvas.x * scale - pending.viewport.x,
|
|
141
|
+
pending.canvas.y * scale + origin() - pending.viewport.y,
|
|
142
|
+
)
|
|
143
|
+
}, [scale])
|
|
144
|
+
|
|
145
|
+
// A plain wheel scrolls, so it pans. A pinch on a trackpad reaches the page as a wheel with
|
|
146
|
+
// `ctrlKey`, and Safari's as gesture events, which would otherwise zoom the whole page.
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
const onWheel = (event: WheelEvent) => {
|
|
149
|
+
if (!event.ctrlKey && !event.metaKey) return
|
|
150
|
+
|
|
151
|
+
event.preventDefault()
|
|
152
|
+
const delta = (() => {
|
|
153
|
+
if (event.deltaMode === WheelEvent.DOM_DELTA_LINE)
|
|
154
|
+
return event.deltaY * 16
|
|
155
|
+
if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE)
|
|
156
|
+
return event.deltaY * window.innerHeight
|
|
157
|
+
return event.deltaY
|
|
158
|
+
})()
|
|
159
|
+
const bounded = Math.min(50, Math.max(-50, delta))
|
|
160
|
+
zoomTo(current.current * Math.exp(-bounded / 200), {
|
|
161
|
+
x: event.clientX,
|
|
162
|
+
y: event.clientY,
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let gestureStart = 1
|
|
167
|
+
const onGestureStart = (event: Event) => {
|
|
168
|
+
event.preventDefault()
|
|
169
|
+
gestureStart = current.current
|
|
170
|
+
}
|
|
171
|
+
// WebKit's `GestureEvent` is not in the DOM types. iOS leaves out the pointer's coordinates.
|
|
172
|
+
type Gesture = Event & {
|
|
173
|
+
clientX?: number | undefined
|
|
174
|
+
clientY?: number | undefined
|
|
175
|
+
scale: number
|
|
176
|
+
}
|
|
177
|
+
const onGestureChange = (event: Event) => {
|
|
178
|
+
event.preventDefault()
|
|
179
|
+
const gesture = event as Gesture
|
|
180
|
+
// Without the pointer, the zoom keeps the viewport's center in place.
|
|
181
|
+
zoomTo(
|
|
182
|
+
gestureStart * gesture.scale,
|
|
183
|
+
typeof gesture.clientX === 'number' &&
|
|
184
|
+
typeof gesture.clientY === 'number'
|
|
185
|
+
? { x: gesture.clientX, y: gesture.clientY }
|
|
186
|
+
: undefined,
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
191
|
+
if (event.altKey || event.ctrlKey || event.metaKey) return
|
|
192
|
+
if (
|
|
193
|
+
event.target instanceof HTMLElement &&
|
|
194
|
+
(event.target.isContentEditable ||
|
|
195
|
+
event.target.closest('input, select, textarea'))
|
|
196
|
+
)
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
if (event.key === '+' || event.key === '=')
|
|
200
|
+
zoomTo(current.current * zoom.step)
|
|
201
|
+
else if (event.key === '-') zoomTo(current.current / zoom.step)
|
|
202
|
+
else if (event.key === '0') zoomTo(1)
|
|
203
|
+
else return
|
|
204
|
+
event.preventDefault()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
window.addEventListener('wheel', onWheel, { passive: false })
|
|
208
|
+
window.addEventListener('gesturestart', onGestureStart)
|
|
209
|
+
window.addEventListener('gesturechange', onGestureChange)
|
|
210
|
+
window.addEventListener('keydown', onKeyDown)
|
|
211
|
+
return () => {
|
|
212
|
+
window.removeEventListener('wheel', onWheel)
|
|
213
|
+
window.removeEventListener('gesturestart', onGestureStart)
|
|
214
|
+
window.removeEventListener('gesturechange', onGestureChange)
|
|
215
|
+
window.removeEventListener('keydown', onKeyDown)
|
|
216
|
+
}
|
|
217
|
+
}, [])
|
|
218
|
+
|
|
219
|
+
// The canvas renders after load, so the browser finds no target for a link's fragment then. Each
|
|
220
|
+
// identifier is a slug, so the fragment needs no decoding.
|
|
221
|
+
useEffect(() => {
|
|
222
|
+
if (window.location.hash) reveal({ id: window.location.hash.slice(1) })
|
|
223
|
+
}, [])
|
|
224
|
+
|
|
225
|
+
// The content stays one element across zoom renders, so a zoom renders only the controls.
|
|
226
|
+
const canvas = useMemo(
|
|
227
|
+
() => <Content sections={all} unit={unit} />,
|
|
228
|
+
[all, unit],
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
const percent = Math.round(scale * 100)
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<main ref={main} {...styles.canvas()}>
|
|
235
|
+
<div {...styles.panels()}>
|
|
236
|
+
<div aria-label="Zoom" role="toolbar" {...styles.zoom()}>
|
|
237
|
+
<ZoomTip label="Zoom out">
|
|
238
|
+
<Button
|
|
239
|
+
aria-label="Zoom out"
|
|
240
|
+
disabled={scale <= zoom.min}
|
|
241
|
+
onClick={() => zoomTo(scale / zoom.step)}
|
|
242
|
+
shape="icon"
|
|
243
|
+
size="small"
|
|
244
|
+
variant="text"
|
|
245
|
+
>
|
|
246
|
+
<ZoomOut {...styles.zoomIcon()} />
|
|
247
|
+
</Button>
|
|
248
|
+
</ZoomTip>
|
|
249
|
+
<ZoomTip label="Reset to 100%">
|
|
250
|
+
<Button
|
|
251
|
+
aria-label={`Zoom ${percent}%. Reset to 100%`}
|
|
252
|
+
onClick={() => zoomTo(1)}
|
|
253
|
+
size="small"
|
|
254
|
+
variant="text"
|
|
255
|
+
{...styles.zoomLevel()}
|
|
256
|
+
>
|
|
257
|
+
{percent}%
|
|
258
|
+
</Button>
|
|
259
|
+
</ZoomTip>
|
|
260
|
+
<ZoomTip label="Zoom in">
|
|
261
|
+
<Button
|
|
262
|
+
aria-label="Zoom in"
|
|
263
|
+
disabled={scale >= zoom.max}
|
|
264
|
+
onClick={() => zoomTo(scale * zoom.step)}
|
|
265
|
+
shape="icon"
|
|
266
|
+
size="small"
|
|
267
|
+
variant="text"
|
|
268
|
+
>
|
|
269
|
+
<ZoomIn {...styles.zoomIcon()} />
|
|
270
|
+
</Button>
|
|
271
|
+
</ZoomTip>
|
|
272
|
+
<span aria-live="polite" role="status" {...styles.hidden()}>
|
|
273
|
+
{`Zoom ${percent}%`}
|
|
274
|
+
</span>
|
|
275
|
+
</div>
|
|
276
|
+
<Legend sections={all} />
|
|
277
|
+
</div>
|
|
278
|
+
{/* `zoom` scales the layout too, so the page's scroll size follows the zoom and scrolling pans. */}
|
|
279
|
+
<div {...styles.world({ style: { zoom: scale } })}>
|
|
280
|
+
<div aria-hidden ref={probe} {...styles.probe()} />
|
|
281
|
+
{canvas}
|
|
282
|
+
</div>
|
|
283
|
+
</main>
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Renders the introduction, the groups, and their flows. */
|
|
288
|
+
function Content(props: Content.Props): ReactNode {
|
|
289
|
+
return (
|
|
290
|
+
<>
|
|
291
|
+
<header {...styles.intro()}>
|
|
292
|
+
<h1 {...styles.title()}>Flows</h1>
|
|
293
|
+
<p {...styles.lede()}>
|
|
294
|
+
Every path through Tempo ID, from the entry point on the left to the
|
|
295
|
+
exit on the right. Each arrow names the request or event that leads to
|
|
296
|
+
the next step. Scroll to pan. To zoom, pinch, or scroll with Control
|
|
297
|
+
or Command held.
|
|
298
|
+
</p>
|
|
299
|
+
</header>
|
|
300
|
+
{props.sections.map((section) => (
|
|
301
|
+
<section {...styles.section()} key={section.title}>
|
|
302
|
+
<h2 {...styles.sectionTitle()} id={fragment.section(section)}>
|
|
303
|
+
{section.title}
|
|
304
|
+
</h2>
|
|
305
|
+
{section.flows.map((flow) => (
|
|
306
|
+
<article
|
|
307
|
+
{...styles.flow()}
|
|
308
|
+
id={fragment.flow(section, flow)}
|
|
309
|
+
key={flow.title}
|
|
310
|
+
>
|
|
311
|
+
<header {...styles.flowHeader()}>
|
|
312
|
+
<h3 {...styles.flowTitle()}>{flow.title}</h3>
|
|
313
|
+
<p {...styles.flowDescription()}>
|
|
314
|
+
<Description text={flow.description} />
|
|
315
|
+
</p>
|
|
316
|
+
</header>
|
|
317
|
+
<Row flow={flow} unit={props.unit} />
|
|
318
|
+
</article>
|
|
319
|
+
))}
|
|
320
|
+
</section>
|
|
321
|
+
))}
|
|
322
|
+
</>
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
declare namespace Content {
|
|
327
|
+
type Props = {
|
|
328
|
+
/** In canvas order. */
|
|
329
|
+
sections: readonly Section[]
|
|
330
|
+
/** Returns the rect pixels per CSS pixel inside the zoom. */
|
|
331
|
+
unit: () => number
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Estimated row heights before a render, in unzoomed pixels. */
|
|
336
|
+
const estimate = { external: 128, screen: 480 }
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Renders a flow's row near the viewport, or a box of its last size, so a zoom lays out few
|
|
340
|
+
* screens.
|
|
341
|
+
*/
|
|
342
|
+
function Row(props: Row.Props): ReactNode {
|
|
343
|
+
const box = useRef<HTMLDivElement>(null)
|
|
344
|
+
const [near, setNear] = useState(false)
|
|
345
|
+
const [size, setSize] = useState(() => ({
|
|
346
|
+
height: props.flow.nodes.some((node) => node.screen !== undefined)
|
|
347
|
+
? estimate.screen
|
|
348
|
+
: estimate.external,
|
|
349
|
+
width: props.flow.nodes.reduce(
|
|
350
|
+
(width, node, index) =>
|
|
351
|
+
width + (node.screen === undefined ? 224 : 384) + (index > 0 ? 192 : 0),
|
|
352
|
+
0,
|
|
353
|
+
),
|
|
354
|
+
}))
|
|
355
|
+
|
|
356
|
+
useEffect(() => {
|
|
357
|
+
const element = box.current
|
|
358
|
+
if (!element) return
|
|
359
|
+
|
|
360
|
+
// The margin of one viewport on each side renders a row before it scrolls into view.
|
|
361
|
+
const observer = new IntersectionObserver(
|
|
362
|
+
(entries) => {
|
|
363
|
+
const entry = entries.at(-1)
|
|
364
|
+
if (!entry) return
|
|
365
|
+
|
|
366
|
+
// A row records its size before it leaves, so its box keeps the canvas's layout. The rect
|
|
367
|
+
// and the unit come from the same geometry, so the size is in CSS pixels in either one.
|
|
368
|
+
if (
|
|
369
|
+
!entry.isIntersecting &&
|
|
370
|
+
element.firstElementChild instanceof HTMLOListElement
|
|
371
|
+
) {
|
|
372
|
+
const rect = element.getBoundingClientRect()
|
|
373
|
+
setSize({
|
|
374
|
+
height: rect.height / props.unit(),
|
|
375
|
+
width: rect.width / props.unit(),
|
|
376
|
+
})
|
|
377
|
+
}
|
|
378
|
+
setNear(entry.isIntersecting)
|
|
379
|
+
},
|
|
380
|
+
{ rootMargin: '100%' },
|
|
381
|
+
)
|
|
382
|
+
observer.observe(element)
|
|
383
|
+
return () => observer.disconnect()
|
|
384
|
+
}, [props.unit])
|
|
385
|
+
|
|
386
|
+
return (
|
|
387
|
+
<div ref={box} {...styles.rowBox()}>
|
|
388
|
+
{near ? (
|
|
389
|
+
<ol {...styles.row()}>
|
|
390
|
+
{props.flow.nodes.map((node, index) => (
|
|
391
|
+
<li {...styles.step()} key={`${index}:${node.name}`}>
|
|
392
|
+
{index > 0 ? <Arrow label={node.edge} /> : null}
|
|
393
|
+
{node.screen ? (
|
|
394
|
+
<figure {...styles.artboard()}>
|
|
395
|
+
<figcaption {...styles.frameLabel()}>
|
|
396
|
+
{node.owner === 'app' ? `App · ${node.name}` : node.name}
|
|
397
|
+
</figcaption>
|
|
398
|
+
{node.owner === 'app' ? (
|
|
399
|
+
<div {...vars({ set: 'thirdParty' })}>
|
|
400
|
+
<div {...styles.frame()}>{node.screen}</div>
|
|
401
|
+
</div>
|
|
402
|
+
) : (
|
|
403
|
+
<div {...styles.frame()}>{node.screen}</div>
|
|
404
|
+
)}
|
|
405
|
+
</figure>
|
|
406
|
+
) : (
|
|
407
|
+
<External
|
|
408
|
+
detail={node.detail}
|
|
409
|
+
kind={(() => {
|
|
410
|
+
if (index === 0) return 'Entry'
|
|
411
|
+
if (index === props.flow.nodes.length - 1) return 'Exit'
|
|
412
|
+
return 'External'
|
|
413
|
+
})()}
|
|
414
|
+
name={node.name}
|
|
415
|
+
/>
|
|
416
|
+
)}
|
|
417
|
+
</li>
|
|
418
|
+
))}
|
|
419
|
+
</ol>
|
|
420
|
+
) : (
|
|
421
|
+
<div
|
|
422
|
+
aria-hidden
|
|
423
|
+
{...styles.placeholder({
|
|
424
|
+
style: { height: size.height, width: size.width },
|
|
425
|
+
})}
|
|
426
|
+
/>
|
|
427
|
+
)}
|
|
428
|
+
</div>
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
declare namespace Row {
|
|
433
|
+
type Props = {
|
|
434
|
+
/** The flow that the row renders. */
|
|
435
|
+
flow: Flow
|
|
436
|
+
/** Returns the rect pixels per CSS pixel inside the zoom. */
|
|
437
|
+
unit: () => number
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/** Fragment identifiers of the groups and flows, from their titles. */
|
|
442
|
+
const fragment = {
|
|
443
|
+
/** Returns the identifier of `flow` under `section`, since flow names repeat across groups. */
|
|
444
|
+
flow(section: Section, flow: Flow) {
|
|
445
|
+
return `${fragment.section(section)}--${slug(flow.title)}`
|
|
446
|
+
},
|
|
447
|
+
/** Returns the identifier of `section`. */
|
|
448
|
+
section(section: Section) {
|
|
449
|
+
return slug(section.title)
|
|
450
|
+
},
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Scrolls the group or flow `id` to the top left, and aligns it for 10 frames while rows render.
|
|
455
|
+
*/
|
|
456
|
+
function reveal(options: reveal.Options) {
|
|
457
|
+
const target = document.getElementById(options.id)
|
|
458
|
+
if (!target) return
|
|
459
|
+
|
|
460
|
+
// The clearance is in viewport pixels, since a scroll margin inside the zoom would shrink with it.
|
|
461
|
+
// `scrollIntoView` places the target in either `zoom` geometry, so the scroll reads no zoomed rect.
|
|
462
|
+
const top = window.matchMedia('(min-width: 64rem)').matches ? 24 : 112
|
|
463
|
+
let settled = 0
|
|
464
|
+
const align = () => {
|
|
465
|
+
target.scrollIntoView({ block: 'start' })
|
|
466
|
+
window.scrollTo({ left: 0, top: window.scrollY - top })
|
|
467
|
+
settled = target.getBoundingClientRect().top
|
|
468
|
+
}
|
|
469
|
+
align()
|
|
470
|
+
|
|
471
|
+
// Each check compares the target's rect with its own rect after the last alignment, which holds in
|
|
472
|
+
// either geometry.
|
|
473
|
+
let frames = 10
|
|
474
|
+
const settle = () => {
|
|
475
|
+
if (Math.abs(target.getBoundingClientRect().top - settled) > 1) align()
|
|
476
|
+
frames -= 1
|
|
477
|
+
if (frames > 0) requestAnimationFrame(settle)
|
|
478
|
+
}
|
|
479
|
+
requestAnimationFrame(settle)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
declare namespace reveal {
|
|
483
|
+
type Options = {
|
|
484
|
+
/** The identifier of the group or flow. */
|
|
485
|
+
id: string
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** Returns `text` in lowercase, with each non-alphanumeric run as `-`. */
|
|
490
|
+
function slug(text: string) {
|
|
491
|
+
return text
|
|
492
|
+
.toLowerCase()
|
|
493
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
494
|
+
.replace(/^-|-$/g, '')
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/** Renders the collapsed contents list, with a link to each group and flow. */
|
|
498
|
+
function Legend(props: Legend.Props): ReactNode {
|
|
499
|
+
return (
|
|
500
|
+
<nav aria-label="Flows" {...styles.legend()}>
|
|
501
|
+
<Accordion.Root>
|
|
502
|
+
<Accordion.Item value="contents" {...styles.legendItem()}>
|
|
503
|
+
<Accordion.Trigger {...styles.legendTrigger()}>
|
|
504
|
+
Contents
|
|
505
|
+
</Accordion.Trigger>
|
|
506
|
+
<Accordion.Panel>
|
|
507
|
+
<ol {...styles.legendList()}>
|
|
508
|
+
{props.sections.map((section) => (
|
|
509
|
+
<li key={section.title}>
|
|
510
|
+
<Entry
|
|
511
|
+
{...styles.legendSection()}
|
|
512
|
+
id={fragment.section(section)}
|
|
513
|
+
>
|
|
514
|
+
{section.title}
|
|
515
|
+
</Entry>
|
|
516
|
+
<ol {...styles.legendFlows()}>
|
|
517
|
+
{section.flows.map((flow) => (
|
|
518
|
+
<li key={flow.title}>
|
|
519
|
+
<Entry
|
|
520
|
+
{...styles.legendFlow()}
|
|
521
|
+
id={fragment.flow(section, flow)}
|
|
522
|
+
>
|
|
523
|
+
{flow.title}
|
|
524
|
+
</Entry>
|
|
525
|
+
</li>
|
|
526
|
+
))}
|
|
527
|
+
</ol>
|
|
528
|
+
</li>
|
|
529
|
+
))}
|
|
530
|
+
</ol>
|
|
531
|
+
</Accordion.Panel>
|
|
532
|
+
</Accordion.Item>
|
|
533
|
+
</Accordion.Root>
|
|
534
|
+
</nav>
|
|
535
|
+
)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
declare namespace Legend {
|
|
539
|
+
type Props = {
|
|
540
|
+
/** In canvas order. */
|
|
541
|
+
sections: readonly Section[]
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/** Renders a link to the group or flow `id`, which {@link reveal} scrolls to. */
|
|
546
|
+
function Entry(props: Entry.Props): ReactNode {
|
|
547
|
+
return (
|
|
548
|
+
<Link
|
|
549
|
+
className={props.className}
|
|
550
|
+
hash={props.id}
|
|
551
|
+
hashScrollIntoView={false}
|
|
552
|
+
onClick={(event) => {
|
|
553
|
+
// A modified click opens the link in another tab or window, so this page keeps its scroll.
|
|
554
|
+
if (
|
|
555
|
+
event.button !== 0 ||
|
|
556
|
+
event.altKey ||
|
|
557
|
+
event.ctrlKey ||
|
|
558
|
+
event.metaKey ||
|
|
559
|
+
event.shiftKey
|
|
560
|
+
)
|
|
561
|
+
return
|
|
562
|
+
reveal({ id: props.id })
|
|
563
|
+
}}
|
|
564
|
+
replace
|
|
565
|
+
resetScroll={false}
|
|
566
|
+
to="/_/flows"
|
|
567
|
+
>
|
|
568
|
+
{props.children}
|
|
569
|
+
</Link>
|
|
570
|
+
)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
declare namespace Entry {
|
|
574
|
+
type Props = {
|
|
575
|
+
/** The link text. */
|
|
576
|
+
children: ReactNode
|
|
577
|
+
/** The link's class names. */
|
|
578
|
+
className?: string | undefined
|
|
579
|
+
/** The identifier of the group or flow. */
|
|
580
|
+
id: string
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/** Renders a tooltip below a zoom control. */
|
|
585
|
+
function ZoomTip(props: ZoomTip.Props): ReactNode {
|
|
586
|
+
return (
|
|
587
|
+
<Tooltip.Root>
|
|
588
|
+
<Tooltip.Trigger delay={300} render={props.children} />
|
|
589
|
+
<Tooltip.Popup side="bottom" sideOffset={8}>
|
|
590
|
+
{props.label}
|
|
591
|
+
</Tooltip.Popup>
|
|
592
|
+
</Tooltip.Root>
|
|
593
|
+
)
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
declare namespace ZoomTip {
|
|
597
|
+
type Props = {
|
|
598
|
+
/** The control, which receives the trigger's props. */
|
|
599
|
+
children: ReactElement
|
|
600
|
+
/** The tooltip text. */
|
|
601
|
+
label: string
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/** Renders `text` with each backtick span as code. */
|
|
606
|
+
function Description(props: Description.Props): ReactNode {
|
|
607
|
+
// Splitting on the backticks puts each code span at an odd index.
|
|
608
|
+
return props.text.split('`').map((part, index) =>
|
|
609
|
+
index % 2 ? (
|
|
610
|
+
<code {...styles.code()} key={index}>
|
|
611
|
+
{part}
|
|
612
|
+
</code>
|
|
613
|
+
) : (
|
|
614
|
+
part
|
|
615
|
+
),
|
|
616
|
+
)
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
declare namespace Description {
|
|
620
|
+
type Props = {
|
|
621
|
+
/** The description, with code spans in backticks. */
|
|
622
|
+
text: string
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/** A labeled arrow between two nodes. */
|
|
627
|
+
function Arrow(props: Arrow.Props): ReactNode {
|
|
628
|
+
return (
|
|
629
|
+
<span {...styles.arrow()} aria-label={props.label ?? 'Then'} role="img">
|
|
630
|
+
<span {...styles.edge()}>{props.label}</span>
|
|
631
|
+
<span {...styles.shaft()}>
|
|
632
|
+
<span {...styles.line()} />
|
|
633
|
+
<ChevronRight {...styles.head()} />
|
|
634
|
+
</span>
|
|
635
|
+
</span>
|
|
636
|
+
)
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
declare namespace Arrow {
|
|
640
|
+
type Props = {
|
|
641
|
+
/** The request or event. */
|
|
642
|
+
label?: string | undefined
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/** A step outside Tempo ID's pages, such as a provider. */
|
|
647
|
+
function External(props: External.Props): ReactNode {
|
|
648
|
+
return (
|
|
649
|
+
<div {...styles.external({ kind: props.kind })}>
|
|
650
|
+
<span {...styles.kind()}>{props.kind}</span>
|
|
651
|
+
<span {...styles.externalName()}>{props.name}</span>
|
|
652
|
+
{props.detail ? <code {...styles.detail()}>{props.detail}</code> : null}
|
|
653
|
+
</div>
|
|
654
|
+
)
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
declare namespace External {
|
|
658
|
+
type Props = {
|
|
659
|
+
/** A secondary line, such as the entry request. */
|
|
660
|
+
detail?: string | undefined
|
|
661
|
+
/** Where the step sits in its flow. */
|
|
662
|
+
kind: 'Entry' | 'Exit' | 'External'
|
|
663
|
+
/** The step's name. */
|
|
664
|
+
name: string
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/** A sample app's own screen, in the style of a food-delivery app. */
|
|
669
|
+
function AppScreen(props: AppScreen.Props): ReactNode {
|
|
670
|
+
return (
|
|
671
|
+
<div {...styles.app()}>
|
|
672
|
+
<header {...styles.appHeader()}>
|
|
673
|
+
<h4 {...styles.appTitle()}>{props.title}</h4>
|
|
674
|
+
{props.description ? (
|
|
675
|
+
<p {...styles.appDescription()}>{props.description}</p>
|
|
676
|
+
) : null}
|
|
677
|
+
</header>
|
|
678
|
+
{props.children}
|
|
679
|
+
</div>
|
|
680
|
+
)
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
declare namespace AppScreen {
|
|
684
|
+
type Props = {
|
|
685
|
+
/** The screen's controls. */
|
|
686
|
+
children?: ReactNode | undefined
|
|
687
|
+
/** The line below the title. */
|
|
688
|
+
description?: string | undefined
|
|
689
|
+
/** The screen's heading. */
|
|
690
|
+
title: string
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/** A pill button in the sample app's style. */
|
|
695
|
+
function AppButton(props: AppButton.Props): ReactNode {
|
|
696
|
+
return (
|
|
697
|
+
// oxlint-disable-next-line tds/prefer-component -- The sample app draws its own controls.
|
|
698
|
+
<button {...styles.appButton({ tone: props.tone })} type="button">
|
|
699
|
+
{props.children}
|
|
700
|
+
</button>
|
|
701
|
+
)
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
declare namespace AppButton {
|
|
705
|
+
type Props = {
|
|
706
|
+
/** The button's label. */
|
|
707
|
+
children: ReactNode
|
|
708
|
+
/** The brand red, or a quiet gray. */
|
|
709
|
+
tone: 'brand' | 'quiet'
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/** The sample app's labeled email field. */
|
|
714
|
+
function AppEmailField(): ReactNode {
|
|
715
|
+
return (
|
|
716
|
+
<label {...styles.appField()}>
|
|
717
|
+
<span {...styles.appLabel()}>Email</span>
|
|
718
|
+
{/* oxlint-disable-next-line tds/prefer-component -- The sample app draws its own controls. */}
|
|
719
|
+
<input {...styles.appInput()} placeholder="Required" readOnly />
|
|
720
|
+
</label>
|
|
721
|
+
)
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/** The sample app's sign-in: a passkey, or an email. */
|
|
725
|
+
function AppSignIn(): ReactNode {
|
|
726
|
+
return (
|
|
727
|
+
<AppScreen title="Sign in or sign up">
|
|
728
|
+
<AppButton tone="quiet">Continue with passkey</AppButton>
|
|
729
|
+
<p {...styles.appDivider()}>or continue with email</p>
|
|
730
|
+
<AppEmailField />
|
|
731
|
+
<AppButton tone="brand">Continue</AppButton>
|
|
732
|
+
</AppScreen>
|
|
733
|
+
)
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/** The sample app's email step for an unlinked passkey. */
|
|
737
|
+
function AppEmail(): ReactNode {
|
|
738
|
+
return (
|
|
739
|
+
<AppScreen
|
|
740
|
+
description="Sign in with your email to link this passkey."
|
|
741
|
+
title="Add your email"
|
|
742
|
+
>
|
|
743
|
+
<AppEmailField />
|
|
744
|
+
<AppButton tone="brand">Continue</AppButton>
|
|
745
|
+
</AppScreen>
|
|
746
|
+
)
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/** The sample app's code step. */
|
|
750
|
+
function AppCode(): ReactNode {
|
|
751
|
+
return (
|
|
752
|
+
<AppScreen
|
|
753
|
+
description={`Enter the 6-digit code sent to ${email}.`}
|
|
754
|
+
title="Check your email"
|
|
755
|
+
>
|
|
756
|
+
<CodeInput aria-label="Code" length={6} readOnly />
|
|
757
|
+
<AppButton tone="brand">Verify</AppButton>
|
|
758
|
+
</AppScreen>
|
|
759
|
+
)
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/** The sample app after the sign-in. */
|
|
763
|
+
function AppSignedIn(): ReactNode {
|
|
764
|
+
return (
|
|
765
|
+
<AppScreen description={email} title="You're signed in">
|
|
766
|
+
<p {...styles.appAddress()}>{accounts[0].address}</p>
|
|
767
|
+
<AppButton tone="quiet">Sign out</AppButton>
|
|
768
|
+
</AppScreen>
|
|
769
|
+
)
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/** A handler that does nothing. */
|
|
773
|
+
function noop() {}
|
|
774
|
+
|
|
775
|
+
/** A form handler that stops the submission. */
|
|
776
|
+
function prevent(event: FormEvent<HTMLFormElement>) {
|
|
777
|
+
event.preventDefault()
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/** The sample accounts: one that Privy owns, and one that a passkey owns. */
|
|
781
|
+
const accounts = [
|
|
782
|
+
{
|
|
783
|
+
address: '0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9f3c',
|
|
784
|
+
balance: { amount: '1250.5', currency: 'USD' },
|
|
785
|
+
owner: 'privy',
|
|
786
|
+
realm: null,
|
|
787
|
+
},
|
|
788
|
+
{
|
|
789
|
+
address: '0x7c4d2e9a1b3f5c7d9e0a2b4c6d8e0f1a3b5c21ab',
|
|
790
|
+
balance: { amount: '42', currency: 'USD' },
|
|
791
|
+
owner: 'webAuthn',
|
|
792
|
+
realm: null,
|
|
793
|
+
},
|
|
794
|
+
] as const satisfies readonly ChooseAccount.Account[]
|
|
795
|
+
|
|
796
|
+
/** The sample email address. */
|
|
797
|
+
const email = 'user@example.com'
|
|
798
|
+
|
|
799
|
+
/** The sample refusal of an upstream service. */
|
|
800
|
+
const unavailable = 'An upstream service is unavailable. Retry later.'
|
|
801
|
+
|
|
802
|
+
/** The sample key: 100 USD a day for a week, in pathUSD. */
|
|
803
|
+
function key(): Approval.Key {
|
|
804
|
+
return {
|
|
805
|
+
calls: [],
|
|
806
|
+
expiry: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60,
|
|
807
|
+
limits: [
|
|
808
|
+
{
|
|
809
|
+
amount: '100',
|
|
810
|
+
currency: 'USD',
|
|
811
|
+
period: 24 * 60 * 60,
|
|
812
|
+
tokens: [{ amount: '100', symbol: 'pathUSD' }],
|
|
813
|
+
},
|
|
814
|
+
],
|
|
815
|
+
unrecognized: [],
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/** Sample `Approval` props for a key, before a decision. */
|
|
820
|
+
function approval(): Approval.Props {
|
|
821
|
+
return {
|
|
822
|
+
app: 'Tempo Wallet',
|
|
823
|
+
approving: false,
|
|
824
|
+
denying: false,
|
|
825
|
+
destination: 'wallet.tempo.xyz',
|
|
826
|
+
email,
|
|
827
|
+
heading: 'h4',
|
|
828
|
+
keyAuthorization: key(),
|
|
829
|
+
onApprove: noop,
|
|
830
|
+
onDeny: noop,
|
|
831
|
+
verified: true,
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/** The sample sign-in request, with its key. */
|
|
836
|
+
function signInRequest(): NonNullable<SignIn.Props['request']> {
|
|
837
|
+
return { app: 'Tempo Wallet', key: key(), verified: true }
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/** The sample sign-in request, without a key. */
|
|
841
|
+
function appRequest(): NonNullable<SignIn.Props['request']> {
|
|
842
|
+
return { app: 'Tempo Wallet', key: null, verified: true }
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/** The sample account that the account step chose. */
|
|
846
|
+
function selectedAccount(): NonNullable<Approval.Props['account']> {
|
|
847
|
+
return { address: accounts[0].address, owner: accounts[0].owner }
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/** The entry of a signed-out redirect with a key. */
|
|
851
|
+
const keyedEntry = {
|
|
852
|
+
detail: 'GET /oauth2/authorize?…&key_authorization',
|
|
853
|
+
name: 'Web app',
|
|
854
|
+
} as const satisfies Node
|
|
855
|
+
|
|
856
|
+
/** Sample `Account` props for a signed-in user. */
|
|
857
|
+
function account(): Account.Props {
|
|
858
|
+
return {
|
|
859
|
+
accounts: [accounts[0]],
|
|
860
|
+
email,
|
|
861
|
+
heading: 'h4',
|
|
862
|
+
onAddPasskey: noop,
|
|
863
|
+
onSignOut: noop,
|
|
864
|
+
signingOut: false,
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/** Sample `Account` props on a configurable chain, whose only account is `accounts[index]`. */
|
|
869
|
+
function configurable(index: 0 | 1): Account.Props {
|
|
870
|
+
return {
|
|
871
|
+
...account(),
|
|
872
|
+
// An account that Tempo ID created is configurable from its creation, and a linked wallet.tempo
|
|
873
|
+
// passkey's account is not until its upgrade.
|
|
874
|
+
accounts: [{ ...accounts[index], configurable: index === 0 }],
|
|
875
|
+
onAddOwner: noop,
|
|
876
|
+
passkeys: true,
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/** Sample `ChooseAccount` props, with nothing chosen. */
|
|
881
|
+
function chooseAccount(): ChooseAccount.Props {
|
|
882
|
+
return { accounts, app: 'Tempo Wallet', heading: 'h4', onChoose: noop }
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/** Sample `DeviceApproval` props, before a submission. */
|
|
886
|
+
function deviceApproval(): DeviceApproval.Props {
|
|
887
|
+
return {
|
|
888
|
+
approving: false,
|
|
889
|
+
code: '',
|
|
890
|
+
denying: false,
|
|
891
|
+
email,
|
|
892
|
+
fetching: false,
|
|
893
|
+
heading: 'h4',
|
|
894
|
+
invalid: false,
|
|
895
|
+
onApprove: noop,
|
|
896
|
+
onDeny: noop,
|
|
897
|
+
onSubmitCode: prevent,
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/** A sample `tempo-id login` request, with its key. */
|
|
902
|
+
function deviceRequest(): DeviceApproval.Request {
|
|
903
|
+
return {
|
|
904
|
+
app: { name: 'Tempo CLI' },
|
|
905
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
906
|
+
device: 'ada-macbook',
|
|
907
|
+
ip: '203.0.113.7',
|
|
908
|
+
key_authorization: key(),
|
|
909
|
+
location: 'San Francisco, US',
|
|
910
|
+
user_code: 'BDWP-HQPK',
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/** The entry of `tempo-id login`, which requests a code and a key. */
|
|
915
|
+
const loginEntry = {
|
|
916
|
+
detail: 'POST /device/code with key_authorization',
|
|
917
|
+
name: 'tempo-id login',
|
|
918
|
+
} as const satisfies Node
|
|
919
|
+
|
|
920
|
+
/** The exit of `tempo-id login`: the poll for the tokens and the signed key. */
|
|
921
|
+
const loginExit = {
|
|
922
|
+
detail:
|
|
923
|
+
'Tokens and key_authorization, stored in ~/.tempo/id/credentials.json. A replaced session’s refresh token goes to POST /oauth2/revoke.',
|
|
924
|
+
edge: 'POST /oauth2/token: authorization_pending, then tokens',
|
|
925
|
+
name: 'tempo-id login',
|
|
926
|
+
} as const satisfies Node
|
|
927
|
+
|
|
928
|
+
/** Sample `WalletUpgrade` props, before the upgrade. */
|
|
929
|
+
function walletUpgrade(): WalletUpgrade.Props {
|
|
930
|
+
return {
|
|
931
|
+
heading: 'h4',
|
|
932
|
+
onContinue: noop,
|
|
933
|
+
onSkip: noop,
|
|
934
|
+
upgrading: false,
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/** Sample `SignIn` props, before a submission, with every method. */
|
|
939
|
+
function signIn(): SignIn.Props {
|
|
940
|
+
return {
|
|
941
|
+
heading: 'h4',
|
|
942
|
+
invalid: false,
|
|
943
|
+
onChangeEmail: noop,
|
|
944
|
+
onPasskey: noop,
|
|
945
|
+
onResend: noop,
|
|
946
|
+
onStartOver: noop,
|
|
947
|
+
onSubmitCode: prevent,
|
|
948
|
+
onSubmitEmail: prevent,
|
|
949
|
+
resend: 'idle',
|
|
950
|
+
sending: false,
|
|
951
|
+
verifying: false,
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/** The entry of an app's redirect to `/oauth2/authorize`. */
|
|
956
|
+
const redirectEntry = {
|
|
957
|
+
detail: 'GET /oauth2/authorize?client_id&redirect_uri&code_challenge&state',
|
|
958
|
+
name: 'Web app',
|
|
959
|
+
} as const satisfies Node
|
|
960
|
+
|
|
961
|
+
/** The page-load requests that select a headless app's own sign-in. */
|
|
962
|
+
const appSignInEdge = 'GET /v1/auth/session: no user'
|
|
963
|
+
|
|
964
|
+
/** The exit of an app's redirect: the code redemption. */
|
|
965
|
+
const tokenExit = {
|
|
966
|
+
detail: 'Access, ID, and refresh tokens',
|
|
967
|
+
edge: 'POST /oauth2/token with code_verifier',
|
|
968
|
+
name: 'Web app',
|
|
969
|
+
} as const satisfies Node
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Every flow, by group. Update it with any flow it shows. It runs on mount, so sample times read
|
|
973
|
+
* the clock then.
|
|
974
|
+
*/
|
|
975
|
+
function sections(): readonly Section[] {
|
|
976
|
+
return [
|
|
977
|
+
{
|
|
978
|
+
flows: [
|
|
979
|
+
{
|
|
980
|
+
description:
|
|
981
|
+
'A visitor opens Tempo ID without a session and signs in with an emailed code. An accepted code shows green for 500 ms before the sign-in continues.',
|
|
982
|
+
nodes: [
|
|
983
|
+
{ detail: 'GET /', name: 'Browser opens Tempo ID' },
|
|
984
|
+
{
|
|
985
|
+
edge: 'Page /login',
|
|
986
|
+
name: 'Sign in',
|
|
987
|
+
screen: <SignIn {...signIn()} />,
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
edge: 'POST /v1/auth/code',
|
|
991
|
+
name: 'Check your email',
|
|
992
|
+
screen: <SignIn {...signIn()} email={email} />,
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
edge: 'POST /v1/auth/code/verify: 200',
|
|
996
|
+
name: 'Check your email (accepted)',
|
|
997
|
+
screen: <SignIn {...signIn()} email={email} verified />,
|
|
998
|
+
},
|
|
999
|
+
{
|
|
1000
|
+
edge: 'after 500 ms, redirect: null',
|
|
1001
|
+
name: 'Your account',
|
|
1002
|
+
screen: <Account {...account()} />,
|
|
1003
|
+
},
|
|
1004
|
+
],
|
|
1005
|
+
title: 'Email code',
|
|
1006
|
+
},
|
|
1007
|
+
{
|
|
1008
|
+
description:
|
|
1009
|
+
'A passkey of the realm signs the user in with one prompt, from the button.',
|
|
1010
|
+
nodes: [
|
|
1011
|
+
{ detail: 'GET /login', name: 'Browser opens sign-in' },
|
|
1012
|
+
{
|
|
1013
|
+
detail: 'POST /v1/auth/passkey/options, as the page shows',
|
|
1014
|
+
name: 'Sign in',
|
|
1015
|
+
screen: <SignIn {...signIn()} />,
|
|
1016
|
+
},
|
|
1017
|
+
{
|
|
1018
|
+
detail: 'The options that the page requested',
|
|
1019
|
+
edge: 'Sign in with a passkey',
|
|
1020
|
+
name: 'Authenticator prompt',
|
|
1021
|
+
},
|
|
1022
|
+
{
|
|
1023
|
+
edge: 'POST /v1/auth/passkey/verify',
|
|
1024
|
+
name: 'Your account',
|
|
1025
|
+
screen: <Account {...account()} />,
|
|
1026
|
+
},
|
|
1027
|
+
],
|
|
1028
|
+
title: 'Passkey',
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
description:
|
|
1032
|
+
'A wallet.tempo passkey without an email verifies one with a code. The passkey signs again with the code, which links it to the user.',
|
|
1033
|
+
nodes: [
|
|
1034
|
+
{ detail: 'GET /login', name: 'Browser opens sign-in' },
|
|
1035
|
+
{
|
|
1036
|
+
detail: 'POST /v1/auth/passkey/options, as the page shows',
|
|
1037
|
+
name: 'Sign in',
|
|
1038
|
+
screen: <SignIn {...signIn()} />,
|
|
1039
|
+
},
|
|
1040
|
+
{
|
|
1041
|
+
detail: 'The options that the page requested',
|
|
1042
|
+
edge: 'Sign in with a passkey',
|
|
1043
|
+
name: 'Authenticator prompt',
|
|
1044
|
+
},
|
|
1045
|
+
{
|
|
1046
|
+
edge: 'POST /v1/auth/passkey/verify: step email',
|
|
1047
|
+
name: 'Sign in (link passkey)',
|
|
1048
|
+
screen: <SignIn {...signIn()} linking />,
|
|
1049
|
+
},
|
|
1050
|
+
{
|
|
1051
|
+
edge: 'POST /v1/auth/code',
|
|
1052
|
+
name: 'Check your email (link passkey)',
|
|
1053
|
+
screen: <SignIn {...signIn()} email={email} linking />,
|
|
1054
|
+
},
|
|
1055
|
+
{
|
|
1056
|
+
detail: 'POST /v1/auth/passkey/options with link and authorize',
|
|
1057
|
+
edge: 'Code complete',
|
|
1058
|
+
name: 'Authenticator prompt',
|
|
1059
|
+
},
|
|
1060
|
+
{
|
|
1061
|
+
edge: 'POST /v1/auth/code/verify with link and assertion: 200',
|
|
1062
|
+
name: 'Check your email (link accepted)',
|
|
1063
|
+
screen: <SignIn {...signIn()} email={email} linking verified />,
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
edge: 'after 500 ms, redirect: null',
|
|
1067
|
+
name: 'Your account',
|
|
1068
|
+
screen: <Account {...account()} />,
|
|
1069
|
+
},
|
|
1070
|
+
],
|
|
1071
|
+
title: 'Passkey: adds an email',
|
|
1072
|
+
},
|
|
1073
|
+
{
|
|
1074
|
+
description:
|
|
1075
|
+
'Start over on the email step drops the passkey’s link and a pending code request, so the passkey stays unlinked.',
|
|
1076
|
+
nodes: [
|
|
1077
|
+
{ detail: 'GET /login', name: 'Browser opens sign-in' },
|
|
1078
|
+
{
|
|
1079
|
+
edge: 'Sign in with a passkey, POST /v1/auth/passkey/verify: step email',
|
|
1080
|
+
name: 'Sign in (link passkey)',
|
|
1081
|
+
screen: <SignIn {...signIn()} linking />,
|
|
1082
|
+
},
|
|
1083
|
+
{
|
|
1084
|
+
edge: 'Start over',
|
|
1085
|
+
name: 'Sign in',
|
|
1086
|
+
screen: <SignIn {...signIn()} />,
|
|
1087
|
+
},
|
|
1088
|
+
],
|
|
1089
|
+
title: 'Passkey: start over',
|
|
1090
|
+
},
|
|
1091
|
+
{
|
|
1092
|
+
description:
|
|
1093
|
+
'Realms without a passkey relying party at Tempo ID’s origin leave only the email field.',
|
|
1094
|
+
nodes: [
|
|
1095
|
+
{ detail: 'GET /login', name: 'Browser opens sign-in' },
|
|
1096
|
+
{
|
|
1097
|
+
edge: '200, passkeys: false',
|
|
1098
|
+
name: 'Sign in (email only)',
|
|
1099
|
+
screen: <SignIn {...signIn()} onPasskey={undefined} />,
|
|
1100
|
+
},
|
|
1101
|
+
],
|
|
1102
|
+
title: 'Email only: no passkey',
|
|
1103
|
+
},
|
|
1104
|
+
{
|
|
1105
|
+
description:
|
|
1106
|
+
'Each sign-in request holds its controls, shows a spinner, and announces its progress until it settles. One sign-in runs at a time.',
|
|
1107
|
+
nodes: [
|
|
1108
|
+
{ detail: 'GET /login', name: 'Browser opens sign-in' },
|
|
1109
|
+
{
|
|
1110
|
+
edge: 'Continue, POST /v1/auth/code',
|
|
1111
|
+
name: 'Sign in (sending)',
|
|
1112
|
+
screen: <SignIn {...signIn()} sending />,
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
edge: 'Or: Sign in with a passkey',
|
|
1116
|
+
name: 'Sign in (passkey pending)',
|
|
1117
|
+
screen: <SignIn {...signIn()} passkeyPending />,
|
|
1118
|
+
},
|
|
1119
|
+
{
|
|
1120
|
+
edge: 'Code complete, POST /v1/auth/code/verify',
|
|
1121
|
+
name: 'Check your email (verifying)',
|
|
1122
|
+
screen: <SignIn {...signIn()} email={email} verifying />,
|
|
1123
|
+
},
|
|
1124
|
+
{
|
|
1125
|
+
edge: 'Or: Resend code, POST /v1/auth/code',
|
|
1126
|
+
name: 'Check your email (resending)',
|
|
1127
|
+
screen: <SignIn {...signIn()} email={email} resend="pending" />,
|
|
1128
|
+
},
|
|
1129
|
+
],
|
|
1130
|
+
title: 'Sign-in: in progress',
|
|
1131
|
+
},
|
|
1132
|
+
],
|
|
1133
|
+
title: 'Sign-in',
|
|
1134
|
+
},
|
|
1135
|
+
{
|
|
1136
|
+
flows: [
|
|
1137
|
+
{
|
|
1138
|
+
description:
|
|
1139
|
+
'The account page lists the user’s Tempo Accounts in the `default` realm, oldest first, each with its balance and a deposit link. A lone wallet shows its balance large, without a list.',
|
|
1140
|
+
nodes: [
|
|
1141
|
+
{ detail: 'GET /', name: 'Browser opens Tempo ID' },
|
|
1142
|
+
{
|
|
1143
|
+
edge: 'GET /v1/account?chain, 200, one wallet',
|
|
1144
|
+
name: 'Your account (one wallet)',
|
|
1145
|
+
screen: <Account {...account()} />,
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
edge: 'Or: several wallets',
|
|
1149
|
+
name: 'Your account (several wallets)',
|
|
1150
|
+
screen: <Account {...account()} accounts={accounts} />,
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
edge: 'Or: a balance read fails',
|
|
1154
|
+
name: 'Your account (balance unavailable)',
|
|
1155
|
+
screen: (
|
|
1156
|
+
<Account
|
|
1157
|
+
{...account()}
|
|
1158
|
+
accounts={[accounts[0], { ...accounts[1], balance: null }]}
|
|
1159
|
+
/>
|
|
1160
|
+
),
|
|
1161
|
+
},
|
|
1162
|
+
{
|
|
1163
|
+
edge: 'Or: no wallet, POST /v1/account with chain creates the first one, 200 with its balance',
|
|
1164
|
+
name: 'Your account (wallet created)',
|
|
1165
|
+
screen: <Account {...account()} />,
|
|
1166
|
+
},
|
|
1167
|
+
{
|
|
1168
|
+
edge: 'Or: the realm creates no wallets on the server, but holds passkeys',
|
|
1169
|
+
name: 'Your account (no wallets)',
|
|
1170
|
+
screen: <Account {...account()} accounts={[]} />,
|
|
1171
|
+
},
|
|
1172
|
+
{
|
|
1173
|
+
edge: 'Or: the realm creates no wallets at all',
|
|
1174
|
+
name: 'Your account (no wallet possible)',
|
|
1175
|
+
screen: (
|
|
1176
|
+
<Account
|
|
1177
|
+
{...account()}
|
|
1178
|
+
accounts={[]}
|
|
1179
|
+
onAddPasskey={undefined}
|
|
1180
|
+
/>
|
|
1181
|
+
),
|
|
1182
|
+
},
|
|
1183
|
+
{
|
|
1184
|
+
edge: 'Or: the accounts cannot be read',
|
|
1185
|
+
name: 'Your account (unavailable)',
|
|
1186
|
+
screen: (
|
|
1187
|
+
<Account
|
|
1188
|
+
{...account()}
|
|
1189
|
+
accountError="Your wallets failed to load. Reload the page to try again."
|
|
1190
|
+
accounts={null}
|
|
1191
|
+
/>
|
|
1192
|
+
),
|
|
1193
|
+
},
|
|
1194
|
+
],
|
|
1195
|
+
title: 'Account page',
|
|
1196
|
+
},
|
|
1197
|
+
{
|
|
1198
|
+
description:
|
|
1199
|
+
'On a test chain, such as testnet, each deposit icon funds its wallet from the chain’s faucet through `POST /i/account/fund`, one request at a time. Tempo mainnet has no faucet, so its deposit icon opens `/deposit`.',
|
|
1200
|
+
nodes: [
|
|
1201
|
+
{ detail: 'GET /?chain=testnet', name: 'Browser' },
|
|
1202
|
+
{
|
|
1203
|
+
edge: 'GET /v1/account?chain=testnet, 200',
|
|
1204
|
+
name: 'Your account (test chain)',
|
|
1205
|
+
screen: (
|
|
1206
|
+
<Account
|
|
1207
|
+
{...account()}
|
|
1208
|
+
accounts={[
|
|
1209
|
+
{
|
|
1210
|
+
...accounts[0],
|
|
1211
|
+
balance: { amount: '0', currency: 'USD' },
|
|
1212
|
+
},
|
|
1213
|
+
]}
|
|
1214
|
+
onFund={noop}
|
|
1215
|
+
/>
|
|
1216
|
+
),
|
|
1217
|
+
},
|
|
1218
|
+
{
|
|
1219
|
+
edge: 'Deposit icon, POST /i/account/fund',
|
|
1220
|
+
name: 'Your account (funding)',
|
|
1221
|
+
screen: (
|
|
1222
|
+
<Account
|
|
1223
|
+
{...account()}
|
|
1224
|
+
accounts={[
|
|
1225
|
+
{
|
|
1226
|
+
...accounts[0],
|
|
1227
|
+
balance: { amount: '0', currency: 'USD' },
|
|
1228
|
+
},
|
|
1229
|
+
]}
|
|
1230
|
+
funding={accounts[0].address}
|
|
1231
|
+
onFund={noop}
|
|
1232
|
+
/>
|
|
1233
|
+
),
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
edge: '200 with the transfers, GET /v1/account?chain=testnet',
|
|
1237
|
+
name: 'Your account (funded)',
|
|
1238
|
+
screen: (
|
|
1239
|
+
<Account
|
|
1240
|
+
{...account()}
|
|
1241
|
+
accounts={[
|
|
1242
|
+
{
|
|
1243
|
+
...accounts[0],
|
|
1244
|
+
balance: { amount: '5000000', currency: 'USD' },
|
|
1245
|
+
},
|
|
1246
|
+
]}
|
|
1247
|
+
onFund={noop}
|
|
1248
|
+
/>
|
|
1249
|
+
),
|
|
1250
|
+
},
|
|
1251
|
+
{
|
|
1252
|
+
edge: 'Or: the node fails, 503 upstream_unavailable',
|
|
1253
|
+
name: 'Your account (faucet unavailable)',
|
|
1254
|
+
screen: (
|
|
1255
|
+
<Account
|
|
1256
|
+
{...account()}
|
|
1257
|
+
accounts={[
|
|
1258
|
+
{
|
|
1259
|
+
...accounts[0],
|
|
1260
|
+
balance: { amount: '0', currency: 'USD' },
|
|
1261
|
+
},
|
|
1262
|
+
]}
|
|
1263
|
+
error={unavailable}
|
|
1264
|
+
onFund={noop}
|
|
1265
|
+
/>
|
|
1266
|
+
),
|
|
1267
|
+
},
|
|
1268
|
+
],
|
|
1269
|
+
title: 'Account page: testnet faucet',
|
|
1270
|
+
},
|
|
1271
|
+
{
|
|
1272
|
+
description:
|
|
1273
|
+
'In one click, the account page adds a passkey of the `default` realm, which signs the user in and owns a new passkey wallet. The page offers it only when the realm holds passkeys and the user has no passkey wallet.',
|
|
1274
|
+
nodes: [
|
|
1275
|
+
{ detail: 'GET /', name: 'Browser opens Tempo ID' },
|
|
1276
|
+
{
|
|
1277
|
+
name: 'Your account',
|
|
1278
|
+
screen: <Account {...account()} />,
|
|
1279
|
+
},
|
|
1280
|
+
{
|
|
1281
|
+
edge: 'Add passkey, POST /v1/account/passkey/options, navigator.credentials.create(), POST /v1/account/passkey, navigator.credentials.get()',
|
|
1282
|
+
name: 'Your account (adding a passkey)',
|
|
1283
|
+
screen: <Account {...account()} addingPasskey />,
|
|
1284
|
+
},
|
|
1285
|
+
{
|
|
1286
|
+
edge: 'POST /v1/account/passkey/verify, 200, then the accounts load again, and a toast: Passkey added',
|
|
1287
|
+
name: 'Your account (passkey added)',
|
|
1288
|
+
screen: (
|
|
1289
|
+
<Account
|
|
1290
|
+
{...account()}
|
|
1291
|
+
accounts={accounts}
|
|
1292
|
+
onAddPasskey={undefined}
|
|
1293
|
+
passkeyAdded
|
|
1294
|
+
/>
|
|
1295
|
+
),
|
|
1296
|
+
},
|
|
1297
|
+
{
|
|
1298
|
+
edge: 'Or: the ceremony is canceled',
|
|
1299
|
+
name: 'Your account (passkey canceled)',
|
|
1300
|
+
screen: (
|
|
1301
|
+
<Account
|
|
1302
|
+
{...account()}
|
|
1303
|
+
error="Adding a passkey was canceled. Try again."
|
|
1304
|
+
/>
|
|
1305
|
+
),
|
|
1306
|
+
},
|
|
1307
|
+
{
|
|
1308
|
+
edge: 'Or: the session signed in more than 10 minutes before',
|
|
1309
|
+
name: 'Your account (sign in again)',
|
|
1310
|
+
screen: (
|
|
1311
|
+
<Account
|
|
1312
|
+
{...account()}
|
|
1313
|
+
error="Sign in again: adding a passkey needs a sign-in within the last 10 minutes."
|
|
1314
|
+
/>
|
|
1315
|
+
),
|
|
1316
|
+
},
|
|
1317
|
+
{
|
|
1318
|
+
edge: 'Or: the accounts failed to load, and the device already holds one of the user’s passkeys',
|
|
1319
|
+
name: 'Your account (passkey held)',
|
|
1320
|
+
screen: (
|
|
1321
|
+
<Account
|
|
1322
|
+
{...account()}
|
|
1323
|
+
error="This device already holds one of your passkeys. Add a passkey on another device."
|
|
1324
|
+
/>
|
|
1325
|
+
),
|
|
1326
|
+
},
|
|
1327
|
+
],
|
|
1328
|
+
title: 'Account page: add a passkey',
|
|
1329
|
+
},
|
|
1330
|
+
{
|
|
1331
|
+
description:
|
|
1332
|
+
'On a chain that `CONFIGURABLE_ACCOUNTS` names, a cloud wallet’s configurable account adds a passkey as a second owner. The cloud wallet signs the configuration update, so the wallet keeps its address, and either owner then signs alone.',
|
|
1333
|
+
nodes: [
|
|
1334
|
+
{ detail: 'GET /?chain=local', name: 'Browser opens Tempo ID' },
|
|
1335
|
+
{
|
|
1336
|
+
name: 'Your account (configurable chain)',
|
|
1337
|
+
screen: <Account {...configurable(0)} />,
|
|
1338
|
+
},
|
|
1339
|
+
{
|
|
1340
|
+
edge: 'Add passkey, POST /v1/account/passkey/options, navigator.credentials.create(), POST /v1/account/passkey, navigator.credentials.get()',
|
|
1341
|
+
name: 'Your account (adding a passkey)',
|
|
1342
|
+
screen: <Account {...configurable(0)} addingPasskey />,
|
|
1343
|
+
},
|
|
1344
|
+
{
|
|
1345
|
+
edge: 'POST /v1/account/passkey/verify with account: the cloud wallet signs the update, and a toast: Passkey added',
|
|
1346
|
+
name: 'Your account (passkey added)',
|
|
1347
|
+
screen: (
|
|
1348
|
+
<Account
|
|
1349
|
+
{...configurable(0)}
|
|
1350
|
+
accounts={[
|
|
1351
|
+
{
|
|
1352
|
+
...accounts[0],
|
|
1353
|
+
configurable: true,
|
|
1354
|
+
owners: ['privy', 'webAuthn'],
|
|
1355
|
+
},
|
|
1356
|
+
]}
|
|
1357
|
+
ownerAdded="passkey"
|
|
1358
|
+
/>
|
|
1359
|
+
),
|
|
1360
|
+
},
|
|
1361
|
+
{
|
|
1362
|
+
edge: 'Or: the chain refuses the change, or the Tempo API’s sponsor fails',
|
|
1363
|
+
name: 'Your account (change failed)',
|
|
1364
|
+
screen: (
|
|
1365
|
+
<Account
|
|
1366
|
+
{...configurable(0)}
|
|
1367
|
+
error="An upstream service is unavailable. Retry later."
|
|
1368
|
+
/>
|
|
1369
|
+
),
|
|
1370
|
+
},
|
|
1371
|
+
],
|
|
1372
|
+
title: 'Add a passkey to a cloud wallet',
|
|
1373
|
+
},
|
|
1374
|
+
{
|
|
1375
|
+
description:
|
|
1376
|
+
'On a chain that `CONFIGURABLE_ACCOUNTS` names, a linked wallet.tempo passkey’s wallet upgrades in place, with the passkey as its only owner. One click signs the prepared passkey step, and the wallet keeps its address.',
|
|
1377
|
+
nodes: [
|
|
1378
|
+
{
|
|
1379
|
+
detail:
|
|
1380
|
+
'GET /?chain=local, then POST /v1/account/upgrade, 200, the passkey step',
|
|
1381
|
+
name: 'Browser opens Tempo ID',
|
|
1382
|
+
},
|
|
1383
|
+
{
|
|
1384
|
+
name: 'Your account (upgrade)',
|
|
1385
|
+
screen: <Account {...configurable(1)} onUpgrade={noop} />,
|
|
1386
|
+
},
|
|
1387
|
+
{
|
|
1388
|
+
edge: 'Or: the user holds a cloud wallet too, and each wallet’s row offers its own change as an icon',
|
|
1389
|
+
name: 'Your account (two wallets)',
|
|
1390
|
+
screen: (
|
|
1391
|
+
<Account
|
|
1392
|
+
{...configurable(1)}
|
|
1393
|
+
accounts={[
|
|
1394
|
+
{ ...accounts[0], configurable: true },
|
|
1395
|
+
{ ...accounts[1], configurable: false },
|
|
1396
|
+
]}
|
|
1397
|
+
onUpgrade={noop}
|
|
1398
|
+
/>
|
|
1399
|
+
),
|
|
1400
|
+
},
|
|
1401
|
+
{
|
|
1402
|
+
edge: 'Upgrade wallet, navigator.credentials.get(), POST /v1/account/upgrade with passkey.assertion',
|
|
1403
|
+
name: 'Your account (upgrading)',
|
|
1404
|
+
screen: (
|
|
1405
|
+
<Account
|
|
1406
|
+
{...configurable(1)}
|
|
1407
|
+
onUpgrade={noop}
|
|
1408
|
+
upgrading={accounts[1].address}
|
|
1409
|
+
/>
|
|
1410
|
+
),
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
edge: '200, the account at version 1, and a toast: Wallet upgraded',
|
|
1414
|
+
name: 'Your account (upgraded)',
|
|
1415
|
+
screen: (
|
|
1416
|
+
<Account
|
|
1417
|
+
{...configurable(1)}
|
|
1418
|
+
accounts={[{ ...accounts[1], configurable: true }]}
|
|
1419
|
+
/>
|
|
1420
|
+
),
|
|
1421
|
+
},
|
|
1422
|
+
{
|
|
1423
|
+
edge: 'Or: the chain refuses the upgrade, or the Tempo API’s sponsor fails',
|
|
1424
|
+
name: 'Your account (upgrade failed)',
|
|
1425
|
+
screen: (
|
|
1426
|
+
<Account
|
|
1427
|
+
{...configurable(1)}
|
|
1428
|
+
error="An upstream service is unavailable. Retry later."
|
|
1429
|
+
onUpgrade={noop}
|
|
1430
|
+
/>
|
|
1431
|
+
),
|
|
1432
|
+
},
|
|
1433
|
+
],
|
|
1434
|
+
title: 'Upgrade a passkey wallet',
|
|
1435
|
+
},
|
|
1436
|
+
{
|
|
1437
|
+
description:
|
|
1438
|
+
'When `CONFIGURABLE_ACCOUNTS` names the sign-in page’s `chain`, a wallet.tempo user receives no cloud wallet. Before the account page, Continue on the upgrade step links the passkey and signs its upgrade.',
|
|
1439
|
+
nodes: [
|
|
1440
|
+
{
|
|
1441
|
+
detail:
|
|
1442
|
+
'GET /?chain=local, page /login?chain=local, then POST /v1/auth/code/verify: 200',
|
|
1443
|
+
name: 'Browser signs in by email',
|
|
1444
|
+
},
|
|
1445
|
+
{
|
|
1446
|
+
edge: 'GET /i/account/upgrades?chain=local, then POST /v1/account/upgrade with credentialId, 200, the passkey step',
|
|
1447
|
+
name: 'Upgrade your wallet',
|
|
1448
|
+
screen: <WalletUpgrade {...walletUpgrade()} />,
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
edge: 'Continue, POST /v1/account/upgrade with passkey.assertion, which links the passkey',
|
|
1452
|
+
name: 'Upgrade your wallet (upgrading)',
|
|
1453
|
+
screen: <WalletUpgrade {...walletUpgrade()} upgrading />,
|
|
1454
|
+
},
|
|
1455
|
+
{
|
|
1456
|
+
edge: 'Or: the passkey ceremony fails, or the upgrade is refused',
|
|
1457
|
+
name: 'Upgrade your wallet (failed)',
|
|
1458
|
+
screen: (
|
|
1459
|
+
<WalletUpgrade
|
|
1460
|
+
{...walletUpgrade()}
|
|
1461
|
+
error="Your passkey did not sign the upgrade. Continue to try again."
|
|
1462
|
+
/>
|
|
1463
|
+
),
|
|
1464
|
+
},
|
|
1465
|
+
{
|
|
1466
|
+
edge: 'redirect: null, GET /?chain=local',
|
|
1467
|
+
name: 'Your account (upgraded)',
|
|
1468
|
+
screen: (
|
|
1469
|
+
<Account
|
|
1470
|
+
{...configurable(1)}
|
|
1471
|
+
accounts={[{ ...accounts[1], configurable: true }]}
|
|
1472
|
+
/>
|
|
1473
|
+
),
|
|
1474
|
+
},
|
|
1475
|
+
],
|
|
1476
|
+
title: 'Sign-in: upgrade a wallet.tempo passkey',
|
|
1477
|
+
},
|
|
1478
|
+
{
|
|
1479
|
+
description:
|
|
1480
|
+
'A wallet.tempo user who chooses Not now on the upgrade step holds no configurable wallet yet. The account page lists that passkey’s wallet from `GET /i/account/passkeys`, and one click links the passkey and signs its upgrade.',
|
|
1481
|
+
nodes: [
|
|
1482
|
+
{
|
|
1483
|
+
detail: 'Not now, then GET /?chain=local',
|
|
1484
|
+
name: 'Browser signs in',
|
|
1485
|
+
},
|
|
1486
|
+
{
|
|
1487
|
+
edge: 'GET /i/account/passkeys, then POST /v1/account/upgrade with credentialId, 200, the passkey step',
|
|
1488
|
+
name: 'Your account (wallet.tempo passkey)',
|
|
1489
|
+
screen: (
|
|
1490
|
+
<Account
|
|
1491
|
+
{...configurable(1)}
|
|
1492
|
+
accounts={[
|
|
1493
|
+
{
|
|
1494
|
+
...accounts[1],
|
|
1495
|
+
credentialId: 'KEbWNCc7NgaYnUyrNeFGX9_3Y-8',
|
|
1496
|
+
},
|
|
1497
|
+
]}
|
|
1498
|
+
onUpgrade={noop}
|
|
1499
|
+
/>
|
|
1500
|
+
),
|
|
1501
|
+
},
|
|
1502
|
+
{
|
|
1503
|
+
edge: 'Upgrade wallet, POST /v1/account/upgrade with passkey.assertion, which links the passkey, and a toast: Wallet upgraded',
|
|
1504
|
+
name: 'Your account (upgraded)',
|
|
1505
|
+
screen: (
|
|
1506
|
+
<Account
|
|
1507
|
+
{...configurable(1)}
|
|
1508
|
+
accounts={[{ ...accounts[1], configurable: true }]}
|
|
1509
|
+
/>
|
|
1510
|
+
),
|
|
1511
|
+
},
|
|
1512
|
+
],
|
|
1513
|
+
title: 'Account page: upgrade a wallet.tempo passkey',
|
|
1514
|
+
},
|
|
1515
|
+
{
|
|
1516
|
+
description:
|
|
1517
|
+
'The account page and the deposit page send a visitor without a session to sign-in, which then opens the account page. The deposit page leaves the address out of the sign-in query.',
|
|
1518
|
+
nodes: [
|
|
1519
|
+
{ detail: 'GET / or GET /deposit?address', name: 'Browser' },
|
|
1520
|
+
{
|
|
1521
|
+
edge: 'GET /v1/account: session_required, page /login',
|
|
1522
|
+
name: 'Sign in',
|
|
1523
|
+
screen: <SignIn {...signIn()} />,
|
|
1524
|
+
},
|
|
1525
|
+
{
|
|
1526
|
+
edge: 'POST /v1/auth/code',
|
|
1527
|
+
name: 'Check your email',
|
|
1528
|
+
screen: <SignIn {...signIn()} email={email} />,
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
edge: 'POST /v1/auth/code/verify: 200',
|
|
1532
|
+
name: 'Check your email (accepted)',
|
|
1533
|
+
screen: <SignIn {...signIn()} email={email} verified />,
|
|
1534
|
+
},
|
|
1535
|
+
{
|
|
1536
|
+
edge: 'after 500 ms, redirect: null, GET /',
|
|
1537
|
+
name: 'Your account',
|
|
1538
|
+
screen: <Account {...account()} />,
|
|
1539
|
+
},
|
|
1540
|
+
],
|
|
1541
|
+
title: 'Account page: signed out',
|
|
1542
|
+
},
|
|
1543
|
+
{
|
|
1544
|
+
description:
|
|
1545
|
+
'Deposit lists the networks and cash methods for one of the user’s wallets. Only Tempo is available, and it shows the wallet’s address as a QR code and as text.',
|
|
1546
|
+
nodes: [
|
|
1547
|
+
{ detail: 'GET /', name: 'Browser opens Tempo ID' },
|
|
1548
|
+
{
|
|
1549
|
+
edge: '200, one wallet',
|
|
1550
|
+
name: 'Your account',
|
|
1551
|
+
screen: <Account {...account()} />,
|
|
1552
|
+
},
|
|
1553
|
+
{
|
|
1554
|
+
edge: 'Deposit, GET /deposit?address',
|
|
1555
|
+
name: 'Deposit',
|
|
1556
|
+
screen: <Deposit address={accounts[0].address} heading="h4" />,
|
|
1557
|
+
},
|
|
1558
|
+
{
|
|
1559
|
+
edge: 'Tempo, GET /deposit?address&method=tempo',
|
|
1560
|
+
name: 'Receive funds',
|
|
1561
|
+
screen: (
|
|
1562
|
+
<Deposit
|
|
1563
|
+
address={accounts[0].address}
|
|
1564
|
+
heading="h4"
|
|
1565
|
+
method="tempo"
|
|
1566
|
+
/>
|
|
1567
|
+
),
|
|
1568
|
+
},
|
|
1569
|
+
{
|
|
1570
|
+
edge: 'Back, GET /deposit?address',
|
|
1571
|
+
name: 'Deposit',
|
|
1572
|
+
screen: <Deposit address={accounts[0].address} heading="h4" />,
|
|
1573
|
+
},
|
|
1574
|
+
{
|
|
1575
|
+
edge: 'Back, GET /',
|
|
1576
|
+
name: 'Your account',
|
|
1577
|
+
screen: <Account {...account()} />,
|
|
1578
|
+
},
|
|
1579
|
+
],
|
|
1580
|
+
title: 'Deposit',
|
|
1581
|
+
},
|
|
1582
|
+
{
|
|
1583
|
+
description: 'Done on Receive funds opens the account page at once.',
|
|
1584
|
+
nodes: [
|
|
1585
|
+
{ detail: 'GET /deposit?address&method=tempo', name: 'Browser' },
|
|
1586
|
+
{
|
|
1587
|
+
edge: '200 deposit page',
|
|
1588
|
+
name: 'Receive funds',
|
|
1589
|
+
screen: (
|
|
1590
|
+
<Deposit
|
|
1591
|
+
address={accounts[0].address}
|
|
1592
|
+
heading="h4"
|
|
1593
|
+
method="tempo"
|
|
1594
|
+
/>
|
|
1595
|
+
),
|
|
1596
|
+
},
|
|
1597
|
+
{
|
|
1598
|
+
edge: 'Done, GET /',
|
|
1599
|
+
name: 'Your account',
|
|
1600
|
+
screen: <Account {...account()} />,
|
|
1601
|
+
},
|
|
1602
|
+
],
|
|
1603
|
+
title: 'Deposit: done',
|
|
1604
|
+
},
|
|
1605
|
+
{
|
|
1606
|
+
description:
|
|
1607
|
+
'An address that is not one of the user’s wallets in the `default` realm shows no address. A spent rate limit or a failed account read shows its own reason in the same place.',
|
|
1608
|
+
nodes: [
|
|
1609
|
+
{
|
|
1610
|
+
detail: 'GET /deposit?address (not the user’s)',
|
|
1611
|
+
name: 'Browser',
|
|
1612
|
+
},
|
|
1613
|
+
{
|
|
1614
|
+
edge: '200, address: null',
|
|
1615
|
+
name: 'Deposit (wallet not found)',
|
|
1616
|
+
screen: (
|
|
1617
|
+
<Deposit
|
|
1618
|
+
error="This wallet was not found. Go back to your account, then choose Deposit."
|
|
1619
|
+
heading="h4"
|
|
1620
|
+
/>
|
|
1621
|
+
),
|
|
1622
|
+
},
|
|
1623
|
+
{
|
|
1624
|
+
edge: 'Or: the account rate limit is spent',
|
|
1625
|
+
name: 'Deposit (rate limited)',
|
|
1626
|
+
screen: (
|
|
1627
|
+
<Deposit
|
|
1628
|
+
error="Too many requests. Try again in 1 minute."
|
|
1629
|
+
heading="h4"
|
|
1630
|
+
/>
|
|
1631
|
+
),
|
|
1632
|
+
},
|
|
1633
|
+
{
|
|
1634
|
+
edge: 'Or: the accounts cannot be read',
|
|
1635
|
+
name: 'Deposit (unavailable)',
|
|
1636
|
+
screen: (
|
|
1637
|
+
<Deposit
|
|
1638
|
+
error="Your wallets failed to load. Reload the page to try again."
|
|
1639
|
+
heading="h4"
|
|
1640
|
+
/>
|
|
1641
|
+
),
|
|
1642
|
+
},
|
|
1643
|
+
],
|
|
1644
|
+
title: 'Deposit: unknown wallet',
|
|
1645
|
+
},
|
|
1646
|
+
{
|
|
1647
|
+
description:
|
|
1648
|
+
'A page path that no route serves responds with status 404 and the not-found screen, which links to the account page. An API path, such as `/v1/…` or `/oauth2/…`, responds with `unknown_endpoint` instead.',
|
|
1649
|
+
nodes: [
|
|
1650
|
+
{ detail: 'GET /nope', name: 'Browser' },
|
|
1651
|
+
{
|
|
1652
|
+
edge: '404',
|
|
1653
|
+
name: 'Page not found',
|
|
1654
|
+
screen: <NotFound heading="h4" />,
|
|
1655
|
+
},
|
|
1656
|
+
{
|
|
1657
|
+
edge: 'Go to your account, GET /',
|
|
1658
|
+
name: 'Your account',
|
|
1659
|
+
screen: <Account {...account()} />,
|
|
1660
|
+
},
|
|
1661
|
+
],
|
|
1662
|
+
title: 'Unknown page: 404',
|
|
1663
|
+
},
|
|
1664
|
+
],
|
|
1665
|
+
title: 'Account and deposit',
|
|
1666
|
+
},
|
|
1667
|
+
{
|
|
1668
|
+
flows: [
|
|
1669
|
+
{
|
|
1670
|
+
description:
|
|
1671
|
+
'A web app redirects a signed-out user to the sign-in page, which names the app of the request that Better Auth signs. A page on Tempo ID’s site can first prefetch the sign-in page’s assets from `GET /v1/auth/assets`.',
|
|
1672
|
+
nodes: [
|
|
1673
|
+
redirectEntry,
|
|
1674
|
+
{
|
|
1675
|
+
edge: 'Page /login?…&sig, GET /login: request without key',
|
|
1676
|
+
name: 'Sign in (app)',
|
|
1677
|
+
screen: <SignIn {...signIn()} request={appRequest()} />,
|
|
1678
|
+
},
|
|
1679
|
+
{
|
|
1680
|
+
edge: 'POST /v1/auth/code',
|
|
1681
|
+
name: 'Check your email',
|
|
1682
|
+
screen: <SignIn {...signIn()} email={email} />,
|
|
1683
|
+
},
|
|
1684
|
+
{
|
|
1685
|
+
edge: 'POST /v1/auth/code/verify with oauth_query: 200',
|
|
1686
|
+
name: 'Check your email (accepted)',
|
|
1687
|
+
screen: <SignIn {...signIn()} email={email} verified />,
|
|
1688
|
+
},
|
|
1689
|
+
{
|
|
1690
|
+
detail: 'GET redirect_uri?code&state',
|
|
1691
|
+
edge: 'after 500 ms, redirect',
|
|
1692
|
+
name: 'Web app callback',
|
|
1693
|
+
},
|
|
1694
|
+
tokenExit,
|
|
1695
|
+
],
|
|
1696
|
+
title: 'Redirect: signed out',
|
|
1697
|
+
},
|
|
1698
|
+
{
|
|
1699
|
+
description:
|
|
1700
|
+
'A signed-out user’s request with a key shows the app and the key on the sign-in page. The first sign-in of the request grants the key, so the browser returns to the app without the approval page.',
|
|
1701
|
+
nodes: [
|
|
1702
|
+
keyedEntry,
|
|
1703
|
+
{
|
|
1704
|
+
edge: 'Page /login?…&sig, GET /login: request',
|
|
1705
|
+
name: 'Sign in (key)',
|
|
1706
|
+
screen: <SignIn {...signIn()} request={signInRequest()} />,
|
|
1707
|
+
},
|
|
1708
|
+
{
|
|
1709
|
+
edge: 'POST /v1/auth/code',
|
|
1710
|
+
name: 'Check your email',
|
|
1711
|
+
screen: <SignIn {...signIn()} email={email} />,
|
|
1712
|
+
},
|
|
1713
|
+
{
|
|
1714
|
+
edge: 'POST /v1/auth/code/verify with oauth_query: 200, grants the key',
|
|
1715
|
+
name: 'Check your email (accepted)',
|
|
1716
|
+
screen: <SignIn {...signIn()} email={email} verified />,
|
|
1717
|
+
},
|
|
1718
|
+
{
|
|
1719
|
+
detail: 'GET redirect_uri?code&state',
|
|
1720
|
+
edge: 'after 500 ms, redirect',
|
|
1721
|
+
name: 'Web app callback',
|
|
1722
|
+
},
|
|
1723
|
+
{
|
|
1724
|
+
...tokenExit,
|
|
1725
|
+
detail: 'Tokens, account, and key_authorization',
|
|
1726
|
+
},
|
|
1727
|
+
],
|
|
1728
|
+
title: 'Sign-in grant',
|
|
1729
|
+
},
|
|
1730
|
+
{
|
|
1731
|
+
description:
|
|
1732
|
+
'On the sign-in page, the passkey’s challenge is the digest of the request’s key. One assertion signs the user in and signs the key, for an account that this passkey owns, or that Privy or secp256k1 owns.',
|
|
1733
|
+
nodes: [
|
|
1734
|
+
keyedEntry,
|
|
1735
|
+
{
|
|
1736
|
+
edge: 'Page /login?…&sig',
|
|
1737
|
+
name: 'Sign in (key)',
|
|
1738
|
+
screen: <SignIn {...signIn()} request={signInRequest()} />,
|
|
1739
|
+
},
|
|
1740
|
+
{
|
|
1741
|
+
detail:
|
|
1742
|
+
'POST /v1/auth/passkey/options with oauth_query, as the page shows: the key’s digest',
|
|
1743
|
+
edge: 'Sign in with a passkey',
|
|
1744
|
+
name: 'Authenticator prompt',
|
|
1745
|
+
},
|
|
1746
|
+
{
|
|
1747
|
+
detail: 'GET redirect_uri?code&state',
|
|
1748
|
+
edge: 'POST /v1/auth/passkey/verify with oauth_query, grants the key',
|
|
1749
|
+
name: 'Web app callback',
|
|
1750
|
+
},
|
|
1751
|
+
{
|
|
1752
|
+
...tokenExit,
|
|
1753
|
+
detail: 'Tokens, account, and key_authorization',
|
|
1754
|
+
},
|
|
1755
|
+
],
|
|
1756
|
+
title: 'Sign-in grant: passkey sign-in with key signature',
|
|
1757
|
+
},
|
|
1758
|
+
{
|
|
1759
|
+
description:
|
|
1760
|
+
'When the account reads are spent, the account step does not run, so a user with several accounts receives no grant. The approval page opens, and holds Approve until a reload after a minute.',
|
|
1761
|
+
nodes: [
|
|
1762
|
+
keyedEntry,
|
|
1763
|
+
{
|
|
1764
|
+
edge: 'Page /login?…&sig',
|
|
1765
|
+
name: 'Sign in (key)',
|
|
1766
|
+
screen: <SignIn {...signIn()} request={signInRequest()} />,
|
|
1767
|
+
},
|
|
1768
|
+
{
|
|
1769
|
+
edge: 'POST /v1/auth/code',
|
|
1770
|
+
name: 'Check your email',
|
|
1771
|
+
screen: <SignIn {...signIn()} email={email} />,
|
|
1772
|
+
},
|
|
1773
|
+
{
|
|
1774
|
+
edge: 'POST /v1/auth/code/verify with oauth_query: ACCOUNT_LIMIT spent, no account step, several accounts: no grant',
|
|
1775
|
+
name: 'Check your email (accepted)',
|
|
1776
|
+
screen: <SignIn {...signIn()} email={email} verified />,
|
|
1777
|
+
},
|
|
1778
|
+
{
|
|
1779
|
+
edge: 'after 500 ms, redirect /authorize?…&sig',
|
|
1780
|
+
name: 'Authorize this app (unavailable)',
|
|
1781
|
+
screen: (
|
|
1782
|
+
<Approval
|
|
1783
|
+
{...approval()}
|
|
1784
|
+
keyAuthorization={{ ...key(), refusal: 'unavailable' }}
|
|
1785
|
+
/>
|
|
1786
|
+
),
|
|
1787
|
+
},
|
|
1788
|
+
{
|
|
1789
|
+
edge: 'Reload after a minute',
|
|
1790
|
+
name: 'Authorize this app',
|
|
1791
|
+
screen: <Approval {...approval()} />,
|
|
1792
|
+
},
|
|
1793
|
+
{
|
|
1794
|
+
detail: 'GET redirect_uri?code&state',
|
|
1795
|
+
edge: 'Approve, POST /v1/authorize/consent',
|
|
1796
|
+
name: 'Web app callback',
|
|
1797
|
+
},
|
|
1798
|
+
{
|
|
1799
|
+
...tokenExit,
|
|
1800
|
+
detail: 'Tokens, account, and key_authorization',
|
|
1801
|
+
},
|
|
1802
|
+
],
|
|
1803
|
+
title: 'Sign-in grant: account reads spent',
|
|
1804
|
+
},
|
|
1805
|
+
{
|
|
1806
|
+
description:
|
|
1807
|
+
'A held grant covers only the request that the sign-in spent. A request with another `scope`, or with a `challenge`, takes no held grant on the account step, so the approval page opens.',
|
|
1808
|
+
nodes: [
|
|
1809
|
+
{
|
|
1810
|
+
detail: 'GET /choose-account?…&sig, another scope or a challenge',
|
|
1811
|
+
name: 'Browser',
|
|
1812
|
+
},
|
|
1813
|
+
{
|
|
1814
|
+
edge: '200 account step',
|
|
1815
|
+
name: 'Choose a wallet',
|
|
1816
|
+
screen: <ChooseAccount {...chooseAccount()} />,
|
|
1817
|
+
},
|
|
1818
|
+
{
|
|
1819
|
+
edge: 'Choose, POST /v1/authorize/account: another scope, no held grant, redirect /authorize?…&sig',
|
|
1820
|
+
name: 'Authorize this app (another scope)',
|
|
1821
|
+
screen: <Approval {...approval()} />,
|
|
1822
|
+
},
|
|
1823
|
+
{
|
|
1824
|
+
edge: 'Or: a challenge, which takes no grant, redirect /authorize?…&sig',
|
|
1825
|
+
name: 'Authorize this app (challenge)',
|
|
1826
|
+
screen: (
|
|
1827
|
+
<Approval
|
|
1828
|
+
{...approval()}
|
|
1829
|
+
keyAuthorization={{
|
|
1830
|
+
...key(),
|
|
1831
|
+
challenge: 'Sign in to api.example.com\nNonce: 8f2a91',
|
|
1832
|
+
}}
|
|
1833
|
+
/>
|
|
1834
|
+
),
|
|
1835
|
+
},
|
|
1836
|
+
{
|
|
1837
|
+
detail: 'GET redirect_uri?code&state',
|
|
1838
|
+
edge: 'Approve, POST /v1/authorize/consent',
|
|
1839
|
+
name: 'Web app callback',
|
|
1840
|
+
},
|
|
1841
|
+
{
|
|
1842
|
+
...tokenExit,
|
|
1843
|
+
detail: 'Tokens, account, and key_authorization',
|
|
1844
|
+
},
|
|
1845
|
+
],
|
|
1846
|
+
title: 'Sign-in grant: another request',
|
|
1847
|
+
},
|
|
1848
|
+
{
|
|
1849
|
+
description:
|
|
1850
|
+
'A user with several accounts in the app’s realms chooses one, and the choice takes the sign-in’s grant, which lasts 10 minutes. A passkey that owns the chosen account first signs the key on this step.',
|
|
1851
|
+
nodes: [
|
|
1852
|
+
keyedEntry,
|
|
1853
|
+
{
|
|
1854
|
+
edge: 'Page /login?…&sig',
|
|
1855
|
+
name: 'Sign in (key)',
|
|
1856
|
+
screen: <SignIn {...signIn()} request={signInRequest()} />,
|
|
1857
|
+
},
|
|
1858
|
+
{
|
|
1859
|
+
edge: 'POST /v1/auth/code',
|
|
1860
|
+
name: 'Check your email',
|
|
1861
|
+
screen: <SignIn {...signIn()} email={email} />,
|
|
1862
|
+
},
|
|
1863
|
+
{
|
|
1864
|
+
edge: 'POST /v1/auth/code/verify with oauth_query: 200, holds the grant',
|
|
1865
|
+
name: 'Check your email (accepted)',
|
|
1866
|
+
screen: <SignIn {...signIn()} email={email} verified />,
|
|
1867
|
+
},
|
|
1868
|
+
{
|
|
1869
|
+
edge: 'after 500 ms, redirect /choose-account?…&sig',
|
|
1870
|
+
name: 'Choose a wallet',
|
|
1871
|
+
screen: <ChooseAccount {...chooseAccount()} />,
|
|
1872
|
+
},
|
|
1873
|
+
{
|
|
1874
|
+
edge: 'Choose, POST /v1/authorize/account',
|
|
1875
|
+
name: 'Choose a wallet (selecting)',
|
|
1876
|
+
screen: (
|
|
1877
|
+
<ChooseAccount
|
|
1878
|
+
{...chooseAccount()}
|
|
1879
|
+
selecting={accounts[0].address}
|
|
1880
|
+
/>
|
|
1881
|
+
),
|
|
1882
|
+
},
|
|
1883
|
+
{
|
|
1884
|
+
detail: 'GET redirect_uri?code&state',
|
|
1885
|
+
edge: 'takes the held grant, redirect (a passkey account: passkey step, then POST again with the assertion)',
|
|
1886
|
+
name: 'Web app callback',
|
|
1887
|
+
},
|
|
1888
|
+
{
|
|
1889
|
+
...tokenExit,
|
|
1890
|
+
detail: 'Tokens, account, and key_authorization',
|
|
1891
|
+
},
|
|
1892
|
+
],
|
|
1893
|
+
title: 'Sign-in grant: account step',
|
|
1894
|
+
},
|
|
1895
|
+
{
|
|
1896
|
+
description:
|
|
1897
|
+
'A sign-in that cannot grant the key keeps the redirect to the approval page, which shows the request again. Causes include another passkey that owns the account, a replayed request, a refused key, a spent rate limit, or an upstream failure.',
|
|
1898
|
+
nodes: [
|
|
1899
|
+
keyedEntry,
|
|
1900
|
+
{
|
|
1901
|
+
edge: 'Page /login?…&sig',
|
|
1902
|
+
name: 'Sign in (key)',
|
|
1903
|
+
screen: <SignIn {...signIn()} request={signInRequest()} />,
|
|
1904
|
+
},
|
|
1905
|
+
{
|
|
1906
|
+
edge: 'Sign-in with oauth_query: the grant fails, redirect /authorize?…&sig',
|
|
1907
|
+
name: 'Authorize this app',
|
|
1908
|
+
screen: <Approval {...approval()} />,
|
|
1909
|
+
},
|
|
1910
|
+
{
|
|
1911
|
+
edge: 'Approve, POST /v1/authorize/consent',
|
|
1912
|
+
name: 'Authorize this app (approving)',
|
|
1913
|
+
screen: <Approval {...approval()} approving />,
|
|
1914
|
+
},
|
|
1915
|
+
{
|
|
1916
|
+
detail: 'GET redirect_uri?code&state',
|
|
1917
|
+
edge: 'redirect, or the passkey step first',
|
|
1918
|
+
name: 'Web app callback',
|
|
1919
|
+
},
|
|
1920
|
+
{
|
|
1921
|
+
...tokenExit,
|
|
1922
|
+
detail: 'Tokens, account, and key_authorization',
|
|
1923
|
+
},
|
|
1924
|
+
],
|
|
1925
|
+
title: 'Sign-in grant: falls back to approval',
|
|
1926
|
+
},
|
|
1927
|
+
{
|
|
1928
|
+
description:
|
|
1929
|
+
'The account step’s signed query expires 10 minutes after the sign-in, just before its held grant. A later choice is refused with `authorization_expired`, so the user starts again in the app.',
|
|
1930
|
+
nodes: [
|
|
1931
|
+
keyedEntry,
|
|
1932
|
+
{
|
|
1933
|
+
edge: 'Page /login?…&sig',
|
|
1934
|
+
name: 'Sign in (key)',
|
|
1935
|
+
screen: <SignIn {...signIn()} request={signInRequest()} />,
|
|
1936
|
+
},
|
|
1937
|
+
{
|
|
1938
|
+
edge: 'POST /v1/auth/code',
|
|
1939
|
+
name: 'Check your email',
|
|
1940
|
+
screen: <SignIn {...signIn()} email={email} />,
|
|
1941
|
+
},
|
|
1942
|
+
{
|
|
1943
|
+
edge: 'POST /v1/auth/code/verify with oauth_query: 200, holds the grant',
|
|
1944
|
+
name: 'Check your email (accepted)',
|
|
1945
|
+
screen: <SignIn {...signIn()} email={email} verified />,
|
|
1946
|
+
},
|
|
1947
|
+
{
|
|
1948
|
+
edge: 'after 500 ms, redirect /choose-account?…&sig',
|
|
1949
|
+
name: 'Choose a wallet',
|
|
1950
|
+
screen: <ChooseAccount {...chooseAccount()} />,
|
|
1951
|
+
},
|
|
1952
|
+
{
|
|
1953
|
+
edge: 'After 10 minutes, Choose, POST /v1/authorize/account: authorization_expired',
|
|
1954
|
+
name: 'Choose a wallet (request expired)',
|
|
1955
|
+
screen: (
|
|
1956
|
+
<ChooseAccount
|
|
1957
|
+
{...chooseAccount()}
|
|
1958
|
+
error="Expired authorization request: the request from the app is no longer valid. Return to the app, and start again."
|
|
1959
|
+
/>
|
|
1960
|
+
),
|
|
1961
|
+
},
|
|
1962
|
+
{
|
|
1963
|
+
detail:
|
|
1964
|
+
'GET /oauth2/authorize?…&key_authorization, a new request',
|
|
1965
|
+
edge: 'Return to the app, and start again',
|
|
1966
|
+
name: 'Web app',
|
|
1967
|
+
},
|
|
1968
|
+
],
|
|
1969
|
+
title: 'Sign-in grant: account step expired',
|
|
1970
|
+
},
|
|
1971
|
+
{
|
|
1972
|
+
description:
|
|
1973
|
+
'A request with a `challenge` signs a message that only the approval page shows. So the sign-in page names the app but shows no key, and the sign-in grants nothing.',
|
|
1974
|
+
nodes: [
|
|
1975
|
+
{
|
|
1976
|
+
...keyedEntry,
|
|
1977
|
+
detail: 'GET /oauth2/authorize?…&key_authorization&challenge',
|
|
1978
|
+
},
|
|
1979
|
+
{
|
|
1980
|
+
edge: 'Page /login?…&sig, GET /login: request without key',
|
|
1981
|
+
name: 'Sign in (app)',
|
|
1982
|
+
screen: <SignIn {...signIn()} request={appRequest()} />,
|
|
1983
|
+
},
|
|
1984
|
+
{
|
|
1985
|
+
edge: 'POST /v1/auth/code',
|
|
1986
|
+
name: 'Check your email',
|
|
1987
|
+
screen: <SignIn {...signIn()} email={email} />,
|
|
1988
|
+
},
|
|
1989
|
+
{
|
|
1990
|
+
edge: 'POST /v1/auth/code/verify with oauth_query: 200',
|
|
1991
|
+
name: 'Check your email (accepted)',
|
|
1992
|
+
screen: <SignIn {...signIn()} email={email} verified />,
|
|
1993
|
+
},
|
|
1994
|
+
{
|
|
1995
|
+
edge: 'after 500 ms, redirect /authorize?…&sig',
|
|
1996
|
+
name: 'Authorize this app (challenge)',
|
|
1997
|
+
screen: (
|
|
1998
|
+
<Approval
|
|
1999
|
+
{...approval()}
|
|
2000
|
+
keyAuthorization={{
|
|
2001
|
+
...key(),
|
|
2002
|
+
challenge: 'Sign in to api.example.com\nNonce: 8f2a91',
|
|
2003
|
+
}}
|
|
2004
|
+
/>
|
|
2005
|
+
),
|
|
2006
|
+
},
|
|
2007
|
+
{
|
|
2008
|
+
detail: 'GET redirect_uri?code&state',
|
|
2009
|
+
edge: 'Approve, POST /v1/authorize/consent',
|
|
2010
|
+
name: 'Web app callback',
|
|
2011
|
+
},
|
|
2012
|
+
{
|
|
2013
|
+
...tokenExit,
|
|
2014
|
+
detail: 'Tokens, account, and key_authorization',
|
|
2015
|
+
},
|
|
2016
|
+
],
|
|
2017
|
+
title: 'Sign-in grant: with a challenge',
|
|
2018
|
+
},
|
|
2019
|
+
{
|
|
2020
|
+
description:
|
|
2021
|
+
'A signed-in user returns to an app that skips consent at once. Tempo ID shows no screen.',
|
|
2022
|
+
nodes: [
|
|
2023
|
+
redirectEntry,
|
|
2024
|
+
{
|
|
2025
|
+
detail: 'GET redirect_uri?code&state',
|
|
2026
|
+
edge: '302, no screen',
|
|
2027
|
+
name: 'Web app callback',
|
|
2028
|
+
},
|
|
2029
|
+
tokenExit,
|
|
2030
|
+
],
|
|
2031
|
+
title: 'Redirect: signed in',
|
|
2032
|
+
},
|
|
2033
|
+
{
|
|
2034
|
+
description:
|
|
2035
|
+
'A request with `prompt=login` asks a signed-in user to sign in again. The sign-in page shows the request, and its key, despite the session.',
|
|
2036
|
+
nodes: [
|
|
2037
|
+
{
|
|
2038
|
+
...keyedEntry,
|
|
2039
|
+
detail: 'GET /oauth2/authorize?…&key_authorization&prompt=login',
|
|
2040
|
+
},
|
|
2041
|
+
{
|
|
2042
|
+
edge: 'Page /login?…&prompt=login consent&sig',
|
|
2043
|
+
name: 'Sign in (key)',
|
|
2044
|
+
screen: <SignIn {...signIn()} request={signInRequest()} />,
|
|
2045
|
+
},
|
|
2046
|
+
{
|
|
2047
|
+
edge: 'POST /v1/auth/code',
|
|
2048
|
+
name: 'Check your email',
|
|
2049
|
+
screen: <SignIn {...signIn()} email={email} />,
|
|
2050
|
+
},
|
|
2051
|
+
{
|
|
2052
|
+
edge: 'POST /v1/auth/code/verify with oauth_query: 200, grants the key',
|
|
2053
|
+
name: 'Check your email (accepted)',
|
|
2054
|
+
screen: <SignIn {...signIn()} email={email} verified />,
|
|
2055
|
+
},
|
|
2056
|
+
{
|
|
2057
|
+
detail: 'GET redirect_uri?code&state',
|
|
2058
|
+
edge: 'after 500 ms, redirect',
|
|
2059
|
+
name: 'Web app callback',
|
|
2060
|
+
},
|
|
2061
|
+
{
|
|
2062
|
+
...tokenExit,
|
|
2063
|
+
detail: 'Tokens, account, and key_authorization',
|
|
2064
|
+
},
|
|
2065
|
+
],
|
|
2066
|
+
title: 'Redirect: prompt=login',
|
|
2067
|
+
},
|
|
2068
|
+
{
|
|
2069
|
+
description:
|
|
2070
|
+
'A signed-in user approves a third-party app, which never skips consent, once for the scopes it requests. The request carries no key, so the page shows none.',
|
|
2071
|
+
nodes: [
|
|
2072
|
+
redirectEntry,
|
|
2073
|
+
{
|
|
2074
|
+
edge: 'Page /authorize?…&sig',
|
|
2075
|
+
name: 'Authorize this app (no key)',
|
|
2076
|
+
screen: <Approval {...approval()} keyAuthorization={undefined} />,
|
|
2077
|
+
},
|
|
2078
|
+
{
|
|
2079
|
+
detail: 'GET redirect_uri?code&state',
|
|
2080
|
+
edge: 'POST /v1/authorize/consent, accept',
|
|
2081
|
+
name: 'Web app callback',
|
|
2082
|
+
},
|
|
2083
|
+
tokenExit,
|
|
2084
|
+
],
|
|
2085
|
+
title: 'Approval: no key',
|
|
2086
|
+
},
|
|
2087
|
+
{
|
|
2088
|
+
description:
|
|
2089
|
+
'A signed-in user’s request with a key opens the approval page, which holds Approve until the account read streams in. A Privy or secp256k1 owner signs the key on the server when the user approves.',
|
|
2090
|
+
nodes: [
|
|
2091
|
+
{
|
|
2092
|
+
...redirectEntry,
|
|
2093
|
+
detail: 'GET /oauth2/authorize?…&key_authorization',
|
|
2094
|
+
},
|
|
2095
|
+
{
|
|
2096
|
+
edge: 'Page /authorize?…&sig, GET /i/authorize/request',
|
|
2097
|
+
name: 'Authorize this app (checking)',
|
|
2098
|
+
screen: <Approval {...approval()} checking />,
|
|
2099
|
+
},
|
|
2100
|
+
{
|
|
2101
|
+
edge: 'GET /v1/authorize/request streams in: the account and any refusal',
|
|
2102
|
+
name: 'Authorize this app',
|
|
2103
|
+
screen: <Approval {...approval()} />,
|
|
2104
|
+
},
|
|
2105
|
+
{
|
|
2106
|
+
edge: 'Approve, POST /v1/authorize/consent',
|
|
2107
|
+
name: 'Authorize this app (approving)',
|
|
2108
|
+
screen: <Approval {...approval()} approving />,
|
|
2109
|
+
},
|
|
2110
|
+
{
|
|
2111
|
+
detail: 'GET redirect_uri?code&state',
|
|
2112
|
+
edge: 'redirect',
|
|
2113
|
+
name: 'Web app callback',
|
|
2114
|
+
},
|
|
2115
|
+
{
|
|
2116
|
+
...tokenExit,
|
|
2117
|
+
detail: 'Tokens, account, and key_authorization',
|
|
2118
|
+
},
|
|
2119
|
+
],
|
|
2120
|
+
title: 'Approval: key',
|
|
2121
|
+
},
|
|
2122
|
+
{
|
|
2123
|
+
description:
|
|
2124
|
+
'A key on a chain other than mainnet shows its chain as a gray tag on the permissions card. The tag shows the name that `GET /v1/chains` lists, or else the chain ID.',
|
|
2125
|
+
nodes: [
|
|
2126
|
+
{
|
|
2127
|
+
...redirectEntry,
|
|
2128
|
+
detail: 'GET /oauth2/authorize?…&key_authorization with chainId',
|
|
2129
|
+
},
|
|
2130
|
+
{
|
|
2131
|
+
edge: 'Page /authorize?…&sig',
|
|
2132
|
+
name: 'Authorize this app (another chain)',
|
|
2133
|
+
screen: (
|
|
2134
|
+
<Approval
|
|
2135
|
+
{...approval()}
|
|
2136
|
+
keyAuthorization={{
|
|
2137
|
+
...key(),
|
|
2138
|
+
chain: { id: 31318, name: 't14' },
|
|
2139
|
+
}}
|
|
2140
|
+
/>
|
|
2141
|
+
),
|
|
2142
|
+
},
|
|
2143
|
+
{
|
|
2144
|
+
detail: 'GET redirect_uri?code&state',
|
|
2145
|
+
edge: 'Approve, POST /v1/authorize/consent',
|
|
2146
|
+
name: 'Web app callback',
|
|
2147
|
+
},
|
|
2148
|
+
{
|
|
2149
|
+
...tokenExit,
|
|
2150
|
+
detail: 'Tokens, account, and key_authorization',
|
|
2151
|
+
},
|
|
2152
|
+
],
|
|
2153
|
+
title: 'Approval: key on another chain',
|
|
2154
|
+
},
|
|
2155
|
+
{
|
|
2156
|
+
description:
|
|
2157
|
+
'A third-party app names its metadata document’s URL, or its origin, as `client_id`, and registers nothing. The page shows the host beside the name, marks no name as verified, and grants only keys that name expiry, limits, and scopes.',
|
|
2158
|
+
nodes: [
|
|
2159
|
+
{
|
|
2160
|
+
detail:
|
|
2161
|
+
'GET /oauth2/authorize?client_id=https://app.example.com/.well-known/oauth-client-metadata.json&key_authorization',
|
|
2162
|
+
name: 'Third-party app',
|
|
2163
|
+
},
|
|
2164
|
+
{
|
|
2165
|
+
edge: "GET client_id: the metadata document or the origin's manifest, page /authorize?…&sig",
|
|
2166
|
+
name: 'Authorize this app (third-party)',
|
|
2167
|
+
screen: (
|
|
2168
|
+
<Approval
|
|
2169
|
+
{...approval()}
|
|
2170
|
+
app="Example"
|
|
2171
|
+
destination="app.example.com"
|
|
2172
|
+
verified={false}
|
|
2173
|
+
/>
|
|
2174
|
+
),
|
|
2175
|
+
},
|
|
2176
|
+
{
|
|
2177
|
+
detail: 'GET redirect_uri?code&state',
|
|
2178
|
+
edge: 'Approve, POST /v1/authorize/consent',
|
|
2179
|
+
name: 'Third-party app callback',
|
|
2180
|
+
},
|
|
2181
|
+
{
|
|
2182
|
+
detail: 'Tokens, account, and key_authorization',
|
|
2183
|
+
edge: 'POST /oauth2/token with code_verifier',
|
|
2184
|
+
name: 'Third-party app',
|
|
2185
|
+
},
|
|
2186
|
+
],
|
|
2187
|
+
title: 'Third-party app',
|
|
2188
|
+
},
|
|
2189
|
+
{
|
|
2190
|
+
description:
|
|
2191
|
+
'A third-party app’s key that leaves out its expiry, limits, or scopes returns to the app with `missing_permissions`, before sign-in.',
|
|
2192
|
+
nodes: [
|
|
2193
|
+
{
|
|
2194
|
+
detail:
|
|
2195
|
+
'GET /oauth2/authorize?client_id=https://app.example.com/.well-known/oauth-client-metadata.json&key_authorization (no limits)',
|
|
2196
|
+
name: 'Third-party app',
|
|
2197
|
+
},
|
|
2198
|
+
{
|
|
2199
|
+
detail: 'GET redirect_uri?error=invalid_authorization_details',
|
|
2200
|
+
edge: 'missing_permissions',
|
|
2201
|
+
name: 'Third-party app callback',
|
|
2202
|
+
},
|
|
2203
|
+
],
|
|
2204
|
+
title: 'Third-party app: missing permissions',
|
|
2205
|
+
},
|
|
2206
|
+
{
|
|
2207
|
+
description:
|
|
2208
|
+
'A passkey owns the account, so the approval returns the passkey step. The passkey signs the key’s digest, and the page approves again.',
|
|
2209
|
+
nodes: [
|
|
2210
|
+
{
|
|
2211
|
+
...redirectEntry,
|
|
2212
|
+
detail: 'GET /oauth2/authorize?…&key_authorization',
|
|
2213
|
+
},
|
|
2214
|
+
{
|
|
2215
|
+
edge: 'Page /authorize?…&sig',
|
|
2216
|
+
name: 'Authorize this app',
|
|
2217
|
+
screen: <Approval {...approval()} />,
|
|
2218
|
+
},
|
|
2219
|
+
{
|
|
2220
|
+
edge: 'Approve, POST /v1/authorize/consent',
|
|
2221
|
+
name: 'Authorize this app (approving)',
|
|
2222
|
+
screen: <Approval {...approval()} approving />,
|
|
2223
|
+
},
|
|
2224
|
+
{
|
|
2225
|
+
detail: 'Signs the key authorization, over the approving page',
|
|
2226
|
+
edge: 'step passkey',
|
|
2227
|
+
name: 'Authenticator prompt',
|
|
2228
|
+
},
|
|
2229
|
+
{
|
|
2230
|
+
detail: 'GET redirect_uri?code&state',
|
|
2231
|
+
edge: 'POST /v1/authorize/consent with passkey.assertion',
|
|
2232
|
+
name: 'Web app callback',
|
|
2233
|
+
},
|
|
2234
|
+
{
|
|
2235
|
+
...tokenExit,
|
|
2236
|
+
detail: 'Tokens, account, and key_authorization',
|
|
2237
|
+
},
|
|
2238
|
+
],
|
|
2239
|
+
title: 'Approval: passkey account',
|
|
2240
|
+
},
|
|
2241
|
+
{
|
|
2242
|
+
description:
|
|
2243
|
+
'When a wallet.tempo passkey’s wallet waits for its upgrade, Approve shows the upgrade step first. The approval follows the upgrade, so the browser returns to the app.',
|
|
2244
|
+
nodes: [
|
|
2245
|
+
{
|
|
2246
|
+
...redirectEntry,
|
|
2247
|
+
detail: 'GET /oauth2/authorize?…&key_authorization',
|
|
2248
|
+
},
|
|
2249
|
+
{
|
|
2250
|
+
edge: 'Page /authorize?…&sig, GET /v1/authorize/request: refusal upgrade, GET /i/account/passkeys for a passkey that has not linked, POST /v1/account/upgrade, the passkey step',
|
|
2251
|
+
name: 'Authorize this app',
|
|
2252
|
+
screen: <Approval {...approval()} />,
|
|
2253
|
+
},
|
|
2254
|
+
{
|
|
2255
|
+
edge: 'Approve',
|
|
2256
|
+
name: 'Upgrade your wallet',
|
|
2257
|
+
screen: <WalletUpgrade {...walletUpgrade()} />,
|
|
2258
|
+
},
|
|
2259
|
+
{
|
|
2260
|
+
edge: 'Or: POST /v1/account/upgrade: reauthentication_required, then Sign in again, POST /v1/auth/logout, and the sign-in page',
|
|
2261
|
+
name: 'Upgrade your wallet (sign in again)',
|
|
2262
|
+
screen: <WalletUpgrade {...walletUpgrade()} onSignIn={noop} />,
|
|
2263
|
+
},
|
|
2264
|
+
{
|
|
2265
|
+
detail:
|
|
2266
|
+
'Signs the upgrade, then the key authorization, over the upgrading step',
|
|
2267
|
+
edge: 'Continue, POST /v1/account/upgrade with passkey.assertion, then POST /v1/authorize/consent, step passkey',
|
|
2268
|
+
name: 'Authenticator prompt',
|
|
2269
|
+
},
|
|
2270
|
+
{
|
|
2271
|
+
detail: 'GET redirect_uri?code&state',
|
|
2272
|
+
edge: 'POST /v1/authorize/consent with passkey.assertion',
|
|
2273
|
+
name: 'Web app callback',
|
|
2274
|
+
},
|
|
2275
|
+
{
|
|
2276
|
+
...tokenExit,
|
|
2277
|
+
detail: 'Tokens, account, and key_authorization',
|
|
2278
|
+
},
|
|
2279
|
+
],
|
|
2280
|
+
title: 'Approval: upgrade after Approve',
|
|
2281
|
+
},
|
|
2282
|
+
{
|
|
2283
|
+
description:
|
|
2284
|
+
'Not now on the upgrade step approves the request without the upgrade, and the passkey signs the key as its own account. The key stops working on that chain once the wallet upgrades, and the account page and the next approval offer the upgrade again.',
|
|
2285
|
+
nodes: [
|
|
2286
|
+
{
|
|
2287
|
+
...redirectEntry,
|
|
2288
|
+
detail: 'GET /oauth2/authorize?…&key_authorization',
|
|
2289
|
+
},
|
|
2290
|
+
{
|
|
2291
|
+
edge: 'Page /authorize?…&sig, GET /v1/authorize/request: refusal upgrade, then Approve',
|
|
2292
|
+
name: 'Upgrade your wallet',
|
|
2293
|
+
screen: <WalletUpgrade {...walletUpgrade()} />,
|
|
2294
|
+
},
|
|
2295
|
+
{
|
|
2296
|
+
edge: 'Not now, POST /v1/authorize/consent with skip_upgrade',
|
|
2297
|
+
name: 'Upgrade your wallet (skipping)',
|
|
2298
|
+
screen: <WalletUpgrade {...walletUpgrade()} skipping />,
|
|
2299
|
+
},
|
|
2300
|
+
{
|
|
2301
|
+
detail:
|
|
2302
|
+
'Signs the key authorization as the passkey’s own account, over the skipping step',
|
|
2303
|
+
edge: 'step passkey',
|
|
2304
|
+
name: 'Authenticator prompt',
|
|
2305
|
+
},
|
|
2306
|
+
{
|
|
2307
|
+
detail: 'GET redirect_uri?code&state',
|
|
2308
|
+
edge: 'POST /v1/authorize/consent with skip_upgrade and passkey.assertion, which links a passkey that has not linked',
|
|
2309
|
+
name: 'Web app callback',
|
|
2310
|
+
},
|
|
2311
|
+
{
|
|
2312
|
+
...tokenExit,
|
|
2313
|
+
detail: 'Tokens, account, and key_authorization',
|
|
2314
|
+
},
|
|
2315
|
+
],
|
|
2316
|
+
title: 'Approval: skip the upgrade',
|
|
2317
|
+
},
|
|
2318
|
+
{
|
|
2319
|
+
description:
|
|
2320
|
+
'An app with the `tempo-id:sign` scope sends a challenge, and the approval page shows the message that the key signs.',
|
|
2321
|
+
nodes: [
|
|
2322
|
+
{
|
|
2323
|
+
detail: 'GET /oauth2/authorize?…&key_authorization&challenge',
|
|
2324
|
+
name: 'Native app',
|
|
2325
|
+
},
|
|
2326
|
+
{
|
|
2327
|
+
edge: 'Page /authorize?…&sig',
|
|
2328
|
+
name: 'Authorize this app (challenge)',
|
|
2329
|
+
screen: (
|
|
2330
|
+
<Approval
|
|
2331
|
+
{...approval()}
|
|
2332
|
+
app="Tempo Mobile"
|
|
2333
|
+
destination="tempo"
|
|
2334
|
+
keyAuthorization={{
|
|
2335
|
+
...key(),
|
|
2336
|
+
challenge: 'Sign in to api.example.com\nNonce: 8f2a91',
|
|
2337
|
+
}}
|
|
2338
|
+
/>
|
|
2339
|
+
),
|
|
2340
|
+
},
|
|
2341
|
+
{
|
|
2342
|
+
detail: 'tempo:/callback?code&state',
|
|
2343
|
+
edge: 'POST /v1/authorize/consent, accept',
|
|
2344
|
+
name: 'Native app callback',
|
|
2345
|
+
},
|
|
2346
|
+
{
|
|
2347
|
+
...tokenExit,
|
|
2348
|
+
detail: 'Tokens, account, and key_authorization',
|
|
2349
|
+
name: 'Native app',
|
|
2350
|
+
},
|
|
2351
|
+
],
|
|
2352
|
+
title: 'Approval: challenge',
|
|
2353
|
+
},
|
|
2354
|
+
{
|
|
2355
|
+
description:
|
|
2356
|
+
'Beside its limits, the approval page lists the `open` and `topUp` calls on the channel reserve as funds held for services. Its info icon links to the Machine Payments Protocol.',
|
|
2357
|
+
nodes: [
|
|
2358
|
+
{
|
|
2359
|
+
...redirectEntry,
|
|
2360
|
+
detail: 'GET /oauth2/authorize?…&key_authorization',
|
|
2361
|
+
},
|
|
2362
|
+
{
|
|
2363
|
+
edge: 'Page /authorize?…&sig',
|
|
2364
|
+
name: 'Authorize this app (MPP sessions)',
|
|
2365
|
+
screen: (
|
|
2366
|
+
<Approval
|
|
2367
|
+
{...approval()}
|
|
2368
|
+
keyAuthorization={{
|
|
2369
|
+
...key(),
|
|
2370
|
+
calls: [{ call: 'open' }, { call: 'topUp' }],
|
|
2371
|
+
}}
|
|
2372
|
+
/>
|
|
2373
|
+
),
|
|
2374
|
+
},
|
|
2375
|
+
{
|
|
2376
|
+
detail: 'GET redirect_uri?code&state',
|
|
2377
|
+
edge: 'POST /v1/authorize/consent, accept',
|
|
2378
|
+
name: 'Web app callback',
|
|
2379
|
+
},
|
|
2380
|
+
{
|
|
2381
|
+
...tokenExit,
|
|
2382
|
+
detail: 'Tokens, account, and key_authorization',
|
|
2383
|
+
},
|
|
2384
|
+
],
|
|
2385
|
+
title: 'Approval: MPP sessions',
|
|
2386
|
+
},
|
|
2387
|
+
{
|
|
2388
|
+
description:
|
|
2389
|
+
'The approval page lists a key’s calls that Tempo ID does not recognize, such as a function on another contract, with a warning. A headless request refuses them unless the app holds the `tempo-id:sign` scope.',
|
|
2390
|
+
nodes: [
|
|
2391
|
+
{
|
|
2392
|
+
...redirectEntry,
|
|
2393
|
+
detail: 'GET /oauth2/authorize?…&key_authorization',
|
|
2394
|
+
},
|
|
2395
|
+
{
|
|
2396
|
+
edge: 'Page /authorize?…&sig',
|
|
2397
|
+
name: 'Authorize this app (unrecognized calls)',
|
|
2398
|
+
screen: (
|
|
2399
|
+
<Approval
|
|
2400
|
+
{...approval()}
|
|
2401
|
+
keyAuthorization={{
|
|
2402
|
+
...key(),
|
|
2403
|
+
unrecognized: [
|
|
2404
|
+
{
|
|
2405
|
+
address: '0xdec0000000000000000000000000000000000000',
|
|
2406
|
+
selector: '0x12345678',
|
|
2407
|
+
},
|
|
2408
|
+
],
|
|
2409
|
+
}}
|
|
2410
|
+
/>
|
|
2411
|
+
),
|
|
2412
|
+
},
|
|
2413
|
+
{
|
|
2414
|
+
detail: 'GET redirect_uri?code&state',
|
|
2415
|
+
edge: 'POST /v1/authorize/consent, accept',
|
|
2416
|
+
name: 'Web app callback',
|
|
2417
|
+
},
|
|
2418
|
+
{
|
|
2419
|
+
...tokenExit,
|
|
2420
|
+
detail: 'Tokens, account, and key_authorization',
|
|
2421
|
+
},
|
|
2422
|
+
],
|
|
2423
|
+
title: 'Approval: unrecognized calls',
|
|
2424
|
+
},
|
|
2425
|
+
{
|
|
2426
|
+
description:
|
|
2427
|
+
'A user with more than one Tempo Account in the app’s realms chooses one, which continues to the approval page. Each row shows the account’s balance on the key’s chain, or Balance unavailable when the read fails.',
|
|
2428
|
+
nodes: [
|
|
2429
|
+
{
|
|
2430
|
+
...redirectEntry,
|
|
2431
|
+
detail: 'GET /oauth2/authorize?…&key_authorization',
|
|
2432
|
+
},
|
|
2433
|
+
{
|
|
2434
|
+
edge: 'Page /choose-account?…&sig',
|
|
2435
|
+
name: 'Choose a wallet',
|
|
2436
|
+
screen: <ChooseAccount {...chooseAccount()} />,
|
|
2437
|
+
},
|
|
2438
|
+
{
|
|
2439
|
+
edge: 'Or: a balance read fails',
|
|
2440
|
+
name: 'Choose a wallet (balance unavailable)',
|
|
2441
|
+
screen: (
|
|
2442
|
+
<ChooseAccount
|
|
2443
|
+
{...chooseAccount()}
|
|
2444
|
+
accounts={[accounts[0], { ...accounts[1], balance: null }]}
|
|
2445
|
+
/>
|
|
2446
|
+
),
|
|
2447
|
+
},
|
|
2448
|
+
{
|
|
2449
|
+
edge: 'Choose, POST /v1/authorize/account',
|
|
2450
|
+
name: 'Choose a wallet (selecting)',
|
|
2451
|
+
screen: (
|
|
2452
|
+
<ChooseAccount
|
|
2453
|
+
{...chooseAccount()}
|
|
2454
|
+
selecting={accounts[0].address}
|
|
2455
|
+
/>
|
|
2456
|
+
),
|
|
2457
|
+
},
|
|
2458
|
+
{
|
|
2459
|
+
edge: 'redirect /authorize?…&sig, GET /v1/authorize/request: account',
|
|
2460
|
+
name: 'Authorize this app (account)',
|
|
2461
|
+
screen: <Approval {...approval()} account={selectedAccount()} />,
|
|
2462
|
+
},
|
|
2463
|
+
{
|
|
2464
|
+
detail: 'GET redirect_uri?code&state',
|
|
2465
|
+
edge: 'POST /v1/authorize/consent, accept',
|
|
2466
|
+
name: 'Web app callback',
|
|
2467
|
+
},
|
|
2468
|
+
{
|
|
2469
|
+
...tokenExit,
|
|
2470
|
+
detail: 'Tokens, account, and key_authorization',
|
|
2471
|
+
},
|
|
2472
|
+
],
|
|
2473
|
+
title: 'Account step',
|
|
2474
|
+
},
|
|
2475
|
+
{
|
|
2476
|
+
description:
|
|
2477
|
+
'A user with several Tempo Accounts who returns in the same session skips the account step, and the approval page shows the selected account. Sign-out from Tempo ID ends the selection.',
|
|
2478
|
+
nodes: [
|
|
2479
|
+
{
|
|
2480
|
+
...redirectEntry,
|
|
2481
|
+
detail: 'GET /oauth2/authorize?…&key_authorization',
|
|
2482
|
+
},
|
|
2483
|
+
{
|
|
2484
|
+
edge: 'Page /authorize?…&sig, GET /v1/authorize/request: account',
|
|
2485
|
+
name: 'Authorize this app (selected account)',
|
|
2486
|
+
screen: <Approval {...approval()} account={selectedAccount()} />,
|
|
2487
|
+
},
|
|
2488
|
+
{
|
|
2489
|
+
detail: 'GET redirect_uri?code&state',
|
|
2490
|
+
edge: 'POST /v1/authorize/consent, accept',
|
|
2491
|
+
name: 'Web app callback',
|
|
2492
|
+
},
|
|
2493
|
+
{
|
|
2494
|
+
...tokenExit,
|
|
2495
|
+
detail: 'Tokens, account, and key_authorization',
|
|
2496
|
+
},
|
|
2497
|
+
],
|
|
2498
|
+
title: 'Account step: selection kept',
|
|
2499
|
+
},
|
|
2500
|
+
{
|
|
2501
|
+
description:
|
|
2502
|
+
'An app in several realms, such as web-shared, lists accounts from each. Each row then names its realm, apart from the `default` realm.',
|
|
2503
|
+
nodes: [
|
|
2504
|
+
{
|
|
2505
|
+
...redirectEntry,
|
|
2506
|
+
detail: 'GET /oauth2/authorize?…&key_authorization',
|
|
2507
|
+
name: 'Web app in two realms',
|
|
2508
|
+
},
|
|
2509
|
+
{
|
|
2510
|
+
edge: 'Page /choose-account?…&sig',
|
|
2511
|
+
name: 'Choose a wallet (two realms)',
|
|
2512
|
+
screen: (
|
|
2513
|
+
<ChooseAccount
|
|
2514
|
+
{...chooseAccount()}
|
|
2515
|
+
accounts={[
|
|
2516
|
+
{ ...accounts[0], realm: 'Tempo Cash' },
|
|
2517
|
+
accounts[1],
|
|
2518
|
+
]}
|
|
2519
|
+
/>
|
|
2520
|
+
),
|
|
2521
|
+
},
|
|
2522
|
+
{
|
|
2523
|
+
edge: 'Choose, POST /v1/authorize/account',
|
|
2524
|
+
name: 'Choose a wallet (two realms, selecting)',
|
|
2525
|
+
screen: (
|
|
2526
|
+
<ChooseAccount
|
|
2527
|
+
{...chooseAccount()}
|
|
2528
|
+
accounts={[
|
|
2529
|
+
{ ...accounts[0], realm: 'Tempo Cash' },
|
|
2530
|
+
accounts[1],
|
|
2531
|
+
]}
|
|
2532
|
+
selecting={accounts[0].address}
|
|
2533
|
+
/>
|
|
2534
|
+
),
|
|
2535
|
+
},
|
|
2536
|
+
{
|
|
2537
|
+
edge: 'redirect /authorize?…&sig',
|
|
2538
|
+
name: 'Authorize this app',
|
|
2539
|
+
screen: <Approval {...approval()} />,
|
|
2540
|
+
},
|
|
2541
|
+
{
|
|
2542
|
+
detail: 'GET redirect_uri?code&state',
|
|
2543
|
+
edge: 'POST /v1/authorize/consent, accept',
|
|
2544
|
+
name: 'Web app callback',
|
|
2545
|
+
},
|
|
2546
|
+
{
|
|
2547
|
+
...tokenExit,
|
|
2548
|
+
detail: 'Tokens, account, and key_authorization',
|
|
2549
|
+
},
|
|
2550
|
+
],
|
|
2551
|
+
title: 'Account step: several realms',
|
|
2552
|
+
},
|
|
2553
|
+
{
|
|
2554
|
+
description:
|
|
2555
|
+
'Denying returns the browser to the app with `error=access_denied`. The app receives no code.',
|
|
2556
|
+
nodes: [
|
|
2557
|
+
{
|
|
2558
|
+
...redirectEntry,
|
|
2559
|
+
detail: 'GET /oauth2/authorize?…&key_authorization',
|
|
2560
|
+
},
|
|
2561
|
+
{
|
|
2562
|
+
edge: 'Page /authorize?…&sig',
|
|
2563
|
+
name: 'Authorize this app',
|
|
2564
|
+
screen: <Approval {...approval()} />,
|
|
2565
|
+
},
|
|
2566
|
+
{
|
|
2567
|
+
edge: 'Deny',
|
|
2568
|
+
name: 'Authorize this app (denying)',
|
|
2569
|
+
screen: <Approval {...approval()} denying />,
|
|
2570
|
+
},
|
|
2571
|
+
{
|
|
2572
|
+
detail: 'GET redirect_uri?error=access_denied&state',
|
|
2573
|
+
edge: 'POST /v1/authorize/consent, accept: false',
|
|
2574
|
+
name: 'Web app callback',
|
|
2575
|
+
},
|
|
2576
|
+
],
|
|
2577
|
+
title: 'Approval: denied',
|
|
2578
|
+
},
|
|
2579
|
+
{
|
|
2580
|
+
description:
|
|
2581
|
+
'Approval cannot sign some keys, so the page holds Approve and states why. Deny still returns to the app.',
|
|
2582
|
+
nodes: [
|
|
2583
|
+
{
|
|
2584
|
+
...redirectEntry,
|
|
2585
|
+
detail: 'GET /oauth2/authorize?…&key_authorization',
|
|
2586
|
+
},
|
|
2587
|
+
{
|
|
2588
|
+
edge: 'Page /authorize?…&sig: no account, and no Privy app to create one',
|
|
2589
|
+
name: 'Authorize this app (no account)',
|
|
2590
|
+
screen: (
|
|
2591
|
+
<Approval
|
|
2592
|
+
{...approval()}
|
|
2593
|
+
keyAuthorization={{ ...key(), refusal: 'no_account' }}
|
|
2594
|
+
/>
|
|
2595
|
+
),
|
|
2596
|
+
},
|
|
2597
|
+
{
|
|
2598
|
+
edge: 'Or: a passkey whose relying party is not Tempo ID’s, or POST /v1/authorize/consent: webauthn_required',
|
|
2599
|
+
name: 'Authorize this app (passkey)',
|
|
2600
|
+
screen: (
|
|
2601
|
+
<Approval
|
|
2602
|
+
{...approval()}
|
|
2603
|
+
keyAuthorization={{ ...key(), refusal: 'passkey' }}
|
|
2604
|
+
/>
|
|
2605
|
+
),
|
|
2606
|
+
},
|
|
2607
|
+
{
|
|
2608
|
+
edge: 'Or: a wallet.tempo passkey’s wallet waits for its upgrade, and the page reads no upgrade for it, such as when GET /i/chains fails',
|
|
2609
|
+
name: 'Authorize this app (upgrade first)',
|
|
2610
|
+
screen: (
|
|
2611
|
+
<Approval
|
|
2612
|
+
{...approval()}
|
|
2613
|
+
keyAuthorization={{ ...key(), refusal: 'upgrade' }}
|
|
2614
|
+
/>
|
|
2615
|
+
),
|
|
2616
|
+
},
|
|
2617
|
+
{
|
|
2618
|
+
edge: 'Or: the key names an account that the user does not hold',
|
|
2619
|
+
name: 'Authorize this app (foreign account)',
|
|
2620
|
+
screen: (
|
|
2621
|
+
<Approval
|
|
2622
|
+
{...approval()}
|
|
2623
|
+
keyAuthorization={{ ...key(), refusal: 'foreign_account' }}
|
|
2624
|
+
/>
|
|
2625
|
+
),
|
|
2626
|
+
},
|
|
2627
|
+
{
|
|
2628
|
+
edge: 'Or: the accounts cannot be read',
|
|
2629
|
+
name: 'Authorize this app (unavailable)',
|
|
2630
|
+
screen: (
|
|
2631
|
+
<Approval
|
|
2632
|
+
{...approval()}
|
|
2633
|
+
keyAuthorization={{ ...key(), refusal: 'unavailable' }}
|
|
2634
|
+
/>
|
|
2635
|
+
),
|
|
2636
|
+
},
|
|
2637
|
+
{
|
|
2638
|
+
detail: 'GET redirect_uri?error=access_denied&state',
|
|
2639
|
+
edge: 'Deny, POST /v1/authorize/consent, accept: false',
|
|
2640
|
+
name: 'Web app callback',
|
|
2641
|
+
},
|
|
2642
|
+
],
|
|
2643
|
+
title: 'Approval: key it cannot sign',
|
|
2644
|
+
},
|
|
2645
|
+
{
|
|
2646
|
+
description:
|
|
2647
|
+
'A key that breaks a rule returns to the app with the refusal, as `/oauth2/authorize` returns one before sign-in. The `error` names the rule, such as `invalid_authorization_details` for an expired key, or `unauthorized_client` for a challenge without the `tempo-id:sign` scope.',
|
|
2648
|
+
nodes: [
|
|
2649
|
+
{
|
|
2650
|
+
...redirectEntry,
|
|
2651
|
+
detail: 'GET /oauth2/authorize?…&key_authorization',
|
|
2652
|
+
},
|
|
2653
|
+
{
|
|
2654
|
+
detail: 'GET redirect_uri?error&error_description',
|
|
2655
|
+
edge: 'key refused',
|
|
2656
|
+
name: 'Web app callback',
|
|
2657
|
+
},
|
|
2658
|
+
],
|
|
2659
|
+
title: 'Refused key',
|
|
2660
|
+
},
|
|
2661
|
+
{
|
|
2662
|
+
description:
|
|
2663
|
+
'A changed or expired query, or a request without a known app, shows an invalid request, on the approval page and on the account step.',
|
|
2664
|
+
nodes: [
|
|
2665
|
+
{ detail: 'GET /authorize?…&sig (changed)', name: 'Browser' },
|
|
2666
|
+
{
|
|
2667
|
+
edge: '200, request: null',
|
|
2668
|
+
name: 'This sign-in request is invalid',
|
|
2669
|
+
screen: <InvalidRequest heading="h4" />,
|
|
2670
|
+
},
|
|
2671
|
+
{
|
|
2672
|
+
edge: 'Or: GET /choose-account?…&sig (changed), 200, request: null',
|
|
2673
|
+
name: 'This sign-in request is invalid (account step)',
|
|
2674
|
+
screen: <InvalidRequest heading="h4" />,
|
|
2675
|
+
},
|
|
2676
|
+
],
|
|
2677
|
+
title: 'Invalid request',
|
|
2678
|
+
},
|
|
2679
|
+
{
|
|
2680
|
+
description:
|
|
2681
|
+
'`/oauth2/authorize` shows the error page, not a redirect, for an unknown or disabled app, a failed metadata document, or an unregistered `redirect_uri`. The page maps `error` to fixed copy and never shows `error_description`.',
|
|
2682
|
+
nodes: [
|
|
2683
|
+
{
|
|
2684
|
+
...redirectEntry,
|
|
2685
|
+
detail: 'GET /oauth2/authorize?client_id (unknown)',
|
|
2686
|
+
},
|
|
2687
|
+
{
|
|
2688
|
+
edge: 'Page /error?error=invalid_client',
|
|
2689
|
+
name: 'This app is not recognized',
|
|
2690
|
+
screen: <InvalidRequest heading="h4" reason="client" />,
|
|
2691
|
+
},
|
|
2692
|
+
{
|
|
2693
|
+
edge: 'Or: redirect_uri not registered, 302 /error?error=invalid_redirect',
|
|
2694
|
+
name: 'This app’s return address is not registered',
|
|
2695
|
+
screen: <InvalidRequest heading="h4" reason="redirect" />,
|
|
2696
|
+
},
|
|
2697
|
+
{
|
|
2698
|
+
edge: 'Or: another error, such as unsupported_response_type',
|
|
2699
|
+
name: 'This sign-in request is invalid (error page)',
|
|
2700
|
+
screen: <InvalidRequest heading="h4" />,
|
|
2701
|
+
},
|
|
2702
|
+
],
|
|
2703
|
+
title: 'Unknown app or redirect URI',
|
|
2704
|
+
},
|
|
2705
|
+
{
|
|
2706
|
+
description:
|
|
2707
|
+
'A backend runs the redirect flow and redeems the code with its client secret, so tokens never reach the browser. In the test harness, no account in the `default` realm can sign the request’s key, so the approval page holds Approve.',
|
|
2708
|
+
nodes: [
|
|
2709
|
+
{
|
|
2710
|
+
detail: 'GET /auth/start on the backend, with a key',
|
|
2711
|
+
name: 'Backend playground',
|
|
2712
|
+
},
|
|
2713
|
+
{
|
|
2714
|
+
edge: '302 /oauth2/authorize with state and code_challenge, page /login?…&sig',
|
|
2715
|
+
name: 'Sign in (key)',
|
|
2716
|
+
screen: (
|
|
2717
|
+
<SignIn
|
|
2718
|
+
{...signIn()}
|
|
2719
|
+
request={{ ...signInRequest(), app: 'Backend playground' }}
|
|
2720
|
+
/>
|
|
2721
|
+
),
|
|
2722
|
+
},
|
|
2723
|
+
{
|
|
2724
|
+
edge: 'POST /v1/auth/code',
|
|
2725
|
+
name: 'Check your email',
|
|
2726
|
+
screen: <SignIn {...signIn()} email={email} />,
|
|
2727
|
+
},
|
|
2728
|
+
{
|
|
2729
|
+
edge: 'POST /v1/auth/code/verify with oauth_query: 200, no account to sign',
|
|
2730
|
+
name: 'Check your email (accepted)',
|
|
2731
|
+
screen: <SignIn {...signIn()} email={email} verified />,
|
|
2732
|
+
},
|
|
2733
|
+
{
|
|
2734
|
+
edge: 'after 500 ms, redirect /authorize?…&sig',
|
|
2735
|
+
name: 'Authorize this app (no account)',
|
|
2736
|
+
screen: (
|
|
2737
|
+
<Approval
|
|
2738
|
+
{...approval()}
|
|
2739
|
+
app="Backend playground"
|
|
2740
|
+
destination="backend.example.com"
|
|
2741
|
+
keyAuthorization={{ ...key(), refusal: 'no_account' }}
|
|
2742
|
+
/>
|
|
2743
|
+
),
|
|
2744
|
+
},
|
|
2745
|
+
{
|
|
2746
|
+
detail: 'GET /auth/callback?error=access_denied&state',
|
|
2747
|
+
edge: 'Deny, POST /v1/authorize/consent, accept: false',
|
|
2748
|
+
name: 'Backend callback',
|
|
2749
|
+
},
|
|
2750
|
+
{
|
|
2751
|
+
detail: 'GET /auth/callback?code&state',
|
|
2752
|
+
edge: '302 /auth/start?key=false, 302 /oauth2/authorize, 302, no screen',
|
|
2753
|
+
name: 'Backend callback',
|
|
2754
|
+
},
|
|
2755
|
+
{
|
|
2756
|
+
detail: 'Tokens stay on the server, without a key',
|
|
2757
|
+
edge: 'POST /oauth2/token with client_secret',
|
|
2758
|
+
name: 'Backend session',
|
|
2759
|
+
},
|
|
2760
|
+
],
|
|
2761
|
+
title: 'Backend app',
|
|
2762
|
+
},
|
|
2763
|
+
],
|
|
2764
|
+
title: 'App authorization',
|
|
2765
|
+
},
|
|
2766
|
+
{
|
|
2767
|
+
flows: [
|
|
2768
|
+
{
|
|
2769
|
+
description:
|
|
2770
|
+
"The app's own pages send the code, then verify it with the authorization request, which signs in, creates the first account, and returns the authorization. Tempo ID shows no screen.",
|
|
2771
|
+
nodes: [
|
|
2772
|
+
{ detail: 'Web playground', name: "App's sign-in" },
|
|
2773
|
+
{
|
|
2774
|
+
edge: appSignInEdge,
|
|
2775
|
+
name: 'Sign in',
|
|
2776
|
+
owner: 'app',
|
|
2777
|
+
screen: <AppSignIn />,
|
|
2778
|
+
},
|
|
2779
|
+
{
|
|
2780
|
+
edge: 'POST /v1/auth/code',
|
|
2781
|
+
name: 'Check your email',
|
|
2782
|
+
owner: 'app',
|
|
2783
|
+
screen: <AppCode />,
|
|
2784
|
+
},
|
|
2785
|
+
{
|
|
2786
|
+
edge: 'POST /v1/auth/code/verify with authorize: user and authorization',
|
|
2787
|
+
name: 'Signed in',
|
|
2788
|
+
owner: 'app',
|
|
2789
|
+
screen: <AppSignedIn />,
|
|
2790
|
+
},
|
|
2791
|
+
],
|
|
2792
|
+
title: 'Email code',
|
|
2793
|
+
},
|
|
2794
|
+
{
|
|
2795
|
+
description:
|
|
2796
|
+
"A wallet.tempo passkey owns the user's account, so the code signs no key, and the sign-in returns the passkey step. The passkey then signs the key's digest on the app's page, with no approval page, when its relying party covers the page.",
|
|
2797
|
+
nodes: [
|
|
2798
|
+
{ detail: 'Web playground', name: "App's sign-in" },
|
|
2799
|
+
{
|
|
2800
|
+
edge: appSignInEdge,
|
|
2801
|
+
name: 'Sign in',
|
|
2802
|
+
owner: 'app',
|
|
2803
|
+
screen: <AppSignIn />,
|
|
2804
|
+
},
|
|
2805
|
+
{
|
|
2806
|
+
edge: 'POST /v1/auth/code',
|
|
2807
|
+
name: 'Check your email',
|
|
2808
|
+
owner: 'app',
|
|
2809
|
+
screen: <AppCode />,
|
|
2810
|
+
},
|
|
2811
|
+
{
|
|
2812
|
+
detail:
|
|
2813
|
+
'POST /v1/auth/code/verify with authorize and passkey: step passkey',
|
|
2814
|
+
edge: 'Code complete',
|
|
2815
|
+
name: 'Authenticator prompt',
|
|
2816
|
+
},
|
|
2817
|
+
{
|
|
2818
|
+
edge: 'POST /v1/authorize with expiry and passkey: { assertion }: authorization',
|
|
2819
|
+
name: 'Signed in',
|
|
2820
|
+
owner: 'app',
|
|
2821
|
+
screen: <AppSignedIn />,
|
|
2822
|
+
},
|
|
2823
|
+
],
|
|
2824
|
+
title: 'Email code: passkey account',
|
|
2825
|
+
},
|
|
2826
|
+
{
|
|
2827
|
+
description:
|
|
2828
|
+
'A signed-in user skips the code. The app requests the authorization at once.',
|
|
2829
|
+
nodes: [
|
|
2830
|
+
{ detail: 'Session cookie set', name: "App's page" },
|
|
2831
|
+
{
|
|
2832
|
+
edge: 'GET /v1/auth/session, POST /v1/authorize',
|
|
2833
|
+
name: 'Signed in',
|
|
2834
|
+
owner: 'app',
|
|
2835
|
+
screen: <AppSignedIn />,
|
|
2836
|
+
},
|
|
2837
|
+
],
|
|
2838
|
+
title: 'Email code: signed in',
|
|
2839
|
+
},
|
|
2840
|
+
{
|
|
2841
|
+
description:
|
|
2842
|
+
"The app's page runs the passkey ceremony with its client ID, which selects the app's realms, and its authorization request. The challenge is the key's digest, so one assertion signs the user in and signs the key of the passkey's account, with no approval page.",
|
|
2843
|
+
nodes: [
|
|
2844
|
+
{ detail: 'Web playground', name: "App's sign-in" },
|
|
2845
|
+
{
|
|
2846
|
+
edge: appSignInEdge,
|
|
2847
|
+
name: 'Sign in',
|
|
2848
|
+
owner: 'app',
|
|
2849
|
+
screen: <AppSignIn />,
|
|
2850
|
+
},
|
|
2851
|
+
{
|
|
2852
|
+
detail:
|
|
2853
|
+
'The options of `Actions.passkey.prepare`, as the form showed: GET /v1/realms, then POST /v1/auth/passkey/options with client_id and authorize',
|
|
2854
|
+
edge: 'Continue with passkey',
|
|
2855
|
+
name: 'Authenticator prompt',
|
|
2856
|
+
},
|
|
2857
|
+
{
|
|
2858
|
+
edge: 'POST /v1/auth/passkey/verify: user and authorization',
|
|
2859
|
+
name: 'Signed in',
|
|
2860
|
+
owner: 'app',
|
|
2861
|
+
screen: <AppSignedIn />,
|
|
2862
|
+
},
|
|
2863
|
+
],
|
|
2864
|
+
title: 'Passkey',
|
|
2865
|
+
},
|
|
2866
|
+
{
|
|
2867
|
+
description:
|
|
2868
|
+
"A wallet.tempo passkey without an email verifies one through the app's own code step. The passkey signs again with the code, which links it, and that assertion signs the key of its account.",
|
|
2869
|
+
nodes: [
|
|
2870
|
+
{ detail: 'Web playground', name: "App's sign-in" },
|
|
2871
|
+
{
|
|
2872
|
+
edge: appSignInEdge,
|
|
2873
|
+
name: 'Sign in',
|
|
2874
|
+
owner: 'app',
|
|
2875
|
+
screen: <AppSignIn />,
|
|
2876
|
+
},
|
|
2877
|
+
{
|
|
2878
|
+
detail:
|
|
2879
|
+
'The options of `Actions.passkey.prepare`, as the form showed: GET /v1/realms, then POST /v1/auth/passkey/options with client_id',
|
|
2880
|
+
edge: 'Continue with passkey',
|
|
2881
|
+
name: 'Authenticator prompt',
|
|
2882
|
+
},
|
|
2883
|
+
{
|
|
2884
|
+
edge: 'POST /v1/auth/passkey/verify: step email',
|
|
2885
|
+
name: 'Add your email',
|
|
2886
|
+
owner: 'app',
|
|
2887
|
+
screen: <AppEmail />,
|
|
2888
|
+
},
|
|
2889
|
+
{
|
|
2890
|
+
edge: 'POST /v1/auth/code',
|
|
2891
|
+
name: 'Check your email',
|
|
2892
|
+
owner: 'app',
|
|
2893
|
+
screen: <AppCode />,
|
|
2894
|
+
},
|
|
2895
|
+
{
|
|
2896
|
+
detail: 'POST /v1/auth/passkey/options with link',
|
|
2897
|
+
edge: 'Code complete',
|
|
2898
|
+
name: 'Authenticator prompt',
|
|
2899
|
+
},
|
|
2900
|
+
{
|
|
2901
|
+
edge: 'POST /v1/auth/code/verify with link and assertion: user and authorization',
|
|
2902
|
+
name: 'Signed in',
|
|
2903
|
+
owner: 'app',
|
|
2904
|
+
screen: <AppSignedIn />,
|
|
2905
|
+
},
|
|
2906
|
+
],
|
|
2907
|
+
title: 'Passkey: adds an email',
|
|
2908
|
+
},
|
|
2909
|
+
{
|
|
2910
|
+
description:
|
|
2911
|
+
'In a sign-in with key signature, a passkey signs only the key of its own account. For another passkey’s account, the sign-in refuses the key with `webauthn_required`, and the app signs in through a redirect without a key.',
|
|
2912
|
+
nodes: [
|
|
2913
|
+
{
|
|
2914
|
+
detail: 'POST /v1/auth/passkey/verify: webauthn_required',
|
|
2915
|
+
name: "App's sign-in",
|
|
2916
|
+
},
|
|
2917
|
+
{
|
|
2918
|
+
detail: 'GET redirect_uri?code&state',
|
|
2919
|
+
edge: 'GET /oauth2/authorize without key_authorization, 302, no screen',
|
|
2920
|
+
name: 'Web app callback',
|
|
2921
|
+
},
|
|
2922
|
+
{
|
|
2923
|
+
edge: 'POST /oauth2/token with code_verifier',
|
|
2924
|
+
name: 'Signed in',
|
|
2925
|
+
owner: 'app',
|
|
2926
|
+
screen: <AppSignedIn />,
|
|
2927
|
+
},
|
|
2928
|
+
],
|
|
2929
|
+
title: 'Passkey: falls back to redirect',
|
|
2930
|
+
},
|
|
2931
|
+
{
|
|
2932
|
+
description:
|
|
2933
|
+
"On a chain whose accounts are configurable, a wallet.tempo passkey's account waits for its upgrade. The sign-in sets `skip_upgrade`, so its one assertion signs the key as the passkey's own account, and returns `upgradeAvailable`. The web playground then calls `Actions.account.upgrade` in the same click. Its prompt signs the upgrade, and Tempo ID first registers the key through a sponsored transaction that the access key signs, so the key keeps working.",
|
|
2934
|
+
nodes: [
|
|
2935
|
+
{ detail: 'Web playground', name: "App's sign-in" },
|
|
2936
|
+
{
|
|
2937
|
+
edge: appSignInEdge,
|
|
2938
|
+
name: 'Sign in',
|
|
2939
|
+
owner: 'app',
|
|
2940
|
+
screen: <AppSignIn />,
|
|
2941
|
+
},
|
|
2942
|
+
{
|
|
2943
|
+
detail:
|
|
2944
|
+
'The options of `Actions.passkey.prepare`, with skip_upgrade in authorize',
|
|
2945
|
+
edge: 'Continue with passkey',
|
|
2946
|
+
name: 'Authenticator prompt',
|
|
2947
|
+
},
|
|
2948
|
+
{
|
|
2949
|
+
detail:
|
|
2950
|
+
'POST /v1/account/upgrade with key_authorization: the passkey step, with step.key.digest, which the access key signs with no prompt',
|
|
2951
|
+
edge: 'POST /v1/auth/passkey/verify: user and authorization with upgrade_available: true',
|
|
2952
|
+
name: 'Authenticator prompt',
|
|
2953
|
+
},
|
|
2954
|
+
{
|
|
2955
|
+
edge: 'POST /v1/account/upgrade with passkey.assertion and key.signature: the key registers, then the account upgrades',
|
|
2956
|
+
name: 'Signed in',
|
|
2957
|
+
owner: 'app',
|
|
2958
|
+
screen: <AppSignedIn />,
|
|
2959
|
+
},
|
|
2960
|
+
],
|
|
2961
|
+
title: 'Passkey: upgrade the wallet',
|
|
2962
|
+
},
|
|
2963
|
+
],
|
|
2964
|
+
title: 'Headless apps',
|
|
2965
|
+
},
|
|
2966
|
+
{
|
|
2967
|
+
flows: [
|
|
2968
|
+
{
|
|
2969
|
+
description:
|
|
2970
|
+
'`tempo-id login` requests a code with a new access key, and prints `verification_uri_complete`. After the user approves on the device page, the CLI’s next poll receives the tokens and the signed key, and stores them.',
|
|
2971
|
+
nodes: [
|
|
2972
|
+
loginEntry,
|
|
2973
|
+
{
|
|
2974
|
+
detail: 'GET /device?code',
|
|
2975
|
+
edge: 'Shows verification_uri_complete',
|
|
2976
|
+
name: 'Browser',
|
|
2977
|
+
},
|
|
2978
|
+
{
|
|
2979
|
+
edge: 'Page /login?return_to=/device?code',
|
|
2980
|
+
name: 'Sign in',
|
|
2981
|
+
screen: <SignIn {...signIn()} device />,
|
|
2982
|
+
},
|
|
2983
|
+
{
|
|
2984
|
+
edge: 'POST /v1/auth/code',
|
|
2985
|
+
name: 'Check your email',
|
|
2986
|
+
screen: <SignIn {...signIn()} device email={email} />,
|
|
2987
|
+
},
|
|
2988
|
+
{
|
|
2989
|
+
edge: 'POST /v1/auth/code/verify: 200',
|
|
2990
|
+
name: 'Check your email (accepted)',
|
|
2991
|
+
screen: <SignIn {...signIn()} device email={email} verified />,
|
|
2992
|
+
},
|
|
2993
|
+
{
|
|
2994
|
+
edge: 'after 500 ms, /device?code',
|
|
2995
|
+
name: 'Authorize a device',
|
|
2996
|
+
screen: (
|
|
2997
|
+
<DeviceApproval
|
|
2998
|
+
{...deviceApproval()}
|
|
2999
|
+
request={deviceRequest()}
|
|
3000
|
+
/>
|
|
3001
|
+
),
|
|
3002
|
+
},
|
|
3003
|
+
{
|
|
3004
|
+
edge: 'POST /v1/device/approve',
|
|
3005
|
+
name: 'Authorization successful',
|
|
3006
|
+
screen: (
|
|
3007
|
+
<DeviceApproval
|
|
3008
|
+
{...deviceApproval()}
|
|
3009
|
+
request={deviceRequest()}
|
|
3010
|
+
status="approved"
|
|
3011
|
+
/>
|
|
3012
|
+
),
|
|
3013
|
+
},
|
|
3014
|
+
loginExit,
|
|
3015
|
+
],
|
|
3016
|
+
title: 'Device code: signed out',
|
|
3017
|
+
},
|
|
3018
|
+
{
|
|
3019
|
+
description:
|
|
3020
|
+
'A user who opens the bare `verification_uri` types the code that the terminal shows.',
|
|
3021
|
+
nodes: [
|
|
3022
|
+
loginEntry,
|
|
3023
|
+
{
|
|
3024
|
+
detail: 'GET /device',
|
|
3025
|
+
edge: 'Shows user_code and verification_uri',
|
|
3026
|
+
name: 'Browser',
|
|
3027
|
+
},
|
|
3028
|
+
{
|
|
3029
|
+
edge: '200 device page',
|
|
3030
|
+
name: 'Authorize a device (enter a code)',
|
|
3031
|
+
screen: <DeviceApproval {...deviceApproval()} />,
|
|
3032
|
+
},
|
|
3033
|
+
{
|
|
3034
|
+
edge: 'Continue, GET /device?code',
|
|
3035
|
+
name: 'Authorize a device',
|
|
3036
|
+
screen: (
|
|
3037
|
+
<DeviceApproval
|
|
3038
|
+
{...deviceApproval()}
|
|
3039
|
+
request={deviceRequest()}
|
|
3040
|
+
/>
|
|
3041
|
+
),
|
|
3042
|
+
},
|
|
3043
|
+
{
|
|
3044
|
+
edge: 'POST /v1/device/approve',
|
|
3045
|
+
name: 'Authorization successful',
|
|
3046
|
+
screen: (
|
|
3047
|
+
<DeviceApproval
|
|
3048
|
+
{...deviceApproval()}
|
|
3049
|
+
request={deviceRequest()}
|
|
3050
|
+
status="approved"
|
|
3051
|
+
/>
|
|
3052
|
+
),
|
|
3053
|
+
},
|
|
3054
|
+
loginExit,
|
|
3055
|
+
],
|
|
3056
|
+
title: 'Device code: typed',
|
|
3057
|
+
},
|
|
3058
|
+
{
|
|
3059
|
+
description:
|
|
3060
|
+
"Denying ends the request. The CLI's next poll is refused.",
|
|
3061
|
+
nodes: [
|
|
3062
|
+
{ detail: 'GET /device?code', name: 'Browser' },
|
|
3063
|
+
{
|
|
3064
|
+
edge: '200 device page',
|
|
3065
|
+
name: 'Authorize a device',
|
|
3066
|
+
screen: (
|
|
3067
|
+
<DeviceApproval
|
|
3068
|
+
{...deviceApproval()}
|
|
3069
|
+
request={deviceRequest()}
|
|
3070
|
+
/>
|
|
3071
|
+
),
|
|
3072
|
+
},
|
|
3073
|
+
{
|
|
3074
|
+
edge: 'POST /v1/device/deny',
|
|
3075
|
+
name: 'Denied',
|
|
3076
|
+
screen: (
|
|
3077
|
+
<DeviceApproval
|
|
3078
|
+
{...deviceApproval()}
|
|
3079
|
+
request={deviceRequest()}
|
|
3080
|
+
status="denied"
|
|
3081
|
+
/>
|
|
3082
|
+
),
|
|
3083
|
+
},
|
|
3084
|
+
{
|
|
3085
|
+
detail: 'Exits with not_signed_in',
|
|
3086
|
+
edge: 'POST /oauth2/token: access_denied',
|
|
3087
|
+
name: 'tempo-id login',
|
|
3088
|
+
},
|
|
3089
|
+
],
|
|
3090
|
+
title: 'Device code: denied',
|
|
3091
|
+
},
|
|
3092
|
+
{
|
|
3093
|
+
description:
|
|
3094
|
+
'A passkey owns the account, so the approval returns the passkey step. The passkey signs the key’s digest in this browser, and the page approves again.',
|
|
3095
|
+
nodes: [
|
|
3096
|
+
{ detail: 'GET /device?code', name: 'Browser' },
|
|
3097
|
+
{
|
|
3098
|
+
edge: '200 device page',
|
|
3099
|
+
name: 'Authorize a device',
|
|
3100
|
+
screen: (
|
|
3101
|
+
<DeviceApproval
|
|
3102
|
+
{...deviceApproval()}
|
|
3103
|
+
request={deviceRequest()}
|
|
3104
|
+
/>
|
|
3105
|
+
),
|
|
3106
|
+
},
|
|
3107
|
+
{
|
|
3108
|
+
edge: 'Approve, POST /v1/device/approve',
|
|
3109
|
+
name: 'Authorize a device (approving)',
|
|
3110
|
+
screen: (
|
|
3111
|
+
<DeviceApproval
|
|
3112
|
+
{...deviceApproval()}
|
|
3113
|
+
approving
|
|
3114
|
+
request={deviceRequest()}
|
|
3115
|
+
/>
|
|
3116
|
+
),
|
|
3117
|
+
},
|
|
3118
|
+
{
|
|
3119
|
+
detail: 'Signs the key authorization, over the approving page',
|
|
3120
|
+
edge: 'step passkey',
|
|
3121
|
+
name: 'Authenticator prompt',
|
|
3122
|
+
},
|
|
3123
|
+
{
|
|
3124
|
+
edge: 'POST /v1/device/approve with passkey.assertion',
|
|
3125
|
+
name: 'Authorization successful',
|
|
3126
|
+
screen: (
|
|
3127
|
+
<DeviceApproval
|
|
3128
|
+
{...deviceApproval()}
|
|
3129
|
+
request={deviceRequest()}
|
|
3130
|
+
status="approved"
|
|
3131
|
+
/>
|
|
3132
|
+
),
|
|
3133
|
+
},
|
|
3134
|
+
loginExit,
|
|
3135
|
+
],
|
|
3136
|
+
title: 'Device code: passkey account',
|
|
3137
|
+
},
|
|
3138
|
+
{
|
|
3139
|
+
description:
|
|
3140
|
+
'When a wallet.tempo passkey’s wallet waits for its upgrade, the device page shows the upgrade step first. Continue upgrades the wallet and loads the request again, and Not now shows the request with its refusal.',
|
|
3141
|
+
nodes: [
|
|
3142
|
+
{ detail: 'GET /device?code', name: 'Browser' },
|
|
3143
|
+
{
|
|
3144
|
+
edge: 'GET /v1/device/requests/:code: refusal upgrade, GET /i/account/passkeys for a passkey that has not linked, POST /v1/account/upgrade, the passkey step',
|
|
3145
|
+
name: 'Upgrade your wallet',
|
|
3146
|
+
screen: <WalletUpgrade {...walletUpgrade()} />,
|
|
3147
|
+
},
|
|
3148
|
+
{
|
|
3149
|
+
edge: 'Continue, POST /v1/account/upgrade with passkey.assertion, then GET /v1/device/requests/:code again',
|
|
3150
|
+
name: 'Authorize a device',
|
|
3151
|
+
screen: (
|
|
3152
|
+
<DeviceApproval
|
|
3153
|
+
{...deviceApproval()}
|
|
3154
|
+
request={deviceRequest()}
|
|
3155
|
+
/>
|
|
3156
|
+
),
|
|
3157
|
+
},
|
|
3158
|
+
{
|
|
3159
|
+
detail: 'Signs the key authorization, over the approving page',
|
|
3160
|
+
edge: 'Approve, POST /v1/device/approve, step passkey',
|
|
3161
|
+
name: 'Authenticator prompt',
|
|
3162
|
+
},
|
|
3163
|
+
{
|
|
3164
|
+
edge: 'POST /v1/device/approve with passkey.assertion',
|
|
3165
|
+
name: 'Authorization successful',
|
|
3166
|
+
screen: (
|
|
3167
|
+
<DeviceApproval
|
|
3168
|
+
{...deviceApproval()}
|
|
3169
|
+
request={deviceRequest()}
|
|
3170
|
+
status="approved"
|
|
3171
|
+
/>
|
|
3172
|
+
),
|
|
3173
|
+
},
|
|
3174
|
+
loginExit,
|
|
3175
|
+
],
|
|
3176
|
+
title: 'Device code: upgrade first',
|
|
3177
|
+
},
|
|
3178
|
+
{
|
|
3179
|
+
description:
|
|
3180
|
+
'Approval cannot sign some keys, so the page holds Approve and states why. Deny still ends the request.',
|
|
3181
|
+
nodes: [
|
|
3182
|
+
{ detail: 'GET /device?code', name: 'Browser' },
|
|
3183
|
+
{
|
|
3184
|
+
edge: 'The key names an account that the user does not hold',
|
|
3185
|
+
name: 'Authorize a device (foreign account)',
|
|
3186
|
+
screen: (
|
|
3187
|
+
<DeviceApproval
|
|
3188
|
+
{...deviceApproval()}
|
|
3189
|
+
request={{
|
|
3190
|
+
...deviceRequest(),
|
|
3191
|
+
key_authorization: { ...key(), refusal: 'foreign_account' },
|
|
3192
|
+
}}
|
|
3193
|
+
/>
|
|
3194
|
+
),
|
|
3195
|
+
},
|
|
3196
|
+
{
|
|
3197
|
+
edge: 'Or: no account, and no Privy app to create one, or Not now on the upgrade step',
|
|
3198
|
+
name: 'Authorize a device (no account)',
|
|
3199
|
+
screen: (
|
|
3200
|
+
<DeviceApproval
|
|
3201
|
+
{...deviceApproval()}
|
|
3202
|
+
request={{
|
|
3203
|
+
...deviceRequest(),
|
|
3204
|
+
key_authorization: { ...key(), refusal: 'no_account' },
|
|
3205
|
+
}}
|
|
3206
|
+
/>
|
|
3207
|
+
),
|
|
3208
|
+
},
|
|
3209
|
+
{
|
|
3210
|
+
edge: 'Or: a passkey whose relying party is not Tempo ID’s, or POST /v1/device/approve: webauthn_required',
|
|
3211
|
+
name: 'Authorize a device (passkey)',
|
|
3212
|
+
screen: (
|
|
3213
|
+
<DeviceApproval
|
|
3214
|
+
{...deviceApproval()}
|
|
3215
|
+
request={{
|
|
3216
|
+
...deviceRequest(),
|
|
3217
|
+
key_authorization: { ...key(), refusal: 'passkey' },
|
|
3218
|
+
}}
|
|
3219
|
+
/>
|
|
3220
|
+
),
|
|
3221
|
+
},
|
|
3222
|
+
{
|
|
3223
|
+
edge: 'Or: the accounts cannot be read',
|
|
3224
|
+
name: 'Authorize a device (unavailable)',
|
|
3225
|
+
screen: (
|
|
3226
|
+
<DeviceApproval
|
|
3227
|
+
{...deviceApproval()}
|
|
3228
|
+
request={{
|
|
3229
|
+
...deviceRequest(),
|
|
3230
|
+
key_authorization: { ...key(), refusal: 'unavailable' },
|
|
3231
|
+
}}
|
|
3232
|
+
/>
|
|
3233
|
+
),
|
|
3234
|
+
},
|
|
3235
|
+
{
|
|
3236
|
+
detail: 'POST /oauth2/token: access_denied',
|
|
3237
|
+
edge: 'Deny, POST /v1/device/deny',
|
|
3238
|
+
name: 'tempo-id login',
|
|
3239
|
+
},
|
|
3240
|
+
],
|
|
3241
|
+
title: 'Device code: key it cannot sign',
|
|
3242
|
+
},
|
|
3243
|
+
{
|
|
3244
|
+
description:
|
|
3245
|
+
'A CLI with the `tempo-id:sign` scope sends a challenge, and the device page shows the message that the key signs.',
|
|
3246
|
+
nodes: [
|
|
3247
|
+
{
|
|
3248
|
+
detail: 'POST /device/code with key_authorization and challenge',
|
|
3249
|
+
name: 'CLI',
|
|
3250
|
+
},
|
|
3251
|
+
{
|
|
3252
|
+
edge: 'GET /device?code, 200 device page',
|
|
3253
|
+
name: 'Authorize a device (challenge)',
|
|
3254
|
+
screen: (
|
|
3255
|
+
<DeviceApproval
|
|
3256
|
+
{...deviceApproval()}
|
|
3257
|
+
request={{
|
|
3258
|
+
...deviceRequest(),
|
|
3259
|
+
challenge: 'Sign in to api.example.com\nNonce: 8f2a91',
|
|
3260
|
+
}}
|
|
3261
|
+
/>
|
|
3262
|
+
),
|
|
3263
|
+
},
|
|
3264
|
+
{
|
|
3265
|
+
edge: 'Approve, POST /v1/device/approve, or the passkey step first',
|
|
3266
|
+
name: 'Authorization successful',
|
|
3267
|
+
screen: (
|
|
3268
|
+
<DeviceApproval
|
|
3269
|
+
{...deviceApproval()}
|
|
3270
|
+
request={deviceRequest()}
|
|
3271
|
+
status="approved"
|
|
3272
|
+
/>
|
|
3273
|
+
),
|
|
3274
|
+
},
|
|
3275
|
+
{
|
|
3276
|
+
detail:
|
|
3277
|
+
'Tokens, and key_authorization bound to the challenge’s hash',
|
|
3278
|
+
edge: 'POST /oauth2/token: authorization_pending, then tokens',
|
|
3279
|
+
name: 'CLI',
|
|
3280
|
+
},
|
|
3281
|
+
],
|
|
3282
|
+
title: 'Device code: challenge',
|
|
3283
|
+
},
|
|
3284
|
+
{
|
|
3285
|
+
description:
|
|
3286
|
+
'A code that is already approved or denied shows its outcome again, without the decision buttons.',
|
|
3287
|
+
nodes: [
|
|
3288
|
+
{ detail: 'GET /device?code', name: 'Browser' },
|
|
3289
|
+
{
|
|
3290
|
+
edge: '200, status: approved',
|
|
3291
|
+
name: 'Authorization successful',
|
|
3292
|
+
screen: (
|
|
3293
|
+
<DeviceApproval
|
|
3294
|
+
{...deviceApproval()}
|
|
3295
|
+
request={deviceRequest()}
|
|
3296
|
+
status="approved"
|
|
3297
|
+
/>
|
|
3298
|
+
),
|
|
3299
|
+
},
|
|
3300
|
+
],
|
|
3301
|
+
title: 'Device code: already decided',
|
|
3302
|
+
},
|
|
3303
|
+
{
|
|
3304
|
+
description:
|
|
3305
|
+
'A code that no one approves before `expires_in` ends the login. The CLI stores nothing, and the user runs `tempo-id login` again.',
|
|
3306
|
+
nodes: [
|
|
3307
|
+
loginEntry,
|
|
3308
|
+
{
|
|
3309
|
+
detail: 'Exits with not_signed_in',
|
|
3310
|
+
edge: 'POST /oauth2/token: authorization_pending, then expired_token',
|
|
3311
|
+
name: 'tempo-id login',
|
|
3312
|
+
},
|
|
3313
|
+
],
|
|
3314
|
+
title: 'tempo-id login: expired',
|
|
3315
|
+
},
|
|
3316
|
+
{
|
|
3317
|
+
description:
|
|
3318
|
+
'The CLI reads the stored session, refreshes its access token when it expires within 30 seconds, and reads the user.',
|
|
3319
|
+
nodes: [
|
|
3320
|
+
{ detail: '~/.tempo/id/credentials.json', name: 'tempo-id whoami' },
|
|
3321
|
+
{
|
|
3322
|
+
detail: 'Email and Tempo Account',
|
|
3323
|
+
edge: 'POST /oauth2/token with refresh_token within 30 s of expiry, GET /oauth2/userinfo',
|
|
3324
|
+
name: 'tempo-id whoami',
|
|
3325
|
+
},
|
|
3326
|
+
],
|
|
3327
|
+
title: 'tempo-id whoami',
|
|
3328
|
+
},
|
|
3329
|
+
{
|
|
3330
|
+
description:
|
|
3331
|
+
'Each device request holds the code field or the decision buttons, and shows a spinner, until it settles.',
|
|
3332
|
+
nodes: [
|
|
3333
|
+
{ detail: 'GET /device', name: 'Browser' },
|
|
3334
|
+
{
|
|
3335
|
+
edge: 'Continue, GET /device?code',
|
|
3336
|
+
name: 'Authorize a device (fetching)',
|
|
3337
|
+
screen: (
|
|
3338
|
+
<DeviceApproval
|
|
3339
|
+
{...deviceApproval()}
|
|
3340
|
+
code="BDWP-HQPK"
|
|
3341
|
+
fetching
|
|
3342
|
+
/>
|
|
3343
|
+
),
|
|
3344
|
+
},
|
|
3345
|
+
{
|
|
3346
|
+
edge: 'Approve, POST /v1/device/approve',
|
|
3347
|
+
name: 'Authorize a device (approving)',
|
|
3348
|
+
screen: (
|
|
3349
|
+
<DeviceApproval
|
|
3350
|
+
{...deviceApproval()}
|
|
3351
|
+
approving
|
|
3352
|
+
request={deviceRequest()}
|
|
3353
|
+
/>
|
|
3354
|
+
),
|
|
3355
|
+
},
|
|
3356
|
+
{
|
|
3357
|
+
edge: 'Or: Deny, POST /v1/device/deny',
|
|
3358
|
+
name: 'Authorize a device (denying)',
|
|
3359
|
+
screen: (
|
|
3360
|
+
<DeviceApproval
|
|
3361
|
+
{...deviceApproval()}
|
|
3362
|
+
denying
|
|
3363
|
+
request={deviceRequest()}
|
|
3364
|
+
/>
|
|
3365
|
+
),
|
|
3366
|
+
},
|
|
3367
|
+
],
|
|
3368
|
+
title: 'Device code: in progress',
|
|
3369
|
+
},
|
|
3370
|
+
{
|
|
3371
|
+
description:
|
|
3372
|
+
'A device app without a display name could be anyone’s, so the request omits `app`, and the page flags an unverified app.',
|
|
3373
|
+
nodes: [
|
|
3374
|
+
{ detail: 'GET /device?code', name: 'Browser' },
|
|
3375
|
+
{
|
|
3376
|
+
edge: '200 device page, request without app',
|
|
3377
|
+
name: 'Authorize a device (unverified app)',
|
|
3378
|
+
screen: (
|
|
3379
|
+
<DeviceApproval
|
|
3380
|
+
{...deviceApproval()}
|
|
3381
|
+
request={{ ...deviceRequest(), app: undefined }}
|
|
3382
|
+
/>
|
|
3383
|
+
),
|
|
3384
|
+
},
|
|
3385
|
+
],
|
|
3386
|
+
title: 'Device code: unverified app',
|
|
3387
|
+
},
|
|
3388
|
+
{
|
|
3389
|
+
description:
|
|
3390
|
+
'A code that matches no pending request, or one that another user holds, keeps the code form.',
|
|
3391
|
+
nodes: [
|
|
3392
|
+
{ detail: 'GET /device?code', name: 'Browser' },
|
|
3393
|
+
{
|
|
3394
|
+
edge: 'unknown_code',
|
|
3395
|
+
name: 'Authorize a device (unknown code)',
|
|
3396
|
+
screen: (
|
|
3397
|
+
<DeviceApproval
|
|
3398
|
+
{...deviceApproval()}
|
|
3399
|
+
code="BDWP-HQPX"
|
|
3400
|
+
error="Invalid or expired code. Check the code in the terminal."
|
|
3401
|
+
invalid
|
|
3402
|
+
/>
|
|
3403
|
+
),
|
|
3404
|
+
},
|
|
3405
|
+
],
|
|
3406
|
+
title: 'Device code: unknown code',
|
|
3407
|
+
},
|
|
3408
|
+
{
|
|
3409
|
+
description:
|
|
3410
|
+
'Each user’s code lookups share one rate limit, so the page refuses a lookup over it and keeps the code form.',
|
|
3411
|
+
nodes: [
|
|
3412
|
+
{ detail: 'GET /device?code', name: 'Browser' },
|
|
3413
|
+
{
|
|
3414
|
+
edge: 'Lookup over the rate limit',
|
|
3415
|
+
name: 'Authorize a device (rate limited)',
|
|
3416
|
+
screen: (
|
|
3417
|
+
<DeviceApproval
|
|
3418
|
+
{...deviceApproval()}
|
|
3419
|
+
code="BDWP-HQPK"
|
|
3420
|
+
error="Too many requests. Try again in 1 minute."
|
|
3421
|
+
invalid
|
|
3422
|
+
/>
|
|
3423
|
+
),
|
|
3424
|
+
},
|
|
3425
|
+
],
|
|
3426
|
+
title: 'Device code: rate limited',
|
|
3427
|
+
},
|
|
3428
|
+
],
|
|
3429
|
+
title: 'Device grant',
|
|
3430
|
+
},
|
|
3431
|
+
{
|
|
3432
|
+
flows: [
|
|
3433
|
+
{
|
|
3434
|
+
description:
|
|
3435
|
+
'Sign-out ends the session and revokes its refresh tokens, then opens the sign-in page.',
|
|
3436
|
+
nodes: [
|
|
3437
|
+
{ detail: 'GET /', name: 'Browser opens Tempo ID' },
|
|
3438
|
+
{
|
|
3439
|
+
name: 'Your account',
|
|
3440
|
+
screen: <Account {...account()} />,
|
|
3441
|
+
},
|
|
3442
|
+
{
|
|
3443
|
+
edge: 'Sign out, POST /v1/auth/logout',
|
|
3444
|
+
name: 'Your account (signing out)',
|
|
3445
|
+
screen: <Account {...account()} signingOut />,
|
|
3446
|
+
},
|
|
3447
|
+
{
|
|
3448
|
+
edge: '200, then /login',
|
|
3449
|
+
name: 'Sign in',
|
|
3450
|
+
screen: <SignIn {...signIn()} />,
|
|
3451
|
+
},
|
|
3452
|
+
],
|
|
3453
|
+
title: 'Sign-out',
|
|
3454
|
+
},
|
|
3455
|
+
{
|
|
3456
|
+
description:
|
|
3457
|
+
'An app signs the user out with its ID token. Tempo ID ends the session that the hint names, if this browser holds it, and redirects to the app.',
|
|
3458
|
+
nodes: [
|
|
3459
|
+
{
|
|
3460
|
+
detail:
|
|
3461
|
+
'GET /oauth2/end-session?id_token_hint&post_logout_redirect_uri&state',
|
|
3462
|
+
name: 'Web app',
|
|
3463
|
+
},
|
|
3464
|
+
{
|
|
3465
|
+
detail: 'GET post_logout_redirect_uri?state',
|
|
3466
|
+
edge: '302, no screen',
|
|
3467
|
+
name: 'Web app',
|
|
3468
|
+
},
|
|
3469
|
+
],
|
|
3470
|
+
title: 'RP-initiated logout',
|
|
3471
|
+
},
|
|
3472
|
+
{
|
|
3473
|
+
description:
|
|
3474
|
+
'A cross-site form POST carries no `SameSite=Lax` session cookie, so Tempo ID seals the parameters and continues as a GET, which carries the cookie.',
|
|
3475
|
+
nodes: [
|
|
3476
|
+
{
|
|
3477
|
+
detail:
|
|
3478
|
+
'POST /oauth2/end-session with id_token_hint, post_logout_redirect_uri, and state',
|
|
3479
|
+
name: 'Web app',
|
|
3480
|
+
},
|
|
3481
|
+
{
|
|
3482
|
+
detail: 'GET /oauth2/end-session?continuation',
|
|
3483
|
+
edge: '303, no session cookie',
|
|
3484
|
+
name: 'Browser',
|
|
3485
|
+
},
|
|
3486
|
+
{
|
|
3487
|
+
detail: 'GET post_logout_redirect_uri?state',
|
|
3488
|
+
edge: '302, no screen',
|
|
3489
|
+
name: 'Web app',
|
|
3490
|
+
},
|
|
3491
|
+
],
|
|
3492
|
+
title: 'RP-initiated logout: form POST',
|
|
3493
|
+
},
|
|
3494
|
+
{
|
|
3495
|
+
description:
|
|
3496
|
+
"An app's own Sign out ends the Tempo ID session. The app forgets the sign-in even if the request fails, and shows the error.",
|
|
3497
|
+
nodes: [
|
|
3498
|
+
{ detail: 'Web playground', name: "App's page" },
|
|
3499
|
+
{
|
|
3500
|
+
edge: 'GET /v1/auth/session, POST /v1/authorize',
|
|
3501
|
+
name: 'Signed in',
|
|
3502
|
+
owner: 'app',
|
|
3503
|
+
screen: <AppSignedIn />,
|
|
3504
|
+
},
|
|
3505
|
+
{
|
|
3506
|
+
edge: 'Sign out, POST /v1/auth/logout',
|
|
3507
|
+
name: 'Sign in',
|
|
3508
|
+
owner: 'app',
|
|
3509
|
+
screen: <AppSignIn />,
|
|
3510
|
+
},
|
|
3511
|
+
],
|
|
3512
|
+
title: 'Headless sign-out',
|
|
3513
|
+
},
|
|
3514
|
+
{
|
|
3515
|
+
description:
|
|
3516
|
+
'The CLI revokes the stored refresh token at the deployment that issued it, and deletes the session even if the revocation fails.',
|
|
3517
|
+
nodes: [
|
|
3518
|
+
{ detail: '~/.tempo/id/credentials.json', name: 'tempo-id logout' },
|
|
3519
|
+
{
|
|
3520
|
+
detail: 'Session deleted',
|
|
3521
|
+
edge: 'POST /oauth2/revoke with the refresh token',
|
|
3522
|
+
name: 'tempo-id logout',
|
|
3523
|
+
},
|
|
3524
|
+
],
|
|
3525
|
+
title: 'tempo-id logout',
|
|
3526
|
+
},
|
|
3527
|
+
],
|
|
3528
|
+
title: 'Sign-out',
|
|
3529
|
+
},
|
|
3530
|
+
{
|
|
3531
|
+
flows: [
|
|
3532
|
+
{
|
|
3533
|
+
description:
|
|
3534
|
+
'A refused send keeps the first step, with the refusal below the email field, which it marks invalid. The user can try again or pick another method.',
|
|
3535
|
+
nodes: [
|
|
3536
|
+
{ detail: 'GET /login', name: 'Browser opens sign-in' },
|
|
3537
|
+
{
|
|
3538
|
+
edge: 'POST /v1/auth/code: 503',
|
|
3539
|
+
name: 'Sign in (send failed)',
|
|
3540
|
+
screen: <SignIn {...signIn()} error={unavailable} invalid />,
|
|
3541
|
+
},
|
|
3542
|
+
],
|
|
3543
|
+
title: 'Email code: send refused',
|
|
3544
|
+
},
|
|
3545
|
+
{
|
|
3546
|
+
description:
|
|
3547
|
+
'A canceled prompt or a refused passkey keeps the first step, with the error below the email field. The email field is not marked invalid, since no address was sent.',
|
|
3548
|
+
nodes: [
|
|
3549
|
+
{ detail: 'GET /login', name: 'Browser opens sign-in' },
|
|
3550
|
+
{
|
|
3551
|
+
edge: 'Canceled prompt, or POST /v1/auth/passkey/verify refused',
|
|
3552
|
+
name: 'Sign in (passkey failed)',
|
|
3553
|
+
screen: (
|
|
3554
|
+
<SignIn
|
|
3555
|
+
{...signIn()}
|
|
3556
|
+
error="Passkey sign-in was canceled. Try again, or continue with email."
|
|
3557
|
+
/>
|
|
3558
|
+
),
|
|
3559
|
+
},
|
|
3560
|
+
],
|
|
3561
|
+
title: 'Passkey: fails',
|
|
3562
|
+
},
|
|
3563
|
+
{
|
|
3564
|
+
description:
|
|
3565
|
+
'A refused code clears the field and marks it invalid, so the next code can be typed at once.',
|
|
3566
|
+
nodes: [
|
|
3567
|
+
{ detail: 'GET /login', name: 'Browser opens sign-in' },
|
|
3568
|
+
{
|
|
3569
|
+
edge: 'POST /v1/auth/code/verify: 401',
|
|
3570
|
+
name: 'Check your email (wrong code)',
|
|
3571
|
+
screen: (
|
|
3572
|
+
<SignIn
|
|
3573
|
+
{...signIn()}
|
|
3574
|
+
email={email}
|
|
3575
|
+
error="Invalid or expired code. Request a new one."
|
|
3576
|
+
invalid
|
|
3577
|
+
/>
|
|
3578
|
+
),
|
|
3579
|
+
},
|
|
3580
|
+
],
|
|
3581
|
+
title: 'Email code: wrong code',
|
|
3582
|
+
},
|
|
3583
|
+
{
|
|
3584
|
+
description:
|
|
3585
|
+
'With `SIGN_UP_DOMAINS`, a verified code for an address with no account, outside those domains, creates no account. An existing account at any domain still signs in.',
|
|
3586
|
+
nodes: [
|
|
3587
|
+
{ detail: 'GET /login', name: 'Browser opens sign-in' },
|
|
3588
|
+
{
|
|
3589
|
+
edge: 'POST /v1/auth/code/verify: 403 sign_up_not_permitted',
|
|
3590
|
+
name: 'Check your email (sign-up refused)',
|
|
3591
|
+
screen: (
|
|
3592
|
+
<SignIn
|
|
3593
|
+
{...signIn()}
|
|
3594
|
+
email={email}
|
|
3595
|
+
error="Sign-up not permitted: new accounts need an address at tempo.xyz. Sign in with an existing account, or with an address at tempo.xyz."
|
|
3596
|
+
invalid
|
|
3597
|
+
/>
|
|
3598
|
+
),
|
|
3599
|
+
},
|
|
3600
|
+
],
|
|
3601
|
+
title: 'Email code: sign-up refused',
|
|
3602
|
+
},
|
|
3603
|
+
{
|
|
3604
|
+
description:
|
|
3605
|
+
'A passkey that does not sign again, or whose assertion is refused, drops the link. The code field clears and takes focus, so the next code signs in with email only.',
|
|
3606
|
+
nodes: [
|
|
3607
|
+
{ detail: 'GET /login', name: 'Browser opens sign-in' },
|
|
3608
|
+
{
|
|
3609
|
+
edge: 'Canceled prompt, or POST /v1/auth/code/verify with link: webauthn_rejected',
|
|
3610
|
+
name: 'Check your email (link failed)',
|
|
3611
|
+
screen: (
|
|
3612
|
+
<SignIn
|
|
3613
|
+
{...signIn()}
|
|
3614
|
+
email={email}
|
|
3615
|
+
error="Your passkey did not confirm the sign-in. Enter the code again to sign in with email only."
|
|
3616
|
+
invalid
|
|
3617
|
+
/>
|
|
3618
|
+
),
|
|
3619
|
+
},
|
|
3620
|
+
],
|
|
3621
|
+
title: 'Passkey: link fails',
|
|
3622
|
+
},
|
|
3623
|
+
{
|
|
3624
|
+
description:
|
|
3625
|
+
'A resent code shows Code sent on the disabled button for 5 seconds, then Resend code again. A hidden status announces the send, and the code field keeps its value.',
|
|
3626
|
+
nodes: [
|
|
3627
|
+
{ detail: 'GET /login', name: 'Browser opens sign-in' },
|
|
3628
|
+
{
|
|
3629
|
+
edge: 'Resend code, POST /v1/auth/code',
|
|
3630
|
+
name: 'Check your email (resent)',
|
|
3631
|
+
screen: <SignIn {...signIn()} email={email} resend="sent" />,
|
|
3632
|
+
},
|
|
3633
|
+
],
|
|
3634
|
+
title: 'Email code: resent',
|
|
3635
|
+
},
|
|
3636
|
+
{
|
|
3637
|
+
description:
|
|
3638
|
+
'A refused resend keeps the code step and its code, with the refusal below the code field. The code stays valid, so the field is not marked invalid.',
|
|
3639
|
+
nodes: [
|
|
3640
|
+
{ detail: 'GET /login', name: 'Browser opens sign-in' },
|
|
3641
|
+
{
|
|
3642
|
+
edge: 'Resend code, POST /v1/auth/code: 429',
|
|
3643
|
+
name: 'Check your email (resend refused)',
|
|
3644
|
+
screen: (
|
|
3645
|
+
<SignIn
|
|
3646
|
+
{...signIn()}
|
|
3647
|
+
email={email}
|
|
3648
|
+
error="Too many requests. Try again in 1 minute."
|
|
3649
|
+
/>
|
|
3650
|
+
),
|
|
3651
|
+
},
|
|
3652
|
+
],
|
|
3653
|
+
title: 'Email code: resend refused',
|
|
3654
|
+
},
|
|
3655
|
+
{
|
|
3656
|
+
description:
|
|
3657
|
+
'A failed selection keeps the account step, so the user can choose again. The page does not reload, so an ended session or an expired request shows its refusal in the same place.',
|
|
3658
|
+
nodes: [
|
|
3659
|
+
{ detail: 'GET /choose-account?…&sig', name: 'Browser' },
|
|
3660
|
+
{
|
|
3661
|
+
edge: 'POST /v1/authorize/account: 503',
|
|
3662
|
+
name: 'Choose a wallet (failed)',
|
|
3663
|
+
screen: (
|
|
3664
|
+
<ChooseAccount {...chooseAccount()} error={unavailable} />
|
|
3665
|
+
),
|
|
3666
|
+
},
|
|
3667
|
+
{
|
|
3668
|
+
edge: 'Or: POST /v1/authorize/account: session_required',
|
|
3669
|
+
name: 'Choose a wallet (session ended)',
|
|
3670
|
+
screen: (
|
|
3671
|
+
<ChooseAccount {...chooseAccount()} error="Sign in first." />
|
|
3672
|
+
),
|
|
3673
|
+
},
|
|
3674
|
+
{
|
|
3675
|
+
edge: 'Or: POST /v1/authorize/account: authorization_expired',
|
|
3676
|
+
name: 'Choose a wallet (request expired)',
|
|
3677
|
+
screen: (
|
|
3678
|
+
<ChooseAccount
|
|
3679
|
+
{...chooseAccount()}
|
|
3680
|
+
error="Expired authorization request: the request from the app is no longer valid. Return to the app, and start again."
|
|
3681
|
+
/>
|
|
3682
|
+
),
|
|
3683
|
+
},
|
|
3684
|
+
],
|
|
3685
|
+
title: 'Account step: fails',
|
|
3686
|
+
},
|
|
3687
|
+
{
|
|
3688
|
+
description:
|
|
3689
|
+
'A failed decision keeps the approval page, with the refusal below the buttons.',
|
|
3690
|
+
nodes: [
|
|
3691
|
+
{ detail: 'GET /authorize?…&sig', name: 'Browser' },
|
|
3692
|
+
{
|
|
3693
|
+
edge: 'POST /v1/authorize/consent: 503',
|
|
3694
|
+
name: 'Authorize this app (failed)',
|
|
3695
|
+
screen: <Approval {...approval()} error={unavailable} />,
|
|
3696
|
+
},
|
|
3697
|
+
],
|
|
3698
|
+
title: 'Approval: fails',
|
|
3699
|
+
},
|
|
3700
|
+
{
|
|
3701
|
+
description:
|
|
3702
|
+
'A spent authorization rate limit refuses Approve, as it refuses the grant at sign-in. The approval page stays until the limit resets.',
|
|
3703
|
+
nodes: [
|
|
3704
|
+
{ detail: 'GET /authorize?…&sig', name: 'Browser' },
|
|
3705
|
+
{
|
|
3706
|
+
edge: 'Approve, POST /v1/authorize/consent: 429 rate_limited',
|
|
3707
|
+
name: 'Authorize this app (rate limited)',
|
|
3708
|
+
screen: (
|
|
3709
|
+
<Approval
|
|
3710
|
+
{...approval()}
|
|
3711
|
+
error="Too many requests. Try again in 1 minute."
|
|
3712
|
+
/>
|
|
3713
|
+
),
|
|
3714
|
+
},
|
|
3715
|
+
],
|
|
3716
|
+
title: 'Approval: rate limited',
|
|
3717
|
+
},
|
|
3718
|
+
{
|
|
3719
|
+
description:
|
|
3720
|
+
'A canceled prompt or a refused assertion keeps the approval page, and Approve starts a new passkey step.',
|
|
3721
|
+
nodes: [
|
|
3722
|
+
{ detail: 'GET /authorize?…&sig', name: 'Browser' },
|
|
3723
|
+
{
|
|
3724
|
+
edge: 'Canceled prompt, or POST /v1/authorize/consent with passkey.assertion: webauthn_rejected',
|
|
3725
|
+
name: 'Authorize this app (passkey failed)',
|
|
3726
|
+
screen: (
|
|
3727
|
+
<Approval
|
|
3728
|
+
{...approval()}
|
|
3729
|
+
error="The passkey request was canceled. Approve again, or deny the request."
|
|
3730
|
+
/>
|
|
3731
|
+
),
|
|
3732
|
+
},
|
|
3733
|
+
],
|
|
3734
|
+
title: 'Approval: passkey fails',
|
|
3735
|
+
},
|
|
3736
|
+
{
|
|
3737
|
+
description:
|
|
3738
|
+
'A session that ends before the decision reloads the page, and the loader sends the browser to sign-in with the signed query. A sign-in already spent this request, so the sign-in page shows only the app, and the next sign-in opens the approval page again.',
|
|
3739
|
+
nodes: [
|
|
3740
|
+
{ detail: 'GET /authorize?…&sig', name: 'Browser' },
|
|
3741
|
+
{
|
|
3742
|
+
edge: '200 approval page',
|
|
3743
|
+
name: 'Authorize this app',
|
|
3744
|
+
screen: <Approval {...approval()} />,
|
|
3745
|
+
},
|
|
3746
|
+
{
|
|
3747
|
+
edge: 'Approve, POST /v1/authorize/consent: session_required, reload, 302 /login?…&sig, GET /login: spent request',
|
|
3748
|
+
name: 'Sign in (app)',
|
|
3749
|
+
screen: <SignIn {...signIn()} request={appRequest()} />,
|
|
3750
|
+
},
|
|
3751
|
+
{
|
|
3752
|
+
edge: 'POST /v1/auth/code',
|
|
3753
|
+
name: 'Check your email',
|
|
3754
|
+
screen: <SignIn {...signIn()} email={email} />,
|
|
3755
|
+
},
|
|
3756
|
+
{
|
|
3757
|
+
edge: 'POST /v1/auth/code/verify with oauth_query: 200',
|
|
3758
|
+
name: 'Check your email (accepted)',
|
|
3759
|
+
screen: <SignIn {...signIn()} email={email} verified />,
|
|
3760
|
+
},
|
|
3761
|
+
{
|
|
3762
|
+
edge: 'after 500 ms, redirect /authorize?…&sig',
|
|
3763
|
+
name: 'Authorize this app',
|
|
3764
|
+
screen: <Approval {...approval()} />,
|
|
3765
|
+
},
|
|
3766
|
+
{
|
|
3767
|
+
detail: 'GET redirect_uri?code&state',
|
|
3768
|
+
edge: 'Approve, POST /v1/authorize/consent',
|
|
3769
|
+
name: 'Web app callback',
|
|
3770
|
+
},
|
|
3771
|
+
{
|
|
3772
|
+
...tokenExit,
|
|
3773
|
+
detail: 'Tokens, account, and key_authorization',
|
|
3774
|
+
},
|
|
3775
|
+
],
|
|
3776
|
+
title: 'Approval: session expired',
|
|
3777
|
+
},
|
|
3778
|
+
{
|
|
3779
|
+
description:
|
|
3780
|
+
'A user who was signed in when the request arrived reached the approval page without a sign-in, so the request is unspent. The sign-in after the reload grants the key and returns to the app at once.',
|
|
3781
|
+
nodes: [
|
|
3782
|
+
{ detail: 'GET /authorize?…&sig', name: 'Browser' },
|
|
3783
|
+
{
|
|
3784
|
+
edge: '200 approval page',
|
|
3785
|
+
name: 'Authorize this app',
|
|
3786
|
+
screen: <Approval {...approval()} />,
|
|
3787
|
+
},
|
|
3788
|
+
{
|
|
3789
|
+
edge: 'Approve, POST /v1/authorize/consent: session_required, reload, 302 /login?…&sig',
|
|
3790
|
+
name: 'Sign in (key)',
|
|
3791
|
+
screen: <SignIn {...signIn()} request={signInRequest()} />,
|
|
3792
|
+
},
|
|
3793
|
+
{
|
|
3794
|
+
edge: 'POST /v1/auth/code',
|
|
3795
|
+
name: 'Check your email',
|
|
3796
|
+
screen: <SignIn {...signIn()} email={email} />,
|
|
3797
|
+
},
|
|
3798
|
+
{
|
|
3799
|
+
edge: 'POST /v1/auth/code/verify with oauth_query: 200, grants the key',
|
|
3800
|
+
name: 'Check your email (accepted)',
|
|
3801
|
+
screen: <SignIn {...signIn()} email={email} verified />,
|
|
3802
|
+
},
|
|
3803
|
+
{
|
|
3804
|
+
detail: 'GET redirect_uri?code&state',
|
|
3805
|
+
edge: 'after 500 ms, redirect',
|
|
3806
|
+
name: 'Web app callback',
|
|
3807
|
+
},
|
|
3808
|
+
{
|
|
3809
|
+
...tokenExit,
|
|
3810
|
+
detail: 'Tokens, account, and key_authorization',
|
|
3811
|
+
},
|
|
3812
|
+
],
|
|
3813
|
+
title: 'Approval: session expired, unspent',
|
|
3814
|
+
},
|
|
3815
|
+
{
|
|
3816
|
+
description:
|
|
3817
|
+
'A failed decision keeps the device page with the request, and the refusal below the buttons.',
|
|
3818
|
+
nodes: [
|
|
3819
|
+
{ detail: 'GET /device?code', name: 'Browser' },
|
|
3820
|
+
{
|
|
3821
|
+
edge: 'POST /v1/device/approve: 503',
|
|
3822
|
+
name: 'Authorize a device (failed)',
|
|
3823
|
+
screen: (
|
|
3824
|
+
<DeviceApproval
|
|
3825
|
+
{...deviceApproval()}
|
|
3826
|
+
error={unavailable}
|
|
3827
|
+
request={deviceRequest()}
|
|
3828
|
+
/>
|
|
3829
|
+
),
|
|
3830
|
+
},
|
|
3831
|
+
],
|
|
3832
|
+
title: 'Device code: decision fails',
|
|
3833
|
+
},
|
|
3834
|
+
{
|
|
3835
|
+
description:
|
|
3836
|
+
'A session that ends before the decision reloads the page, and sign-in returns to the same code. The request is bound to its user, so another user’s sign-in shows the unknown code.',
|
|
3837
|
+
nodes: [
|
|
3838
|
+
{ detail: 'GET /device?code', name: 'Browser' },
|
|
3839
|
+
{
|
|
3840
|
+
edge: '200 device page',
|
|
3841
|
+
name: 'Authorize a device',
|
|
3842
|
+
screen: (
|
|
3843
|
+
<DeviceApproval
|
|
3844
|
+
{...deviceApproval()}
|
|
3845
|
+
request={deviceRequest()}
|
|
3846
|
+
/>
|
|
3847
|
+
),
|
|
3848
|
+
},
|
|
3849
|
+
{
|
|
3850
|
+
edge: 'Approve, POST /v1/device/approve: session_required, reload, 302 /login?return_to=/device?code',
|
|
3851
|
+
name: 'Sign in',
|
|
3852
|
+
screen: <SignIn {...signIn()} />,
|
|
3853
|
+
},
|
|
3854
|
+
{
|
|
3855
|
+
edge: 'Sign-in, /device?code',
|
|
3856
|
+
name: 'Authorize a device',
|
|
3857
|
+
screen: (
|
|
3858
|
+
<DeviceApproval
|
|
3859
|
+
{...deviceApproval()}
|
|
3860
|
+
request={deviceRequest()}
|
|
3861
|
+
/>
|
|
3862
|
+
),
|
|
3863
|
+
},
|
|
3864
|
+
],
|
|
3865
|
+
title: 'Device code: session expired',
|
|
3866
|
+
},
|
|
3867
|
+
{
|
|
3868
|
+
description:
|
|
3869
|
+
'A canceled prompt or a refused assertion keeps the device page, and Approve starts a new passkey step.',
|
|
3870
|
+
nodes: [
|
|
3871
|
+
{ detail: 'GET /device?code', name: 'Browser' },
|
|
3872
|
+
{
|
|
3873
|
+
edge: 'Canceled prompt, or POST /v1/device/approve with passkey.assertion: webauthn_rejected',
|
|
3874
|
+
name: 'Authorize a device (passkey failed)',
|
|
3875
|
+
screen: (
|
|
3876
|
+
<DeviceApproval
|
|
3877
|
+
{...deviceApproval()}
|
|
3878
|
+
error="The passkey request was canceled. Approve again, or deny the request."
|
|
3879
|
+
request={deviceRequest()}
|
|
3880
|
+
/>
|
|
3881
|
+
),
|
|
3882
|
+
},
|
|
3883
|
+
],
|
|
3884
|
+
title: 'Device code: passkey fails',
|
|
3885
|
+
},
|
|
3886
|
+
{
|
|
3887
|
+
description:
|
|
3888
|
+
'A failed sign-out keeps the account page, with the refusal above the button.',
|
|
3889
|
+
nodes: [
|
|
3890
|
+
{ detail: 'GET /', name: 'Browser opens Tempo ID' },
|
|
3891
|
+
{
|
|
3892
|
+
edge: 'POST /v1/auth/logout: 503',
|
|
3893
|
+
name: 'Your account (sign-out failed)',
|
|
3894
|
+
screen: <Account {...account()} error={unavailable} />,
|
|
3895
|
+
},
|
|
3896
|
+
],
|
|
3897
|
+
title: 'Sign-out: fails',
|
|
3898
|
+
},
|
|
3899
|
+
],
|
|
3900
|
+
title: 'Request failures',
|
|
3901
|
+
},
|
|
3902
|
+
]
|
|
3903
|
+
}
|
|
3904
|
+
|
|
3905
|
+
namespace styles {
|
|
3906
|
+
export const app = style({ display: 'grid', gap: '12' })
|
|
3907
|
+
|
|
3908
|
+
export const appAddress = style({
|
|
3909
|
+
color: 'gray.900',
|
|
3910
|
+
margin: '0',
|
|
3911
|
+
overflowWrap: 'anywhere',
|
|
3912
|
+
typography: 'copy.13.mono',
|
|
3913
|
+
})
|
|
3914
|
+
|
|
3915
|
+
export const appButton = variants({
|
|
3916
|
+
base: {
|
|
3917
|
+
alignItems: 'center',
|
|
3918
|
+
border: 'none',
|
|
3919
|
+
borderRadius: 'full',
|
|
3920
|
+
cursor: 'pointer',
|
|
3921
|
+
display: 'flex',
|
|
3922
|
+
gap: '12',
|
|
3923
|
+
height: '48',
|
|
3924
|
+
justifyContent: 'center',
|
|
3925
|
+
paddingInline: '24',
|
|
3926
|
+
typography: 'button.16',
|
|
3927
|
+
width: '100% !custom',
|
|
3928
|
+
},
|
|
3929
|
+
variants: {
|
|
3930
|
+
tone: {
|
|
3931
|
+
brand: { backgroundColor: 'red.700', color: 'white' },
|
|
3932
|
+
quiet: { backgroundColor: 'gray.100', color: 'foreground' },
|
|
3933
|
+
},
|
|
3934
|
+
},
|
|
3935
|
+
})
|
|
3936
|
+
|
|
3937
|
+
export const appDescription = style({
|
|
3938
|
+
color: 'gray.900',
|
|
3939
|
+
typography: 'copy.16',
|
|
3940
|
+
})
|
|
3941
|
+
|
|
3942
|
+
// A rule on each side of the label, as the sample app's divider draws it.
|
|
3943
|
+
export const appDivider = style({
|
|
3944
|
+
alignItems: 'center',
|
|
3945
|
+
color: 'gray.900',
|
|
3946
|
+
display: 'flex',
|
|
3947
|
+
gap: '16',
|
|
3948
|
+
margin: '0',
|
|
3949
|
+
typography: 'copy.14',
|
|
3950
|
+
whiteSpace: 'nowrap',
|
|
3951
|
+
'&::before, &::after': {
|
|
3952
|
+
backgroundColor: 'gray.400',
|
|
3953
|
+
content: '""',
|
|
3954
|
+
flexGrow: 1,
|
|
3955
|
+
height: '1',
|
|
3956
|
+
},
|
|
3957
|
+
})
|
|
3958
|
+
|
|
3959
|
+
export const appField = style({ display: 'grid', gap: '8' })
|
|
3960
|
+
|
|
3961
|
+
export const appHeader = style({
|
|
3962
|
+
display: 'grid',
|
|
3963
|
+
gap: '8',
|
|
3964
|
+
marginBottom: '8',
|
|
3965
|
+
})
|
|
3966
|
+
|
|
3967
|
+
export const appInput = style({
|
|
3968
|
+
backgroundColor: 'gray.100',
|
|
3969
|
+
border: 'none',
|
|
3970
|
+
borderRadius: '6',
|
|
3971
|
+
color: 'foreground',
|
|
3972
|
+
height: '48',
|
|
3973
|
+
paddingInline: '16',
|
|
3974
|
+
typography: 'copy.16',
|
|
3975
|
+
width: '100% !custom',
|
|
3976
|
+
})
|
|
3977
|
+
|
|
3978
|
+
export const appLabel = style({ typography: 'label.16' })
|
|
3979
|
+
|
|
3980
|
+
export const appTitle = style({ typography: 'heading.32' })
|
|
3981
|
+
|
|
3982
|
+
export const arrow = style({
|
|
3983
|
+
alignItems: 'center',
|
|
3984
|
+
display: 'grid',
|
|
3985
|
+
flexShrink: 0,
|
|
3986
|
+
gap: '8',
|
|
3987
|
+
justifyItems: 'center',
|
|
3988
|
+
paddingInline: '12',
|
|
3989
|
+
width: '192',
|
|
3990
|
+
})
|
|
3991
|
+
|
|
3992
|
+
export const artboard = style({ display: 'grid', gap: '8', margin: '0' })
|
|
3993
|
+
|
|
3994
|
+
export const canvas = style({
|
|
3995
|
+
backgroundColor: 'gray.100',
|
|
3996
|
+
minHeight: '100dvh !custom',
|
|
3997
|
+
// The canvas grows to its widest flow at the current zoom, so the page scrolls to pan.
|
|
3998
|
+
minWidth: '100% !custom',
|
|
3999
|
+
// Below 64rem, the fixed controls reach over the introduction, so the canvas starts below them.
|
|
4000
|
+
// The space sits outside the zoom, so it holds at every scale.
|
|
4001
|
+
paddingTop: '96',
|
|
4002
|
+
width: 'max-content !custom',
|
|
4003
|
+
'@media (min-width: 64rem)': { paddingTop: '0' },
|
|
4004
|
+
})
|
|
4005
|
+
|
|
4006
|
+
export const code = style({ typography: 'copy.13.mono' })
|
|
4007
|
+
|
|
4008
|
+
export const detail = style({
|
|
4009
|
+
color: 'gray.900',
|
|
4010
|
+
overflowWrap: 'anywhere',
|
|
4011
|
+
typography: 'copy.13.mono',
|
|
4012
|
+
})
|
|
4013
|
+
|
|
4014
|
+
export const edge = style({
|
|
4015
|
+
color: 'gray.900',
|
|
4016
|
+
overflowWrap: 'anywhere',
|
|
4017
|
+
textAlign: 'center',
|
|
4018
|
+
typography: 'copy.13.mono',
|
|
4019
|
+
})
|
|
4020
|
+
|
|
4021
|
+
export const external = variants({
|
|
4022
|
+
base: {
|
|
4023
|
+
backgroundColor: 'background.surface',
|
|
4024
|
+
border: '1px dashed',
|
|
4025
|
+
borderColor: 'gray.500',
|
|
4026
|
+
borderRadius: '8',
|
|
4027
|
+
display: 'grid',
|
|
4028
|
+
flexShrink: 0,
|
|
4029
|
+
gap: '4',
|
|
4030
|
+
padding: '16',
|
|
4031
|
+
width: '224',
|
|
4032
|
+
},
|
|
4033
|
+
variants: {
|
|
4034
|
+
kind: {
|
|
4035
|
+
Entry: { borderColor: 'blue.700', borderStyle: 'solid' },
|
|
4036
|
+
Exit: { borderColor: 'green.700', borderStyle: 'solid' },
|
|
4037
|
+
External: {},
|
|
4038
|
+
},
|
|
4039
|
+
},
|
|
4040
|
+
})
|
|
4041
|
+
|
|
4042
|
+
export const externalName = style({ typography: 'label.14' })
|
|
4043
|
+
|
|
4044
|
+
export const flow = style({ display: 'grid', gap: '16' })
|
|
4045
|
+
|
|
4046
|
+
export const flowDescription = style({
|
|
4047
|
+
color: 'gray.900',
|
|
4048
|
+
typography: 'copy.14',
|
|
4049
|
+
})
|
|
4050
|
+
|
|
4051
|
+
// The flow's text stays at the canvas's left edge while the row scrolls.
|
|
4052
|
+
export const flowHeader = style({
|
|
4053
|
+
display: 'grid',
|
|
4054
|
+
gap: '4',
|
|
4055
|
+
left: '16',
|
|
4056
|
+
maxWidth: 'min(36rem, calc(100vw - 2rem)) !custom',
|
|
4057
|
+
position: 'sticky',
|
|
4058
|
+
'@media (min-width: 48rem)': { left: '48' },
|
|
4059
|
+
})
|
|
4060
|
+
|
|
4061
|
+
export const flowTitle = style({ typography: 'heading.16' })
|
|
4062
|
+
|
|
4063
|
+
// Each frame draws the page's color, so its screen reads as it does in the app.
|
|
4064
|
+
export const frame = style({
|
|
4065
|
+
// oxlint-disable-next-line typescript/restrict-template-expressions -- zyzz reads each token reference at build time, so it stays a direct interpolation.
|
|
4066
|
+
backgroundColor: `light-dark(${vars.color.background.surface}, ${vars.color.background.primary}) !custom`,
|
|
4067
|
+
borderRadius: '8',
|
|
4068
|
+
boxShadow: '6',
|
|
4069
|
+
paddingBlock: '40',
|
|
4070
|
+
paddingInline: '24',
|
|
4071
|
+
width: '384',
|
|
4072
|
+
})
|
|
4073
|
+
|
|
4074
|
+
export const frameLabel = style({ color: 'gray.900', typography: 'label.12' })
|
|
4075
|
+
|
|
4076
|
+
export const head = style({
|
|
4077
|
+
color: 'gray.600',
|
|
4078
|
+
flexShrink: 0,
|
|
4079
|
+
height: '16',
|
|
4080
|
+
width: '16',
|
|
4081
|
+
})
|
|
4082
|
+
|
|
4083
|
+
export const hidden = style({
|
|
4084
|
+
clipPath: 'inset(50%) !custom',
|
|
4085
|
+
height: '1px !custom',
|
|
4086
|
+
overflow: 'hidden',
|
|
4087
|
+
position: 'absolute',
|
|
4088
|
+
whiteSpace: 'nowrap',
|
|
4089
|
+
width: '1px !custom',
|
|
4090
|
+
})
|
|
4091
|
+
|
|
4092
|
+
export const intro = style({
|
|
4093
|
+
display: 'grid',
|
|
4094
|
+
gap: '8',
|
|
4095
|
+
left: '16',
|
|
4096
|
+
maxWidth: 'min(36rem, calc(100vw - 2rem)) !custom',
|
|
4097
|
+
position: 'sticky',
|
|
4098
|
+
'@media (min-width: 48rem)': { left: '48' },
|
|
4099
|
+
})
|
|
4100
|
+
|
|
4101
|
+
export const kind = style({
|
|
4102
|
+
color: 'gray.900',
|
|
4103
|
+
textTransform: 'uppercase',
|
|
4104
|
+
typography: 'label.12',
|
|
4105
|
+
})
|
|
4106
|
+
|
|
4107
|
+
export const legend = style({
|
|
4108
|
+
backgroundColor: 'background.surface',
|
|
4109
|
+
borderRadius: '12',
|
|
4110
|
+
boxShadow: 'popover',
|
|
4111
|
+
// The list ends above the developer toolbar at the bottom of the viewport.
|
|
4112
|
+
maxHeight: 'calc(100dvh - 9.5rem) !custom',
|
|
4113
|
+
overflowY: 'auto',
|
|
4114
|
+
pointerEvents: 'auto',
|
|
4115
|
+
width: 'min(18rem, calc(100vw - 2rem)) !custom',
|
|
4116
|
+
})
|
|
4117
|
+
|
|
4118
|
+
export const legendFlow = style({
|
|
4119
|
+
borderRadius: '6',
|
|
4120
|
+
color: 'gray.900',
|
|
4121
|
+
display: 'block',
|
|
4122
|
+
paddingBlock: '4',
|
|
4123
|
+
paddingInline: '8',
|
|
4124
|
+
textDecoration: 'none',
|
|
4125
|
+
typography: 'copy.13',
|
|
4126
|
+
'&:hover': { backgroundColor: 'grayAlpha.100', color: 'foreground' },
|
|
4127
|
+
})
|
|
4128
|
+
|
|
4129
|
+
export const legendFlows = style({
|
|
4130
|
+
listStyle: 'none',
|
|
4131
|
+
margin: '0',
|
|
4132
|
+
padding: '0',
|
|
4133
|
+
})
|
|
4134
|
+
|
|
4135
|
+
export const legendList = style({
|
|
4136
|
+
display: 'grid',
|
|
4137
|
+
gap: '12',
|
|
4138
|
+
listStyle: 'none',
|
|
4139
|
+
margin: '0',
|
|
4140
|
+
paddingBlockEnd: '8',
|
|
4141
|
+
paddingBlockStart: '0',
|
|
4142
|
+
paddingInline: '8',
|
|
4143
|
+
})
|
|
4144
|
+
|
|
4145
|
+
export const legendSection = style({
|
|
4146
|
+
borderRadius: '6',
|
|
4147
|
+
color: 'foreground',
|
|
4148
|
+
display: 'block',
|
|
4149
|
+
paddingBlock: '4',
|
|
4150
|
+
paddingInline: '8',
|
|
4151
|
+
textDecoration: 'none',
|
|
4152
|
+
typography: 'label.14',
|
|
4153
|
+
'&:hover': { backgroundColor: 'grayAlpha.100' },
|
|
4154
|
+
})
|
|
4155
|
+
|
|
4156
|
+
export const legendItem = style({ borderBottom: 'none !custom' })
|
|
4157
|
+
|
|
4158
|
+
export const legendTrigger = style({
|
|
4159
|
+
paddingInline: '1rem !custom',
|
|
4160
|
+
})
|
|
4161
|
+
|
|
4162
|
+
export const lede = style({ color: 'gray.900', typography: 'copy.16' })
|
|
4163
|
+
|
|
4164
|
+
export const line = style({
|
|
4165
|
+
backgroundColor: 'gray.600',
|
|
4166
|
+
flexGrow: 1,
|
|
4167
|
+
height: '1',
|
|
4168
|
+
})
|
|
4169
|
+
|
|
4170
|
+
// The zoom controls and the contents stay at the top right, clear of the developer toolbar at the
|
|
4171
|
+
// bottom. Clicks between them reach the canvas.
|
|
4172
|
+
export const panels = style({
|
|
4173
|
+
alignItems: 'flex-end',
|
|
4174
|
+
display: 'flex',
|
|
4175
|
+
flexDirection: 'column',
|
|
4176
|
+
gap: '8',
|
|
4177
|
+
pointerEvents: 'none',
|
|
4178
|
+
position: 'fixed',
|
|
4179
|
+
right: '16',
|
|
4180
|
+
top: '16',
|
|
4181
|
+
zIndex: 'overlay',
|
|
4182
|
+
})
|
|
4183
|
+
|
|
4184
|
+
export const placeholder = style({ flexShrink: 0 })
|
|
4185
|
+
|
|
4186
|
+
// A box of 100 CSS pixels inside the zoom, whose rect height measures the zoom's geometry.
|
|
4187
|
+
export const probe = style({
|
|
4188
|
+
height: '100px !custom',
|
|
4189
|
+
pointerEvents: 'none',
|
|
4190
|
+
position: 'absolute',
|
|
4191
|
+
visibility: 'hidden',
|
|
4192
|
+
width: '1px !custom',
|
|
4193
|
+
})
|
|
4194
|
+
|
|
4195
|
+
export const row = style({
|
|
4196
|
+
alignItems: 'center',
|
|
4197
|
+
display: 'flex',
|
|
4198
|
+
listStyle: 'none',
|
|
4199
|
+
margin: '0',
|
|
4200
|
+
padding: '0',
|
|
4201
|
+
})
|
|
4202
|
+
|
|
4203
|
+
// The box spans its row, so the observer sees the row's full width at every scroll position.
|
|
4204
|
+
export const rowBox = style({ width: 'max-content !custom' })
|
|
4205
|
+
|
|
4206
|
+
export const section = style({ display: 'grid', gap: '40' })
|
|
4207
|
+
|
|
4208
|
+
export const sectionTitle = style({
|
|
4209
|
+
borderBottom: '1px solid',
|
|
4210
|
+
borderBottomColor: 'grayAlpha.400',
|
|
4211
|
+
paddingBottom: '8',
|
|
4212
|
+
typography: 'heading.20',
|
|
4213
|
+
})
|
|
4214
|
+
|
|
4215
|
+
export const shaft = style({
|
|
4216
|
+
alignItems: 'center',
|
|
4217
|
+
display: 'flex',
|
|
4218
|
+
width: '100% !custom',
|
|
4219
|
+
})
|
|
4220
|
+
|
|
4221
|
+
export const step = style({ alignItems: 'center', display: 'flex' })
|
|
4222
|
+
|
|
4223
|
+
export const title = style({ typography: 'heading.32' })
|
|
4224
|
+
|
|
4225
|
+
export const world = style({
|
|
4226
|
+
display: 'grid',
|
|
4227
|
+
gap: '64',
|
|
4228
|
+
padding: '16',
|
|
4229
|
+
'@media (min-width: 48rem)': { padding: '48' },
|
|
4230
|
+
})
|
|
4231
|
+
|
|
4232
|
+
export const zoom = style({
|
|
4233
|
+
alignItems: 'center',
|
|
4234
|
+
backgroundColor: 'background.surface',
|
|
4235
|
+
borderRadius: 'full',
|
|
4236
|
+
boxShadow: 'popover',
|
|
4237
|
+
display: 'flex',
|
|
4238
|
+
gap: '2',
|
|
4239
|
+
padding: '4',
|
|
4240
|
+
pointerEvents: 'auto',
|
|
4241
|
+
})
|
|
4242
|
+
|
|
4243
|
+
export const zoomIcon = style({
|
|
4244
|
+
flexShrink: '0 !custom',
|
|
4245
|
+
height: '18',
|
|
4246
|
+
width: '18',
|
|
4247
|
+
})
|
|
4248
|
+
|
|
4249
|
+
// A fixed width keeps the zoom buttons in place as the percentage changes.
|
|
4250
|
+
export const zoomLevel = style({
|
|
4251
|
+
fontVariantNumeric: 'tabular-nums',
|
|
4252
|
+
minWidth: '56',
|
|
4253
|
+
})
|
|
4254
|
+
}
|