@dexterai/connect 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -60,10 +60,101 @@ function derInteger(component) {
60
60
  return out;
61
61
  }
62
62
 
63
+ // src/popup.ts
64
+ var DEFAULT_CONNECT_HOST = "https://dexter.cash/connect";
65
+ var POPUP_TIMEOUT_MS = 12e4;
66
+ var CANONICAL_ORIGIN = "https://dexter.cash";
67
+ function shouldUsePopup(transport) {
68
+ if (transport === "popup") return true;
69
+ if (transport === "inline") return false;
70
+ if (typeof window === "undefined") return false;
71
+ return window.location.origin !== CANONICAL_ORIGIN;
72
+ }
73
+ function openCeremonyPopup(op, config = {}) {
74
+ if (typeof window === "undefined") {
75
+ return Promise.reject(new ConnectError("not_browser", "popup ceremony requires a browser"));
76
+ }
77
+ const host = (config.connectHost ?? DEFAULT_CONNECT_HOST).replace(/\/$/, "");
78
+ const hostOrigin = new URL(host).origin;
79
+ const openerOrigin = window.location.origin;
80
+ const requestId = makeNonce();
81
+ const params = new URLSearchParams({ v: "1", op, requestId, origin: openerOrigin });
82
+ if (config.name) params.set("name", config.name);
83
+ if (config.apiBase) params.set("apiBase", config.apiBase);
84
+ const url = `${host}?${params.toString()}`;
85
+ return new Promise((resolve, reject) => {
86
+ const popup = window.open(url, "dexter-connect", popupFeatures());
87
+ if (!popup) {
88
+ reject(
89
+ new ConnectError("popup_blocked", "the Dexter sign-in popup was blocked \u2014 allow popups for this site")
90
+ );
91
+ return;
92
+ }
93
+ let settled = false;
94
+ const onMessage = (event) => {
95
+ if (event.origin !== hostOrigin) return;
96
+ const data = event.data;
97
+ if (!data || data.type !== "dexter-connect:result" || data.requestId !== requestId) return;
98
+ if (data.ok) finish(() => resolve(data.result));
99
+ else
100
+ finish(
101
+ () => reject(new ConnectError(data.error?.code ?? "popup_failed", data.error?.message))
102
+ );
103
+ };
104
+ const closedTimer = window.setInterval(() => {
105
+ if (popup.closed) finish(() => reject(new ConnectError("popup_closed", "the sign-in window was closed")));
106
+ }, 500);
107
+ const timeout = window.setTimeout(
108
+ () => finish(() => reject(new ConnectError("popup_timeout", "the sign-in window timed out"))),
109
+ POPUP_TIMEOUT_MS
110
+ );
111
+ function finish(act) {
112
+ if (settled) return;
113
+ settled = true;
114
+ window.removeEventListener("message", onMessage);
115
+ window.clearInterval(closedTimer);
116
+ window.clearTimeout(timeout);
117
+ try {
118
+ popup?.close();
119
+ } catch {
120
+ }
121
+ act();
122
+ }
123
+ window.addEventListener("message", onMessage);
124
+ });
125
+ }
126
+ function popupFeatures() {
127
+ const w = 420;
128
+ const h = 660;
129
+ const sy = typeof window !== "undefined" ? window.screenY || 0 : 0;
130
+ const sx = typeof window !== "undefined" ? window.screenX || 0 : 0;
131
+ const sh = typeof window !== "undefined" ? window.screen?.height ?? h : h;
132
+ const sw = typeof window !== "undefined" ? window.screen?.width ?? w : w;
133
+ const top = Math.max(0, Math.round((sh - h) / 2 + sy));
134
+ const left = Math.max(0, Math.round((sw - w) / 2 + sx));
135
+ return `popup,width=${w},height=${h},left=${left},top=${top}`;
136
+ }
137
+ function makeNonce() {
138
+ const c = typeof crypto !== "undefined" ? crypto : void 0;
139
+ if (c?.randomUUID) return c.randomUUID();
140
+ if (c?.getRandomValues) {
141
+ const a = new Uint8Array(16);
142
+ c.getRandomValues(a);
143
+ return Array.from(a, (b) => b.toString(16).padStart(2, "0")).join("");
144
+ }
145
+ return `rid-${new URL(location.href).searchParams.get("v") ?? ""}-${typeof performance !== "undefined" ? performance.now() : 0}`;
146
+ }
147
+
63
148
  // src/relay.ts
64
149
  var DEFAULT_API_BASE = "https://api.dexter.cash";
65
150
  var ANON_SIGN_BASE = "/api/passkey-anon/sign";
66
151
  async function passkeyLogin(config = {}, onPhase) {
152
+ if (shouldUsePopup(config.transport)) {
153
+ return openCeremonyPopup("signin", {
154
+ connectHost: config.connectHost,
155
+ apiBase: config.apiBase
156
+ });
157
+ }
67
158
  const apiBase = (config.apiBase ?? DEFAULT_API_BASE).replace(/\/$/, "");
68
159
  onPhase?.("challenge");
69
160
  const options = await fetchLoginChallenge(apiBase);
@@ -422,6 +513,8 @@ export {
422
513
  ConnectError,
423
514
  base64urlToBytes,
424
515
  bytesToBase64url,
516
+ shouldUsePopup,
517
+ openCeremonyPopup,
425
518
  passkeyLogin,
426
519
  createAnonServerPolicy,
427
520
  createPasskeySigner,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { D as DexterConnectConfig, C as CeremonyPhase, S as SignInResult, a as ConnectVault } from './signals-CXkNJfIo.js';
2
- export { A as ACTIVE_WALLET_STORAGE_KEY, b as ConnectError, P as PasskeyLoginTokens, c as PasskeySignalSupport, d as StoredWallet, e as ejectActiveWallet, f as forgetWallet, g as getActiveHandle, h as getCredentialId, l as listWallets, p as passkeySignalSupport, i as prunePasskey, r as renamePasskey, s as setActiveHandle, j as subscribeWallet, k as switchWallet, m as syncAcceptedPasskeys } from './signals-CXkNJfIo.js';
1
+ import { D as DexterConnectConfig, C as CeremonyPhase, S as SignInResult, a as ConnectVault } from './signals-B7GXneoj.js';
2
+ export { A as ACTIVE_WALLET_STORAGE_KEY, b as ConnectError, P as PasskeyLoginTokens, c as PasskeySignalSupport, d as StoredWallet, e as ejectActiveWallet, f as forgetWallet, g as getActiveHandle, h as getCredentialId, l as listWallets, p as passkeySignalSupport, i as prunePasskey, r as renamePasskey, s as setActiveHandle, j as subscribeWallet, k as switchWallet, m as syncAcceptedPasskeys } from './signals-B7GXneoj.js';
3
3
  import { DexterApiBrowserPasskeySigner } from '@dexterai/vault/signers/browser';
4
4
 
5
5
  /**
package/dist/index.js CHANGED
@@ -11,21 +11,30 @@ import {
11
11
  getActiveHandle,
12
12
  getCredentialId,
13
13
  listWallets,
14
+ openCeremonyPopup,
14
15
  passkeyLogin,
15
16
  passkeySignalSupport,
16
17
  prunePasskey,
17
18
  renamePasskey,
18
19
  setActiveHandle,
20
+ shouldUsePopup,
19
21
  subscribe,
20
22
  switchWallet,
21
23
  syncAcceptedPasskeys
22
- } from "./chunk-ZHJ557FI.js";
24
+ } from "./chunk-YCA2UT4N.js";
23
25
 
24
26
  // src/enroll.ts
25
27
  var DEFAULT_API_BASE = "https://api.dexter.cash";
26
28
  var DEFAULT_RP_ID = "dexter.cash";
27
29
  var DEFAULT_WALLET_NAME = "Dexter Wallet";
28
30
  async function createWallet(config = {}) {
31
+ if (shouldUsePopup(config.transport)) {
32
+ return openCeremonyPopup("create", {
33
+ connectHost: config.connectHost,
34
+ name: config.name,
35
+ apiBase: config.apiBase
36
+ });
37
+ }
29
38
  if (typeof navigator === "undefined" || !navigator.credentials) {
30
39
  throw new ConnectError("webauthn_unsupported", "WebAuthn unavailable in this environment");
31
40
  }
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DexterApiBrowserPasskeySigner } from '@dexterai/vault/signers/browser';
2
- import { C as CeremonyPhase, S as SignInResult, P as PasskeyLoginTokens, a as ConnectVault, b as ConnectError, d as StoredWallet, c as PasskeySignalSupport } from './signals-CXkNJfIo.js';
2
+ import { C as CeremonyPhase, S as SignInResult, P as PasskeyLoginTokens, a as ConnectVault, b as ConnectError, d as StoredWallet, c as PasskeySignalSupport } from './signals-B7GXneoj.js';
3
3
  import { ReactElement, ReactNode } from 'react';
4
4
 
5
5
  type ConnectStatus = 'idle' | 'pending' | 'done' | 'error';
package/dist/react.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  setActiveHandle,
14
14
  subscribe,
15
15
  switchWallet
16
- } from "./chunk-ZHJ557FI.js";
16
+ } from "./chunk-YCA2UT4N.js";
17
17
 
18
18
  // src/useSignInWithDexter.ts
19
19
  import { useCallback, useEffect, useMemo, useState } from "react";
@@ -41,6 +41,18 @@ type CeremonyPhase = 'challenge' | 'passkey' | 'verifying' | 'finalizing';
41
41
  interface DexterConnectConfig {
42
42
  /** dexter-api base. Default https://api.dexter.cash. */
43
43
  apiBase?: string;
44
+ /**
45
+ * Where the WebAuthn ceremony runs:
46
+ * - 'auto' (default): inline on the canonical Dexter origin (dexter.cash),
47
+ * popup on ANY other origin — so a third-party site works without the
48
+ * WebAuthn rpId-origin problem (in-page only works on dexter.cash).
49
+ * - 'popup': always via the hosted popup (works on any website).
50
+ * - 'inline': always in-page — only valid on a Dexter origin; this is what
51
+ * the hosted ceremony page itself uses.
52
+ */
53
+ transport?: 'auto' | 'popup' | 'inline';
54
+ /** Hosted ceremony page (popup transport). Default https://dexter.cash/connect. */
55
+ connectHost?: string;
44
56
  }
45
57
  /** Typed error whose `code` is the server's snake_case error string. */
46
58
  declare class ConnectError extends Error {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dexterai/connect",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Sign in with Dexter — passkey connector. Composes @dexterai/vault.",
5
5
  "type": "module",
6
6
  "exports": {