@rhinestone/1auth 0.6.8 → 0.6.9
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 +171 -12
- package/dist/chunk-IHBVEU33.mjs +20 -0
- package/dist/chunk-IHBVEU33.mjs.map +1 -0
- package/dist/chunk-IIACVHR3.mjs +28 -0
- package/dist/chunk-IIACVHR3.mjs.map +1 -0
- package/dist/chunk-N6KE5CII.mjs +72 -0
- package/dist/chunk-N6KE5CII.mjs.map +1 -0
- package/dist/{chunk-SXISYG2P.mjs → chunk-VZYHCFEH.mjs} +195 -140
- package/dist/chunk-VZYHCFEH.mjs.map +1 -0
- package/dist/{client-BrMrhetG.d.mts → client-BNluVe4_.d.ts} +305 -849
- package/dist/{client-BrMrhetG.d.ts → client-CLCdahyj.d.mts} +305 -849
- package/dist/headless.d.mts +90 -0
- package/dist/headless.d.ts +90 -0
- package/dist/headless.js +327 -0
- package/dist/headless.js.map +1 -0
- package/dist/headless.mjs +280 -0
- package/dist/headless.mjs.map +1 -0
- package/dist/index.d.mts +96 -144
- package/dist/index.d.ts +96 -144
- package/dist/index.js +2984 -718
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2740 -627
- package/dist/index.mjs.map +1 -1
- package/dist/{provider-CDl9wYEc.d.mts → provider-BgD9PeDX.d.ts} +6 -5
- package/dist/{provider-Dgv533YQ.d.ts → provider-hxHGb6SX.d.mts} +6 -5
- package/dist/react.d.mts +42 -2
- package/dist/react.d.ts +42 -2
- package/dist/react.js +92 -2
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +66 -1
- package/dist/react.mjs.map +1 -1
- package/dist/server.d.mts +118 -0
- package/dist/server.d.ts +118 -0
- package/dist/server.js +356 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +282 -0
- package/dist/server.mjs.map +1 -0
- package/dist/types-1BMD1PSH.d.mts +1425 -0
- package/dist/types-1BMD1PSH.d.ts +1425 -0
- package/dist/verify-CZe-m_Vf.d.ts +150 -0
- package/dist/verify-D3FaLeAi.d.mts +150 -0
- package/dist/wagmi.d.mts +5 -2
- package/dist/wagmi.d.ts +5 -2
- package/dist/wagmi.js +138 -43
- package/dist/wagmi.js.map +1 -1
- package/dist/wagmi.mjs +2 -1
- package/dist/wagmi.mjs.map +1 -1
- package/package.json +15 -2
- package/dist/chunk-SXISYG2P.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
tokenFetchError
|
|
3
|
+
} from "./chunk-IIACVHR3.mjs";
|
|
4
|
+
import {
|
|
5
|
+
ETHEREUM_MESSAGE_PREFIX,
|
|
6
|
+
hashMessage,
|
|
7
|
+
verifyMessageHash
|
|
8
|
+
} from "./chunk-IHBVEU33.mjs";
|
|
9
|
+
import {
|
|
3
10
|
createOneAuthProvider,
|
|
4
|
-
encodeWebAuthnSignature,
|
|
5
11
|
getAllSupportedChainsAndTokens,
|
|
6
12
|
getChainById,
|
|
7
13
|
getChainExplorerUrl,
|
|
@@ -14,19 +20,258 @@ import {
|
|
|
14
20
|
getTokenAddress,
|
|
15
21
|
getTokenDecimals,
|
|
16
22
|
getTokenSymbol,
|
|
17
|
-
hashCalls,
|
|
18
23
|
isTestnet,
|
|
19
24
|
isTokenAddressSupported,
|
|
20
25
|
resolveTokenAddress
|
|
21
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-VZYHCFEH.mjs";
|
|
27
|
+
import {
|
|
28
|
+
buildTransactionReview,
|
|
29
|
+
encodeWebAuthnSignature,
|
|
30
|
+
hashCalls
|
|
31
|
+
} from "./chunk-N6KE5CII.mjs";
|
|
32
|
+
|
|
33
|
+
// src/client.ts
|
|
34
|
+
import { hashTypedData } from "viem";
|
|
35
|
+
|
|
36
|
+
// src/assets.ts
|
|
37
|
+
function withNetworkBucket(balance) {
|
|
38
|
+
return {
|
|
39
|
+
...balance,
|
|
40
|
+
isTestnet: balance.isTestnet ?? isTestnet(balance.chainId)
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function bucketAssetBalances(balances) {
|
|
44
|
+
const mainnetBalances = [];
|
|
45
|
+
const testnetBalances = [];
|
|
46
|
+
for (const balance of balances) {
|
|
47
|
+
const normalized = withNetworkBucket(balance);
|
|
48
|
+
if (normalized.isTestnet) {
|
|
49
|
+
testnetBalances.push(normalized);
|
|
50
|
+
} else {
|
|
51
|
+
mainnetBalances.push(normalized);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
mainnets: { balances: mainnetBalances },
|
|
56
|
+
testnets: { balances: testnetBalances }
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function normalizeAssetsResponse(payload) {
|
|
60
|
+
const data = payload && typeof payload === "object" ? payload : {};
|
|
61
|
+
if (Array.isArray(data.balances)) {
|
|
62
|
+
const balances2 = data.balances.map(withNetworkBucket);
|
|
63
|
+
const buckets = bucketAssetBalances(balances2);
|
|
64
|
+
return { ...data, balances: balances2, ...buckets };
|
|
65
|
+
}
|
|
66
|
+
const balances = [];
|
|
67
|
+
const assets = Array.isArray(data.assets) ? data.assets : [];
|
|
68
|
+
for (const asset of assets) {
|
|
69
|
+
const symbol = asset.symbol ?? "UNKNOWN";
|
|
70
|
+
const decimals = asset.decimals ?? 18;
|
|
71
|
+
const chains = Array.isArray(asset.chains) ? asset.chains : [];
|
|
72
|
+
if (chains.length === 0) {
|
|
73
|
+
const token = asset.token ?? asset.address;
|
|
74
|
+
if (token) {
|
|
75
|
+
balances.push(withNetworkBucket({
|
|
76
|
+
chainId: 0,
|
|
77
|
+
token,
|
|
78
|
+
symbol,
|
|
79
|
+
decimals,
|
|
80
|
+
balance: asset.balance ?? asset.amount ?? "0",
|
|
81
|
+
...asset.usdValue !== void 0 && { usdValue: asset.usdValue }
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
for (const chain of chains) {
|
|
87
|
+
const chainId = chain.chainId ?? chain.chain;
|
|
88
|
+
const token = chain.token ?? chain.address;
|
|
89
|
+
if (typeof chainId !== "number" || !token) continue;
|
|
90
|
+
balances.push(withNetworkBucket({
|
|
91
|
+
chainId,
|
|
92
|
+
token,
|
|
93
|
+
symbol,
|
|
94
|
+
decimals: chain.decimals ?? decimals,
|
|
95
|
+
balance: chain.balance ?? chain.amount ?? "0",
|
|
96
|
+
...asset.usdValue !== void 0 && { usdValue: asset.usdValue },
|
|
97
|
+
...chain.isTestnet !== void 0 && { isTestnet: chain.isTestnet }
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { ...data, balances, ...bucketAssetBalances(balances) };
|
|
102
|
+
}
|
|
103
|
+
async function fetchAssetsResponse(options) {
|
|
104
|
+
const portfolioUrl = new URL(
|
|
105
|
+
`/api/users/${encodeURIComponent(options.identifier)}/portfolio`,
|
|
106
|
+
options.providerUrl
|
|
107
|
+
);
|
|
108
|
+
portfolioUrl.searchParams.set("all", "true");
|
|
109
|
+
const response = await fetch(portfolioUrl.toString(), {
|
|
110
|
+
headers: {
|
|
111
|
+
...options.headers,
|
|
112
|
+
...options.accessToken ? { Authorization: `Bearer ${options.accessToken}` } : {},
|
|
113
|
+
...options.clientId ? { "x-client-id": options.clientId } : {}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
const data = await response.json().catch(() => ({}));
|
|
118
|
+
throw new Error(data.error || "Failed to get assets");
|
|
119
|
+
}
|
|
120
|
+
return normalizeAssetsResponse(await response.json());
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ../loading-tokens/src/index.ts
|
|
124
|
+
var SPINNER_SIZE = 82;
|
|
125
|
+
var SPINNER_STROKE = 2.4118;
|
|
126
|
+
var SPINNER_RADIUS = (SPINNER_SIZE - SPINNER_STROKE) / 2;
|
|
127
|
+
var SPINNER_CIRCUMFERENCE = 2 * Math.PI * SPINNER_RADIUS;
|
|
128
|
+
var SPINNER_ARC_FRACTION = 0.17;
|
|
129
|
+
var SPINNER_ARC = SPINNER_CIRCUMFERENCE * SPINNER_ARC_FRACTION;
|
|
130
|
+
var LOADING_MODAL_WIDTH = 380;
|
|
131
|
+
var LOADING_MODAL_PADDING = 12;
|
|
132
|
+
var LOADING_MODAL_RADIUS = 16;
|
|
133
|
+
var LOADING_MOBILE_BREAKPOINT = 640;
|
|
134
|
+
var LOADING_CARD_GAP = 16;
|
|
135
|
+
var LOADING_BODY_GAP = 12;
|
|
136
|
+
var LOADING_ICON_PADDING = 12;
|
|
137
|
+
var LOADING_TITLE_BLOCK_GAP = 4;
|
|
138
|
+
var LOADING_FOOTER_GAP = 4;
|
|
139
|
+
var LOADING_TITLE_FONT_SIZE = 20;
|
|
140
|
+
var LOADING_TITLE_FONT_WEIGHT = 700;
|
|
141
|
+
var LOADING_SUBTITLE_FONT_SIZE = 12;
|
|
142
|
+
var LOADING_SUBTITLE_FONT_WEIGHT = 500;
|
|
143
|
+
var LOADING_FOOTER_FONT_SIZE = 11;
|
|
144
|
+
var LOADING_FOOTER_FONT_WEIGHT = 500;
|
|
145
|
+
var LOADING_LINE_HEIGHT = "normal";
|
|
146
|
+
var LOADING_FONT_FAMILY = "'Inter', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'";
|
|
147
|
+
var LOGO_WIDTH = 72;
|
|
148
|
+
var LOGO_HEIGHT = 16;
|
|
149
|
+
var BRAND_PURPLE = "#685bff";
|
|
150
|
+
var LIGHT_TOKENS = {
|
|
151
|
+
bgPrimary: "#ffffff",
|
|
152
|
+
borderColor: "#f4f4f5",
|
|
153
|
+
textPrimary: "#27272a",
|
|
154
|
+
textSecondary: "#71717b"
|
|
155
|
+
};
|
|
156
|
+
var DARK_TOKENS = {
|
|
157
|
+
bgPrimary: "#0a0a0a",
|
|
158
|
+
borderColor: "#18181b",
|
|
159
|
+
textPrimary: "#e4e4e7",
|
|
160
|
+
textSecondary: "#9f9fa9"
|
|
161
|
+
};
|
|
162
|
+
var FOOTER_TEXT_COLOR = "#71717b";
|
|
163
|
+
var LOADING_TITLE = "Loading...";
|
|
164
|
+
var LOADING_SUBTITLE = "This will only take a few moments, do not close the window.";
|
|
22
165
|
|
|
23
166
|
// src/client.ts
|
|
24
|
-
|
|
25
|
-
var POPUP_WIDTH = 450;
|
|
167
|
+
var POPUP_WIDTH = LOADING_MODAL_WIDTH;
|
|
26
168
|
var POPUP_HEIGHT = 600;
|
|
27
|
-
var DEFAULT_EMBED_WIDTH =
|
|
169
|
+
var DEFAULT_EMBED_WIDTH = `${LOADING_MODAL_WIDTH}px`;
|
|
28
170
|
var DEFAULT_EMBED_HEIGHT = "500px";
|
|
29
171
|
var DEFAULT_PROVIDER_URL = "https://passkey.1auth.box";
|
|
172
|
+
var MAX_LABEL_LEN = 20;
|
|
173
|
+
function generateModalNonce() {
|
|
174
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
175
|
+
return crypto.randomUUID();
|
|
176
|
+
}
|
|
177
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
178
|
+
}
|
|
179
|
+
function generateTelemetryOperationId() {
|
|
180
|
+
return `1auth-${generateModalNonce()}`;
|
|
181
|
+
}
|
|
182
|
+
function cleanTelemetryAttributes(attributes) {
|
|
183
|
+
if (!attributes) return void 0;
|
|
184
|
+
const clean = {};
|
|
185
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
186
|
+
if (value !== void 0) clean[key] = value;
|
|
187
|
+
}
|
|
188
|
+
return Object.keys(clean).length > 0 ? clean : void 0;
|
|
189
|
+
}
|
|
190
|
+
function describeTelemetryError(error) {
|
|
191
|
+
if (!error) return {};
|
|
192
|
+
if (error instanceof Error) {
|
|
193
|
+
return { errorCode: error.name, errorMessage: error.message };
|
|
194
|
+
}
|
|
195
|
+
if (typeof error === "object") {
|
|
196
|
+
const err = error;
|
|
197
|
+
return {
|
|
198
|
+
errorCode: typeof err.code === "string" ? err.code : void 0,
|
|
199
|
+
errorMessage: typeof err.message === "string" ? err.message : String(error)
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return { errorMessage: String(error) };
|
|
203
|
+
}
|
|
204
|
+
function capCallLabels(calls) {
|
|
205
|
+
if (!calls) return calls;
|
|
206
|
+
return calls.map((c) => {
|
|
207
|
+
const cappedLabel = c.label && c.label.length > MAX_LABEL_LEN ? c.label.slice(0, MAX_LABEL_LEN - 1) + "\u2026" : c.label;
|
|
208
|
+
const cappedSublabel = c.sublabel && c.sublabel.length > MAX_LABEL_LEN ? c.sublabel.slice(0, MAX_LABEL_LEN - 1) + "\u2026" : c.sublabel;
|
|
209
|
+
if (cappedLabel === c.label && cappedSublabel === c.sublabel) return c;
|
|
210
|
+
return { ...c, label: cappedLabel, sublabel: cappedSublabel };
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function getStoredSignerType() {
|
|
214
|
+
if (typeof window === "undefined") return "passkey";
|
|
215
|
+
try {
|
|
216
|
+
const raw = window.localStorage.getItem("1auth-user");
|
|
217
|
+
if (!raw) return "passkey";
|
|
218
|
+
const parsed = JSON.parse(raw);
|
|
219
|
+
return parsed?.signerType === "eoa" ? "eoa" : "passkey";
|
|
220
|
+
} catch {
|
|
221
|
+
return "passkey";
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
var DEFAULT_BLIND_SIGNING = true;
|
|
225
|
+
var MISSING_APP_CREDENTIALS_MESSAGE = "No sponsorship configured on OneAuthClient. Every user intent must carry the app's JWT \u2014 pass `sponsorship: { accessToken, getExtensionToken }` (or URL form) to the constructor.";
|
|
226
|
+
function normalizeSponsorship(config) {
|
|
227
|
+
if (!config) return null;
|
|
228
|
+
if ("accessToken" in config && "getExtensionToken" in config) {
|
|
229
|
+
return config;
|
|
230
|
+
}
|
|
231
|
+
const { accessTokenUrl, extensionTokenUrl } = config;
|
|
232
|
+
return {
|
|
233
|
+
accessToken: async () => {
|
|
234
|
+
const response = await fetch(accessTokenUrl, { credentials: "include" });
|
|
235
|
+
if (!response.ok) {
|
|
236
|
+
throw await tokenFetchError("access token", response);
|
|
237
|
+
}
|
|
238
|
+
const data = await response.json();
|
|
239
|
+
if (!data.token) {
|
|
240
|
+
throw new Error("Access token response missing `token` field");
|
|
241
|
+
}
|
|
242
|
+
return data.token;
|
|
243
|
+
},
|
|
244
|
+
getExtensionToken: async (intentOp) => {
|
|
245
|
+
const response = await fetch(extensionTokenUrl, {
|
|
246
|
+
method: "POST",
|
|
247
|
+
credentials: "include",
|
|
248
|
+
headers: { "Content-Type": "application/json" },
|
|
249
|
+
body: JSON.stringify({ intentOp })
|
|
250
|
+
});
|
|
251
|
+
if (!response.ok) {
|
|
252
|
+
throw await tokenFetchError("extension token", response);
|
|
253
|
+
}
|
|
254
|
+
const data = await response.json();
|
|
255
|
+
if (!data.token) {
|
|
256
|
+
throw new Error("Extension token response missing `token` field");
|
|
257
|
+
}
|
|
258
|
+
return data.token;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function grantPermissionReplacer(_key, value) {
|
|
263
|
+
return typeof value === "bigint" ? value.toString() : value;
|
|
264
|
+
}
|
|
265
|
+
function cloneGrantPayload(value) {
|
|
266
|
+
return JSON.parse(JSON.stringify(value, grantPermissionReplacer));
|
|
267
|
+
}
|
|
268
|
+
function normalizeGrantTargetChains(options) {
|
|
269
|
+
const chainCandidates = options.targetChains ?? (options.targetChain !== void 0 ? [options.targetChain] : []);
|
|
270
|
+
return Array.from(new Set(chainCandidates));
|
|
271
|
+
}
|
|
272
|
+
function normalizeGrantSourceChains(options) {
|
|
273
|
+
return Array.from(new Set(options.sourceChains ?? []));
|
|
274
|
+
}
|
|
30
275
|
var OneAuthClient = class {
|
|
31
276
|
/**
|
|
32
277
|
* Create a new OneAuthClient.
|
|
@@ -40,16 +285,229 @@ var OneAuthClient = class {
|
|
|
40
285
|
* clientId, theme, and redirect settings.
|
|
41
286
|
*/
|
|
42
287
|
constructor(config) {
|
|
288
|
+
// Persistent state for EOA forwarding via `/dialog/sign-eoa`. The iframe
|
|
289
|
+
// is created lazily on the first `requestWithWallet` call and reused for
|
|
290
|
+
// every subsequent call so its WalletConnect SignClient survives between
|
|
291
|
+
// transactions — otherwise each call would spin up a fresh SignClient
|
|
292
|
+
// that restores stale relay state from localStorage, producing
|
|
293
|
+
// "Restore will override" warnings and "emitting session_request:N
|
|
294
|
+
// without any listeners" errors on the second and later txs.
|
|
295
|
+
this.eoaDialogState = null;
|
|
296
|
+
// Serialises concurrent `requestWithWallet` calls — they would otherwise
|
|
297
|
+
// both try to drive the same iframe and cross-resolve each other.
|
|
298
|
+
this.eoaSerialQueue = Promise.resolve();
|
|
43
299
|
const providerUrl = config.providerUrl || DEFAULT_PROVIDER_URL;
|
|
44
300
|
const dialogUrl = config.dialogUrl || providerUrl;
|
|
45
301
|
this.config = { ...config, providerUrl, dialogUrl };
|
|
46
302
|
this.theme = this.config.theme || {};
|
|
303
|
+
this.sponsorship = normalizeSponsorship(config.sponsorship);
|
|
47
304
|
if (typeof document !== "undefined") {
|
|
48
305
|
this.injectPreconnect(providerUrl);
|
|
49
306
|
if (dialogUrl !== providerUrl) {
|
|
50
307
|
this.injectPreconnect(dialogUrl);
|
|
51
308
|
}
|
|
52
309
|
}
|
|
310
|
+
const initOperation = this.createTelemetryOperation("client", {
|
|
311
|
+
hasClientId: !!this.config.clientId,
|
|
312
|
+
hasDialogUrlOverride: dialogUrl !== providerUrl
|
|
313
|
+
});
|
|
314
|
+
this.emitTelemetry("client.init", initOperation, { outcome: "started" });
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Update the sponsorship configuration at runtime. Pass `undefined` to
|
|
318
|
+
* disable sponsorship.
|
|
319
|
+
*/
|
|
320
|
+
setSponsorship(sponsorship) {
|
|
321
|
+
this.sponsorship = normalizeSponsorship(sponsorship);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Whether the caller opted into SDK telemetry propagation or callbacks.
|
|
325
|
+
*/
|
|
326
|
+
shouldUseTelemetry() {
|
|
327
|
+
return !!this.config.telemetry && this.config.telemetry.enabled !== false;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Resolve the host app's current trace context. Callback failures are
|
|
331
|
+
* ignored because observability can never be allowed to break signing.
|
|
332
|
+
*/
|
|
333
|
+
getTelemetryTraceContext() {
|
|
334
|
+
const traceContext = this.config.telemetry?.traceContext;
|
|
335
|
+
if (!traceContext || !this.shouldUseTelemetry()) return void 0;
|
|
336
|
+
try {
|
|
337
|
+
return typeof traceContext === "function" ? traceContext() : traceContext;
|
|
338
|
+
} catch {
|
|
339
|
+
return void 0;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Resolve host-provided SDK event attributes and remove undefined fields.
|
|
344
|
+
*/
|
|
345
|
+
getTelemetryAttributes() {
|
|
346
|
+
const attributes = this.config.telemetry?.attributes;
|
|
347
|
+
if (!attributes || !this.shouldUseTelemetry()) return void 0;
|
|
348
|
+
try {
|
|
349
|
+
const resolved = typeof attributes === "function" ? attributes() : attributes;
|
|
350
|
+
return cleanTelemetryAttributes(resolved);
|
|
351
|
+
} catch {
|
|
352
|
+
return void 0;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Create per-operation telemetry context shared by callbacks, HTTP headers,
|
|
357
|
+
* dialog URL params, and PASSKEY_INIT messages.
|
|
358
|
+
*/
|
|
359
|
+
createTelemetryOperation(flow, attributes) {
|
|
360
|
+
return {
|
|
361
|
+
operationId: generateTelemetryOperationId(),
|
|
362
|
+
flow,
|
|
363
|
+
startedAt: Date.now(),
|
|
364
|
+
trace: this.getTelemetryTraceContext(),
|
|
365
|
+
attributes: cleanTelemetryAttributes({
|
|
366
|
+
...this.getTelemetryAttributes(),
|
|
367
|
+
...attributes
|
|
368
|
+
})
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Emit a telemetry event to the host app. The callback is intentionally
|
|
373
|
+
* fire-and-forget and exception-safe so app telemetry cannot affect users.
|
|
374
|
+
*/
|
|
375
|
+
emitTelemetry(name, operation, patch = {}) {
|
|
376
|
+
const onEvent = this.config.telemetry?.onEvent;
|
|
377
|
+
if (!onEvent || !this.shouldUseTelemetry()) return;
|
|
378
|
+
const attributes = cleanTelemetryAttributes({
|
|
379
|
+
...operation.attributes,
|
|
380
|
+
...patch.attributes
|
|
381
|
+
});
|
|
382
|
+
const event = {
|
|
383
|
+
name,
|
|
384
|
+
operationId: operation.operationId,
|
|
385
|
+
flow: operation.flow,
|
|
386
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
387
|
+
durationMs: patch.durationMs ?? Date.now() - operation.startedAt,
|
|
388
|
+
clientId: this.config.clientId,
|
|
389
|
+
providerUrl: this.config.providerUrl,
|
|
390
|
+
dialogUrl: this.getDialogUrl(),
|
|
391
|
+
trace: operation.trace,
|
|
392
|
+
...patch,
|
|
393
|
+
attributes
|
|
394
|
+
};
|
|
395
|
+
try {
|
|
396
|
+
onEvent(event);
|
|
397
|
+
} catch {
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Attach SDK correlation and optional W3C trace context to dialog URLs.
|
|
402
|
+
*/
|
|
403
|
+
appendTelemetryParams(params, operation) {
|
|
404
|
+
if (!this.shouldUseTelemetry()) return;
|
|
405
|
+
params.set("sdkOperationId", operation.operationId);
|
|
406
|
+
params.set("sdkTelemetryFlow", operation.flow);
|
|
407
|
+
if (operation.trace?.traceparent) {
|
|
408
|
+
params.set("traceparent", operation.trace.traceparent);
|
|
409
|
+
}
|
|
410
|
+
if (operation.trace?.tracestate) {
|
|
411
|
+
params.set("tracestate", operation.trace.tracestate);
|
|
412
|
+
}
|
|
413
|
+
if (operation.trace?.traceId) {
|
|
414
|
+
params.set("sdkTraceId", operation.trace.traceId);
|
|
415
|
+
}
|
|
416
|
+
if (operation.trace?.spanId) {
|
|
417
|
+
params.set("sdkSpanId", operation.trace.spanId);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Add SDK correlation headers to passkey API calls. These headers are also
|
|
422
|
+
* valid W3C trace propagation inputs when the host app supplies traceparent.
|
|
423
|
+
*/
|
|
424
|
+
telemetryHeaders(operation) {
|
|
425
|
+
if (!operation || !this.shouldUseTelemetry()) return {};
|
|
426
|
+
return {
|
|
427
|
+
"x-1auth-sdk-operation-id": operation.operationId,
|
|
428
|
+
"x-1auth-sdk-flow": operation.flow,
|
|
429
|
+
...operation.trace?.traceparent ? { traceparent: operation.trace.traceparent } : {},
|
|
430
|
+
...operation.trace?.tracestate ? { tracestate: operation.trace.tracestate } : {}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Embed telemetry context in PASSKEY_INIT payloads for dialog-side API calls.
|
|
435
|
+
*/
|
|
436
|
+
telemetryPayload(operation) {
|
|
437
|
+
if (!this.shouldUseTelemetry()) return void 0;
|
|
438
|
+
return {
|
|
439
|
+
operationId: operation.operationId,
|
|
440
|
+
flow: operation.flow,
|
|
441
|
+
...operation.trace?.traceparent ? { traceparent: operation.trace.traceparent } : {},
|
|
442
|
+
...operation.trace?.tracestate ? { tracestate: operation.trace.tracestate } : {},
|
|
443
|
+
...operation.trace?.traceId ? { traceId: operation.trace.traceId } : {},
|
|
444
|
+
...operation.trace?.spanId ? { spanId: operation.trace.spanId } : {}
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Determine whether this signing call should request invisible dialog mode.
|
|
449
|
+
* Resolution order: per-call option wins over the constructor config, which
|
|
450
|
+
* wins over the platform-wide {@link DEFAULT_BLIND_SIGNING}. This lets an app
|
|
451
|
+
* flip the global default for itself while still overriding individual
|
|
452
|
+
* high-risk calls — e.g. default-blind but force the review UI for a large
|
|
453
|
+
* transfer, or default-visible but blind-sign selected low-risk signatures.
|
|
454
|
+
*/
|
|
455
|
+
shouldRequestBlindSigning(options) {
|
|
456
|
+
return options?.blind_signing ?? this.config.blind_signing ?? DEFAULT_BLIND_SIGNING;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Fetch the app's access token (JWT). Called up front, before
|
|
460
|
+
* `/api/intent/prepare`, so the passkey server can authenticate the
|
|
461
|
+
* orchestrator quote call with the app's JWT instead of a service-level
|
|
462
|
+
* API key.
|
|
463
|
+
*
|
|
464
|
+
* Does not depend on `intentOp` — can run in parallel with anything that
|
|
465
|
+
* also doesn't depend on the quote.
|
|
466
|
+
*/
|
|
467
|
+
async fetchAccessToken() {
|
|
468
|
+
if (!this.sponsorship) {
|
|
469
|
+
return {
|
|
470
|
+
ok: false,
|
|
471
|
+
code: "MISSING_APP_CREDENTIALS",
|
|
472
|
+
message: MISSING_APP_CREDENTIALS_MESSAGE
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
try {
|
|
476
|
+
const accessToken = await this.sponsorship.accessToken();
|
|
477
|
+
return { ok: true, accessToken };
|
|
478
|
+
} catch (err) {
|
|
479
|
+
return {
|
|
480
|
+
ok: false,
|
|
481
|
+
message: err instanceof Error ? err.message : String(err)
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Fetch one extension token per intent, aligned with `sponsorFlags`.
|
|
487
|
+
* Returns an empty array when no intent is sponsored. Fired after
|
|
488
|
+
* `/prepare` so each token can be bound to its quote's `intentOp`, and
|
|
489
|
+
* overlaps the dialog / WebAuthn ceremony for no added serial latency.
|
|
490
|
+
*/
|
|
491
|
+
async fetchExtensionTokens(intentOps, sponsorFlags) {
|
|
492
|
+
if (!this.sponsorship) {
|
|
493
|
+
return { ok: false, message: MISSING_APP_CREDENTIALS_MESSAGE };
|
|
494
|
+
}
|
|
495
|
+
const anySponsored = sponsorFlags.some(Boolean);
|
|
496
|
+
if (!anySponsored) return { ok: true, extensionTokens: [] };
|
|
497
|
+
try {
|
|
498
|
+
const sponsorship = this.sponsorship;
|
|
499
|
+
const tokens = await Promise.all(
|
|
500
|
+
intentOps.map(
|
|
501
|
+
(op, i) => sponsorFlags[i] ? sponsorship.getExtensionToken(op) : Promise.resolve("")
|
|
502
|
+
)
|
|
503
|
+
);
|
|
504
|
+
return { ok: true, extensionTokens: tokens };
|
|
505
|
+
} catch (err) {
|
|
506
|
+
return {
|
|
507
|
+
ok: false,
|
|
508
|
+
message: err instanceof Error ? err.message : String(err)
|
|
509
|
+
};
|
|
510
|
+
}
|
|
53
511
|
}
|
|
54
512
|
/**
|
|
55
513
|
* Update the theme configuration at runtime
|
|
@@ -75,8 +533,11 @@ var OneAuthClient = class {
|
|
|
75
533
|
if (theme.mode) {
|
|
76
534
|
params.set("theme", theme.mode);
|
|
77
535
|
}
|
|
78
|
-
|
|
79
|
-
|
|
536
|
+
const instancePrimary = this.theme.primaryColor ?? this.theme.accent;
|
|
537
|
+
const overridePrimary = overrideTheme?.primaryColor ?? overrideTheme?.accent;
|
|
538
|
+
const primary = overridePrimary ?? instancePrimary;
|
|
539
|
+
if (primary) {
|
|
540
|
+
params.set("accent", primary);
|
|
80
541
|
}
|
|
81
542
|
return params.toString();
|
|
82
543
|
}
|
|
@@ -126,6 +587,60 @@ var OneAuthClient = class {
|
|
|
126
587
|
getClientId() {
|
|
127
588
|
return this.config.clientId;
|
|
128
589
|
}
|
|
590
|
+
/**
|
|
591
|
+
* Whether this client operates on testnet chains only.
|
|
592
|
+
*/
|
|
593
|
+
getTestnets() {
|
|
594
|
+
return this.config.testnets ?? false;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Get a unified token portfolio for a 1auth account across mainnets and
|
|
598
|
+
* testnets.
|
|
599
|
+
*
|
|
600
|
+
* @example
|
|
601
|
+
* ```typescript
|
|
602
|
+
* const assets = await client.getAssets({
|
|
603
|
+
* accountAddress: "0x1111111111111111111111111111111111111111",
|
|
604
|
+
* });
|
|
605
|
+
* console.log(assets.mainnets.balances, assets.testnets.balances);
|
|
606
|
+
* ```
|
|
607
|
+
*/
|
|
608
|
+
async getAssets(options) {
|
|
609
|
+
if (!options.accountAddress) {
|
|
610
|
+
throw new Error("getAssets requires accountAddress");
|
|
611
|
+
}
|
|
612
|
+
const telemetry = this.createTelemetryOperation("assets", {
|
|
613
|
+
hasAccountAddress: !!options.accountAddress
|
|
614
|
+
});
|
|
615
|
+
try {
|
|
616
|
+
const accessTokenResult = await this.fetchAccessToken();
|
|
617
|
+
if (!accessTokenResult.ok) {
|
|
618
|
+
throw new Error(accessTokenResult.message);
|
|
619
|
+
}
|
|
620
|
+
const result = await fetchAssetsResponse({
|
|
621
|
+
providerUrl: this.config.providerUrl,
|
|
622
|
+
identifier: options.accountAddress,
|
|
623
|
+
clientId: this.config.clientId,
|
|
624
|
+
accessToken: accessTokenResult.accessToken,
|
|
625
|
+
headers: this.telemetryHeaders(telemetry)
|
|
626
|
+
});
|
|
627
|
+
this.emitTelemetry("assets.succeeded", telemetry, {
|
|
628
|
+
outcome: "success",
|
|
629
|
+
attributes: {
|
|
630
|
+
balanceCount: result.balances.length,
|
|
631
|
+
mainnetBalanceCount: result.mainnets.balances.length,
|
|
632
|
+
testnetBalanceCount: result.testnets.balances.length
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
return result;
|
|
636
|
+
} catch (error) {
|
|
637
|
+
this.emitTelemetry("assets.failed", telemetry, {
|
|
638
|
+
outcome: "failure",
|
|
639
|
+
...describeTelemetryError(error)
|
|
640
|
+
});
|
|
641
|
+
throw error;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
129
644
|
/**
|
|
130
645
|
* Poll the intent status endpoint until a transaction hash appears or the
|
|
131
646
|
* intent reaches a terminal failure state.
|
|
@@ -144,7 +659,7 @@ var OneAuthClient = class {
|
|
|
144
659
|
* @returns The transaction hash string, or `undefined` if the deadline was
|
|
145
660
|
* reached or the intent failed/expired before a hash was produced.
|
|
146
661
|
*/
|
|
147
|
-
async waitForTransactionHash(intentId, options = {}) {
|
|
662
|
+
async waitForTransactionHash(intentId, options = {}, telemetry) {
|
|
148
663
|
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
149
664
|
const intervalMs = options.intervalMs ?? 2e3;
|
|
150
665
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -153,7 +668,10 @@ var OneAuthClient = class {
|
|
|
153
668
|
const response = await fetch(
|
|
154
669
|
`${this.config.providerUrl}/api/intent/status/${intentId}`,
|
|
155
670
|
{
|
|
156
|
-
headers:
|
|
671
|
+
headers: {
|
|
672
|
+
...this.telemetryHeaders(telemetry),
|
|
673
|
+
...this.config.clientId ? { "x-client-id": this.config.clientId } : {}
|
|
674
|
+
}
|
|
157
675
|
}
|
|
158
676
|
);
|
|
159
677
|
if (response.ok) {
|
|
@@ -181,6 +699,8 @@ var OneAuthClient = class {
|
|
|
181
699
|
* @param options.username - Pre-fill the username field
|
|
182
700
|
* @param options.theme - Override the theme for this modal invocation
|
|
183
701
|
* @param options.oauthEnabled - Set to false to hide OAuth sign-in buttons
|
|
702
|
+
* @param options.flow - Force the login or account-creation route instead
|
|
703
|
+
* of the default auto-detect entry route
|
|
184
704
|
* @returns {@link AuthResult} with user details and typed address
|
|
185
705
|
*
|
|
186
706
|
* @example
|
|
@@ -196,9 +716,54 @@ var OneAuthClient = class {
|
|
|
196
716
|
* ```
|
|
197
717
|
*/
|
|
198
718
|
async authWithModal(options) {
|
|
719
|
+
return this.openAuthModal(options);
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Open the sign-in modal directly.
|
|
723
|
+
*
|
|
724
|
+
* This bypasses the `/dialog/auth` auto-router and targets the dedicated
|
|
725
|
+
* returning-user flow. Use it when the embedding app already knows the user
|
|
726
|
+
* is creating a login session, for example after an app-level "Log in"
|
|
727
|
+
* button. The result intentionally matches {@link authWithModal}.
|
|
728
|
+
*
|
|
729
|
+
* @param options.username - Optional account hint for the sign-in ceremony
|
|
730
|
+
* @param options.theme - Override the theme for this modal invocation
|
|
731
|
+
* @returns {@link AuthResult} with user details and typed address
|
|
732
|
+
*/
|
|
733
|
+
async loginWithModal(options) {
|
|
734
|
+
return this.openAuthModal({ ...options, flow: "login" });
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Open the account-creation modal directly.
|
|
738
|
+
*
|
|
739
|
+
* This bypasses the `/dialog/auth` auto-router and targets the dedicated
|
|
740
|
+
* sign-up flow. Account creation still happens server-side in the passkey
|
|
741
|
+
* service; the SDK only opens the dialog and receives the postMessage result.
|
|
742
|
+
*
|
|
743
|
+
* @param options.theme - Override the theme for this modal invocation
|
|
744
|
+
* @param options.oauthEnabled - Set to false to hide OAuth sign-in buttons
|
|
745
|
+
* @returns {@link AuthResult} with the newly authenticated account details
|
|
746
|
+
*/
|
|
747
|
+
async createAccountWithModal(options) {
|
|
748
|
+
return this.openAuthModal({ ...options, flow: "create-account" });
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Open one of the passkey authentication modal routes and wait for the
|
|
752
|
+
* login-result message.
|
|
753
|
+
*
|
|
754
|
+
* Keeping URL construction in one helper prevents the explicit login/signup
|
|
755
|
+
* methods from drifting away from the legacy `authWithModal` behavior.
|
|
756
|
+
*/
|
|
757
|
+
async openAuthModal(options) {
|
|
758
|
+
const telemetry = this.createTelemetryOperation("auth", {
|
|
759
|
+
flow: options?.flow ?? "auto",
|
|
760
|
+
hasUsernameHint: !!options?.username
|
|
761
|
+
});
|
|
199
762
|
const dialogUrl = this.getDialogUrl();
|
|
763
|
+
const requestId = this.createDialogRequestId();
|
|
200
764
|
const params = new URLSearchParams({
|
|
201
|
-
mode: "iframe"
|
|
765
|
+
mode: "iframe",
|
|
766
|
+
requestId
|
|
202
767
|
});
|
|
203
768
|
if (this.config.clientId) {
|
|
204
769
|
params.set("clientId", this.config.clientId);
|
|
@@ -209,14 +774,60 @@ var OneAuthClient = class {
|
|
|
209
774
|
if (options?.oauthEnabled === false) {
|
|
210
775
|
params.set("oauth", "0");
|
|
211
776
|
}
|
|
777
|
+
if (options?.flow === "create-account") {
|
|
778
|
+
params.set("switch", "1");
|
|
779
|
+
}
|
|
780
|
+
if (typeof window !== "undefined" && window.location?.origin) {
|
|
781
|
+
params.set("parentOrigin", window.location.origin);
|
|
782
|
+
}
|
|
783
|
+
this.appendTelemetryParams(params, telemetry);
|
|
212
784
|
const themeParams = this.getThemeParams(options?.theme);
|
|
213
785
|
if (themeParams) {
|
|
214
786
|
const themeParsed = new URLSearchParams(themeParams);
|
|
215
787
|
themeParsed.forEach((value, key) => params.set(key, value));
|
|
216
788
|
}
|
|
217
|
-
const
|
|
789
|
+
const authPath = this.getAuthDialogPath(options?.flow);
|
|
790
|
+
const url = `${dialogUrl}${authPath}?${params.toString()}`;
|
|
218
791
|
const { dialog, iframe, cleanup } = this.createModalDialog(url);
|
|
219
|
-
|
|
792
|
+
this.emitTelemetry("dialog.opened", telemetry, { outcome: "started" });
|
|
793
|
+
const result = await this.waitForModalAuthResponse(dialog, iframe, cleanup, requestId);
|
|
794
|
+
if (result.success) {
|
|
795
|
+
this.emitTelemetry("auth.succeeded", telemetry, {
|
|
796
|
+
outcome: "success",
|
|
797
|
+
attributes: { signerType: result.signerType ?? "passkey" }
|
|
798
|
+
});
|
|
799
|
+
} else {
|
|
800
|
+
this.emitTelemetry(
|
|
801
|
+
result.error?.code === "USER_CANCELLED" ? "dialog.cancelled" : "auth.failed",
|
|
802
|
+
telemetry,
|
|
803
|
+
{
|
|
804
|
+
outcome: result.error?.code === "USER_CANCELLED" ? "cancelled" : "failure",
|
|
805
|
+
...describeTelemetryError(result.error)
|
|
806
|
+
}
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
return result;
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Generate a per-dialog nonce used to correlate iframe close messages with
|
|
813
|
+
* the SDK modal that opened them. Chromium can report a different
|
|
814
|
+
* `MessageEvent.source` WindowProxy after the `/dialog/auth` redirect, so
|
|
815
|
+
* auth close handling needs a stable in-band identifier without accepting
|
|
816
|
+
* stale close messages from earlier dialogs.
|
|
817
|
+
*/
|
|
818
|
+
createDialogRequestId() {
|
|
819
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
|
820
|
+
return crypto.randomUUID();
|
|
821
|
+
}
|
|
822
|
+
return `dialog-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Resolve the passkey app route for a requested auth flow.
|
|
826
|
+
*/
|
|
827
|
+
getAuthDialogPath(flow = "auto") {
|
|
828
|
+
if (flow === "login") return "/dialog/auth/signin";
|
|
829
|
+
if (flow === "create-account") return "/dialog/auth/signup";
|
|
830
|
+
return "/dialog/auth";
|
|
220
831
|
}
|
|
221
832
|
/**
|
|
222
833
|
* Open the connect dialog (lightweight connection without passkey auth).
|
|
@@ -241,6 +852,7 @@ var OneAuthClient = class {
|
|
|
241
852
|
* ```
|
|
242
853
|
*/
|
|
243
854
|
async connectWithModal(options) {
|
|
855
|
+
const telemetry = this.createTelemetryOperation("connect");
|
|
244
856
|
const dialogUrl = this.getDialogUrl();
|
|
245
857
|
const params = new URLSearchParams({
|
|
246
858
|
mode: "iframe"
|
|
@@ -248,6 +860,10 @@ var OneAuthClient = class {
|
|
|
248
860
|
if (this.config.clientId) {
|
|
249
861
|
params.set("clientId", this.config.clientId);
|
|
250
862
|
}
|
|
863
|
+
if (typeof window !== "undefined" && window.location?.origin) {
|
|
864
|
+
params.set("parentOrigin", window.location.origin);
|
|
865
|
+
}
|
|
866
|
+
this.appendTelemetryParams(params, telemetry);
|
|
251
867
|
const themeParams = this.getThemeParams(options?.theme);
|
|
252
868
|
if (themeParams) {
|
|
253
869
|
const themeParsed = new URLSearchParams(themeParams);
|
|
@@ -255,17 +871,104 @@ var OneAuthClient = class {
|
|
|
255
871
|
}
|
|
256
872
|
const url = `${dialogUrl}/dialog/connect?${params.toString()}`;
|
|
257
873
|
const { dialog, iframe, cleanup } = this.createModalDialog(url);
|
|
874
|
+
this.emitTelemetry("dialog.opened", telemetry, { outcome: "started" });
|
|
258
875
|
const ready = await this.waitForDialogReady(dialog, iframe, cleanup, {
|
|
259
|
-
mode: "iframe"
|
|
876
|
+
mode: "iframe",
|
|
877
|
+
telemetry: this.telemetryPayload(telemetry)
|
|
260
878
|
});
|
|
261
879
|
if (!ready) {
|
|
880
|
+
this.emitTelemetry("dialog.cancelled", telemetry, {
|
|
881
|
+
outcome: "cancelled",
|
|
882
|
+
errorCode: "USER_CANCELLED",
|
|
883
|
+
errorMessage: "Connection was cancelled before the dialog became ready"
|
|
884
|
+
});
|
|
262
885
|
return {
|
|
263
886
|
success: false,
|
|
264
887
|
action: "cancel",
|
|
265
888
|
error: { code: "USER_CANCELLED", message: "Connection was cancelled" }
|
|
266
889
|
};
|
|
267
890
|
}
|
|
268
|
-
|
|
891
|
+
this.emitTelemetry("dialog.ready", telemetry, { outcome: "started" });
|
|
892
|
+
const result = await this.waitForConnectResponse(dialog, iframe, cleanup);
|
|
893
|
+
if (result.success) {
|
|
894
|
+
this.emitTelemetry("connect.succeeded", telemetry, {
|
|
895
|
+
outcome: "success",
|
|
896
|
+
attributes: {
|
|
897
|
+
autoConnected: result.autoConnected === true,
|
|
898
|
+
signerType: result.signerType ?? "passkey"
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
} else {
|
|
902
|
+
this.emitTelemetry(
|
|
903
|
+
result.error?.code === "USER_CANCELLED" ? "dialog.cancelled" : "connect.failed",
|
|
904
|
+
telemetry,
|
|
905
|
+
{
|
|
906
|
+
outcome: result.error?.code === "USER_CANCELLED" ? "cancelled" : "failure",
|
|
907
|
+
...describeTelemetryError(result.error),
|
|
908
|
+
attributes: { action: result.action }
|
|
909
|
+
}
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
return result;
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* High-level connect entry point — the method most integrators want.
|
|
916
|
+
*
|
|
917
|
+
* Tries the lightweight {@link connectWithModal} first (no passkey ceremony
|
|
918
|
+
* for returning users on the same browser), and transparently falls back to
|
|
919
|
+
* {@link authWithModal} when the connect dialog reports it has no stored
|
|
920
|
+
* credentials for this origin (`action: "switch"`). First-time visitors get
|
|
921
|
+
* one passkey ceremony to register; subsequent connects on the same browser
|
|
922
|
+
* resolve with no biometric prompt — and silently when the user has enabled
|
|
923
|
+
* "Connect automatically".
|
|
924
|
+
*
|
|
925
|
+
* The return shape is the same {@link ConnectResult} discriminated union as
|
|
926
|
+
* {@link connectWithModal}, so callers can treat both paths uniformly. When
|
|
927
|
+
* the auth-modal fallback runs, its successful result is normalized into a
|
|
928
|
+
* `ConnectResult` (no `id` field — the embedder origin must not see it).
|
|
929
|
+
*
|
|
930
|
+
* Cancellation: if the user cancels either dialog, the result is
|
|
931
|
+
* `{ success: false, action: "cancel", error: { code: "USER_CANCELLED", … } }`.
|
|
932
|
+
*
|
|
933
|
+
* @param options - Forwarded to whichever underlying modal is shown.
|
|
934
|
+
* `username` and `oauthEnabled` only apply to the auth-modal fallback path.
|
|
935
|
+
* @returns A {@link ConnectResult} discriminated union.
|
|
936
|
+
*
|
|
937
|
+
* @example
|
|
938
|
+
* ```typescript
|
|
939
|
+
* const result = await client.connect();
|
|
940
|
+
* if (result.success) {
|
|
941
|
+
* console.log("Connected as", result.user?.username ?? result.user?.address);
|
|
942
|
+
* }
|
|
943
|
+
* ```
|
|
944
|
+
*/
|
|
945
|
+
async connect(options) {
|
|
946
|
+
const connectResult = await this.connectWithModal(
|
|
947
|
+
options?.theme ? { theme: options.theme } : void 0
|
|
948
|
+
);
|
|
949
|
+
if (connectResult.success) return connectResult;
|
|
950
|
+
if (connectResult.action === "switch") {
|
|
951
|
+
const authResult = await this.authWithModal(options);
|
|
952
|
+
if (authResult.success && authResult.user) {
|
|
953
|
+
return {
|
|
954
|
+
success: true,
|
|
955
|
+
user: {
|
|
956
|
+
username: authResult.user.username,
|
|
957
|
+
address: authResult.user.address
|
|
958
|
+
},
|
|
959
|
+
autoConnected: false
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
return {
|
|
963
|
+
success: false,
|
|
964
|
+
action: "cancel",
|
|
965
|
+
error: authResult.error ?? {
|
|
966
|
+
code: "USER_CANCELLED",
|
|
967
|
+
message: "Connection was cancelled"
|
|
968
|
+
}
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
return connectResult;
|
|
269
972
|
}
|
|
270
973
|
/**
|
|
271
974
|
* Open the account management dialog.
|
|
@@ -315,131 +1018,273 @@ var OneAuthClient = class {
|
|
|
315
1018
|
});
|
|
316
1019
|
}
|
|
317
1020
|
/**
|
|
318
|
-
*
|
|
319
|
-
* This is a read-only check — no dialog is shown.
|
|
1021
|
+
* Open the account-recovery backup flow directly.
|
|
320
1022
|
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
* }
|
|
330
|
-
*
|
|
1023
|
+
* Deep-links into the "Backup your account" flow inside the account dialog
|
|
1024
|
+
* (rather than the account overview): the user verifies with their passkey,
|
|
1025
|
+
* then saves a recovery passphrase and downloads an encrypted backup file.
|
|
1026
|
+
* Use this to prompt users to secure their account after sign-up.
|
|
1027
|
+
*
|
|
1028
|
+
* Requires an authenticated user (call `authWithModal()` / `connect()`
|
|
1029
|
+
* first) — the dialog reads the signed-in user from the passkey origin.
|
|
1030
|
+
*
|
|
1031
|
+
* Resolves `{ completed: true }` when the user finishes the backup, or
|
|
1032
|
+
* `{ completed: false }` if they dismiss it — so callers can mark an
|
|
1033
|
+
* onboarding step done or re-prompt later.
|
|
331
1034
|
*/
|
|
332
|
-
async
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
try {
|
|
346
|
-
const res = await fetch(`${this.config.providerUrl || "https://passkey.1auth.box"}/api/consent`, {
|
|
347
|
-
method: "POST",
|
|
348
|
-
headers: {
|
|
349
|
-
"Content-Type": "application/json",
|
|
350
|
-
...clientId ? { "x-client-id": clientId } : {}
|
|
351
|
-
},
|
|
352
|
-
body: JSON.stringify({
|
|
353
|
-
username,
|
|
354
|
-
requestedFields: options.fields,
|
|
355
|
-
clientId
|
|
356
|
-
})
|
|
357
|
-
});
|
|
358
|
-
if (!res.ok) {
|
|
359
|
-
return { hasConsent: false };
|
|
360
|
-
}
|
|
361
|
-
const data = await res.json();
|
|
362
|
-
return {
|
|
363
|
-
hasConsent: data.hasConsent ?? false,
|
|
364
|
-
data: data.data,
|
|
365
|
-
grantedAt: data.grantedAt
|
|
366
|
-
};
|
|
367
|
-
} catch {
|
|
368
|
-
return { hasConsent: false };
|
|
1035
|
+
async setupRecovery(options) {
|
|
1036
|
+
const dialogUrl = this.getDialogUrl();
|
|
1037
|
+
const params = new URLSearchParams({
|
|
1038
|
+
mode: "iframe",
|
|
1039
|
+
// Deep-link the account dialog straight into the backup nudge instead
|
|
1040
|
+
// of the account overview.
|
|
1041
|
+
action: "backup"
|
|
1042
|
+
});
|
|
1043
|
+
const themeParams = this.getThemeParams(options?.theme);
|
|
1044
|
+
if (themeParams) {
|
|
1045
|
+
const themeParsed = new URLSearchParams(themeParams);
|
|
1046
|
+
themeParsed.forEach((value, key) => params.set(key, value));
|
|
369
1047
|
}
|
|
1048
|
+
const url = `${dialogUrl}/dialog/account?${params.toString()}`;
|
|
1049
|
+
const { dialog, iframe, cleanup } = this.createModalDialog(url);
|
|
1050
|
+
const ready = await this.waitForDialogReady(dialog, iframe, cleanup, {
|
|
1051
|
+
mode: "iframe"
|
|
1052
|
+
});
|
|
1053
|
+
if (!ready) return { completed: false };
|
|
1054
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
1055
|
+
return new Promise((resolve) => {
|
|
1056
|
+
let completed = false;
|
|
1057
|
+
const finish = () => {
|
|
1058
|
+
window.removeEventListener("message", handleMessage);
|
|
1059
|
+
dialog.removeEventListener("close", handleClose);
|
|
1060
|
+
cleanup();
|
|
1061
|
+
resolve({ completed });
|
|
1062
|
+
};
|
|
1063
|
+
const handleMessage = (event) => {
|
|
1064
|
+
if (event.origin !== dialogOrigin) return;
|
|
1065
|
+
const type = event.data?.type;
|
|
1066
|
+
if (type === "PASSKEY_RECOVERY_RESULT") {
|
|
1067
|
+
completed = Boolean(event.data?.data?.completed ?? event.data?.success);
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
if (type === "PASSKEY_CLOSE" || type === "PASSKEY_DISCONNECT") {
|
|
1071
|
+
finish();
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
const handleClose = () => {
|
|
1075
|
+
window.removeEventListener("message", handleMessage);
|
|
1076
|
+
resolve({ completed });
|
|
1077
|
+
};
|
|
1078
|
+
window.addEventListener("message", handleMessage);
|
|
1079
|
+
dialog.addEventListener("close", handleClose);
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Check if a user has already granted consent for the requested fields.
|
|
1084
|
+
*
|
|
1085
|
+
* @deprecated Data-sharing consent is disabled.
|
|
1086
|
+
*
|
|
1087
|
+
* Always returns `{ hasConsent: false }`.
|
|
1088
|
+
*/
|
|
1089
|
+
async checkConsent(options) {
|
|
1090
|
+
void options;
|
|
1091
|
+
return { hasConsent: false };
|
|
370
1092
|
}
|
|
371
1093
|
/**
|
|
372
1094
|
* Request consent from the user to share their data.
|
|
373
1095
|
*
|
|
374
|
-
*
|
|
375
|
-
* If not, opens the consent dialog where the user can review and approve sharing.
|
|
1096
|
+
* @deprecated Data-sharing consent is disabled.
|
|
376
1097
|
*
|
|
377
|
-
*
|
|
378
|
-
* ```typescript
|
|
379
|
-
* const result = await client.requestConsent({
|
|
380
|
-
* username: "alice",
|
|
381
|
-
* fields: ["email", "deviceNames"],
|
|
382
|
-
* });
|
|
383
|
-
* if (result.success) {
|
|
384
|
-
* console.log(result.data?.email);
|
|
385
|
-
* console.log(result.cached); // true if no dialog was shown
|
|
386
|
-
* }
|
|
387
|
-
* ```
|
|
1098
|
+
* Always returns an `INVALID_REQUEST` result.
|
|
388
1099
|
*/
|
|
389
1100
|
async requestConsent(options) {
|
|
390
|
-
|
|
391
|
-
|
|
1101
|
+
void options;
|
|
1102
|
+
return {
|
|
1103
|
+
success: false,
|
|
1104
|
+
error: {
|
|
1105
|
+
code: "INVALID_REQUEST",
|
|
1106
|
+
message: "Data-sharing consent is disabled"
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Open the dedicated SmartSession permission grant dialog.
|
|
1112
|
+
*
|
|
1113
|
+
* The app owns the session key material and sends only the public
|
|
1114
|
+
* `sessionKeyAddress`. 1auth renders the permission review, collects the
|
|
1115
|
+
* owner's passkey authorization, submits the install/enable intent, and
|
|
1116
|
+
* returns a persistable session handle.
|
|
1117
|
+
*/
|
|
1118
|
+
async grantPermissions(options) {
|
|
1119
|
+
const telemetry = this.createTelemetryOperation("grant_permission", {
|
|
1120
|
+
targetChains: normalizeGrantTargetChains(options).join(","),
|
|
1121
|
+
sponsor: options.sponsor !== false
|
|
1122
|
+
});
|
|
1123
|
+
if (!options.username && !options.accountAddress) {
|
|
1124
|
+
this.emitTelemetry("grant_permission.failed", telemetry, {
|
|
1125
|
+
outcome: "failure",
|
|
1126
|
+
errorCode: "INVALID_OPTIONS",
|
|
1127
|
+
errorMessage: "Either username or accountAddress is required"
|
|
1128
|
+
});
|
|
392
1129
|
return {
|
|
393
1130
|
success: false,
|
|
394
|
-
error: {
|
|
1131
|
+
error: {
|
|
1132
|
+
code: "INVALID_OPTIONS",
|
|
1133
|
+
message: "Either username or accountAddress is required"
|
|
1134
|
+
}
|
|
395
1135
|
};
|
|
396
1136
|
}
|
|
397
|
-
const
|
|
398
|
-
|
|
1137
|
+
const targetChains = normalizeGrantTargetChains(options);
|
|
1138
|
+
const sourceChains = normalizeGrantSourceChains(options);
|
|
1139
|
+
if (targetChains.length === 0 || !options.sessionKeyAddress || !options.permissions?.length) {
|
|
1140
|
+
this.emitTelemetry("grant_permission.failed", telemetry, {
|
|
1141
|
+
outcome: "failure",
|
|
1142
|
+
errorCode: "INVALID_OPTIONS",
|
|
1143
|
+
errorMessage: "targetChains, sessionKeyAddress, and permissions are required"
|
|
1144
|
+
});
|
|
399
1145
|
return {
|
|
400
1146
|
success: false,
|
|
401
|
-
error: {
|
|
1147
|
+
error: {
|
|
1148
|
+
code: "INVALID_OPTIONS",
|
|
1149
|
+
message: "targetChains, sessionKeyAddress, and permissions are required"
|
|
1150
|
+
}
|
|
402
1151
|
};
|
|
403
1152
|
}
|
|
404
|
-
if (!
|
|
1153
|
+
if (!this.sponsorship) {
|
|
1154
|
+
this.emitTelemetry("grant_permission.failed", telemetry, {
|
|
1155
|
+
outcome: "failure",
|
|
1156
|
+
errorCode: "MISSING_APP_CREDENTIALS",
|
|
1157
|
+
errorMessage: MISSING_APP_CREDENTIALS_MESSAGE
|
|
1158
|
+
});
|
|
405
1159
|
return {
|
|
406
1160
|
success: false,
|
|
407
|
-
error: {
|
|
1161
|
+
error: {
|
|
1162
|
+
code: "MISSING_APP_CREDENTIALS",
|
|
1163
|
+
message: MISSING_APP_CREDENTIALS_MESSAGE
|
|
1164
|
+
}
|
|
408
1165
|
};
|
|
409
1166
|
}
|
|
410
|
-
const
|
|
411
|
-
if (
|
|
1167
|
+
const accessTokenResult = await this.fetchAccessToken();
|
|
1168
|
+
if (!accessTokenResult.ok) {
|
|
1169
|
+
this.emitTelemetry("grant_permission.failed", telemetry, {
|
|
1170
|
+
outcome: "failure",
|
|
1171
|
+
errorCode: "SPONSORSHIP_FETCH_FAILED",
|
|
1172
|
+
errorMessage: accessTokenResult.message
|
|
1173
|
+
});
|
|
412
1174
|
return {
|
|
413
|
-
success:
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
1175
|
+
success: false,
|
|
1176
|
+
error: {
|
|
1177
|
+
code: "SPONSORSHIP_FETCH_FAILED",
|
|
1178
|
+
message: accessTokenResult.message
|
|
1179
|
+
}
|
|
417
1180
|
};
|
|
418
1181
|
}
|
|
419
1182
|
const dialogUrl = this.getDialogUrl();
|
|
420
|
-
const params = new URLSearchParams({
|
|
421
|
-
|
|
422
|
-
username,
|
|
423
|
-
clientId,
|
|
424
|
-
fields: options.fields.join(",")
|
|
425
|
-
});
|
|
1183
|
+
const params = new URLSearchParams({ mode: "iframe" });
|
|
1184
|
+
this.appendTelemetryParams(params, telemetry);
|
|
426
1185
|
const themeParams = this.getThemeParams(options.theme);
|
|
427
1186
|
if (themeParams) {
|
|
428
1187
|
const themeParsed = new URLSearchParams(themeParams);
|
|
429
1188
|
themeParsed.forEach((value, key) => params.set(key, value));
|
|
430
1189
|
}
|
|
431
|
-
const
|
|
432
|
-
const { dialog, iframe, cleanup } = this.createModalDialog(
|
|
433
|
-
const
|
|
434
|
-
mode: "iframe"
|
|
1190
|
+
const grantUrl = `${dialogUrl}/dialog/grant-permission?${params.toString()}`;
|
|
1191
|
+
const { dialog, iframe, cleanup } = this.createModalDialog(grantUrl);
|
|
1192
|
+
const initPayload = {
|
|
1193
|
+
mode: "iframe",
|
|
1194
|
+
clientId: this.config.clientId,
|
|
1195
|
+
grant: cloneGrantPayload({
|
|
1196
|
+
username: options.username,
|
|
1197
|
+
accountAddress: options.accountAddress,
|
|
1198
|
+
targetChain: targetChains[0],
|
|
1199
|
+
targetChains,
|
|
1200
|
+
sourceChains,
|
|
1201
|
+
sessionKeyAddress: options.sessionKeyAddress,
|
|
1202
|
+
permissions: options.permissions,
|
|
1203
|
+
crossChainPermits: options.crossChainPermits,
|
|
1204
|
+
validUntil: options.validUntil,
|
|
1205
|
+
validAfter: options.validAfter,
|
|
1206
|
+
maxUses: options.maxUses,
|
|
1207
|
+
contracts: options.contracts
|
|
1208
|
+
}),
|
|
1209
|
+
auth: { accessToken: accessTokenResult.accessToken },
|
|
1210
|
+
sponsor: options.sponsor !== false,
|
|
1211
|
+
telemetry: this.telemetryPayload(telemetry)
|
|
1212
|
+
};
|
|
1213
|
+
this.emitTelemetry("dialog.opened", telemetry, {
|
|
1214
|
+
outcome: "started",
|
|
1215
|
+
targetChains
|
|
435
1216
|
});
|
|
436
|
-
|
|
1217
|
+
const result = await this.waitForGrantPermissionResponse(
|
|
1218
|
+
dialog,
|
|
1219
|
+
iframe,
|
|
1220
|
+
cleanup,
|
|
1221
|
+
options.sponsor !== false,
|
|
1222
|
+
initPayload
|
|
1223
|
+
);
|
|
1224
|
+
if (result.success) {
|
|
1225
|
+
this.emitTelemetry("grant_permission.succeeded", telemetry, {
|
|
1226
|
+
outcome: "success",
|
|
1227
|
+
targetChains,
|
|
1228
|
+
status: result.status ? String(result.status) : void 0
|
|
1229
|
+
});
|
|
1230
|
+
} else {
|
|
1231
|
+
this.emitTelemetry(
|
|
1232
|
+
result.error?.code === "USER_CANCELLED" ? "dialog.cancelled" : "grant_permission.failed",
|
|
1233
|
+
telemetry,
|
|
1234
|
+
{
|
|
1235
|
+
outcome: result.error?.code === "USER_CANCELLED" ? "cancelled" : "failure",
|
|
1236
|
+
targetChains,
|
|
1237
|
+
...describeTelemetryError(result.error)
|
|
1238
|
+
}
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
return result;
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* List SmartSession grants previously granted to the current browser origin.
|
|
1245
|
+
*
|
|
1246
|
+
* 1auth derives the app identity from the request Origin header. The SDK does
|
|
1247
|
+
* not send `clientId` for this lookup because grants are origin-scoped.
|
|
1248
|
+
*/
|
|
1249
|
+
async listSessionGrants(options) {
|
|
1250
|
+
if (!options.username && !options.accountAddress) {
|
|
437
1251
|
return {
|
|
438
1252
|
success: false,
|
|
439
|
-
error: {
|
|
1253
|
+
error: {
|
|
1254
|
+
code: "INVALID_OPTIONS",
|
|
1255
|
+
message: "Either username or accountAddress is required"
|
|
1256
|
+
}
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
const params = new URLSearchParams();
|
|
1260
|
+
if (options.username) params.set("username", options.username);
|
|
1261
|
+
if (options.accountAddress) params.set("accountAddress", options.accountAddress);
|
|
1262
|
+
if (options.includeRevoked) params.set("includeRevoked", "true");
|
|
1263
|
+
try {
|
|
1264
|
+
const response = await fetch(
|
|
1265
|
+
`${this.config.providerUrl}/api/sessions/grants?${params.toString()}`,
|
|
1266
|
+
{ credentials: "include" }
|
|
1267
|
+
);
|
|
1268
|
+
const data = await response.json();
|
|
1269
|
+
if (!response.ok) {
|
|
1270
|
+
return {
|
|
1271
|
+
success: false,
|
|
1272
|
+
error: {
|
|
1273
|
+
code: "LIST_SESSION_GRANTS_FAILED",
|
|
1274
|
+
message: data?.error ?? `Request failed with status ${response.status}`
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
return { success: true, grants: data.grants ?? [] };
|
|
1279
|
+
} catch (err) {
|
|
1280
|
+
return {
|
|
1281
|
+
success: false,
|
|
1282
|
+
error: {
|
|
1283
|
+
code: "LIST_SESSION_GRANTS_FAILED",
|
|
1284
|
+
message: err instanceof Error ? err.message : String(err)
|
|
1285
|
+
}
|
|
440
1286
|
};
|
|
441
1287
|
}
|
|
442
|
-
return this.waitForConsentResponse(dialog, iframe, cleanup);
|
|
443
1288
|
}
|
|
444
1289
|
/**
|
|
445
1290
|
* Authenticate a user with an optional challenge to sign.
|
|
@@ -448,13 +1293,13 @@ var OneAuthClient = class {
|
|
|
448
1293
|
* challenge signing, enabling off-chain login without on-chain transactions.
|
|
449
1294
|
*
|
|
450
1295
|
* When a challenge is provided:
|
|
451
|
-
* 1. User authenticates (sign in or sign up)
|
|
452
|
-
* 2.
|
|
453
|
-
* 3.
|
|
454
|
-
* 4. Returns user info + signature for server-side verification
|
|
1296
|
+
* 1. User authenticates (sign in or sign up) through the normal auth dialog
|
|
1297
|
+
* 2. User signs the challenge through the normal signing dialog
|
|
1298
|
+
* 3. Returns user info + signature for server-side verification
|
|
455
1299
|
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
1300
|
+
* This composes the same supported provider routes as calling
|
|
1301
|
+
* `authWithModal()` followed by `signMessage()`, so challenge auth cannot
|
|
1302
|
+
* drift onto a provider route that the passkey service does not expose.
|
|
458
1303
|
*
|
|
459
1304
|
* @example
|
|
460
1305
|
* ```typescript
|
|
@@ -473,42 +1318,130 @@ var OneAuthClient = class {
|
|
|
473
1318
|
* ```
|
|
474
1319
|
*/
|
|
475
1320
|
async authenticate(options) {
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
mode: "iframe"
|
|
1321
|
+
const telemetry = this.createTelemetryOperation("authenticate", {
|
|
1322
|
+
hasChallenge: !!options?.challenge
|
|
479
1323
|
});
|
|
480
|
-
|
|
481
|
-
|
|
1324
|
+
const authResult = await this.authWithModal(options?.theme ? { theme: options.theme } : void 0);
|
|
1325
|
+
if (!authResult.success) {
|
|
1326
|
+
this.emitTelemetry(
|
|
1327
|
+
authResult.error?.code === "USER_CANCELLED" ? "dialog.cancelled" : "auth.failed",
|
|
1328
|
+
telemetry,
|
|
1329
|
+
{
|
|
1330
|
+
outcome: authResult.error?.code === "USER_CANCELLED" ? "cancelled" : "failure",
|
|
1331
|
+
...describeTelemetryError(authResult.error)
|
|
1332
|
+
}
|
|
1333
|
+
);
|
|
1334
|
+
return authResult;
|
|
482
1335
|
}
|
|
483
|
-
if (options?.challenge) {
|
|
484
|
-
|
|
1336
|
+
if (!options?.challenge) {
|
|
1337
|
+
this.emitTelemetry("auth.succeeded", telemetry, {
|
|
1338
|
+
outcome: "success",
|
|
1339
|
+
attributes: { challengeSigned: false }
|
|
1340
|
+
});
|
|
1341
|
+
return authResult;
|
|
485
1342
|
}
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
1343
|
+
const accountAddress = authResult.user?.address;
|
|
1344
|
+
const username = authResult.user?.username;
|
|
1345
|
+
if (!accountAddress && !username) {
|
|
1346
|
+
const result2 = {
|
|
1347
|
+
success: false,
|
|
1348
|
+
user: authResult.user,
|
|
1349
|
+
signerType: authResult.signerType,
|
|
1350
|
+
error: {
|
|
1351
|
+
code: "AUTHENTICATE_USER_MISSING",
|
|
1352
|
+
message: "Authentication succeeded but no username or account address was returned for challenge signing"
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
this.emitTelemetry("auth.failed", telemetry, {
|
|
1356
|
+
outcome: "failure",
|
|
1357
|
+
...describeTelemetryError(result2.error)
|
|
1358
|
+
});
|
|
1359
|
+
return result2;
|
|
490
1360
|
}
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
1361
|
+
const signResult = await this.signMessage({
|
|
1362
|
+
username,
|
|
1363
|
+
accountAddress,
|
|
1364
|
+
message: options.challenge,
|
|
1365
|
+
description: "Verify your identity",
|
|
1366
|
+
theme: options.theme
|
|
1367
|
+
});
|
|
1368
|
+
if (signResult.success && signResult.signature && signResult.signedHash) {
|
|
1369
|
+
const result2 = {
|
|
1370
|
+
...authResult,
|
|
1371
|
+
challenge: {
|
|
1372
|
+
signature: signResult.signature,
|
|
1373
|
+
signedHash: signResult.signedHash
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
this.emitTelemetry("auth.succeeded", telemetry, {
|
|
1377
|
+
outcome: "success",
|
|
1378
|
+
attributes: { challengeSigned: true }
|
|
1379
|
+
});
|
|
1380
|
+
return result2;
|
|
1381
|
+
}
|
|
1382
|
+
const error = signResult.error ? { code: signResult.error.code, message: signResult.error.message } : { code: "SIGNING_FAILED", message: "Challenge signing failed" };
|
|
1383
|
+
const result = {
|
|
1384
|
+
success: false,
|
|
1385
|
+
user: authResult.user,
|
|
1386
|
+
signerType: authResult.signerType,
|
|
1387
|
+
error
|
|
1388
|
+
};
|
|
1389
|
+
const cancelled = error.code === "USER_CANCELLED" || error.code === "USER_REJECTED";
|
|
1390
|
+
this.emitTelemetry(
|
|
1391
|
+
cancelled ? "dialog.cancelled" : "auth.failed",
|
|
1392
|
+
telemetry,
|
|
1393
|
+
{
|
|
1394
|
+
outcome: cancelled ? "cancelled" : "failure",
|
|
1395
|
+
...describeTelemetryError(error)
|
|
1396
|
+
}
|
|
1397
|
+
);
|
|
1398
|
+
return result;
|
|
494
1399
|
}
|
|
495
1400
|
/**
|
|
496
1401
|
* Show signing in a modal overlay (iframe dialog)
|
|
497
1402
|
*/
|
|
498
1403
|
async signWithModal(options) {
|
|
1404
|
+
const telemetry = this.createTelemetryOperation("sign", {
|
|
1405
|
+
signingMode: "transaction",
|
|
1406
|
+
hasTransaction: !!options.transaction,
|
|
1407
|
+
hasAccountAddress: !!options.accountAddress
|
|
1408
|
+
});
|
|
499
1409
|
const dialogUrl = this.getDialogUrl();
|
|
1410
|
+
const params = new URLSearchParams({ mode: "iframe" });
|
|
1411
|
+
this.appendTelemetryParams(params, telemetry);
|
|
500
1412
|
const themeParams = this.getThemeParams(options?.theme);
|
|
501
|
-
|
|
502
|
-
|
|
1413
|
+
if (themeParams) {
|
|
1414
|
+
const themeParsed = new URLSearchParams(themeParams);
|
|
1415
|
+
themeParsed.forEach((value, key) => params.set(key, value));
|
|
1416
|
+
}
|
|
1417
|
+
const signingUrl = `${dialogUrl}/dialog/sign?${params.toString()}`;
|
|
1418
|
+
const requestedBlindSigning = this.shouldRequestBlindSigning(options);
|
|
1419
|
+
const { dialog, iframe, cleanup, reveal } = this.createModalDialog(signingUrl, {
|
|
1420
|
+
hidden: requestedBlindSigning
|
|
1421
|
+
});
|
|
1422
|
+
this.emitTelemetry("dialog.opened", telemetry, {
|
|
1423
|
+
outcome: "started",
|
|
1424
|
+
attributes: { blindSigning: requestedBlindSigning }
|
|
1425
|
+
});
|
|
503
1426
|
const ready = await this.waitForDialogReady(dialog, iframe, cleanup, {
|
|
504
1427
|
mode: "iframe",
|
|
505
1428
|
challenge: options.challenge,
|
|
506
1429
|
username: options.username,
|
|
1430
|
+
accountAddress: options.accountAddress,
|
|
507
1431
|
description: options.description,
|
|
508
1432
|
transaction: options.transaction,
|
|
509
|
-
metadata: options.metadata
|
|
1433
|
+
metadata: options.metadata,
|
|
1434
|
+
telemetry: this.telemetryPayload(telemetry)
|
|
1435
|
+
}, {
|
|
1436
|
+
blindSigning: requestedBlindSigning,
|
|
1437
|
+
reveal
|
|
510
1438
|
});
|
|
511
1439
|
if (!ready) {
|
|
1440
|
+
this.emitTelemetry("dialog.cancelled", telemetry, {
|
|
1441
|
+
outcome: "cancelled",
|
|
1442
|
+
errorCode: "USER_REJECTED",
|
|
1443
|
+
errorMessage: "User closed the dialog before signing"
|
|
1444
|
+
});
|
|
512
1445
|
return {
|
|
513
1446
|
success: false,
|
|
514
1447
|
error: {
|
|
@@ -517,7 +1450,21 @@ var OneAuthClient = class {
|
|
|
517
1450
|
}
|
|
518
1451
|
};
|
|
519
1452
|
}
|
|
520
|
-
|
|
1453
|
+
this.emitTelemetry("dialog.ready", telemetry, { outcome: "started" });
|
|
1454
|
+
const result = await this.waitForSigningResponse(dialog, iframe, cleanup);
|
|
1455
|
+
if (result.success) {
|
|
1456
|
+
this.emitTelemetry("sign.succeeded", telemetry, { outcome: "success" });
|
|
1457
|
+
} else {
|
|
1458
|
+
this.emitTelemetry(
|
|
1459
|
+
result.error?.code === "USER_REJECTED" ? "dialog.cancelled" : "sign.failed",
|
|
1460
|
+
telemetry,
|
|
1461
|
+
{
|
|
1462
|
+
outcome: result.error?.code === "USER_REJECTED" ? "cancelled" : "failure",
|
|
1463
|
+
...describeTelemetryError(result.error)
|
|
1464
|
+
}
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1467
|
+
return result;
|
|
521
1468
|
}
|
|
522
1469
|
/**
|
|
523
1470
|
* Send an intent to the Rhinestone orchestrator
|
|
@@ -549,8 +1496,37 @@ var OneAuthClient = class {
|
|
|
549
1496
|
* ```
|
|
550
1497
|
*/
|
|
551
1498
|
async sendIntent(options) {
|
|
552
|
-
const { username, accountAddress, targetChain
|
|
1499
|
+
const { username, accountAddress, targetChain } = options;
|
|
1500
|
+
const calls = capCallLabels(options.calls);
|
|
1501
|
+
const sponsor = options.sponsor !== false;
|
|
1502
|
+
const telemetry = this.createTelemetryOperation("intent", {
|
|
1503
|
+
targetChain: targetChain ?? null,
|
|
1504
|
+
callsCount: calls?.length ?? 0,
|
|
1505
|
+
sponsor,
|
|
1506
|
+
blindSigning: this.shouldRequestBlindSigning(options)
|
|
1507
|
+
});
|
|
1508
|
+
if (getStoredSignerType() === "eoa") {
|
|
1509
|
+
this.emitTelemetry("intent.completed", telemetry, {
|
|
1510
|
+
outcome: "failure",
|
|
1511
|
+
errorCode: "E_SIGNER_UNSUPPORTED",
|
|
1512
|
+
errorMessage: "Cross-chain intents are not supported for EOA sessions"
|
|
1513
|
+
});
|
|
1514
|
+
return {
|
|
1515
|
+
success: false,
|
|
1516
|
+
intentId: "",
|
|
1517
|
+
status: "failed",
|
|
1518
|
+
error: {
|
|
1519
|
+
code: "E_SIGNER_UNSUPPORTED",
|
|
1520
|
+
message: "Cross-chain intents are not supported for EOA sessions. Use the connected wallet's native RPC instead."
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
553
1524
|
if (!username && !accountAddress) {
|
|
1525
|
+
this.emitTelemetry("intent.completed", telemetry, {
|
|
1526
|
+
outcome: "failure",
|
|
1527
|
+
errorCode: "INVALID_OPTIONS",
|
|
1528
|
+
errorMessage: "Either username or accountAddress is required"
|
|
1529
|
+
});
|
|
554
1530
|
return {
|
|
555
1531
|
success: false,
|
|
556
1532
|
intentId: "",
|
|
@@ -561,14 +1537,37 @@ var OneAuthClient = class {
|
|
|
561
1537
|
}
|
|
562
1538
|
};
|
|
563
1539
|
}
|
|
564
|
-
|
|
1540
|
+
const hasCalls = !!calls?.length;
|
|
1541
|
+
const hasTokenRequests = !!options.tokenRequests?.length;
|
|
1542
|
+
if (!targetChain || !hasCalls && !hasTokenRequests) {
|
|
1543
|
+
this.emitTelemetry("intent.completed", telemetry, {
|
|
1544
|
+
outcome: "failure",
|
|
1545
|
+
errorCode: "INVALID_OPTIONS",
|
|
1546
|
+
errorMessage: "targetChain and calls or tokenRequests are required"
|
|
1547
|
+
});
|
|
565
1548
|
return {
|
|
566
1549
|
success: false,
|
|
567
1550
|
intentId: "",
|
|
568
1551
|
status: "failed",
|
|
569
1552
|
error: {
|
|
570
1553
|
code: "INVALID_OPTIONS",
|
|
571
|
-
message: "targetChain and calls are required"
|
|
1554
|
+
message: "targetChain and calls or tokenRequests are required"
|
|
1555
|
+
}
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
if (!this.sponsorship) {
|
|
1559
|
+
this.emitTelemetry("intent.prepare.failed", telemetry, {
|
|
1560
|
+
outcome: "failure",
|
|
1561
|
+
errorCode: "MISSING_APP_CREDENTIALS",
|
|
1562
|
+
errorMessage: "No sponsorship configured on OneAuthClient"
|
|
1563
|
+
});
|
|
1564
|
+
return {
|
|
1565
|
+
success: false,
|
|
1566
|
+
intentId: "",
|
|
1567
|
+
status: "failed",
|
|
1568
|
+
error: {
|
|
1569
|
+
code: "MISSING_APP_CREDENTIALS",
|
|
1570
|
+
message: "No sponsorship configured on OneAuthClient. Every user intent must carry the app's JWT \u2014 configure `sponsorship` on the client."
|
|
572
1571
|
}
|
|
573
1572
|
};
|
|
574
1573
|
}
|
|
@@ -577,6 +1576,111 @@ var OneAuthClient = class {
|
|
|
577
1576
|
amount: r.amount.toString()
|
|
578
1577
|
}));
|
|
579
1578
|
let prepareResponse;
|
|
1579
|
+
const dialogUrl = this.getDialogUrl();
|
|
1580
|
+
const params = new URLSearchParams({ mode: "iframe" });
|
|
1581
|
+
this.appendTelemetryParams(params, telemetry);
|
|
1582
|
+
const themeParams = this.getThemeParams();
|
|
1583
|
+
if (themeParams) {
|
|
1584
|
+
const themeParsed = new URLSearchParams(themeParams);
|
|
1585
|
+
themeParsed.forEach((value, key) => params.set(key, value));
|
|
1586
|
+
}
|
|
1587
|
+
const signingUrl = `${dialogUrl}/dialog/sign?${params.toString()}`;
|
|
1588
|
+
const requestedBlindSigning = this.shouldRequestBlindSigning(options);
|
|
1589
|
+
const { dialog, iframe, cleanup, reveal } = this.createModalDialog(signingUrl, {
|
|
1590
|
+
hidden: requestedBlindSigning
|
|
1591
|
+
});
|
|
1592
|
+
this.emitTelemetry("dialog.opened", telemetry, {
|
|
1593
|
+
outcome: "started",
|
|
1594
|
+
attributes: { blindSigning: requestedBlindSigning }
|
|
1595
|
+
});
|
|
1596
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
1597
|
+
const dialogOpenedAt = Date.now();
|
|
1598
|
+
let iframeReadyObserved = false;
|
|
1599
|
+
let stopEarlyCloseWatch = () => void 0;
|
|
1600
|
+
const earlyDialogClosePromise = new Promise((resolve) => {
|
|
1601
|
+
let settled = false;
|
|
1602
|
+
const stop = () => {
|
|
1603
|
+
if (settled) return;
|
|
1604
|
+
settled = true;
|
|
1605
|
+
window.removeEventListener("message", handleMessage);
|
|
1606
|
+
dialog.removeEventListener("close", handleClose);
|
|
1607
|
+
};
|
|
1608
|
+
stopEarlyCloseWatch = stop;
|
|
1609
|
+
const resolveClosed = (ready) => {
|
|
1610
|
+
stop();
|
|
1611
|
+
cleanup();
|
|
1612
|
+
resolve({ type: "dialog_closed", ready });
|
|
1613
|
+
};
|
|
1614
|
+
const handleMessage = (event) => {
|
|
1615
|
+
if (event.origin !== dialogOrigin) return;
|
|
1616
|
+
if (event.data?.type === "PASSKEY_CLOSE") {
|
|
1617
|
+
resolveClosed(iframeReadyObserved);
|
|
1618
|
+
}
|
|
1619
|
+
};
|
|
1620
|
+
const handleClose = () => {
|
|
1621
|
+
resolveClosed(iframeReadyObserved);
|
|
1622
|
+
};
|
|
1623
|
+
window.addEventListener("message", handleMessage);
|
|
1624
|
+
dialog.addEventListener("close", handleClose);
|
|
1625
|
+
});
|
|
1626
|
+
let dialogReadyDurationMs;
|
|
1627
|
+
const dialogReadyPromise = this.waitForDialogReadyDeferred(dialog, iframe, cleanup, {
|
|
1628
|
+
blindSigning: requestedBlindSigning,
|
|
1629
|
+
reveal
|
|
1630
|
+
}).then((result) => {
|
|
1631
|
+
dialogReadyDurationMs = Date.now() - dialogOpenedAt;
|
|
1632
|
+
iframeReadyObserved = result.ready;
|
|
1633
|
+
return result;
|
|
1634
|
+
});
|
|
1635
|
+
const dialogClosedBeforeReadyPromise = dialogReadyPromise.then(
|
|
1636
|
+
(result) => result.ready ? new Promise(() => void 0) : { type: "dialog_closed", ready: false }
|
|
1637
|
+
);
|
|
1638
|
+
const accessTokenStartedAt = Date.now();
|
|
1639
|
+
let accessTokenDurationMs;
|
|
1640
|
+
const accessTokenPromise = this.fetchAccessToken().then((result) => {
|
|
1641
|
+
accessTokenDurationMs = Date.now() - accessTokenStartedAt;
|
|
1642
|
+
return result;
|
|
1643
|
+
});
|
|
1644
|
+
const accessOrClose = await Promise.race([
|
|
1645
|
+
accessTokenPromise.then((result) => ({ type: "access_token", result })),
|
|
1646
|
+
dialogClosedBeforeReadyPromise,
|
|
1647
|
+
earlyDialogClosePromise
|
|
1648
|
+
]);
|
|
1649
|
+
if (accessOrClose.type === "dialog_closed") {
|
|
1650
|
+
stopEarlyCloseWatch();
|
|
1651
|
+
this.emitTelemetry("dialog.cancelled", telemetry, {
|
|
1652
|
+
outcome: "cancelled",
|
|
1653
|
+
errorCode: "USER_CANCELLED",
|
|
1654
|
+
errorMessage: accessOrClose.ready ? "User closed the dialog while intent setup was still loading" : "User closed the dialog before it became ready",
|
|
1655
|
+
attributes: {
|
|
1656
|
+
accessTokenDurationMs,
|
|
1657
|
+
dialogReadyDurationMs
|
|
1658
|
+
}
|
|
1659
|
+
});
|
|
1660
|
+
return {
|
|
1661
|
+
success: false,
|
|
1662
|
+
intentId: "",
|
|
1663
|
+
status: "failed",
|
|
1664
|
+
error: { code: "USER_CANCELLED", message: "User closed the dialog" }
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
const accessTokenResult = accessOrClose.result;
|
|
1668
|
+
let accessTokenFailure = null;
|
|
1669
|
+
if (!accessTokenResult.ok) {
|
|
1670
|
+
const errorCode = accessTokenResult.code ?? "SPONSORSHIP_FETCH_FAILED";
|
|
1671
|
+
this.emitTelemetry("intent.prepare.failed", telemetry, {
|
|
1672
|
+
outcome: "failure",
|
|
1673
|
+
errorCode,
|
|
1674
|
+
errorMessage: accessTokenResult.message,
|
|
1675
|
+
attributes: {
|
|
1676
|
+
accessTokenDurationMs,
|
|
1677
|
+
dialogReadyDurationMs,
|
|
1678
|
+
loadingPhase: "access_token"
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
accessTokenFailure = { code: errorCode, message: accessTokenResult.message };
|
|
1682
|
+
}
|
|
1683
|
+
let accessToken = accessTokenResult.ok ? accessTokenResult.accessToken : "";
|
|
580
1684
|
const requestBody = {
|
|
581
1685
|
username,
|
|
582
1686
|
accountAddress,
|
|
@@ -585,20 +1689,112 @@ var OneAuthClient = class {
|
|
|
585
1689
|
tokenRequests: serializedTokenRequests,
|
|
586
1690
|
sourceAssets: options.sourceAssets,
|
|
587
1691
|
sourceChainId: options.sourceChainId,
|
|
1692
|
+
auth: { accessToken },
|
|
588
1693
|
...this.config.clientId && { clientId: this.config.clientId }
|
|
589
1694
|
};
|
|
590
|
-
const dialogUrl = this.getDialogUrl();
|
|
591
|
-
const themeParams = this.getThemeParams();
|
|
592
|
-
const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe${themeParams ? `&${themeParams}` : ""}`;
|
|
593
|
-
const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
|
|
594
|
-
const dialogOrigin = this.getDialogOrigin();
|
|
595
1695
|
let earlyPrepareResult = null;
|
|
596
|
-
|
|
1696
|
+
let extensionTokensPromise = Promise.resolve({ ok: true, extensionTokens: [] });
|
|
1697
|
+
let extensionTokensDurationMs;
|
|
1698
|
+
this.emitTelemetry("intent.prepare.started", telemetry, {
|
|
1699
|
+
outcome: "started",
|
|
1700
|
+
targetChain,
|
|
1701
|
+
attributes: {
|
|
1702
|
+
accessTokenDurationMs,
|
|
1703
|
+
dialogReadyDurationMs
|
|
1704
|
+
}
|
|
1705
|
+
});
|
|
1706
|
+
const prepareStartedAt = Date.now();
|
|
1707
|
+
let prepareDurationMs;
|
|
1708
|
+
const runPrepare = () => this.prepareIntent(requestBody, telemetry).then((r) => {
|
|
597
1709
|
earlyPrepareResult = r;
|
|
1710
|
+
prepareDurationMs = Date.now() - prepareStartedAt;
|
|
1711
|
+
if (r.success) {
|
|
1712
|
+
this.emitTelemetry("intent.prepare.succeeded", telemetry, {
|
|
1713
|
+
outcome: "success",
|
|
1714
|
+
targetChain,
|
|
1715
|
+
status: "quoted",
|
|
1716
|
+
attributes: {
|
|
1717
|
+
accessTokenDurationMs,
|
|
1718
|
+
dialogReadyDurationMs,
|
|
1719
|
+
prepareDurationMs
|
|
1720
|
+
}
|
|
1721
|
+
});
|
|
1722
|
+
const extensionTokensStartedAt = Date.now();
|
|
1723
|
+
extensionTokensPromise = this.fetchExtensionTokens(
|
|
1724
|
+
[r.data.intentOp],
|
|
1725
|
+
[sponsor]
|
|
1726
|
+
).then((result) => {
|
|
1727
|
+
extensionTokensDurationMs = Date.now() - extensionTokensStartedAt;
|
|
1728
|
+
return result;
|
|
1729
|
+
});
|
|
1730
|
+
} else {
|
|
1731
|
+
this.emitTelemetry("intent.prepare.failed", telemetry, {
|
|
1732
|
+
outcome: "failure",
|
|
1733
|
+
targetChain,
|
|
1734
|
+
...describeTelemetryError(r.error),
|
|
1735
|
+
attributes: {
|
|
1736
|
+
accessTokenDurationMs,
|
|
1737
|
+
dialogReadyDurationMs,
|
|
1738
|
+
prepareDurationMs,
|
|
1739
|
+
loadingPhase: "prepare"
|
|
1740
|
+
}
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
598
1743
|
return r;
|
|
599
1744
|
});
|
|
600
|
-
const
|
|
1745
|
+
const preparePromise = accessTokenFailure ? Promise.resolve({
|
|
1746
|
+
success: false,
|
|
1747
|
+
error: accessTokenFailure
|
|
1748
|
+
}).then((r) => {
|
|
1749
|
+
earlyPrepareResult = r;
|
|
1750
|
+
return r;
|
|
1751
|
+
}) : runPrepare();
|
|
1752
|
+
const retryPrepare = async () => {
|
|
1753
|
+
const retryTokenResult = await this.fetchAccessToken();
|
|
1754
|
+
if (!retryTokenResult.ok) {
|
|
1755
|
+
return {
|
|
1756
|
+
success: false,
|
|
1757
|
+
error: {
|
|
1758
|
+
code: retryTokenResult.code ?? "SPONSORSHIP_FETCH_FAILED",
|
|
1759
|
+
message: retryTokenResult.message
|
|
1760
|
+
}
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
accessToken = retryTokenResult.accessToken;
|
|
1764
|
+
requestBody.auth = { accessToken };
|
|
1765
|
+
return runPrepare();
|
|
1766
|
+
};
|
|
1767
|
+
const readyOrClose = await Promise.race([
|
|
1768
|
+
dialogReadyPromise.then((result) => ({ type: "ready", result })),
|
|
1769
|
+
earlyDialogClosePromise
|
|
1770
|
+
]);
|
|
1771
|
+
if (readyOrClose.type === "dialog_closed") {
|
|
1772
|
+
stopEarlyCloseWatch();
|
|
1773
|
+
this.emitTelemetry("dialog.cancelled", telemetry, {
|
|
1774
|
+
outcome: "cancelled",
|
|
1775
|
+
errorCode: "USER_CANCELLED",
|
|
1776
|
+
errorMessage: readyOrClose.ready ? "User closed the dialog while intent setup was still loading" : "User closed the dialog before it became ready",
|
|
1777
|
+
attributes: {
|
|
1778
|
+
accessTokenDurationMs,
|
|
1779
|
+
dialogReadyDurationMs,
|
|
1780
|
+
prepareDurationMs
|
|
1781
|
+
}
|
|
1782
|
+
});
|
|
1783
|
+
return {
|
|
1784
|
+
success: false,
|
|
1785
|
+
intentId: "",
|
|
1786
|
+
status: "failed",
|
|
1787
|
+
error: { code: "USER_CANCELLED", message: "User closed the dialog" }
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1790
|
+
const dialogResult = readyOrClose.result;
|
|
601
1791
|
if (!dialogResult.ready) {
|
|
1792
|
+
stopEarlyCloseWatch();
|
|
1793
|
+
this.emitTelemetry("dialog.cancelled", telemetry, {
|
|
1794
|
+
outcome: "cancelled",
|
|
1795
|
+
errorCode: "USER_CANCELLED",
|
|
1796
|
+
errorMessage: "User closed the dialog before it became ready"
|
|
1797
|
+
});
|
|
602
1798
|
return {
|
|
603
1799
|
success: false,
|
|
604
1800
|
intentId: "",
|
|
@@ -606,126 +1802,348 @@ var OneAuthClient = class {
|
|
|
606
1802
|
error: { code: "USER_CANCELLED", message: "User closed the dialog" }
|
|
607
1803
|
};
|
|
608
1804
|
}
|
|
1805
|
+
const blindSigning = dialogResult.blindSigning;
|
|
1806
|
+
this.emitTelemetry("dialog.ready", telemetry, {
|
|
1807
|
+
outcome: "started",
|
|
1808
|
+
attributes: {
|
|
1809
|
+
accessTokenDurationMs,
|
|
1810
|
+
dialogReadyDurationMs
|
|
1811
|
+
}
|
|
1812
|
+
});
|
|
609
1813
|
let currentInitPayload;
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
await this.
|
|
1814
|
+
const refreshQuote = async () => {
|
|
1815
|
+
try {
|
|
1816
|
+
const refreshedTokenResult = await this.fetchAccessToken();
|
|
1817
|
+
const refreshToken = refreshedTokenResult.ok ? refreshedTokenResult.accessToken : accessToken;
|
|
1818
|
+
const refreshResponse = await fetch(`${this.config.providerUrl}/api/intent/prepare`, {
|
|
1819
|
+
method: "POST",
|
|
1820
|
+
headers: { "Content-Type": "application/json", ...this.telemetryHeaders(telemetry) },
|
|
1821
|
+
body: JSON.stringify({ ...requestBody, auth: { accessToken: refreshToken } }),
|
|
1822
|
+
credentials: "include"
|
|
1823
|
+
});
|
|
1824
|
+
if (!refreshResponse.ok) {
|
|
1825
|
+
console.error("[SDK] Quote refresh failed:", await refreshResponse.text());
|
|
1826
|
+
return null;
|
|
1827
|
+
}
|
|
1828
|
+
const refreshedData = await refreshResponse.json();
|
|
1829
|
+
const refreshedExtensionPromise = this.fetchExtensionTokens(
|
|
1830
|
+
[refreshedData.intentOp],
|
|
1831
|
+
[sponsor]
|
|
1832
|
+
);
|
|
1833
|
+
const extensionResult = await refreshedExtensionPromise;
|
|
1834
|
+
if (!extensionResult.ok) {
|
|
1835
|
+
console.error(
|
|
1836
|
+
"[SDK] Extension token refresh failed:",
|
|
1837
|
+
extensionResult.message
|
|
1838
|
+
);
|
|
1839
|
+
return null;
|
|
1840
|
+
}
|
|
1841
|
+
const refreshedAuth = {
|
|
1842
|
+
accessToken: refreshToken,
|
|
1843
|
+
extensionTokens: extensionResult.extensionTokens
|
|
1844
|
+
};
|
|
1845
|
+
prepareResponse = refreshedData;
|
|
1846
|
+
accessToken = refreshToken;
|
|
1847
|
+
requestBody.auth = { accessToken: refreshToken };
|
|
1848
|
+
extensionTokensPromise = refreshedExtensionPromise;
|
|
1849
|
+
currentInitPayload = {
|
|
1850
|
+
...currentInitPayload,
|
|
1851
|
+
transaction: refreshedData.transaction,
|
|
1852
|
+
challenge: refreshedData.challenge,
|
|
1853
|
+
originMessages: refreshedData.originMessages,
|
|
1854
|
+
expiresAt: refreshedData.expiresAt,
|
|
1855
|
+
intentOp: refreshedData.intentOp,
|
|
1856
|
+
digestResult: refreshedData.digestResult,
|
|
1857
|
+
auth: refreshedAuth
|
|
1858
|
+
};
|
|
615
1859
|
return {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
1860
|
+
intentOp: refreshedData.intentOp,
|
|
1861
|
+
expiresAt: refreshedData.expiresAt,
|
|
1862
|
+
challenge: refreshedData.challenge,
|
|
1863
|
+
originMessages: refreshedData.originMessages,
|
|
1864
|
+
transaction: refreshedData.transaction,
|
|
1865
|
+
digestResult: refreshedData.digestResult,
|
|
1866
|
+
// Dialog-executed intents read auth from dialog state — ship the
|
|
1867
|
+
// rebound bundle with the refresh so PASSKEY_REFRESH_COMPLETE
|
|
1868
|
+
// updates it alongside the quote.
|
|
1869
|
+
auth: refreshedAuth
|
|
620
1870
|
};
|
|
1871
|
+
} catch (error) {
|
|
1872
|
+
console.error("[SDK] Quote refresh error:", error);
|
|
1873
|
+
return null;
|
|
1874
|
+
}
|
|
1875
|
+
};
|
|
1876
|
+
const signingResultPromise = this.waitForSigningWithRefresh(
|
|
1877
|
+
dialog,
|
|
1878
|
+
iframe,
|
|
1879
|
+
cleanup,
|
|
1880
|
+
dialogOrigin,
|
|
1881
|
+
refreshQuote
|
|
1882
|
+
);
|
|
1883
|
+
stopEarlyCloseWatch();
|
|
1884
|
+
if (earlyPrepareResult) {
|
|
1885
|
+
let prepareResult = earlyPrepareResult;
|
|
1886
|
+
for (; ; ) {
|
|
1887
|
+
while (!prepareResult.success) {
|
|
1888
|
+
this.sendPrepareError(iframe, prepareResult.error.message);
|
|
1889
|
+
if (blindSigning) {
|
|
1890
|
+
cleanup();
|
|
1891
|
+
return {
|
|
1892
|
+
success: false,
|
|
1893
|
+
intentId: "",
|
|
1894
|
+
status: "failed",
|
|
1895
|
+
error: prepareResult.error
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
const next2 = await this.waitForPrepareRetryOrClose(dialog, cleanup);
|
|
1899
|
+
if (next2 === "closed") {
|
|
1900
|
+
return {
|
|
1901
|
+
success: false,
|
|
1902
|
+
intentId: "",
|
|
1903
|
+
status: "failed",
|
|
1904
|
+
error: prepareResult.error
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
prepareResult = await retryPrepare();
|
|
1908
|
+
}
|
|
1909
|
+
prepareResponse = prepareResult.data;
|
|
1910
|
+
const extensionResult = await extensionTokensPromise;
|
|
1911
|
+
if (extensionResult.ok) {
|
|
1912
|
+
const sponsorshipTokens = {
|
|
1913
|
+
accessToken,
|
|
1914
|
+
extensionTokens: extensionResult.extensionTokens
|
|
1915
|
+
};
|
|
1916
|
+
currentInitPayload = {
|
|
1917
|
+
mode: "iframe",
|
|
1918
|
+
calls,
|
|
1919
|
+
chainId: targetChain,
|
|
1920
|
+
transaction: prepareResponse.transaction,
|
|
1921
|
+
challenge: prepareResponse.challenge,
|
|
1922
|
+
username,
|
|
1923
|
+
accountAddress: prepareResponse.accountAddress,
|
|
1924
|
+
originMessages: prepareResponse.originMessages,
|
|
1925
|
+
tokenRequests: serializedTokenRequests,
|
|
1926
|
+
expiresAt: prepareResponse.expiresAt,
|
|
1927
|
+
userId: prepareResponse.userId,
|
|
1928
|
+
intentOp: prepareResponse.intentOp,
|
|
1929
|
+
digestResult: prepareResponse.digestResult,
|
|
1930
|
+
tier: prepareResult.tier,
|
|
1931
|
+
auth: sponsorshipTokens,
|
|
1932
|
+
telemetry: this.telemetryPayload(telemetry)
|
|
1933
|
+
};
|
|
1934
|
+
dialogResult.sendInit(currentInitPayload);
|
|
1935
|
+
break;
|
|
1936
|
+
}
|
|
1937
|
+
this.emitTelemetry("intent.prepare.failed", telemetry, {
|
|
1938
|
+
outcome: "failure",
|
|
1939
|
+
errorCode: "SPONSORSHIP_FETCH_FAILED",
|
|
1940
|
+
errorMessage: extensionResult.message,
|
|
1941
|
+
attributes: {
|
|
1942
|
+
accessTokenDurationMs,
|
|
1943
|
+
dialogReadyDurationMs,
|
|
1944
|
+
prepareDurationMs,
|
|
1945
|
+
extensionTokensDurationMs,
|
|
1946
|
+
loadingPhase: "extension_token"
|
|
1947
|
+
}
|
|
1948
|
+
});
|
|
1949
|
+
this.sendPrepareError(iframe, extensionResult.message);
|
|
1950
|
+
if (blindSigning) {
|
|
1951
|
+
cleanup();
|
|
1952
|
+
return {
|
|
1953
|
+
success: false,
|
|
1954
|
+
intentId: "",
|
|
1955
|
+
status: "failed",
|
|
1956
|
+
error: { code: "SPONSORSHIP_FETCH_FAILED", message: extensionResult.message }
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
const next = await this.waitForPrepareRetryOrClose(dialog, cleanup);
|
|
1960
|
+
if (next === "closed") {
|
|
1961
|
+
return {
|
|
1962
|
+
success: false,
|
|
1963
|
+
intentId: "",
|
|
1964
|
+
status: "failed",
|
|
1965
|
+
error: { code: "SPONSORSHIP_FETCH_FAILED", message: extensionResult.message }
|
|
1966
|
+
};
|
|
1967
|
+
}
|
|
1968
|
+
prepareResult = await retryPrepare();
|
|
621
1969
|
}
|
|
622
|
-
prepareResponse = prepareResult.data;
|
|
623
|
-
currentInitPayload = {
|
|
624
|
-
mode: "iframe",
|
|
625
|
-
calls,
|
|
626
|
-
chainId: targetChain,
|
|
627
|
-
transaction: prepareResponse.transaction,
|
|
628
|
-
challenge: prepareResponse.challenge,
|
|
629
|
-
username,
|
|
630
|
-
accountAddress: prepareResponse.accountAddress,
|
|
631
|
-
originMessages: prepareResponse.originMessages,
|
|
632
|
-
tokenRequests: serializedTokenRequests,
|
|
633
|
-
expiresAt: prepareResponse.expiresAt,
|
|
634
|
-
userId: prepareResponse.userId,
|
|
635
|
-
intentOp: prepareResponse.intentOp,
|
|
636
|
-
digestResult: prepareResponse.digestResult,
|
|
637
|
-
tier: prepareResult.tier
|
|
638
|
-
};
|
|
639
|
-
dialogResult.sendInit(currentInitPayload);
|
|
640
1970
|
} else {
|
|
641
1971
|
currentInitPayload = {
|
|
642
1972
|
mode: "iframe",
|
|
1973
|
+
signingMode: "transaction",
|
|
643
1974
|
calls,
|
|
644
1975
|
chainId: targetChain,
|
|
645
1976
|
username,
|
|
646
1977
|
accountAddress: options.accountAddress,
|
|
647
|
-
tokenRequests: serializedTokenRequests
|
|
1978
|
+
tokenRequests: serializedTokenRequests,
|
|
1979
|
+
telemetry: this.telemetryPayload(telemetry)
|
|
648
1980
|
};
|
|
649
1981
|
dialogResult.sendInit(currentInitPayload);
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
1982
|
+
const prepareOrCancel = await Promise.race([
|
|
1983
|
+
preparePromise.then((prepareResult2) => ({
|
|
1984
|
+
type: "prepare",
|
|
1985
|
+
prepareResult: prepareResult2
|
|
1986
|
+
})),
|
|
1987
|
+
signingResultPromise.then((signingResult2) => ({
|
|
1988
|
+
type: "signing",
|
|
1989
|
+
signingResult: signingResult2
|
|
1990
|
+
}))
|
|
1991
|
+
]);
|
|
1992
|
+
let prepareResult;
|
|
1993
|
+
if (prepareOrCancel.type === "signing") {
|
|
1994
|
+
const earlySigningResult = prepareOrCancel.signingResult;
|
|
1995
|
+
if (earlySigningResult.success) {
|
|
1996
|
+
prepareResult = await preparePromise;
|
|
1997
|
+
} else {
|
|
1998
|
+
const signingError = earlySigningResult.error;
|
|
1999
|
+
this.emitTelemetry(
|
|
2000
|
+
signingError?.code === "USER_REJECTED" ? "dialog.cancelled" : "intent.sign.failed",
|
|
2001
|
+
telemetry,
|
|
2002
|
+
{
|
|
2003
|
+
outcome: signingError?.code === "USER_REJECTED" ? "cancelled" : "failure",
|
|
2004
|
+
...describeTelemetryError(signingError)
|
|
2005
|
+
}
|
|
2006
|
+
);
|
|
2007
|
+
if (blindSigning) cleanup();
|
|
2008
|
+
return {
|
|
2009
|
+
success: false,
|
|
2010
|
+
intentId: "",
|
|
2011
|
+
status: "failed",
|
|
2012
|
+
error: signingError
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
} else {
|
|
2016
|
+
prepareResult = prepareOrCancel.prepareResult;
|
|
660
2017
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
2018
|
+
for (; ; ) {
|
|
2019
|
+
while (!prepareResult.success) {
|
|
2020
|
+
this.sendPrepareError(iframe, prepareResult.error.message);
|
|
2021
|
+
if (blindSigning) {
|
|
2022
|
+
cleanup();
|
|
2023
|
+
return {
|
|
2024
|
+
success: false,
|
|
2025
|
+
intentId: "",
|
|
2026
|
+
status: "failed",
|
|
2027
|
+
error: prepareResult.error
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
const next2 = await this.waitForPrepareRetryOrClose(dialog, cleanup);
|
|
2031
|
+
if (next2 === "closed") {
|
|
2032
|
+
return {
|
|
2033
|
+
success: false,
|
|
2034
|
+
intentId: "",
|
|
2035
|
+
status: "failed",
|
|
2036
|
+
error: prepareResult.error
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
2039
|
+
prepareResult = await retryPrepare();
|
|
2040
|
+
}
|
|
2041
|
+
prepareResponse = prepareResult.data;
|
|
2042
|
+
const authReady = !sponsor ? {
|
|
2043
|
+
accessToken,
|
|
2044
|
+
extensionTokens: []
|
|
2045
|
+
} : void 0;
|
|
2046
|
+
currentInitPayload = {
|
|
2047
|
+
mode: "iframe",
|
|
2048
|
+
calls,
|
|
2049
|
+
chainId: targetChain,
|
|
2050
|
+
transaction: prepareResponse.transaction,
|
|
2051
|
+
challenge: prepareResponse.challenge,
|
|
2052
|
+
username,
|
|
2053
|
+
accountAddress: prepareResponse.accountAddress,
|
|
2054
|
+
originMessages: prepareResponse.originMessages,
|
|
2055
|
+
tokenRequests: serializedTokenRequests,
|
|
2056
|
+
expiresAt: prepareResponse.expiresAt,
|
|
2057
|
+
userId: prepareResponse.userId,
|
|
2058
|
+
intentOp: prepareResponse.intentOp,
|
|
2059
|
+
digestResult: prepareResponse.digestResult,
|
|
2060
|
+
tier: prepareResult.tier,
|
|
2061
|
+
...authReady && { auth: authReady },
|
|
2062
|
+
telemetry: this.telemetryPayload(telemetry)
|
|
2063
|
+
};
|
|
686
2064
|
iframe.contentWindow?.postMessage(
|
|
687
|
-
{ type: "
|
|
2065
|
+
{ type: "PASSKEY_QUOTE_READY", ...currentInitPayload, fullViewport: true, blindSigning },
|
|
688
2066
|
dialogOrigin
|
|
689
2067
|
);
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
2068
|
+
if (!sponsor) break;
|
|
2069
|
+
const extensionResult = await extensionTokensPromise;
|
|
2070
|
+
if (extensionResult.ok) {
|
|
2071
|
+
const sponsorshipTokens = {
|
|
2072
|
+
accessToken,
|
|
2073
|
+
extensionTokens: extensionResult.extensionTokens
|
|
2074
|
+
};
|
|
2075
|
+
currentInitPayload = {
|
|
2076
|
+
...currentInitPayload,
|
|
2077
|
+
auth: sponsorshipTokens
|
|
2078
|
+
};
|
|
2079
|
+
iframe.contentWindow?.postMessage(
|
|
2080
|
+
{ type: "PASSKEY_QUOTE_READY", auth: sponsorshipTokens, fullViewport: true, blindSigning },
|
|
2081
|
+
dialogOrigin
|
|
2082
|
+
);
|
|
2083
|
+
break;
|
|
2084
|
+
}
|
|
2085
|
+
this.emitTelemetry("intent.prepare.failed", telemetry, {
|
|
2086
|
+
outcome: "failure",
|
|
2087
|
+
errorCode: "SPONSORSHIP_FETCH_FAILED",
|
|
2088
|
+
errorMessage: extensionResult.message,
|
|
2089
|
+
attributes: {
|
|
2090
|
+
accessTokenDurationMs,
|
|
2091
|
+
dialogReadyDurationMs,
|
|
2092
|
+
prepareDurationMs,
|
|
2093
|
+
extensionTokensDurationMs,
|
|
2094
|
+
loadingPhase: "extension_token"
|
|
710
2095
|
}
|
|
711
|
-
|
|
712
|
-
|
|
2096
|
+
});
|
|
2097
|
+
this.sendPrepareError(iframe, extensionResult.message);
|
|
2098
|
+
if (blindSigning) {
|
|
2099
|
+
cleanup();
|
|
713
2100
|
return {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
transaction: refreshedData.transaction,
|
|
719
|
-
digestResult: refreshedData.digestResult
|
|
2101
|
+
success: false,
|
|
2102
|
+
intentId: "",
|
|
2103
|
+
status: "failed",
|
|
2104
|
+
error: { code: "SPONSORSHIP_FETCH_FAILED", message: extensionResult.message }
|
|
720
2105
|
};
|
|
721
|
-
} catch (error) {
|
|
722
|
-
console.error("[SDK] Quote refresh error:", error);
|
|
723
|
-
return null;
|
|
724
2106
|
}
|
|
2107
|
+
const next = await this.waitForPrepareRetryOrClose(dialog, cleanup);
|
|
2108
|
+
if (next === "closed") {
|
|
2109
|
+
return {
|
|
2110
|
+
success: false,
|
|
2111
|
+
intentId: "",
|
|
2112
|
+
status: "failed",
|
|
2113
|
+
error: { code: "SPONSORSHIP_FETCH_FAILED", message: extensionResult.message }
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2116
|
+
prepareResult = await retryPrepare();
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
const handleReReady = (event) => {
|
|
2120
|
+
if (event.origin !== dialogOrigin) return;
|
|
2121
|
+
if (event.data?.type === "PASSKEY_READY") {
|
|
2122
|
+
iframe.contentWindow?.postMessage(
|
|
2123
|
+
{ type: "PASSKEY_INIT", ...currentInitPayload, fullViewport: true, blindSigning },
|
|
2124
|
+
dialogOrigin
|
|
2125
|
+
);
|
|
725
2126
|
}
|
|
726
|
-
|
|
2127
|
+
};
|
|
2128
|
+
window.addEventListener("message", handleReReady);
|
|
2129
|
+
const signingResult = await signingResultPromise;
|
|
727
2130
|
window.removeEventListener("message", handleReReady);
|
|
728
2131
|
if (!signingResult.success) {
|
|
2132
|
+
this.emitTelemetry(
|
|
2133
|
+
signingResult.error?.code === "USER_REJECTED" ? "dialog.cancelled" : "intent.sign.failed",
|
|
2134
|
+
telemetry,
|
|
2135
|
+
{
|
|
2136
|
+
outcome: signingResult.error?.code === "USER_REJECTED" ? "cancelled" : "failure",
|
|
2137
|
+
...describeTelemetryError(signingResult.error),
|
|
2138
|
+
attributes: {
|
|
2139
|
+
accessTokenDurationMs,
|
|
2140
|
+
dialogReadyDurationMs,
|
|
2141
|
+
prepareDurationMs,
|
|
2142
|
+
extensionTokensDurationMs
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
);
|
|
2146
|
+
if (blindSigning) cleanup();
|
|
729
2147
|
return {
|
|
730
2148
|
success: false,
|
|
731
2149
|
intentId: "",
|
|
@@ -742,12 +2160,67 @@ var OneAuthClient = class {
|
|
|
742
2160
|
intentId: signingResult.intentId,
|
|
743
2161
|
status: "pending"
|
|
744
2162
|
};
|
|
2163
|
+
this.emitTelemetry("intent.sign.succeeded", telemetry, {
|
|
2164
|
+
outcome: "success",
|
|
2165
|
+
attributes: {
|
|
2166
|
+
accessTokenDurationMs,
|
|
2167
|
+
dialogReadyDurationMs,
|
|
2168
|
+
prepareDurationMs,
|
|
2169
|
+
extensionTokensDurationMs,
|
|
2170
|
+
dialogExecuted: true
|
|
2171
|
+
}
|
|
2172
|
+
});
|
|
745
2173
|
} else {
|
|
2174
|
+
const extensionResult = await extensionTokensPromise;
|
|
2175
|
+
if (!extensionResult.ok) {
|
|
2176
|
+
this.emitTelemetry("intent.sign.failed", telemetry, {
|
|
2177
|
+
outcome: "failure",
|
|
2178
|
+
errorCode: "SPONSORSHIP_FETCH_FAILED",
|
|
2179
|
+
errorMessage: extensionResult.message,
|
|
2180
|
+
attributes: {
|
|
2181
|
+
accessTokenDurationMs,
|
|
2182
|
+
dialogReadyDurationMs,
|
|
2183
|
+
prepareDurationMs,
|
|
2184
|
+
extensionTokensDurationMs,
|
|
2185
|
+
loadingPhase: "extension_token"
|
|
2186
|
+
}
|
|
2187
|
+
});
|
|
2188
|
+
this.sendTransactionStatus(iframe, "failed");
|
|
2189
|
+
await this.waitForDialogCloseUnlessBlind(blindSigning, dialog, cleanup);
|
|
2190
|
+
return {
|
|
2191
|
+
success: false,
|
|
2192
|
+
intentId: "",
|
|
2193
|
+
status: "failed",
|
|
2194
|
+
error: { code: "SPONSORSHIP_FETCH_FAILED", message: extensionResult.message }
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
const extensionTokens = extensionResult.extensionTokens;
|
|
746
2198
|
try {
|
|
2199
|
+
this.emitTelemetry("intent.sign.succeeded", telemetry, {
|
|
2200
|
+
outcome: "success",
|
|
2201
|
+
attributes: {
|
|
2202
|
+
accessTokenDurationMs,
|
|
2203
|
+
dialogReadyDurationMs,
|
|
2204
|
+
prepareDurationMs,
|
|
2205
|
+
extensionTokensDurationMs,
|
|
2206
|
+
dialogExecuted: false
|
|
2207
|
+
}
|
|
2208
|
+
});
|
|
2209
|
+
this.emitTelemetry("intent.execute.started", telemetry, {
|
|
2210
|
+
outcome: "started",
|
|
2211
|
+
targetChain: prepareResponse.targetChain,
|
|
2212
|
+
attributes: {
|
|
2213
|
+
accessTokenDurationMs,
|
|
2214
|
+
dialogReadyDurationMs,
|
|
2215
|
+
prepareDurationMs,
|
|
2216
|
+
extensionTokensDurationMs
|
|
2217
|
+
}
|
|
2218
|
+
});
|
|
747
2219
|
const response = await fetch(`${this.config.providerUrl}/api/intent/execute`, {
|
|
748
2220
|
method: "POST",
|
|
749
2221
|
headers: {
|
|
750
|
-
"Content-Type": "application/json"
|
|
2222
|
+
"Content-Type": "application/json",
|
|
2223
|
+
...this.telemetryHeaders(telemetry)
|
|
751
2224
|
},
|
|
752
2225
|
body: JSON.stringify({
|
|
753
2226
|
// Data from prepare response (no intentId yet - created on execute)
|
|
@@ -759,14 +2232,28 @@ var OneAuthClient = class {
|
|
|
759
2232
|
digestResult: prepareResponse.digestResult,
|
|
760
2233
|
// Signature from dialog
|
|
761
2234
|
signature: signingResult.signature,
|
|
762
|
-
passkey: signingResult.passkey
|
|
2235
|
+
passkey: signingResult.passkey,
|
|
763
2236
|
// Include passkey info for signature encoding
|
|
2237
|
+
// App JWT — `accessToken` is always present. `extensionToken` is
|
|
2238
|
+
// only included when sponsorship was requested (sponsor !== false).
|
|
2239
|
+
auth: {
|
|
2240
|
+
accessToken,
|
|
2241
|
+
...extensionTokens.length > 0 && {
|
|
2242
|
+
extensionToken: extensionTokens[0]
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
764
2245
|
})
|
|
765
2246
|
});
|
|
766
2247
|
if (!response.ok) {
|
|
767
2248
|
const errorData = await response.json().catch(() => ({}));
|
|
768
2249
|
this.sendTransactionStatus(iframe, "failed");
|
|
769
|
-
await this.
|
|
2250
|
+
await this.waitForDialogCloseUnlessBlind(blindSigning, dialog, cleanup);
|
|
2251
|
+
this.emitTelemetry("intent.execute.failed", telemetry, {
|
|
2252
|
+
outcome: "failure",
|
|
2253
|
+
targetChain: prepareResponse.targetChain,
|
|
2254
|
+
errorCode: "EXECUTE_FAILED",
|
|
2255
|
+
errorMessage: errorData.error || "Failed to execute intent"
|
|
2256
|
+
});
|
|
770
2257
|
return {
|
|
771
2258
|
success: false,
|
|
772
2259
|
intentId: "",
|
|
@@ -779,9 +2266,19 @@ var OneAuthClient = class {
|
|
|
779
2266
|
};
|
|
780
2267
|
}
|
|
781
2268
|
executeResponse = await response.json();
|
|
2269
|
+
this.emitTelemetry("intent.execute.succeeded", telemetry, {
|
|
2270
|
+
outcome: "success",
|
|
2271
|
+
targetChain: prepareResponse.targetChain,
|
|
2272
|
+
status: executeResponse.status
|
|
2273
|
+
});
|
|
782
2274
|
} catch (error) {
|
|
783
2275
|
this.sendTransactionStatus(iframe, "failed");
|
|
784
|
-
await this.
|
|
2276
|
+
await this.waitForDialogCloseUnlessBlind(blindSigning, dialog, cleanup);
|
|
2277
|
+
this.emitTelemetry("intent.execute.failed", telemetry, {
|
|
2278
|
+
outcome: "failure",
|
|
2279
|
+
targetChain: prepareResponse.targetChain,
|
|
2280
|
+
...describeTelemetryError(error)
|
|
2281
|
+
});
|
|
785
2282
|
return {
|
|
786
2283
|
success: false,
|
|
787
2284
|
intentId: "",
|
|
@@ -798,6 +2295,10 @@ var OneAuthClient = class {
|
|
|
798
2295
|
let finalTxHash = executeResponse.transactionHash;
|
|
799
2296
|
if (finalStatus === "pending") {
|
|
800
2297
|
this.sendTransactionStatus(iframe, "pending");
|
|
2298
|
+
this.emitTelemetry("intent.status.started", telemetry, {
|
|
2299
|
+
outcome: "started",
|
|
2300
|
+
status: finalStatus
|
|
2301
|
+
});
|
|
801
2302
|
let userClosedEarly = false;
|
|
802
2303
|
const dialogOrigin2 = this.getDialogOrigin();
|
|
803
2304
|
const earlyCloseHandler = (event) => {
|
|
@@ -818,7 +2319,10 @@ var OneAuthClient = class {
|
|
|
818
2319
|
`${this.config.providerUrl}/api/intent/status/${executeResponse.intentId}`,
|
|
819
2320
|
{
|
|
820
2321
|
method: "GET",
|
|
821
|
-
headers:
|
|
2322
|
+
headers: {
|
|
2323
|
+
...this.telemetryHeaders(telemetry),
|
|
2324
|
+
...this.config.clientId ? { "x-client-id": this.config.clientId } : {}
|
|
2325
|
+
}
|
|
822
2326
|
}
|
|
823
2327
|
);
|
|
824
2328
|
if (statusResponse.ok) {
|
|
@@ -850,6 +2354,12 @@ var OneAuthClient = class {
|
|
|
850
2354
|
window.removeEventListener("message", earlyCloseHandler);
|
|
851
2355
|
if (userClosedEarly) {
|
|
852
2356
|
cleanup();
|
|
2357
|
+
this.emitTelemetry("intent.status.failed", telemetry, {
|
|
2358
|
+
outcome: "cancelled",
|
|
2359
|
+
status: finalStatus,
|
|
2360
|
+
errorCode: "USER_CANCELLED",
|
|
2361
|
+
errorMessage: "User closed the dialog while status polling"
|
|
2362
|
+
});
|
|
853
2363
|
return {
|
|
854
2364
|
success: false,
|
|
855
2365
|
intentId: executeResponse.intentId,
|
|
@@ -872,19 +2382,25 @@ var OneAuthClient = class {
|
|
|
872
2382
|
};
|
|
873
2383
|
const isSuccessStatus = successStatuses[closeOn]?.includes(finalStatus) ?? false;
|
|
874
2384
|
const displayStatus = isSuccessStatus ? "confirmed" : finalStatus;
|
|
875
|
-
const closePromise = this.
|
|
2385
|
+
const closePromise = this.waitForDialogCloseUnlessBlind(blindSigning, dialog, cleanup);
|
|
876
2386
|
this.sendTransactionStatus(iframe, displayStatus, finalTxHash);
|
|
877
2387
|
await closePromise;
|
|
878
2388
|
if (options.waitForHash && !finalTxHash) {
|
|
879
2389
|
const hash = await this.waitForTransactionHash(executeResponse.intentId, {
|
|
880
2390
|
timeoutMs: options.hashTimeoutMs,
|
|
881
2391
|
intervalMs: options.hashIntervalMs
|
|
882
|
-
});
|
|
2392
|
+
}, telemetry);
|
|
883
2393
|
if (hash) {
|
|
884
2394
|
finalTxHash = hash;
|
|
885
2395
|
finalStatus = "completed";
|
|
886
2396
|
} else {
|
|
887
2397
|
finalStatus = "failed";
|
|
2398
|
+
this.emitTelemetry("intent.status.failed", telemetry, {
|
|
2399
|
+
outcome: "failure",
|
|
2400
|
+
status: finalStatus,
|
|
2401
|
+
errorCode: "HASH_TIMEOUT",
|
|
2402
|
+
errorMessage: "Timed out waiting for transaction hash"
|
|
2403
|
+
});
|
|
888
2404
|
return {
|
|
889
2405
|
success: false,
|
|
890
2406
|
intentId: executeResponse.intentId,
|
|
@@ -898,6 +2414,15 @@ var OneAuthClient = class {
|
|
|
898
2414
|
};
|
|
899
2415
|
}
|
|
900
2416
|
}
|
|
2417
|
+
this.emitTelemetry("intent.status.succeeded", telemetry, {
|
|
2418
|
+
outcome: isSuccessStatus ? "success" : "failure",
|
|
2419
|
+
status: finalStatus
|
|
2420
|
+
});
|
|
2421
|
+
this.emitTelemetry("intent.completed", telemetry, {
|
|
2422
|
+
outcome: isSuccessStatus ? "success" : "failure",
|
|
2423
|
+
status: finalStatus,
|
|
2424
|
+
targetChain
|
|
2425
|
+
});
|
|
901
2426
|
return {
|
|
902
2427
|
success: isSuccessStatus,
|
|
903
2428
|
intentId: executeResponse.intentId,
|
|
@@ -935,7 +2460,30 @@ var OneAuthClient = class {
|
|
|
935
2460
|
* ```
|
|
936
2461
|
*/
|
|
937
2462
|
async sendBatchIntent(options) {
|
|
2463
|
+
const telemetry = this.createTelemetryOperation("batch_intent", {
|
|
2464
|
+
intentCount: options.intents?.length ?? 0,
|
|
2465
|
+
blindSigning: this.shouldRequestBlindSigning(options)
|
|
2466
|
+
});
|
|
2467
|
+
if (getStoredSignerType() === "eoa") {
|
|
2468
|
+
this.emitTelemetry("batch.completed", telemetry, {
|
|
2469
|
+
outcome: "failure",
|
|
2470
|
+
errorCode: "E_SIGNER_UNSUPPORTED",
|
|
2471
|
+
errorMessage: "Batch intents are not supported for EOA sessions"
|
|
2472
|
+
});
|
|
2473
|
+
return {
|
|
2474
|
+
success: false,
|
|
2475
|
+
results: [],
|
|
2476
|
+
successCount: 0,
|
|
2477
|
+
failureCount: 0,
|
|
2478
|
+
error: "Batch intents are not supported for EOA sessions. Use the connected wallet's native RPC instead."
|
|
2479
|
+
};
|
|
2480
|
+
}
|
|
938
2481
|
if (!options.username && !options.accountAddress) {
|
|
2482
|
+
this.emitTelemetry("batch.completed", telemetry, {
|
|
2483
|
+
outcome: "failure",
|
|
2484
|
+
errorCode: "INVALID_OPTIONS",
|
|
2485
|
+
errorMessage: "Either username or accountAddress is required"
|
|
2486
|
+
});
|
|
939
2487
|
return {
|
|
940
2488
|
success: false,
|
|
941
2489
|
results: [],
|
|
@@ -944,6 +2492,11 @@ var OneAuthClient = class {
|
|
|
944
2492
|
};
|
|
945
2493
|
}
|
|
946
2494
|
if (!options.intents?.length) {
|
|
2495
|
+
this.emitTelemetry("batch.completed", telemetry, {
|
|
2496
|
+
outcome: "failure",
|
|
2497
|
+
errorCode: "INVALID_OPTIONS",
|
|
2498
|
+
errorMessage: "At least one intent is required"
|
|
2499
|
+
});
|
|
947
2500
|
return {
|
|
948
2501
|
success: false,
|
|
949
2502
|
results: [],
|
|
@@ -951,9 +2504,40 @@ var OneAuthClient = class {
|
|
|
951
2504
|
failureCount: 0
|
|
952
2505
|
};
|
|
953
2506
|
}
|
|
2507
|
+
if (!this.sponsorship) {
|
|
2508
|
+
this.emitTelemetry("batch.prepare.failed", telemetry, {
|
|
2509
|
+
outcome: "failure",
|
|
2510
|
+
errorCode: "MISSING_APP_CREDENTIALS",
|
|
2511
|
+
errorMessage: "No sponsorship configured on OneAuthClient"
|
|
2512
|
+
});
|
|
2513
|
+
return {
|
|
2514
|
+
success: false,
|
|
2515
|
+
results: [],
|
|
2516
|
+
successCount: 0,
|
|
2517
|
+
failureCount: 0,
|
|
2518
|
+
error: "No sponsorship configured on OneAuthClient. Every user intent must carry the app's JWT \u2014 configure `sponsorship` on the client."
|
|
2519
|
+
};
|
|
2520
|
+
}
|
|
2521
|
+
const sponsorFlags = options.intents.map((intent) => intent.sponsor !== false);
|
|
2522
|
+
const accessTokenResult = await this.fetchAccessToken();
|
|
2523
|
+
if (!accessTokenResult.ok) {
|
|
2524
|
+
this.emitTelemetry("batch.prepare.failed", telemetry, {
|
|
2525
|
+
outcome: "failure",
|
|
2526
|
+
errorCode: accessTokenResult.code ?? "SPONSORSHIP_FETCH_FAILED",
|
|
2527
|
+
errorMessage: accessTokenResult.message
|
|
2528
|
+
});
|
|
2529
|
+
return {
|
|
2530
|
+
success: false,
|
|
2531
|
+
results: [],
|
|
2532
|
+
successCount: 0,
|
|
2533
|
+
failureCount: 0,
|
|
2534
|
+
error: accessTokenResult.message
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
let accessToken = accessTokenResult.accessToken;
|
|
954
2538
|
const serializedIntents = options.intents.map((intent) => ({
|
|
955
2539
|
targetChain: intent.targetChain,
|
|
956
|
-
calls: intent.calls,
|
|
2540
|
+
calls: capCallLabels(intent.calls),
|
|
957
2541
|
tokenRequests: intent.tokenRequests?.map((r) => ({
|
|
958
2542
|
token: r.token,
|
|
959
2543
|
amount: r.amount.toString()
|
|
@@ -966,20 +2550,66 @@ var OneAuthClient = class {
|
|
|
966
2550
|
...options.username && { username: options.username },
|
|
967
2551
|
...options.accountAddress && { accountAddress: options.accountAddress },
|
|
968
2552
|
intents: serializedIntents,
|
|
2553
|
+
auth: { accessToken },
|
|
969
2554
|
...this.config.clientId && { clientId: this.config.clientId }
|
|
970
2555
|
};
|
|
971
2556
|
const dialogUrl = this.getDialogUrl();
|
|
2557
|
+
const params = new URLSearchParams({ mode: "iframe" });
|
|
2558
|
+
this.appendTelemetryParams(params, telemetry);
|
|
972
2559
|
const themeParams = this.getThemeParams();
|
|
973
|
-
|
|
974
|
-
|
|
2560
|
+
if (themeParams) {
|
|
2561
|
+
const themeParsed = new URLSearchParams(themeParams);
|
|
2562
|
+
themeParsed.forEach((value, key) => params.set(key, value));
|
|
2563
|
+
}
|
|
2564
|
+
const signingUrl = `${dialogUrl}/dialog/sign?${params.toString()}`;
|
|
2565
|
+
const requestedBlindSigning = this.shouldRequestBlindSigning(options);
|
|
2566
|
+
const { dialog, iframe, cleanup, reveal } = this.createModalDialog(signingUrl, {
|
|
2567
|
+
hidden: requestedBlindSigning
|
|
2568
|
+
});
|
|
2569
|
+
this.emitTelemetry("dialog.opened", telemetry, {
|
|
2570
|
+
outcome: "started",
|
|
2571
|
+
intentCount: options.intents.length,
|
|
2572
|
+
attributes: { blindSigning: requestedBlindSigning }
|
|
2573
|
+
});
|
|
975
2574
|
const dialogOrigin = this.getDialogOrigin();
|
|
976
2575
|
let earlyBatchResult = null;
|
|
977
|
-
|
|
2576
|
+
let batchExtensionTokensPromise = Promise.resolve({ ok: true, extensionTokens: [] });
|
|
2577
|
+
this.emitTelemetry("batch.prepare.started", telemetry, {
|
|
2578
|
+
outcome: "started",
|
|
2579
|
+
intentCount: options.intents.length,
|
|
2580
|
+
targetChains: serializedIntents.map((intent) => intent.targetChain)
|
|
2581
|
+
});
|
|
2582
|
+
const preparePromise = this.prepareBatchIntent(requestBody, telemetry).then((r) => {
|
|
978
2583
|
earlyBatchResult = r;
|
|
2584
|
+
if (r.success) {
|
|
2585
|
+
this.emitTelemetry("batch.prepare.succeeded", telemetry, {
|
|
2586
|
+
outcome: "success",
|
|
2587
|
+
intentCount: r.data.intents.length,
|
|
2588
|
+
targetChains: serializedIntents.map((intent) => intent.targetChain)
|
|
2589
|
+
});
|
|
2590
|
+
batchExtensionTokensPromise = this.fetchExtensionTokens(
|
|
2591
|
+
r.data.intents.map((intent) => intent.intentOp),
|
|
2592
|
+
sponsorFlags
|
|
2593
|
+
);
|
|
2594
|
+
} else {
|
|
2595
|
+
this.emitTelemetry("batch.prepare.failed", telemetry, {
|
|
2596
|
+
outcome: "failure",
|
|
2597
|
+
errorMessage: r.error,
|
|
2598
|
+
intentCount: options.intents.length
|
|
2599
|
+
});
|
|
2600
|
+
}
|
|
979
2601
|
return r;
|
|
980
2602
|
});
|
|
981
|
-
const dialogResult = await this.waitForDialogReadyDeferred(dialog, iframe, cleanup
|
|
2603
|
+
const dialogResult = await this.waitForDialogReadyDeferred(dialog, iframe, cleanup, {
|
|
2604
|
+
blindSigning: requestedBlindSigning,
|
|
2605
|
+
reveal
|
|
2606
|
+
});
|
|
982
2607
|
if (!dialogResult.ready) {
|
|
2608
|
+
this.emitTelemetry("dialog.cancelled", telemetry, {
|
|
2609
|
+
outcome: "cancelled",
|
|
2610
|
+
errorCode: "USER_CANCELLED",
|
|
2611
|
+
errorMessage: "User closed the dialog before it became ready"
|
|
2612
|
+
});
|
|
983
2613
|
return {
|
|
984
2614
|
success: false,
|
|
985
2615
|
results: [],
|
|
@@ -987,6 +2617,8 @@ var OneAuthClient = class {
|
|
|
987
2617
|
failureCount: 0
|
|
988
2618
|
};
|
|
989
2619
|
}
|
|
2620
|
+
const blindSigning = dialogResult.blindSigning;
|
|
2621
|
+
this.emitTelemetry("dialog.ready", telemetry, { outcome: "started" });
|
|
990
2622
|
let currentBatchPayload;
|
|
991
2623
|
const handleBatchPrepareFailure = async (prepareResult) => {
|
|
992
2624
|
const failedIntents = prepareResult.failedIntents;
|
|
@@ -998,7 +2630,7 @@ var OneAuthClient = class {
|
|
|
998
2630
|
error: { message: f.error, code: "PREPARE_FAILED" }
|
|
999
2631
|
})) ?? [];
|
|
1000
2632
|
this.sendPrepareError(iframe, prepareResult.error);
|
|
1001
|
-
await this.
|
|
2633
|
+
await this.waitForDialogCloseUnlessBlind(blindSigning, dialog, cleanup);
|
|
1002
2634
|
return {
|
|
1003
2635
|
success: false,
|
|
1004
2636
|
results: failureResults,
|
|
@@ -1007,6 +2639,17 @@ var OneAuthClient = class {
|
|
|
1007
2639
|
error: prepareResult.error
|
|
1008
2640
|
};
|
|
1009
2641
|
};
|
|
2642
|
+
const handleSponsorshipFailure = async (message) => {
|
|
2643
|
+
this.sendPrepareError(iframe, message);
|
|
2644
|
+
await this.waitForDialogCloseUnlessBlind(blindSigning, dialog, cleanup);
|
|
2645
|
+
return {
|
|
2646
|
+
success: false,
|
|
2647
|
+
results: [],
|
|
2648
|
+
successCount: 0,
|
|
2649
|
+
failureCount: 0,
|
|
2650
|
+
error: `Sponsorship token fetch failed: ${message}`
|
|
2651
|
+
};
|
|
2652
|
+
};
|
|
1010
2653
|
let prepareResponse;
|
|
1011
2654
|
if (earlyBatchResult) {
|
|
1012
2655
|
const prepareResult = earlyBatchResult;
|
|
@@ -1014,6 +2657,14 @@ var OneAuthClient = class {
|
|
|
1014
2657
|
return handleBatchPrepareFailure(prepareResult);
|
|
1015
2658
|
}
|
|
1016
2659
|
prepareResponse = prepareResult.data;
|
|
2660
|
+
const batchExtensionResult = await batchExtensionTokensPromise;
|
|
2661
|
+
if (!batchExtensionResult.ok) {
|
|
2662
|
+
return handleSponsorshipFailure(batchExtensionResult.message);
|
|
2663
|
+
}
|
|
2664
|
+
const batchSponsorshipTokens = {
|
|
2665
|
+
accessToken,
|
|
2666
|
+
extensionTokens: batchExtensionResult.extensionTokens
|
|
2667
|
+
};
|
|
1017
2668
|
currentBatchPayload = {
|
|
1018
2669
|
mode: "iframe",
|
|
1019
2670
|
batchMode: true,
|
|
@@ -1024,7 +2675,9 @@ var OneAuthClient = class {
|
|
|
1024
2675
|
accountAddress: prepareResponse.accountAddress,
|
|
1025
2676
|
userId: prepareResponse.userId,
|
|
1026
2677
|
expiresAt: prepareResponse.expiresAt,
|
|
1027
|
-
tier: prepareResult.tier
|
|
2678
|
+
tier: prepareResult.tier,
|
|
2679
|
+
auth: batchSponsorshipTokens,
|
|
2680
|
+
telemetry: this.telemetryPayload(telemetry)
|
|
1028
2681
|
};
|
|
1029
2682
|
dialogResult.sendInit(currentBatchPayload);
|
|
1030
2683
|
} else {
|
|
@@ -1038,7 +2691,8 @@ var OneAuthClient = class {
|
|
|
1038
2691
|
// No: transaction, intentOp, expiresAt, originMessages
|
|
1039
2692
|
})),
|
|
1040
2693
|
username: options.username,
|
|
1041
|
-
accountAddress: options.accountAddress
|
|
2694
|
+
accountAddress: options.accountAddress,
|
|
2695
|
+
telemetry: this.telemetryPayload(telemetry)
|
|
1042
2696
|
};
|
|
1043
2697
|
dialogResult.sendInit(currentBatchPayload);
|
|
1044
2698
|
const prepareResult = await preparePromise;
|
|
@@ -1046,6 +2700,14 @@ var OneAuthClient = class {
|
|
|
1046
2700
|
return handleBatchPrepareFailure(prepareResult);
|
|
1047
2701
|
}
|
|
1048
2702
|
prepareResponse = prepareResult.data;
|
|
2703
|
+
const batchExtensionResult = await batchExtensionTokensPromise;
|
|
2704
|
+
if (!batchExtensionResult.ok) {
|
|
2705
|
+
return handleSponsorshipFailure(batchExtensionResult.message);
|
|
2706
|
+
}
|
|
2707
|
+
const batchSponsorshipTokens = {
|
|
2708
|
+
accessToken,
|
|
2709
|
+
extensionTokens: batchExtensionResult.extensionTokens
|
|
2710
|
+
};
|
|
1049
2711
|
currentBatchPayload = {
|
|
1050
2712
|
mode: "iframe",
|
|
1051
2713
|
batchMode: true,
|
|
@@ -1056,10 +2718,12 @@ var OneAuthClient = class {
|
|
|
1056
2718
|
accountAddress: prepareResponse.accountAddress,
|
|
1057
2719
|
userId: prepareResponse.userId,
|
|
1058
2720
|
expiresAt: prepareResponse.expiresAt,
|
|
1059
|
-
tier: prepareResult.tier
|
|
2721
|
+
tier: prepareResult.tier,
|
|
2722
|
+
auth: batchSponsorshipTokens,
|
|
2723
|
+
telemetry: this.telemetryPayload(telemetry)
|
|
1060
2724
|
};
|
|
1061
2725
|
iframe.contentWindow?.postMessage(
|
|
1062
|
-
{ type: "PASSKEY_INIT", ...currentBatchPayload, fullViewport: true },
|
|
2726
|
+
{ type: "PASSKEY_INIT", ...currentBatchPayload, fullViewport: true, blindSigning },
|
|
1063
2727
|
dialogOrigin
|
|
1064
2728
|
);
|
|
1065
2729
|
}
|
|
@@ -1067,7 +2731,7 @@ var OneAuthClient = class {
|
|
|
1067
2731
|
if (event.origin !== dialogOrigin) return;
|
|
1068
2732
|
if (event.data?.type === "PASSKEY_READY") {
|
|
1069
2733
|
iframe.contentWindow?.postMessage(
|
|
1070
|
-
{ type: "PASSKEY_INIT", ...currentBatchPayload, fullViewport: true },
|
|
2734
|
+
{ type: "PASSKEY_INIT", ...currentBatchPayload, fullViewport: true, blindSigning },
|
|
1071
2735
|
dialogOrigin
|
|
1072
2736
|
);
|
|
1073
2737
|
}
|
|
@@ -1079,19 +2743,53 @@ var OneAuthClient = class {
|
|
|
1079
2743
|
const message = event.data;
|
|
1080
2744
|
if (message?.type === "PASSKEY_REFRESH_QUOTE") {
|
|
1081
2745
|
try {
|
|
2746
|
+
const refreshedTokenResult = await this.fetchAccessToken();
|
|
2747
|
+
const refreshToken = refreshedTokenResult.ok ? refreshedTokenResult.accessToken : accessToken;
|
|
1082
2748
|
const refreshResponse = await fetch(`${this.config.providerUrl}/api/intent/batch-prepare`, {
|
|
1083
2749
|
method: "POST",
|
|
1084
|
-
headers: { "Content-Type": "application/json" },
|
|
1085
|
-
body: JSON.stringify(requestBody)
|
|
2750
|
+
headers: { "Content-Type": "application/json", ...this.telemetryHeaders(telemetry) },
|
|
2751
|
+
body: JSON.stringify({ ...requestBody, auth: { accessToken: refreshToken } })
|
|
1086
2752
|
});
|
|
1087
2753
|
if (refreshResponse.ok) {
|
|
1088
2754
|
const refreshed = await refreshResponse.json();
|
|
2755
|
+
const refreshedExtensionPromise = this.fetchExtensionTokens(
|
|
2756
|
+
refreshed.intents.map((intent) => intent.intentOp),
|
|
2757
|
+
sponsorFlags
|
|
2758
|
+
);
|
|
2759
|
+
const extensionResult = await refreshedExtensionPromise;
|
|
2760
|
+
if (!extensionResult.ok) {
|
|
2761
|
+
console.error(
|
|
2762
|
+
"[SDK] Batch extension token refresh failed:",
|
|
2763
|
+
extensionResult.message
|
|
2764
|
+
);
|
|
2765
|
+
iframe.contentWindow?.postMessage({
|
|
2766
|
+
type: "PASSKEY_REFRESH_ERROR",
|
|
2767
|
+
error: "Failed to refresh batch quotes"
|
|
2768
|
+
}, dialogOrigin);
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
const refreshedAuth = {
|
|
2772
|
+
accessToken: refreshToken,
|
|
2773
|
+
extensionTokens: extensionResult.extensionTokens
|
|
2774
|
+
};
|
|
1089
2775
|
prepareResponse = refreshed;
|
|
2776
|
+
accessToken = refreshToken;
|
|
2777
|
+
requestBody.auth = { accessToken: refreshToken };
|
|
2778
|
+
batchExtensionTokensPromise = refreshedExtensionPromise;
|
|
2779
|
+
currentBatchPayload = {
|
|
2780
|
+
...currentBatchPayload,
|
|
2781
|
+
batchIntents: refreshed.intents,
|
|
2782
|
+
batchFailedIntents: refreshed.failedIntents,
|
|
2783
|
+
challenge: refreshed.challenge,
|
|
2784
|
+
expiresAt: refreshed.expiresAt,
|
|
2785
|
+
auth: refreshedAuth
|
|
2786
|
+
};
|
|
1090
2787
|
iframe.contentWindow?.postMessage({
|
|
1091
2788
|
type: "PASSKEY_REFRESH_COMPLETE",
|
|
1092
2789
|
batchIntents: refreshed.intents,
|
|
1093
2790
|
challenge: refreshed.challenge,
|
|
1094
|
-
expiresAt: refreshed.expiresAt
|
|
2791
|
+
expiresAt: refreshed.expiresAt,
|
|
2792
|
+
auth: refreshedAuth
|
|
1095
2793
|
}, dialogOrigin);
|
|
1096
2794
|
} else {
|
|
1097
2795
|
iframe.contentWindow?.postMessage({
|
|
@@ -1127,7 +2825,11 @@ var OneAuthClient = class {
|
|
|
1127
2825
|
}));
|
|
1128
2826
|
const allResults = [...results, ...prepareFailures].sort((a, b) => a.index - b.index);
|
|
1129
2827
|
const successCount = allResults.filter((r) => r.success).length;
|
|
1130
|
-
await this.
|
|
2828
|
+
await this.waitForDialogCloseUnlessBlind(blindSigning, dialog, cleanup);
|
|
2829
|
+
this.emitTelemetry("batch.sign.succeeded", telemetry, {
|
|
2830
|
+
outcome: "success",
|
|
2831
|
+
intentCount: allResults.length
|
|
2832
|
+
});
|
|
1131
2833
|
resolve({
|
|
1132
2834
|
success: successCount === allResults.length,
|
|
1133
2835
|
results: allResults,
|
|
@@ -1136,6 +2838,11 @@ var OneAuthClient = class {
|
|
|
1136
2838
|
});
|
|
1137
2839
|
} else {
|
|
1138
2840
|
cleanup();
|
|
2841
|
+
this.emitTelemetry("batch.sign.failed", telemetry, {
|
|
2842
|
+
outcome: "failure",
|
|
2843
|
+
errorCode: "SIGNING_FAILED",
|
|
2844
|
+
errorMessage: "Batch signing failed or was cancelled"
|
|
2845
|
+
});
|
|
1139
2846
|
resolve({
|
|
1140
2847
|
success: false,
|
|
1141
2848
|
results: [],
|
|
@@ -1147,6 +2854,11 @@ var OneAuthClient = class {
|
|
|
1147
2854
|
if (message?.type === "PASSKEY_CLOSE") {
|
|
1148
2855
|
window.removeEventListener("message", handleMessage);
|
|
1149
2856
|
cleanup();
|
|
2857
|
+
this.emitTelemetry("dialog.cancelled", telemetry, {
|
|
2858
|
+
outcome: "cancelled",
|
|
2859
|
+
errorCode: "USER_CANCELLED",
|
|
2860
|
+
errorMessage: "User closed the batch dialog"
|
|
2861
|
+
});
|
|
1150
2862
|
resolve({
|
|
1151
2863
|
success: false,
|
|
1152
2864
|
results: [],
|
|
@@ -1158,6 +2870,15 @@ var OneAuthClient = class {
|
|
|
1158
2870
|
window.addEventListener("message", handleMessage);
|
|
1159
2871
|
});
|
|
1160
2872
|
window.removeEventListener("message", handleBatchReReady);
|
|
2873
|
+
this.emitTelemetry("batch.completed", telemetry, {
|
|
2874
|
+
outcome: batchResult.success ? "success" : "failure",
|
|
2875
|
+
intentCount: batchResult.results.length,
|
|
2876
|
+
attributes: {
|
|
2877
|
+
successCount: batchResult.successCount,
|
|
2878
|
+
failureCount: batchResult.failureCount
|
|
2879
|
+
},
|
|
2880
|
+
...batchResult.error ? { errorMessage: batchResult.error } : {}
|
|
2881
|
+
});
|
|
1161
2882
|
return batchResult;
|
|
1162
2883
|
}
|
|
1163
2884
|
/**
|
|
@@ -1276,6 +2997,7 @@ var OneAuthClient = class {
|
|
|
1276
2997
|
signedHash: payload.signedHash
|
|
1277
2998
|
});
|
|
1278
2999
|
} else {
|
|
3000
|
+
cleanup();
|
|
1279
3001
|
resolve({
|
|
1280
3002
|
success: false,
|
|
1281
3003
|
error: message.error || {
|
|
@@ -1358,6 +3080,7 @@ var OneAuthClient = class {
|
|
|
1358
3080
|
signedHash: payload.signedHash
|
|
1359
3081
|
});
|
|
1360
3082
|
} else {
|
|
3083
|
+
cleanup();
|
|
1361
3084
|
resolve({
|
|
1362
3085
|
success: false,
|
|
1363
3086
|
error: message.error || {
|
|
@@ -1418,6 +3141,56 @@ var OneAuthClient = class {
|
|
|
1418
3141
|
dialog.addEventListener("close", handleClose);
|
|
1419
3142
|
});
|
|
1420
3143
|
}
|
|
3144
|
+
/**
|
|
3145
|
+
* Hidden blind-signing iframes have no user-visible Done or Close button.
|
|
3146
|
+
* Once the SDK has the result it needs, tear them down immediately instead
|
|
3147
|
+
* of waiting for an iframe close event that can never be clicked.
|
|
3148
|
+
*/
|
|
3149
|
+
async waitForDialogCloseUnlessBlind(blindSigning, dialog, cleanup) {
|
|
3150
|
+
if (blindSigning) {
|
|
3151
|
+
cleanup();
|
|
3152
|
+
return;
|
|
3153
|
+
}
|
|
3154
|
+
await this.waitForDialogClose(dialog, cleanup);
|
|
3155
|
+
}
|
|
3156
|
+
/**
|
|
3157
|
+
* After a prepare error has been surfaced in the dialog, wait for the user
|
|
3158
|
+
* to either close the dialog (give up) or click "Try Again", which the
|
|
3159
|
+
* dialog forwards as `PASSKEY_RETRY_PREPARE`.
|
|
3160
|
+
*
|
|
3161
|
+
* On `"closed"` the dialog has been cleaned up; on `"retry"` it stays open
|
|
3162
|
+
* (showing its loading skeleton) while the caller re-runs prepare. Without
|
|
3163
|
+
* this, "Try Again" after a prepare failure was a dead end: the dialog
|
|
3164
|
+
* reset its local state but the SDK had already given up, so the quote
|
|
3165
|
+
* never arrived and the Sign button stayed disabled forever.
|
|
3166
|
+
*/
|
|
3167
|
+
waitForPrepareRetryOrClose(dialog, cleanup) {
|
|
3168
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
3169
|
+
return new Promise((resolve) => {
|
|
3170
|
+
const stop = () => {
|
|
3171
|
+
window.removeEventListener("message", handleMessage);
|
|
3172
|
+
dialog.removeEventListener("close", handleClose);
|
|
3173
|
+
};
|
|
3174
|
+
const handleMessage = (event) => {
|
|
3175
|
+
if (event.origin !== dialogOrigin) return;
|
|
3176
|
+
if (event.data?.type === "PASSKEY_RETRY_PREPARE") {
|
|
3177
|
+
stop();
|
|
3178
|
+
resolve("retry");
|
|
3179
|
+
} else if (event.data?.type === "PASSKEY_CLOSE") {
|
|
3180
|
+
stop();
|
|
3181
|
+
cleanup();
|
|
3182
|
+
resolve("closed");
|
|
3183
|
+
}
|
|
3184
|
+
};
|
|
3185
|
+
const handleClose = () => {
|
|
3186
|
+
stop();
|
|
3187
|
+
cleanup();
|
|
3188
|
+
resolve("closed");
|
|
3189
|
+
};
|
|
3190
|
+
window.addEventListener("message", handleMessage);
|
|
3191
|
+
dialog.addEventListener("close", handleClose);
|
|
3192
|
+
});
|
|
3193
|
+
}
|
|
1421
3194
|
/**
|
|
1422
3195
|
* Inject a `<link rel="preconnect">` tag for the given URL's origin.
|
|
1423
3196
|
*
|
|
@@ -1463,7 +3236,7 @@ var OneAuthClient = class {
|
|
|
1463
3236
|
* @returns `{ ready: true, sendInit }` when the iframe is ready, or
|
|
1464
3237
|
* `{ ready: false }` if the dialog was closed or timed out first.
|
|
1465
3238
|
*/
|
|
1466
|
-
waitForDialogReadyDeferred(dialog, iframe, cleanup) {
|
|
3239
|
+
waitForDialogReadyDeferred(dialog, iframe, cleanup, options = {}) {
|
|
1467
3240
|
const dialogOrigin = this.getDialogOrigin();
|
|
1468
3241
|
return new Promise((resolve) => {
|
|
1469
3242
|
let settled = false;
|
|
@@ -1477,12 +3250,15 @@ var OneAuthClient = class {
|
|
|
1477
3250
|
const handleMessage = (event) => {
|
|
1478
3251
|
if (event.origin !== dialogOrigin) return;
|
|
1479
3252
|
if (event.data?.type === "PASSKEY_READY") {
|
|
3253
|
+
const blindSigning = options.blindSigning === true;
|
|
1480
3254
|
teardown();
|
|
1481
3255
|
resolve({
|
|
1482
3256
|
ready: true,
|
|
3257
|
+
blindSigning,
|
|
1483
3258
|
sendInit: (initMessage) => {
|
|
3259
|
+
if (blindSigning) iframe.focus({ preventScroll: true });
|
|
1484
3260
|
iframe.contentWindow?.postMessage(
|
|
1485
|
-
{ type: "PASSKEY_INIT", ...initMessage, fullViewport: true },
|
|
3261
|
+
{ type: "PASSKEY_INIT", ...initMessage, fullViewport: true, blindSigning },
|
|
1486
3262
|
dialogOrigin
|
|
1487
3263
|
);
|
|
1488
3264
|
}
|
|
@@ -1523,13 +3299,13 @@ var OneAuthClient = class {
|
|
|
1523
3299
|
* @param requestBody - Serialized intent options (bigint amounts converted to
|
|
1524
3300
|
* strings before this call).
|
|
1525
3301
|
* @returns On success: `{ success: true, data: PrepareIntentResponse, tier }`.
|
|
1526
|
-
* On failure: `{ success: false, error: { code, message } }`.
|
|
3302
|
+
* On failure: `{ success: false, error: { code, message, details? } }`.
|
|
1527
3303
|
*/
|
|
1528
|
-
async prepareIntent(requestBody) {
|
|
3304
|
+
async prepareIntent(requestBody, telemetry) {
|
|
1529
3305
|
try {
|
|
1530
3306
|
const response = await fetch(`${this.config.providerUrl}/api/intent/prepare`, {
|
|
1531
3307
|
method: "POST",
|
|
1532
|
-
headers: { "Content-Type": "application/json" },
|
|
3308
|
+
headers: { "Content-Type": "application/json", ...this.telemetryHeaders(telemetry) },
|
|
1533
3309
|
body: JSON.stringify(requestBody)
|
|
1534
3310
|
});
|
|
1535
3311
|
if (!response.ok) {
|
|
@@ -1542,7 +3318,8 @@ var OneAuthClient = class {
|
|
|
1542
3318
|
success: false,
|
|
1543
3319
|
error: {
|
|
1544
3320
|
code: errorMessage.includes("User not found") ? "USER_NOT_FOUND" : "PREPARE_FAILED",
|
|
1545
|
-
message: errorMessage
|
|
3321
|
+
message: errorMessage,
|
|
3322
|
+
details: errorData.details ?? errorData.candidateErrors ?? errorData
|
|
1546
3323
|
}
|
|
1547
3324
|
};
|
|
1548
3325
|
}
|
|
@@ -1575,11 +3352,11 @@ var OneAuthClient = class {
|
|
|
1575
3352
|
* @returns On success: `{ success: true, data: PrepareBatchIntentResponse, tier }`.
|
|
1576
3353
|
* On failure: `{ success: false, error, failedIntents? }`.
|
|
1577
3354
|
*/
|
|
1578
|
-
async prepareBatchIntent(requestBody) {
|
|
3355
|
+
async prepareBatchIntent(requestBody, telemetry) {
|
|
1579
3356
|
try {
|
|
1580
3357
|
const response = await fetch(`${this.config.providerUrl}/api/intent/batch-prepare`, {
|
|
1581
3358
|
method: "POST",
|
|
1582
|
-
headers: { "Content-Type": "application/json" },
|
|
3359
|
+
headers: { "Content-Type": "application/json", ...this.telemetryHeaders(telemetry) },
|
|
1583
3360
|
body: JSON.stringify(requestBody)
|
|
1584
3361
|
});
|
|
1585
3362
|
if (!response.ok) {
|
|
@@ -1625,15 +3402,28 @@ var OneAuthClient = class {
|
|
|
1625
3402
|
* that hasn't completed yet.
|
|
1626
3403
|
*/
|
|
1627
3404
|
async getIntentStatus(intentId) {
|
|
3405
|
+
const telemetry = this.createTelemetryOperation("status");
|
|
3406
|
+
this.emitTelemetry("intent.status.started", telemetry, {
|
|
3407
|
+
outcome: "started",
|
|
3408
|
+
attributes: { directStatusCheck: true }
|
|
3409
|
+
});
|
|
1628
3410
|
try {
|
|
1629
3411
|
const response = await fetch(
|
|
1630
3412
|
`${this.config.providerUrl}/api/intent/status/${intentId}`,
|
|
1631
3413
|
{
|
|
1632
|
-
headers:
|
|
3414
|
+
headers: {
|
|
3415
|
+
...this.telemetryHeaders(telemetry),
|
|
3416
|
+
...this.config.clientId ? { "x-client-id": this.config.clientId } : {}
|
|
3417
|
+
}
|
|
1633
3418
|
}
|
|
1634
3419
|
);
|
|
1635
3420
|
if (!response.ok) {
|
|
1636
3421
|
const errorData = await response.json().catch(() => ({}));
|
|
3422
|
+
this.emitTelemetry("status.failed", telemetry, {
|
|
3423
|
+
outcome: "failure",
|
|
3424
|
+
errorCode: "STATUS_FAILED",
|
|
3425
|
+
errorMessage: errorData.error || "Failed to get intent status"
|
|
3426
|
+
});
|
|
1637
3427
|
return {
|
|
1638
3428
|
success: false,
|
|
1639
3429
|
intentId,
|
|
@@ -1645,6 +3435,10 @@ var OneAuthClient = class {
|
|
|
1645
3435
|
};
|
|
1646
3436
|
}
|
|
1647
3437
|
const data = await response.json();
|
|
3438
|
+
this.emitTelemetry("status.succeeded", telemetry, {
|
|
3439
|
+
outcome: data.status === "completed" ? "success" : "started",
|
|
3440
|
+
status: data.status
|
|
3441
|
+
});
|
|
1648
3442
|
return {
|
|
1649
3443
|
success: data.status === "completed",
|
|
1650
3444
|
intentId,
|
|
@@ -1653,6 +3447,10 @@ var OneAuthClient = class {
|
|
|
1653
3447
|
operationId: data.operationId
|
|
1654
3448
|
};
|
|
1655
3449
|
} catch (error) {
|
|
3450
|
+
this.emitTelemetry("status.failed", telemetry, {
|
|
3451
|
+
outcome: "failure",
|
|
3452
|
+
...describeTelemetryError(error)
|
|
3453
|
+
});
|
|
1656
3454
|
return {
|
|
1657
3455
|
success: false,
|
|
1658
3456
|
intentId,
|
|
@@ -1684,6 +3482,9 @@ var OneAuthClient = class {
|
|
|
1684
3482
|
* ```
|
|
1685
3483
|
*/
|
|
1686
3484
|
async getIntentHistory(options) {
|
|
3485
|
+
const telemetry = this.createTelemetryOperation("history", {
|
|
3486
|
+
hasStatusFilter: !!options?.status
|
|
3487
|
+
});
|
|
1687
3488
|
const queryParams = new URLSearchParams();
|
|
1688
3489
|
if (options?.limit) queryParams.set("limit", String(options.limit));
|
|
1689
3490
|
if (options?.offset) queryParams.set("offset", String(options.offset));
|
|
@@ -1692,184 +3493,183 @@ var OneAuthClient = class {
|
|
|
1692
3493
|
if (options?.to) queryParams.set("to", options.to);
|
|
1693
3494
|
const url = `${this.config.providerUrl}/api/intent/history${queryParams.toString() ? `?${queryParams}` : ""}`;
|
|
1694
3495
|
const response = await fetch(url, {
|
|
1695
|
-
headers:
|
|
3496
|
+
headers: {
|
|
3497
|
+
...this.telemetryHeaders(telemetry),
|
|
3498
|
+
...this.config.clientId ? { "x-client-id": this.config.clientId } : {}
|
|
3499
|
+
},
|
|
1696
3500
|
credentials: "include"
|
|
1697
3501
|
});
|
|
1698
3502
|
if (!response.ok) {
|
|
1699
3503
|
const errorData = await response.json().catch(() => ({}));
|
|
3504
|
+
this.emitTelemetry("history.failed", telemetry, {
|
|
3505
|
+
outcome: "failure",
|
|
3506
|
+
errorCode: "HISTORY_FAILED",
|
|
3507
|
+
errorMessage: errorData.error || "Failed to get intent history"
|
|
3508
|
+
});
|
|
1700
3509
|
throw new Error(errorData.error || "Failed to get intent history");
|
|
1701
3510
|
}
|
|
1702
|
-
|
|
3511
|
+
const result = await response.json();
|
|
3512
|
+
this.emitTelemetry("history.succeeded", telemetry, {
|
|
3513
|
+
outcome: "success",
|
|
3514
|
+
attributes: {
|
|
3515
|
+
total: result.total,
|
|
3516
|
+
returned: result.intents?.length ?? 0
|
|
3517
|
+
}
|
|
3518
|
+
});
|
|
3519
|
+
return result;
|
|
1703
3520
|
}
|
|
1704
3521
|
/**
|
|
1705
|
-
*
|
|
1706
|
-
*
|
|
1707
|
-
*
|
|
1708
|
-
*
|
|
1709
|
-
*
|
|
1710
|
-
* 3. The orchestrator's solver network finds the best route
|
|
1711
|
-
* 4. Executes via the standard intent flow
|
|
1712
|
-
*
|
|
1713
|
-
* NOTE: The `amount` parameter specifies the OUTPUT amount (what the user wants to receive),
|
|
1714
|
-
* not the input amount. The orchestrator will calculate the required input from sourceAssets.
|
|
1715
|
-
*
|
|
1716
|
-
* @example
|
|
1717
|
-
* ```typescript
|
|
1718
|
-
* // Buy 100 USDC using ETH on Base
|
|
1719
|
-
* const result = await client.sendSwap({
|
|
1720
|
-
* username: 'alice',
|
|
1721
|
-
* targetChain: 8453,
|
|
1722
|
-
* fromToken: 'ETH',
|
|
1723
|
-
* toToken: 'USDC',
|
|
1724
|
-
* amount: '100', // Receive 100 USDC
|
|
1725
|
-
* });
|
|
1726
|
-
*
|
|
1727
|
-
* // Cross-chain: Buy 50 USDC on Base, paying with ETH from any chain
|
|
1728
|
-
* const result = await client.sendSwap({
|
|
1729
|
-
* username: 'alice',
|
|
1730
|
-
* targetChain: 8453, // Base
|
|
1731
|
-
* fromToken: 'ETH',
|
|
1732
|
-
* toToken: 'USDC',
|
|
1733
|
-
* amount: '50', // Receive 50 USDC
|
|
1734
|
-
* });
|
|
1735
|
-
* ```
|
|
3522
|
+
* Forward a raw EIP-1193 request to the iframe so the active EOA connector
|
|
3523
|
+
* can handle it (wallet-connect session or injected wallet). Opens
|
|
3524
|
+
* `/dialog/sign-eoa`, which looks up the wagmi connector and delegates to
|
|
3525
|
+
* `connector.getProvider().request({ method, params })`. The wallet's own
|
|
3526
|
+
* UI is the review screen; the dialog just transports the request.
|
|
1736
3527
|
*/
|
|
1737
|
-
async
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
try {
|
|
1753
|
-
const address = resolveTokenAddress(token, options.targetChain);
|
|
1754
|
-
if (!isTokenAddressSupported(address, options.targetChain)) {
|
|
1755
|
-
return {
|
|
1756
|
-
error: `Unsupported ${label}: ${token} on chain ${options.targetChain}`
|
|
1757
|
-
};
|
|
1758
|
-
}
|
|
1759
|
-
return { address };
|
|
1760
|
-
} catch (error) {
|
|
1761
|
-
return {
|
|
1762
|
-
error: error instanceof Error ? error.message : `Unsupported ${label}: ${token} on chain ${options.targetChain}`
|
|
1763
|
-
};
|
|
1764
|
-
}
|
|
1765
|
-
};
|
|
1766
|
-
let fromTokenAddress;
|
|
1767
|
-
if (options.fromToken) {
|
|
1768
|
-
const fromTokenResult = resolveToken(options.fromToken, "fromToken");
|
|
1769
|
-
if (!fromTokenResult.address) {
|
|
1770
|
-
return {
|
|
1771
|
-
success: false,
|
|
1772
|
-
intentId: "",
|
|
1773
|
-
status: "failed",
|
|
1774
|
-
error: {
|
|
1775
|
-
code: "INVALID_TOKEN",
|
|
1776
|
-
message: fromTokenResult.error || `Unknown fromToken: ${options.fromToken}`
|
|
1777
|
-
}
|
|
1778
|
-
};
|
|
1779
|
-
}
|
|
1780
|
-
fromTokenAddress = fromTokenResult.address;
|
|
1781
|
-
}
|
|
1782
|
-
const toTokenResult = resolveToken(options.toToken, "toToken");
|
|
1783
|
-
if (!toTokenResult.address) {
|
|
3528
|
+
async requestWithWallet(options) {
|
|
3529
|
+
const job = this.eoaSerialQueue.then(
|
|
3530
|
+
() => this.doRequestWithWallet(options),
|
|
3531
|
+
() => this.doRequestWithWallet(options)
|
|
3532
|
+
);
|
|
3533
|
+
this.eoaSerialQueue = job.then(
|
|
3534
|
+
() => void 0,
|
|
3535
|
+
() => void 0
|
|
3536
|
+
);
|
|
3537
|
+
return job;
|
|
3538
|
+
}
|
|
3539
|
+
async doRequestWithWallet(options) {
|
|
3540
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
3541
|
+
const ensureResult = await this.ensureEoaDialog(options?.theme);
|
|
3542
|
+
if (!ensureResult.ok) {
|
|
1784
3543
|
return {
|
|
1785
3544
|
success: false,
|
|
1786
|
-
|
|
1787
|
-
status: "failed",
|
|
1788
|
-
error: {
|
|
1789
|
-
code: "INVALID_TOKEN",
|
|
1790
|
-
message: toTokenResult.error || `Unknown toToken: ${options.toToken}`
|
|
1791
|
-
}
|
|
3545
|
+
error: { code: "USER_REJECTED", message: "User closed the dialog" }
|
|
1792
3546
|
};
|
|
1793
3547
|
}
|
|
1794
|
-
const
|
|
1795
|
-
const
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
}
|
|
1799
|
-
try {
|
|
1800
|
-
return getTokenSymbol(token, options.targetChain);
|
|
1801
|
-
} catch {
|
|
1802
|
-
return fallback;
|
|
1803
|
-
}
|
|
3548
|
+
const { dialog, iframe } = ensureResult;
|
|
3549
|
+
const requestId = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
3550
|
+
const hideDialog = () => {
|
|
3551
|
+
if (dialog.open) dialog.close();
|
|
1804
3552
|
};
|
|
1805
|
-
const
|
|
1806
|
-
|
|
1807
|
-
|
|
3553
|
+
const responsePromise = this.waitForEoaResponse(
|
|
3554
|
+
iframe,
|
|
3555
|
+
hideDialog,
|
|
3556
|
+
requestId,
|
|
3557
|
+
dialogOrigin
|
|
1808
3558
|
);
|
|
1809
|
-
const
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
USDT: 6,
|
|
1816
|
-
USDT0: 6
|
|
3559
|
+
const initPayload = {
|
|
3560
|
+
mode: "iframe",
|
|
3561
|
+
method: options.method,
|
|
3562
|
+
params: options.params,
|
|
3563
|
+
expectedAddress: options.expectedAddress,
|
|
3564
|
+
requestId
|
|
1817
3565
|
};
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
3566
|
+
iframe.contentWindow?.postMessage(
|
|
3567
|
+
{ type: "PASSKEY_INIT", ...initPayload, fullViewport: true },
|
|
3568
|
+
dialogOrigin
|
|
3569
|
+
);
|
|
3570
|
+
const handleReReady = (event) => {
|
|
3571
|
+
if (event.origin !== dialogOrigin) return;
|
|
3572
|
+
if (event.source !== iframe.contentWindow) return;
|
|
3573
|
+
if (event.data?.type === "PASSKEY_READY") {
|
|
3574
|
+
iframe.contentWindow?.postMessage(
|
|
3575
|
+
{ type: "PASSKEY_INIT", ...initPayload, fullViewport: true },
|
|
3576
|
+
dialogOrigin
|
|
1822
3577
|
);
|
|
1823
|
-
if (match) return match.decimals;
|
|
1824
|
-
return getTokenDecimals(symbol, chainId);
|
|
1825
|
-
} catch {
|
|
1826
|
-
const upperSymbol = symbol.toUpperCase();
|
|
1827
|
-
return KNOWN_DECIMALS[upperSymbol] ?? 18;
|
|
1828
3578
|
}
|
|
1829
3579
|
};
|
|
1830
|
-
|
|
1831
|
-
const
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
3580
|
+
window.addEventListener("message", handleReReady);
|
|
3581
|
+
const result = await responsePromise;
|
|
3582
|
+
window.removeEventListener("message", handleReReady);
|
|
3583
|
+
hideDialog();
|
|
3584
|
+
return result;
|
|
3585
|
+
}
|
|
3586
|
+
/**
|
|
3587
|
+
* Lazily create the persistent `/dialog/sign-eoa` iframe and reveal it.
|
|
3588
|
+
* Subsequent calls just re-open the existing `<dialog>` so the iframe's
|
|
3589
|
+
* in-page state (notably the WalletConnect SignClient) is preserved
|
|
3590
|
+
* between transactions.
|
|
3591
|
+
*/
|
|
3592
|
+
async ensureEoaDialog(theme) {
|
|
3593
|
+
if (this.eoaDialogState) {
|
|
3594
|
+
const { dialog: dialog2, iframe: iframe2 } = this.eoaDialogState;
|
|
3595
|
+
if (!dialog2.open) {
|
|
3596
|
+
dialog2.showModal();
|
|
3597
|
+
}
|
|
3598
|
+
return { ok: true, dialog: dialog2, iframe: iframe2 };
|
|
3599
|
+
}
|
|
3600
|
+
const dialogUrl = this.getDialogUrl();
|
|
3601
|
+
const themeParams = this.getThemeParams(theme);
|
|
3602
|
+
const parentOrigin = typeof window !== "undefined" ? window.location.origin : "";
|
|
3603
|
+
const parentOriginParam = parentOrigin ? `&parentOrigin=${encodeURIComponent(parentOrigin)}` : "";
|
|
3604
|
+
const signingUrl = `${dialogUrl}/dialog/sign-eoa?mode=iframe${parentOriginParam}${themeParams ? `&${themeParams}` : ""}`;
|
|
3605
|
+
const { dialog, iframe, destroy } = this.createModalDialog(signingUrl, {
|
|
3606
|
+
persistent: true
|
|
3607
|
+
});
|
|
3608
|
+
const ready = await this.waitForEoaIframeReady(iframe);
|
|
3609
|
+
if (!ready) {
|
|
3610
|
+
destroy();
|
|
3611
|
+
return { ok: false };
|
|
3612
|
+
}
|
|
3613
|
+
this.eoaDialogState = { dialog, iframe };
|
|
3614
|
+
return { ok: true, dialog, iframe };
|
|
3615
|
+
}
|
|
3616
|
+
waitForEoaIframeReady(iframe) {
|
|
3617
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
3618
|
+
return new Promise((resolve) => {
|
|
3619
|
+
let timeoutId = null;
|
|
3620
|
+
const cleanup = () => {
|
|
3621
|
+
if (timeoutId !== null) clearTimeout(timeoutId);
|
|
3622
|
+
window.removeEventListener("message", handler);
|
|
3623
|
+
};
|
|
3624
|
+
const handler = (event) => {
|
|
3625
|
+
if (event.origin !== dialogOrigin) return;
|
|
3626
|
+
if (event.source !== iframe.contentWindow) return;
|
|
3627
|
+
if (event.data?.type === "PASSKEY_READY") {
|
|
3628
|
+
cleanup();
|
|
3629
|
+
resolve(true);
|
|
3630
|
+
}
|
|
3631
|
+
};
|
|
3632
|
+
window.addEventListener("message", handler);
|
|
3633
|
+
timeoutId = setTimeout(() => {
|
|
3634
|
+
cleanup();
|
|
3635
|
+
resolve(false);
|
|
3636
|
+
}, 1e4);
|
|
3637
|
+
});
|
|
3638
|
+
}
|
|
3639
|
+
waitForEoaResponse(iframe, cleanup, requestId, dialogOrigin) {
|
|
3640
|
+
return new Promise((resolve) => {
|
|
3641
|
+
const handleMessage = (event) => {
|
|
3642
|
+
if (event.origin !== dialogOrigin) return;
|
|
3643
|
+
if (event.source !== iframe.contentWindow) return;
|
|
3644
|
+
const message = event.data;
|
|
3645
|
+
if (message?.type === "PASSKEY_EOA_RESULT" || message?.type === "PASSKEY_EOA_CLOSE") {
|
|
3646
|
+
if (message.requestId !== requestId) return;
|
|
3647
|
+
} else {
|
|
3648
|
+
return;
|
|
3649
|
+
}
|
|
3650
|
+
window.removeEventListener("message", handleMessage);
|
|
3651
|
+
if (message.type === "PASSKEY_EOA_CLOSE") {
|
|
3652
|
+
cleanup();
|
|
3653
|
+
resolve({
|
|
3654
|
+
success: false,
|
|
3655
|
+
error: { code: "USER_REJECTED", message: "User closed the dialog" }
|
|
3656
|
+
});
|
|
3657
|
+
return;
|
|
3658
|
+
}
|
|
3659
|
+
if (message.success) {
|
|
3660
|
+
resolve({ success: true, result: message.data?.result });
|
|
3661
|
+
} else {
|
|
3662
|
+
resolve({
|
|
3663
|
+
success: false,
|
|
3664
|
+
error: message.error || {
|
|
3665
|
+
code: "EOA_REQUEST_FAILED",
|
|
3666
|
+
message: "Wallet request failed"
|
|
3667
|
+
}
|
|
3668
|
+
});
|
|
3669
|
+
}
|
|
3670
|
+
};
|
|
3671
|
+
window.addEventListener("message", handleMessage);
|
|
1861
3672
|
});
|
|
1862
|
-
return {
|
|
1863
|
-
...result,
|
|
1864
|
-
quote: result.success ? {
|
|
1865
|
-
fromToken: fromTokenAddress ?? options.fromToken,
|
|
1866
|
-
toToken: toTokenAddress,
|
|
1867
|
-
amountIn: options.amount,
|
|
1868
|
-
amountOut: "",
|
|
1869
|
-
// Filled by orchestrator quote
|
|
1870
|
-
rate: ""
|
|
1871
|
-
} : void 0
|
|
1872
|
-
};
|
|
1873
3673
|
}
|
|
1874
3674
|
/**
|
|
1875
3675
|
* Sign an arbitrary message with the user's passkey
|
|
@@ -1900,12 +3700,37 @@ var OneAuthClient = class {
|
|
|
1900
3700
|
* ```
|
|
1901
3701
|
*/
|
|
1902
3702
|
async signMessage(options) {
|
|
3703
|
+
const telemetry = this.createTelemetryOperation("sign", {
|
|
3704
|
+
signingMode: "message",
|
|
3705
|
+
hasAccountAddress: !!options.accountAddress
|
|
3706
|
+
});
|
|
1903
3707
|
const dialogUrl = this.getDialogUrl();
|
|
3708
|
+
const params = new URLSearchParams({ mode: "iframe" });
|
|
3709
|
+
this.appendTelemetryParams(params, telemetry);
|
|
1904
3710
|
const themeParams = this.getThemeParams(options?.theme);
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
3711
|
+
if (themeParams) {
|
|
3712
|
+
const themeParsed = new URLSearchParams(themeParams);
|
|
3713
|
+
themeParsed.forEach((value, key) => params.set(key, value));
|
|
3714
|
+
}
|
|
3715
|
+
const signingUrl = `${dialogUrl}/dialog/sign?${params.toString()}`;
|
|
3716
|
+
const requestedBlindSigning = this.shouldRequestBlindSigning(options);
|
|
3717
|
+
const { dialog, iframe, cleanup, reveal } = this.createModalDialog(signingUrl, {
|
|
3718
|
+
hidden: requestedBlindSigning
|
|
3719
|
+
});
|
|
3720
|
+
this.emitTelemetry("dialog.opened", telemetry, {
|
|
3721
|
+
outcome: "started",
|
|
3722
|
+
attributes: { blindSigning: requestedBlindSigning }
|
|
3723
|
+
});
|
|
3724
|
+
const dialogResult = await this.waitForDialogReadyDeferred(dialog, iframe, cleanup, {
|
|
3725
|
+
blindSigning: requestedBlindSigning,
|
|
3726
|
+
reveal
|
|
3727
|
+
});
|
|
1908
3728
|
if (!dialogResult.ready) {
|
|
3729
|
+
this.emitTelemetry("dialog.cancelled", telemetry, {
|
|
3730
|
+
outcome: "cancelled",
|
|
3731
|
+
errorCode: "USER_REJECTED",
|
|
3732
|
+
errorMessage: "User closed the dialog before signing"
|
|
3733
|
+
});
|
|
1909
3734
|
return {
|
|
1910
3735
|
success: false,
|
|
1911
3736
|
error: {
|
|
@@ -1921,15 +3746,18 @@ var OneAuthClient = class {
|
|
|
1921
3746
|
username: options.username,
|
|
1922
3747
|
accountAddress: options.accountAddress,
|
|
1923
3748
|
description: options.description,
|
|
1924
|
-
metadata: options.metadata
|
|
3749
|
+
metadata: options.metadata,
|
|
3750
|
+
telemetry: this.telemetryPayload(telemetry)
|
|
1925
3751
|
};
|
|
1926
3752
|
dialogResult.sendInit(initPayload);
|
|
3753
|
+
const blindSigning = dialogResult.blindSigning;
|
|
3754
|
+
this.emitTelemetry("dialog.ready", telemetry, { outcome: "started" });
|
|
1927
3755
|
const dialogOrigin = this.getDialogOrigin();
|
|
1928
3756
|
const handleReReady = (event) => {
|
|
1929
3757
|
if (event.origin !== dialogOrigin) return;
|
|
1930
3758
|
if (event.data?.type === "PASSKEY_READY") {
|
|
1931
3759
|
iframe.contentWindow?.postMessage(
|
|
1932
|
-
{ type: "PASSKEY_INIT", ...initPayload, fullViewport: true },
|
|
3760
|
+
{ type: "PASSKEY_INIT", ...initPayload, fullViewport: true, blindSigning },
|
|
1933
3761
|
dialogOrigin
|
|
1934
3762
|
);
|
|
1935
3763
|
}
|
|
@@ -1939,6 +3767,10 @@ var OneAuthClient = class {
|
|
|
1939
3767
|
window.removeEventListener("message", handleReReady);
|
|
1940
3768
|
cleanup();
|
|
1941
3769
|
if (signingResult.success) {
|
|
3770
|
+
this.emitTelemetry("sign.succeeded", telemetry, {
|
|
3771
|
+
outcome: "success",
|
|
3772
|
+
attributes: { signingMode: "message" }
|
|
3773
|
+
});
|
|
1942
3774
|
return {
|
|
1943
3775
|
success: true,
|
|
1944
3776
|
signature: signingResult.signature,
|
|
@@ -1947,6 +3779,15 @@ var OneAuthClient = class {
|
|
|
1947
3779
|
passkey: signingResult.passkey
|
|
1948
3780
|
};
|
|
1949
3781
|
}
|
|
3782
|
+
this.emitTelemetry(
|
|
3783
|
+
signingResult.error?.code === "USER_REJECTED" ? "dialog.cancelled" : "sign.failed",
|
|
3784
|
+
telemetry,
|
|
3785
|
+
{
|
|
3786
|
+
outcome: signingResult.error?.code === "USER_REJECTED" ? "cancelled" : "failure",
|
|
3787
|
+
...describeTelemetryError(signingResult.error),
|
|
3788
|
+
attributes: { signingMode: "message" }
|
|
3789
|
+
}
|
|
3790
|
+
);
|
|
1950
3791
|
return {
|
|
1951
3792
|
success: false,
|
|
1952
3793
|
error: signingResult.error
|
|
@@ -1994,6 +3835,11 @@ var OneAuthClient = class {
|
|
|
1994
3835
|
* ```
|
|
1995
3836
|
*/
|
|
1996
3837
|
async signTypedData(options) {
|
|
3838
|
+
const telemetry = this.createTelemetryOperation("sign", {
|
|
3839
|
+
signingMode: "typedData",
|
|
3840
|
+
primaryType: options.primaryType,
|
|
3841
|
+
hasAccountAddress: !!options.accountAddress
|
|
3842
|
+
});
|
|
1997
3843
|
const signedHash = hashTypedData({
|
|
1998
3844
|
domain: options.domain,
|
|
1999
3845
|
types: options.types,
|
|
@@ -2001,11 +3847,32 @@ var OneAuthClient = class {
|
|
|
2001
3847
|
message: options.message
|
|
2002
3848
|
});
|
|
2003
3849
|
const dialogUrl = this.getDialogUrl();
|
|
3850
|
+
const params = new URLSearchParams({ mode: "iframe" });
|
|
3851
|
+
this.appendTelemetryParams(params, telemetry);
|
|
2004
3852
|
const themeParams = this.getThemeParams(options?.theme);
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
3853
|
+
if (themeParams) {
|
|
3854
|
+
const themeParsed = new URLSearchParams(themeParams);
|
|
3855
|
+
themeParsed.forEach((value, key) => params.set(key, value));
|
|
3856
|
+
}
|
|
3857
|
+
const signingUrl = `${dialogUrl}/dialog/sign?${params.toString()}`;
|
|
3858
|
+
const requestedBlindSigning = this.shouldRequestBlindSigning(options);
|
|
3859
|
+
const { dialog, iframe, cleanup, reveal } = this.createModalDialog(signingUrl, {
|
|
3860
|
+
hidden: requestedBlindSigning
|
|
3861
|
+
});
|
|
3862
|
+
this.emitTelemetry("dialog.opened", telemetry, {
|
|
3863
|
+
outcome: "started",
|
|
3864
|
+
attributes: { blindSigning: requestedBlindSigning }
|
|
3865
|
+
});
|
|
3866
|
+
const dialogResult = await this.waitForDialogReadyDeferred(dialog, iframe, cleanup, {
|
|
3867
|
+
blindSigning: requestedBlindSigning,
|
|
3868
|
+
reveal
|
|
3869
|
+
});
|
|
2008
3870
|
if (!dialogResult.ready) {
|
|
3871
|
+
this.emitTelemetry("dialog.cancelled", telemetry, {
|
|
3872
|
+
outcome: "cancelled",
|
|
3873
|
+
errorCode: "USER_REJECTED",
|
|
3874
|
+
errorMessage: "User closed the dialog before signing"
|
|
3875
|
+
});
|
|
2009
3876
|
return {
|
|
2010
3877
|
success: false,
|
|
2011
3878
|
error: {
|
|
@@ -2026,15 +3893,18 @@ var OneAuthClient = class {
|
|
|
2026
3893
|
challenge: signedHash,
|
|
2027
3894
|
username: options.username,
|
|
2028
3895
|
accountAddress: options.accountAddress,
|
|
2029
|
-
description: options.description
|
|
3896
|
+
description: options.description,
|
|
3897
|
+
telemetry: this.telemetryPayload(telemetry)
|
|
2030
3898
|
};
|
|
2031
3899
|
dialogResult.sendInit(initPayload);
|
|
3900
|
+
const blindSigning = dialogResult.blindSigning;
|
|
3901
|
+
this.emitTelemetry("dialog.ready", telemetry, { outcome: "started" });
|
|
2032
3902
|
const dialogOrigin = this.getDialogOrigin();
|
|
2033
3903
|
const handleReReady = (event) => {
|
|
2034
3904
|
if (event.origin !== dialogOrigin) return;
|
|
2035
3905
|
if (event.data?.type === "PASSKEY_READY") {
|
|
2036
3906
|
iframe.contentWindow?.postMessage(
|
|
2037
|
-
{ type: "PASSKEY_INIT", ...initPayload, fullViewport: true },
|
|
3907
|
+
{ type: "PASSKEY_INIT", ...initPayload, fullViewport: true, blindSigning },
|
|
2038
3908
|
dialogOrigin
|
|
2039
3909
|
);
|
|
2040
3910
|
}
|
|
@@ -2044,6 +3914,10 @@ var OneAuthClient = class {
|
|
|
2044
3914
|
window.removeEventListener("message", handleReReady);
|
|
2045
3915
|
cleanup();
|
|
2046
3916
|
if (signingResult.success) {
|
|
3917
|
+
this.emitTelemetry("sign.succeeded", telemetry, {
|
|
3918
|
+
outcome: "success",
|
|
3919
|
+
attributes: { signingMode: "typedData", primaryType: options.primaryType }
|
|
3920
|
+
});
|
|
2047
3921
|
return {
|
|
2048
3922
|
success: true,
|
|
2049
3923
|
signature: signingResult.signature,
|
|
@@ -2051,6 +3925,15 @@ var OneAuthClient = class {
|
|
|
2051
3925
|
passkey: signingResult.passkey
|
|
2052
3926
|
};
|
|
2053
3927
|
}
|
|
3928
|
+
this.emitTelemetry(
|
|
3929
|
+
signingResult.error?.code === "USER_REJECTED" ? "dialog.cancelled" : "sign.failed",
|
|
3930
|
+
telemetry,
|
|
3931
|
+
{
|
|
3932
|
+
outcome: signingResult.error?.code === "USER_REJECTED" ? "cancelled" : "failure",
|
|
3933
|
+
...describeTelemetryError(signingResult.error),
|
|
3934
|
+
attributes: { signingMode: "typedData", primaryType: options.primaryType }
|
|
3935
|
+
}
|
|
3936
|
+
);
|
|
2054
3937
|
return {
|
|
2055
3938
|
success: false,
|
|
2056
3939
|
error: signingResult.error
|
|
@@ -2122,8 +4005,66 @@ var OneAuthClient = class {
|
|
|
2122
4005
|
options.onReady?.();
|
|
2123
4006
|
};
|
|
2124
4007
|
options.container.appendChild(iframe);
|
|
4008
|
+
this.wireViewportInfo(iframe);
|
|
4009
|
+
const host = new URL(dialogUrl);
|
|
4010
|
+
const onResize = (event) => {
|
|
4011
|
+
if (event.source !== iframe.contentWindow) return;
|
|
4012
|
+
if (event.origin !== host.origin) return;
|
|
4013
|
+
if (event.data?.type !== "PASSKEY_RESIZE") return;
|
|
4014
|
+
const h = Number(event.data.height);
|
|
4015
|
+
if (Number.isFinite(h) && h > 0) {
|
|
4016
|
+
iframe.style.height = `${h}px`;
|
|
4017
|
+
}
|
|
4018
|
+
};
|
|
4019
|
+
window.addEventListener("message", onResize);
|
|
2125
4020
|
return iframe;
|
|
2126
4021
|
}
|
|
4022
|
+
/**
|
|
4023
|
+
* Post `PASSKEY_VIEWPORT_INFO` with the parent's
|
|
4024
|
+
* `window.innerHeight` to the iframe whenever it signals
|
|
4025
|
+
* `PASSKEY_READY`, and re-post on `window.resize` (rAF-debounced)
|
|
4026
|
+
* so the dialog's internal max-height cap follows the user's
|
|
4027
|
+
* actual screen.
|
|
4028
|
+
*
|
|
4029
|
+
* Inside the iframe, `vh`/`dvh` units refer to the iframe's own
|
|
4030
|
+
* height (recursive when the iframe auto-sizes), so the parent is
|
|
4031
|
+
* the only reliable source of "what 75% of the user's screen is".
|
|
4032
|
+
* The dialog stores the value in `dialog-context` and falls back
|
|
4033
|
+
* to auto-grow when the value is absent — so it's safe to call
|
|
4034
|
+
* this even for embedders that don't actually cap (modal mode
|
|
4035
|
+
* uses `fullViewport` which has its own outer cap).
|
|
4036
|
+
*/
|
|
4037
|
+
wireViewportInfo(iframe) {
|
|
4038
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
4039
|
+
const post = () => {
|
|
4040
|
+
iframe.contentWindow?.postMessage(
|
|
4041
|
+
{ type: "PASSKEY_VIEWPORT_INFO", height: window.innerHeight },
|
|
4042
|
+
dialogOrigin
|
|
4043
|
+
);
|
|
4044
|
+
};
|
|
4045
|
+
const handleMessage = (event) => {
|
|
4046
|
+
if (event.origin !== dialogOrigin) return;
|
|
4047
|
+
if (event.source !== iframe.contentWindow) return;
|
|
4048
|
+
if (event.data?.type === "PASSKEY_READY") {
|
|
4049
|
+
post();
|
|
4050
|
+
}
|
|
4051
|
+
};
|
|
4052
|
+
window.addEventListener("message", handleMessage);
|
|
4053
|
+
let raf = null;
|
|
4054
|
+
const onResize = () => {
|
|
4055
|
+
if (raf !== null) return;
|
|
4056
|
+
raf = requestAnimationFrame(() => {
|
|
4057
|
+
raf = null;
|
|
4058
|
+
post();
|
|
4059
|
+
});
|
|
4060
|
+
};
|
|
4061
|
+
window.addEventListener("resize", onResize);
|
|
4062
|
+
return () => {
|
|
4063
|
+
window.removeEventListener("message", handleMessage);
|
|
4064
|
+
window.removeEventListener("resize", onResize);
|
|
4065
|
+
if (raf !== null) cancelAnimationFrame(raf);
|
|
4066
|
+
};
|
|
4067
|
+
}
|
|
2127
4068
|
/**
|
|
2128
4069
|
* Listen for a signing result from an embedded (inline) signing iframe.
|
|
2129
4070
|
*
|
|
@@ -2258,6 +4199,7 @@ var OneAuthClient = class {
|
|
|
2258
4199
|
body: JSON.stringify({
|
|
2259
4200
|
...this.config.clientId && { clientId: this.config.clientId },
|
|
2260
4201
|
username: options.username,
|
|
4202
|
+
accountAddress: options.accountAddress,
|
|
2261
4203
|
challenge: options.challenge,
|
|
2262
4204
|
description: options.description,
|
|
2263
4205
|
metadata: options.metadata,
|
|
@@ -2313,7 +4255,7 @@ var OneAuthClient = class {
|
|
|
2313
4255
|
* @returns `true` if the dialog is ready and init was sent; `false` if the
|
|
2314
4256
|
* dialog was closed or timed out before becoming ready.
|
|
2315
4257
|
*/
|
|
2316
|
-
waitForDialogReady(dialog, iframe, cleanup, initMessage) {
|
|
4258
|
+
waitForDialogReady(dialog, iframe, cleanup, initMessage, options = {}) {
|
|
2317
4259
|
const dialogOrigin = this.getDialogOrigin();
|
|
2318
4260
|
return new Promise((resolve) => {
|
|
2319
4261
|
let settled = false;
|
|
@@ -2327,11 +4269,14 @@ var OneAuthClient = class {
|
|
|
2327
4269
|
const handleMessage = (event) => {
|
|
2328
4270
|
if (event.origin !== dialogOrigin) return;
|
|
2329
4271
|
if (event.data?.type === "PASSKEY_READY") {
|
|
4272
|
+
const blindSigning = options.blindSigning === true;
|
|
2330
4273
|
teardown();
|
|
4274
|
+
if (blindSigning) iframe.focus({ preventScroll: true });
|
|
2331
4275
|
iframe.contentWindow?.postMessage({
|
|
2332
4276
|
type: "PASSKEY_INIT",
|
|
2333
4277
|
...initMessage,
|
|
2334
|
-
fullViewport: true
|
|
4278
|
+
fullViewport: true,
|
|
4279
|
+
blindSigning
|
|
2335
4280
|
}, dialogOrigin);
|
|
2336
4281
|
resolve(true);
|
|
2337
4282
|
} else if (event.data?.type === "PASSKEY_CLOSE") {
|
|
@@ -2356,7 +4301,7 @@ var OneAuthClient = class {
|
|
|
2356
4301
|
/**
|
|
2357
4302
|
* Create and open a full-viewport `<dialog>` containing a passkey iframe.
|
|
2358
4303
|
*
|
|
2359
|
-
* The SDK owns only the
|
|
4304
|
+
* The SDK owns only the hidden outer shell; all visible UI (backdrop,
|
|
2360
4305
|
* card, animations, close button) is rendered by the passkey app inside the
|
|
2361
4306
|
* iframe. This approach means design changes can be shipped server-side
|
|
2362
4307
|
* without updating SDK consumers.
|
|
@@ -2378,34 +4323,42 @@ var OneAuthClient = class {
|
|
|
2378
4323
|
* restores those as well.
|
|
2379
4324
|
* - The iframe sandbox includes `allow-popups-to-escape-sandbox` so that
|
|
2380
4325
|
* 1Password's popup UI can render outside the sandboxed context.
|
|
4326
|
+
* - `allow-downloads` lets the passkey iframe trigger the recovery-key
|
|
4327
|
+
* JSON fallback download without sending sensitive data to the parent.
|
|
2381
4328
|
*
|
|
2382
4329
|
* @param url - The full URL (including query params) to load in the iframe.
|
|
2383
4330
|
* @returns References to the dialog, iframe, and a cleanup function.
|
|
2384
4331
|
*/
|
|
2385
|
-
createModalDialog(url) {
|
|
4332
|
+
createModalDialog(url, options) {
|
|
2386
4333
|
const dialogUrl = this.getDialogUrl();
|
|
2387
4334
|
const hostUrl = new URL(dialogUrl);
|
|
2388
4335
|
const urlParams = new URL(url, window.location.href).searchParams;
|
|
2389
4336
|
const themeMode = urlParams.get("theme") || "light";
|
|
2390
|
-
const accentColor = urlParams.get("accent") || "#0090ff";
|
|
2391
4337
|
const isDark = themeMode === "dark" || themeMode !== "light" && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
2392
|
-
const
|
|
2393
|
-
const
|
|
2394
|
-
const
|
|
2395
|
-
const
|
|
2396
|
-
const textSecondary = isDark ? "#7b7b7b" : "#838383";
|
|
2397
|
-
const bgSurface = isDark ? "#222222" : "#f0f0f0";
|
|
2398
|
-
const ah = accentColor.replace("#", "");
|
|
2399
|
-
const accentRgb = `${parseInt(ah.slice(0, 2), 16)},${parseInt(ah.slice(2, 4), 16)},${parseInt(ah.slice(4, 6), 16)}`;
|
|
2400
|
-
const accentTint = isDark ? `rgba(${accentRgb},0.15)` : `rgba(${accentRgb},0.1)`;
|
|
2401
|
-
const hostname = window.location.hostname;
|
|
4338
|
+
const theme = isDark ? DARK_TOKENS : LIGHT_TOKENS;
|
|
4339
|
+
const { bgPrimary, borderColor, textPrimary, textSecondary } = theme;
|
|
4340
|
+
const textFooter = FOOTER_TEXT_COLOR;
|
|
4341
|
+
const spinnerColor = BRAND_PURPLE;
|
|
2402
4342
|
const dialog = document.createElement("dialog");
|
|
2403
4343
|
dialog.dataset.passkey = "";
|
|
4344
|
+
if (options?.hidden) {
|
|
4345
|
+
dialog.dataset.passkeyHidden = "";
|
|
4346
|
+
}
|
|
2404
4347
|
dialog.style.opacity = "1";
|
|
2405
4348
|
dialog.style.background = "transparent";
|
|
2406
4349
|
document.body.appendChild(dialog);
|
|
2407
4350
|
const style = document.createElement("style");
|
|
2408
4351
|
style.textContent = `
|
|
4352
|
+
/* Load Inter so the overlay's title/subtitle widths match the
|
|
4353
|
+
iframe (which loads Inter via next/font). Without this, host
|
|
4354
|
+
pages on Arial / system-ui produce wider text that wraps to two
|
|
4355
|
+
lines and breaks the visual match with the Figma. font-display:
|
|
4356
|
+
swap renders immediately with a fallback and swaps when Inter
|
|
4357
|
+
is ready \u2014 usually well before PASSKEY_RENDERED fires, but the
|
|
4358
|
+
overlay is short-lived either way so a single-frame swap is
|
|
4359
|
+
acceptable. */
|
|
4360
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@500;700&display=swap');
|
|
4361
|
+
|
|
2409
4362
|
dialog[data-passkey] {
|
|
2410
4363
|
position: fixed;
|
|
2411
4364
|
top: 0;
|
|
@@ -2427,6 +4380,22 @@ var OneAuthClient = class {
|
|
|
2427
4380
|
dialog[data-passkey]::backdrop {
|
|
2428
4381
|
background: transparent !important;
|
|
2429
4382
|
}
|
|
4383
|
+
dialog[data-passkey][data-passkey-hidden] {
|
|
4384
|
+
width: 1px;
|
|
4385
|
+
height: 1px;
|
|
4386
|
+
max-width: 1px;
|
|
4387
|
+
max-height: 1px;
|
|
4388
|
+
opacity: 0;
|
|
4389
|
+
pointer-events: none;
|
|
4390
|
+
}
|
|
4391
|
+
dialog[data-passkey][data-passkey-hidden] iframe {
|
|
4392
|
+
width: 1px;
|
|
4393
|
+
height: 1px;
|
|
4394
|
+
opacity: 0 !important;
|
|
4395
|
+
pointer-events: none;
|
|
4396
|
+
backdrop-filter: none;
|
|
4397
|
+
-webkit-backdrop-filter: none;
|
|
4398
|
+
}
|
|
2430
4399
|
dialog[data-passkey] iframe {
|
|
2431
4400
|
position: fixed;
|
|
2432
4401
|
top: 0;
|
|
@@ -2447,34 +4416,56 @@ var OneAuthClient = class {
|
|
|
2447
4416
|
width: 100%;
|
|
2448
4417
|
height: 100%;
|
|
2449
4418
|
display: flex;
|
|
2450
|
-
align-items:
|
|
4419
|
+
align-items: center;
|
|
2451
4420
|
justify-content: center;
|
|
2452
|
-
padding-top: 50px;
|
|
2453
4421
|
background: rgba(0, 0, 0, 0.4);
|
|
2454
4422
|
backdrop-filter: blur(8px);
|
|
2455
4423
|
-webkit-backdrop-filter: blur(8px);
|
|
2456
4424
|
z-index: 1;
|
|
4425
|
+
/* Purely a visual loading screen \u2014 never intercept clicks.
|
|
4426
|
+
If the iframe's X button or backdrop-dismiss area happens to
|
|
4427
|
+
be reachable while the overlay is still painting (slow CI
|
|
4428
|
+
runners, redirect remounts), the click goes straight through
|
|
4429
|
+
instead of being trapped by the SDK preload. The previous
|
|
4430
|
+
rAF-deferred display:none lost this race in GitHub Actions
|
|
4431
|
+
\u2014 dialog-close.spec.ts repeatedly hit
|
|
4432
|
+
"<div data-passkey-overlay> intercepts pointer events". */
|
|
4433
|
+
pointer-events: none;
|
|
2457
4434
|
animation: _1auth-backdrop-in 0.2s ease-out;
|
|
2458
4435
|
}
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
align-items: flex-end;
|
|
2462
|
-
padding-top: 0;
|
|
2463
|
-
}
|
|
4436
|
+
dialog[data-passkey][data-passkey-hidden] [data-passkey-overlay] {
|
|
4437
|
+
display: none;
|
|
2464
4438
|
}
|
|
2465
4439
|
dialog[data-passkey] [data-passkey-card] {
|
|
2466
|
-
width:
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
4440
|
+
width: ${LOADING_MODAL_WIDTH}px;
|
|
4441
|
+
box-sizing: border-box;
|
|
4442
|
+
padding: ${LOADING_MODAL_PADDING}px;
|
|
4443
|
+
border-radius: ${LOADING_MODAL_RADIUS}px;
|
|
4444
|
+
box-shadow: inset 0 0 0 1px ${borderColor};
|
|
2470
4445
|
animation: _1auth-card-in 0.2s cubic-bezier(0.32, 0.72, 0, 1);
|
|
2471
|
-
max-height:
|
|
4446
|
+
max-height: 100dvh;
|
|
4447
|
+
overflow-y: auto;
|
|
4448
|
+
display: flex;
|
|
4449
|
+
flex-direction: column;
|
|
4450
|
+
gap: ${LOADING_CARD_GAP}px;
|
|
4451
|
+
font-family: ${LOADING_FONT_FAMILY};
|
|
4452
|
+
/* line-height: normal must be set on the card so the host page's
|
|
4453
|
+
Tailwind preflight (line-height: 1.5 on <body>) doesn't bleed
|
|
4454
|
+
into our card via inheritance \u2014 that's what made the SDK preload
|
|
4455
|
+
render taller than the iframe screen. */
|
|
4456
|
+
line-height: ${LOADING_LINE_HEIGHT};
|
|
4457
|
+
-webkit-font-smoothing: antialiased;
|
|
4458
|
+
-moz-osx-font-smoothing: grayscale;
|
|
2472
4459
|
}
|
|
2473
|
-
@media (max-width:
|
|
4460
|
+
@media (max-width: ${LOADING_MOBILE_BREAKPOINT - 1}px) {
|
|
4461
|
+
dialog[data-passkey] [data-passkey-overlay] {
|
|
4462
|
+
align-items: flex-end;
|
|
4463
|
+
}
|
|
2474
4464
|
dialog[data-passkey] [data-passkey-card] {
|
|
2475
4465
|
width: 100%;
|
|
2476
4466
|
border-bottom-left-radius: 0;
|
|
2477
4467
|
border-bottom-right-radius: 0;
|
|
4468
|
+
padding-bottom: calc(${LOADING_MODAL_PADDING}px + env(safe-area-inset-bottom));
|
|
2478
4469
|
animation: _1auth-card-slide 0.3s cubic-bezier(0.32, 0.72, 0, 1);
|
|
2479
4470
|
}
|
|
2480
4471
|
}
|
|
@@ -2497,13 +4488,11 @@ var OneAuthClient = class {
|
|
|
2497
4488
|
dialog.appendChild(style);
|
|
2498
4489
|
const overlay = document.createElement("div");
|
|
2499
4490
|
overlay.dataset.passkeyOverlay = "";
|
|
2500
|
-
const
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
}
|
|
2505
|
-
const overlayCloseBtn = overlay.querySelector("[data-passkey-close]");
|
|
2506
|
-
if (overlayCloseBtn) overlayCloseBtn.addEventListener("click", () => cleanup());
|
|
4491
|
+
const spinnerCenter = SPINNER_SIZE / 2;
|
|
4492
|
+
const spinnerGap = SPINNER_CIRCUMFERENCE - SPINNER_ARC;
|
|
4493
|
+
const spinnerSvg = `<svg style="position:absolute;inset:0;width:100%;height:100%;animation:_1auth-spin 0.8s linear infinite;color:${spinnerColor}" viewBox="0 0 ${SPINNER_SIZE} ${SPINNER_SIZE}" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="${spinnerCenter}" cy="${spinnerCenter}" r="${SPINNER_RADIUS}" stroke="currentColor" opacity="0.3" stroke-width="${SPINNER_STROKE}"/><circle cx="${spinnerCenter}" cy="${spinnerCenter}" r="${SPINNER_RADIUS}" stroke="currentColor" stroke-width="${SPINNER_STROKE}" stroke-linecap="round" stroke-dasharray="${SPINNER_ARC} ${spinnerGap}" transform="rotate(-90 ${spinnerCenter} ${spinnerCenter})"/></svg>`;
|
|
4494
|
+
const rhinestoneLogo = `<svg width="${LOGO_WIDTH}" height="${LOGO_HEIGHT}" viewBox="0 0 72 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M10.4767 14.815C9.80706 15.3364 9.0433 15.7628 8.08753 15.8207C7.91561 15.8312 7.74321 15.8312 7.5713 15.8207C6.616 15.7628 5.85177 15.336 5.18212 14.815C4.56047 14.3303 3.87765 13.648 3.10071 12.871L2.9713 12.7416C2.19483 11.9647 1.51153 11.2818 1.02777 10.6602C0.505414 9.99055 0.0790608 9.22679 0.0211784 8.27102C0.0106814 8.0991 0.0106814 7.9267 0.0211784 7.75479C0.0790608 6.79949 0.505884 6.03526 1.02683 5.36561C1.51153 4.74349 2.19388 4.06114 2.97083 3.2842L3.03577 3.21926L3.10024 3.15479C3.87718 2.37784 4.56 1.69502 5.18165 1.21079C5.85177 0.689374 6.61647 0.26255 7.57177 0.205138C7.74368 0.194641 7.91608 0.194641 8.088 0.205138C9.04377 0.26255 9.80753 0.689374 10.4772 1.21079C11.0993 1.69502 11.7816 2.37784 12.5586 3.15431L12.6885 3.2842C13.4649 4.06067 14.1478 4.74349 14.632 5.36561C15.1534 6.03526 15.5802 6.79902 15.6381 7.75479C15.6485 7.92671 15.6485 8.09879 15.6381 8.27102C15.5802 9.22632 15.1534 9.99055 14.632 10.6602C14.1478 11.2818 13.4649 11.9647 12.6885 12.7416L12.6235 12.8061L12.5586 12.871C11.7821 13.6475 11.0993 14.3308 10.4772 14.8146M1.58306 8.17643C1.64518 9.20326 2.47718 10.0353 4.14212 11.7002C5.80706 13.3647 6.64 14.1971 7.66588 14.2588C7.77506 14.2654 7.88424 14.2654 7.99341 14.2588C9.0193 14.1967 9.85224 13.3647 11.5167 11.6997C13.1812 10.0348 14.0136 9.20279 14.0758 8.17596C14.0821 8.06688 14.0821 7.95752 14.0758 7.84843C14.0136 6.82255 13.1816 5.98961 11.5167 4.32514C9.85177 2.66067 9.01977 1.8282 7.99341 1.76608C7.88433 1.75974 7.77497 1.75974 7.66588 1.76608C6.63953 1.8282 5.80706 2.6602 4.14212 4.32514C2.47765 5.98961 1.64518 6.82208 1.58306 7.84843C1.57672 7.95752 1.57672 8.06735 1.58306 8.17643Z" fill="${textFooter}"/><path d="M3.65796 8.01173C3.65796 7.84138 3.7789 7.69549 3.94455 7.65597C4.78502 7.45502 5.44714 7.1402 6.0029 6.64797C6.16666 6.50271 6.32102 6.34836 6.46596 6.18491C6.95819 5.62914 7.27302 4.96702 7.47396 4.12655C7.49245 4.04577 7.53761 3.97356 7.60215 3.92157C7.66669 3.86958 7.74685 3.84084 7.82972 3.83997C8.00008 3.83997 8.14596 3.96044 8.18549 4.12655C8.3869 4.96702 8.70125 5.62914 9.19349 6.18491C9.33874 6.34836 9.4931 6.50271 9.65655 6.64797C10.2123 7.1402 10.8744 7.45502 11.7154 7.65597C11.7961 7.67455 11.8682 7.71975 11.9201 7.78428C11.972 7.84881 12.0006 7.92892 12.0015 8.01173C12.0006 8.09454 11.972 8.17465 11.9201 8.23918C11.8682 8.30371 11.7961 8.34891 11.7154 8.3675C10.8744 8.56844 10.2128 8.88326 9.65655 9.37549C9.4931 9.52075 9.33874 9.6751 9.19349 9.83855C8.70125 10.3943 8.38643 11.0564 8.18549 11.8969C8.16699 11.9777 8.12184 12.0499 8.0573 12.1019C7.99276 12.1539 7.91259 12.1826 7.82972 12.1835C7.74685 12.1826 7.66669 12.1539 7.60215 12.1019C7.53761 12.0499 7.49245 11.9777 7.47396 11.8969C7.27302 11.0564 6.95819 10.3943 6.46596 9.83855C6.32069 9.67537 6.16608 9.52076 6.0029 9.37549C5.44714 8.88326 4.78502 8.56844 3.94455 8.3675C3.86376 8.349 3.79156 8.30384 3.73957 8.23931C3.68758 8.17477 3.65883 8.0946 3.65796 8.01173Z" fill="${textFooter}"/><path d="M30.1186 4.56946C30.1186 5.08193 30.5031 5.44476 31.08 5.44476C31.6569 5.44476 32.0306 5.08193 32.0306 4.56946C32.0306 4.04711 31.6569 3.70593 31.08 3.70593C30.5031 3.70593 30.1186 4.04711 30.1186 4.56993M30.3106 6.00946V11.3422H31.8494V6.00946H30.3106ZM25.9115 11.3422H24.3732V3.87676H25.9115V6.86311C26.1572 6.35111 26.7129 5.84946 27.5035 5.84946C28.7958 5.84946 29.3835 6.68146 29.3835 8.13182V11.3422H27.8447V8.30264C27.8447 7.53464 27.5031 7.14029 26.9158 7.14029C26.2532 7.14029 25.9115 7.67346 25.9115 8.44146V11.3422ZM21.4682 11.3422H19.9304V6.00946H21.4687L21.1977 7.23299C21.1939 7.25002 21.1941 7.26767 21.1981 7.28464C21.202 7.30161 21.2098 7.31747 21.2207 7.33106C21.2316 7.34465 21.2455 7.35561 21.2612 7.36315C21.2769 7.3707 21.2941 7.37462 21.3115 7.37464C21.3657 7.37464 21.4127 7.33699 21.4268 7.28429C21.656 6.44193 22.3482 5.92429 23.0602 5.92429C23.3379 5.92429 23.5087 5.94546 23.6264 5.96664V7.38546C23.4061 7.36702 23.1853 7.35635 22.9642 7.35346C22.2913 7.35346 21.4687 7.66264 21.4687 9.34782L21.4682 11.3422ZM34.4626 11.3422H32.9242V6.00946H34.4631L34.4414 6.90546C34.6871 6.38311 35.264 5.84946 36.0546 5.84946C37.3473 5.84946 37.9346 6.68146 37.9346 8.13182V11.3422H36.3962V8.30264C36.3962 7.53464 36.0546 7.14029 35.4668 7.14029C34.8047 7.14029 34.4626 7.67346 34.4626 8.44146V11.3422Z" fill="${textFooter}"/><path fill-rule="evenodd" clip-rule="evenodd" d="M38.7355 8.68664C38.7355 10.3186 39.7506 11.513 41.4489 11.513C42.9548 11.513 43.7567 10.6918 43.9915 9.78499L42.56 9.51864C42.464 9.89182 42.0899 10.3398 41.4381 10.3398C40.7652 10.3398 40.2099 9.74217 40.1991 9.02782H44.0024C44.0344 8.89982 44.0555 8.71864 44.0555 8.47346C44.0555 6.92711 42.9016 5.83911 41.4598 5.83911C39.9534 5.83911 38.7355 7.04382 38.7355 8.68664ZM42.56 8.11064H40.2527C40.267 7.80585 40.3982 7.51829 40.6191 7.30777C40.8399 7.09724 41.1335 6.97995 41.4386 6.98029C42.0362 6.98029 42.5812 7.42829 42.56 8.11064Z" fill="${textFooter}"/><path d="M47.1016 11.513C45.8946 11.513 44.7732 11.097 44.5487 9.85982L45.9802 9.64664C46.1511 10.201 46.5355 10.4358 47.0805 10.4358C47.5826 10.4358 47.8494 10.201 47.8494 9.89182C47.8494 9.62499 47.6466 9.44382 47.1016 9.33746L46.5995 9.23064C45.5101 9.00664 44.8264 8.50546 44.8264 7.62029C44.8264 6.56429 45.7449 5.83911 47.0805 5.83911C48.3412 5.83911 49.2706 6.42546 49.3986 7.52382L47.9887 7.73746C47.9242 7.21511 47.5609 6.91629 47.0805 6.91629C46.5892 6.91629 46.3322 7.18264 46.3322 7.49229C46.3322 7.73699 46.5035 7.90829 46.9628 8.00429L47.4649 8.09982C48.7365 8.35582 49.3986 8.86782 49.3986 9.84899C49.3986 10.8626 48.3732 11.513 47.1016 11.513Z" fill="${textFooter}"/><path fill-rule="evenodd" clip-rule="evenodd" d="M54.4423 8.67582C54.4423 10.2118 55.5214 11.513 57.2197 11.513C58.9181 11.513 60.008 10.2118 60.008 8.67582C60.008 7.12946 58.9186 5.83911 57.2197 5.83911C55.5209 5.83911 54.4423 7.12946 54.4423 8.67582ZM58.4588 8.67582C58.4588 9.65699 57.8715 10.1266 57.2197 10.1266C56.5788 10.1266 55.991 9.65746 55.991 8.67582C55.991 7.70546 56.5788 7.21464 57.2197 7.21464C57.8715 7.21464 58.4588 7.69464 58.4588 8.67582Z" fill="${textFooter}"/><path d="M62.4071 11.3421H60.8692V6.00937H62.4071L62.3859 6.90536C62.6316 6.38301 63.2085 5.84937 63.9991 5.84937C65.2918 5.84937 65.8791 6.68137 65.8791 8.13172V11.3421H64.3407V8.30254C64.3407 7.53454 63.9991 7.14019 63.4113 7.14019C62.7492 7.14019 62.4071 7.67336 62.4071 8.44137V11.3421Z" fill="${textFooter}"/><path fill-rule="evenodd" clip-rule="evenodd" d="M66.68 8.68664C66.68 10.3186 67.6951 11.513 69.3934 11.513C70.8998 11.513 71.7012 10.6918 71.936 9.78499L70.5045 9.51864C70.4085 9.89182 70.0344 10.3398 69.3826 10.3398C68.7097 10.3398 68.1544 9.74217 68.1435 9.02782H71.9468C71.9788 8.89982 72 8.71864 72 8.47346C72 6.92711 70.8461 5.83911 69.4043 5.83911C67.8979 5.83911 66.68 7.04382 66.68 8.68664ZM70.5045 8.11064H68.1972C68.2111 7.80581 68.3422 7.51812 68.563 7.30754C68.7839 7.09696 69.0775 6.97973 69.3826 6.98029C69.9812 6.98029 70.5257 7.42829 70.5045 8.11064Z" fill="${textFooter}"/><path d="M52.4418 9.04852V7.26217H53.968V6.00005H52.4418V4.42029H50.9124V6.00005H49.8042V7.26217H50.9124V9.10923C50.9124 9.25511 50.9044 9.43394 50.9266 9.59958C50.9647 9.88194 51.0753 10.3384 51.4691 10.7313C51.8626 11.1243 52.32 11.2349 52.6023 11.2725C52.8216 11.3017 53.0654 11.3012 53.2197 11.3008L53.8743 11.3026V9.86923H53.256C52.8687 9.86923 52.6748 9.86923 52.5543 9.74876C52.4414 9.63582 52.4414 9.45793 52.4418 9.11488V9.04852Z" fill="${textFooter}"/></svg>`;
|
|
4495
|
+
overlay.innerHTML = `<div data-passkey-card style="background:${bgPrimary}"><div style="display:flex;width:100%;flex-direction:column;align-items:flex-start;gap:${LOADING_CARD_GAP}px"><div style="display:flex;width:100%;flex-direction:column;align-items:center;gap:${LOADING_BODY_GAP}px"><div style="display:flex;align-items:center;padding:${LOADING_ICON_PADDING}px"><div style="position:relative;width:${SPINNER_SIZE}px;height:${SPINNER_SIZE}px">${spinnerSvg}</div></div><div style="display:flex;width:100%;flex-direction:column;align-items:center;gap:${LOADING_TITLE_BLOCK_GAP}px;text-align:center"><p style="margin:0;font-size:${LOADING_TITLE_FONT_SIZE}px;font-weight:${LOADING_TITLE_FONT_WEIGHT};color:${textPrimary};line-height:${LOADING_LINE_HEIGHT}">${LOADING_TITLE}</p><p style="margin:0;font-size:${LOADING_SUBTITLE_FONT_SIZE}px;font-weight:${LOADING_SUBTITLE_FONT_WEIGHT};color:${textSecondary};line-height:${LOADING_LINE_HEIGHT}">${LOADING_SUBTITLE}</p></div></div><div style="display:flex;width:100%;align-items:center;justify-content:center;gap:${LOADING_FOOTER_GAP}px"><span style="font-size:${LOADING_FOOTER_FONT_SIZE}px;font-weight:${LOADING_FOOTER_FONT_WEIGHT};color:${textFooter};line-height:${LOADING_LINE_HEIGHT}">Powered by</span>` + rhinestoneLogo + `</div></div></div>`;
|
|
2507
4496
|
dialog.appendChild(overlay);
|
|
2508
4497
|
const iframe = document.createElement("iframe");
|
|
2509
4498
|
iframe.setAttribute(
|
|
@@ -2519,32 +4508,24 @@ var OneAuthClient = class {
|
|
|
2519
4508
|
iframe.setAttribute("tabindex", "0");
|
|
2520
4509
|
iframe.setAttribute(
|
|
2521
4510
|
"sandbox",
|
|
2522
|
-
"allow-forms allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"
|
|
4511
|
+
"allow-forms allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-downloads"
|
|
2523
4512
|
);
|
|
2524
|
-
iframe.setAttribute("src", url);
|
|
2525
4513
|
iframe.setAttribute("title", "Passkey");
|
|
2526
4514
|
iframe.style.opacity = "0";
|
|
2527
|
-
dialog.appendChild(iframe);
|
|
2528
|
-
const inertObserver = new MutationObserver((mutations) => {
|
|
2529
|
-
for (const mutation of mutations) {
|
|
2530
|
-
if (mutation.attributeName === "inert") {
|
|
2531
|
-
dialog.removeAttribute("inert");
|
|
2532
|
-
}
|
|
2533
|
-
}
|
|
2534
|
-
});
|
|
2535
|
-
inertObserver.observe(dialog, { attributes: true });
|
|
2536
4515
|
let overlayHidden = false;
|
|
2537
4516
|
const hideOverlay = () => {
|
|
2538
4517
|
if (overlayHidden) return;
|
|
2539
4518
|
overlayHidden = true;
|
|
2540
4519
|
iframe.style.opacity = "1";
|
|
4520
|
+
overlay.style.pointerEvents = "none";
|
|
2541
4521
|
requestAnimationFrame(() => {
|
|
2542
4522
|
overlay.style.display = "none";
|
|
2543
4523
|
});
|
|
2544
4524
|
};
|
|
4525
|
+
const overlayFailsafe = setTimeout(hideOverlay, 8e3);
|
|
2545
4526
|
const handleMessage = (event) => {
|
|
2546
4527
|
if (event.origin !== hostUrl.origin) return;
|
|
2547
|
-
if (event.data?.type === "PASSKEY_RENDERED") {
|
|
4528
|
+
if (event.data?.type === "PASSKEY_RENDERED" || event.data?.type === "PASSKEY_READY") {
|
|
2548
4529
|
hideOverlay();
|
|
2549
4530
|
}
|
|
2550
4531
|
if (event.data?.type === "PASSKEY_DISCONNECT") {
|
|
@@ -2552,18 +4533,41 @@ var OneAuthClient = class {
|
|
|
2552
4533
|
}
|
|
2553
4534
|
};
|
|
2554
4535
|
window.addEventListener("message", handleMessage);
|
|
4536
|
+
const iframeUrl = new URL(url, window.location.href);
|
|
4537
|
+
iframeUrl.searchParams.set("fullViewport", "1");
|
|
4538
|
+
iframe.setAttribute("src", iframeUrl.toString());
|
|
4539
|
+
dialog.appendChild(iframe);
|
|
4540
|
+
const viewportInfoCleanup = this.wireViewportInfo(iframe);
|
|
4541
|
+
const inertObserver = new MutationObserver((mutations) => {
|
|
4542
|
+
for (const mutation of mutations) {
|
|
4543
|
+
if (mutation.attributeName === "inert") {
|
|
4544
|
+
dialog.removeAttribute("inert");
|
|
4545
|
+
}
|
|
4546
|
+
}
|
|
4547
|
+
});
|
|
4548
|
+
inertObserver.observe(dialog, { attributes: true });
|
|
2555
4549
|
const handleEscape = (event) => {
|
|
2556
4550
|
if (event.key === "Escape") {
|
|
2557
4551
|
cleanup();
|
|
2558
4552
|
}
|
|
2559
4553
|
};
|
|
2560
4554
|
document.addEventListener("keydown", handleEscape);
|
|
2561
|
-
|
|
4555
|
+
const reveal = () => {
|
|
4556
|
+
delete dialog.dataset.passkeyHidden;
|
|
4557
|
+
iframe.style.opacity = "1";
|
|
4558
|
+
};
|
|
4559
|
+
if (options?.hidden) {
|
|
4560
|
+
dialog.show();
|
|
4561
|
+
} else {
|
|
4562
|
+
dialog.showModal();
|
|
4563
|
+
}
|
|
2562
4564
|
let cleanedUp = false;
|
|
2563
|
-
const
|
|
4565
|
+
const destroy = () => {
|
|
2564
4566
|
if (cleanedUp) return;
|
|
2565
4567
|
cleanedUp = true;
|
|
4568
|
+
clearTimeout(overlayFailsafe);
|
|
2566
4569
|
inertObserver.disconnect();
|
|
4570
|
+
viewportInfoCleanup();
|
|
2567
4571
|
window.removeEventListener("message", handleMessage);
|
|
2568
4572
|
document.removeEventListener("keydown", handleEscape);
|
|
2569
4573
|
dialog.close();
|
|
@@ -2576,14 +4580,23 @@ var OneAuthClient = class {
|
|
|
2576
4580
|
}
|
|
2577
4581
|
dialog.remove();
|
|
2578
4582
|
};
|
|
2579
|
-
|
|
4583
|
+
const cleanup = options?.persistent ? () => {
|
|
4584
|
+
if (dialog.open) dialog.close();
|
|
4585
|
+
} : destroy;
|
|
4586
|
+
return { dialog, iframe, cleanup, destroy, reveal };
|
|
2580
4587
|
}
|
|
2581
4588
|
/**
|
|
2582
4589
|
* Listen for the auth result from the modal dialog.
|
|
2583
4590
|
*
|
|
2584
|
-
*
|
|
2585
|
-
*
|
|
2586
|
-
*
|
|
4591
|
+
* Each modal call generates a per-session nonce that the iframe must
|
|
4592
|
+
* echo back in `PASSKEY_CLOSE` / `PASSKEY_LOGIN_RESULT` /
|
|
4593
|
+
* `PASSKEY_RETRY_POPUP` messages. Without an echo, a queued CLOSE
|
|
4594
|
+
* from the previous iframe could be dispatched to the new modal's
|
|
4595
|
+
* listener (postMessage events tagged with `event.source` from a now-
|
|
4596
|
+
* destroyed iframe still reach the parent's message handler) and
|
|
4597
|
+
* silently cancel the fresh auth attempt. The nonce makes that race
|
|
4598
|
+
* harmless: a stale message that doesn't carry the current modal's
|
|
4599
|
+
* nonce is ignored.
|
|
2587
4600
|
*
|
|
2588
4601
|
* Also handles the `PASSKEY_RETRY_POPUP` message: sent by the dialog when a
|
|
2589
4602
|
* password manager (e.g. Bitwarden) intercepts the WebAuthn call inside the
|
|
@@ -2596,25 +4609,42 @@ var OneAuthClient = class {
|
|
|
2596
4609
|
* @param cleanup - Idempotent teardown for the modal dialog.
|
|
2597
4610
|
* @returns An `AuthResult` discriminated union.
|
|
2598
4611
|
*/
|
|
2599
|
-
waitForModalAuthResponse(_dialog, iframe, cleanup) {
|
|
4612
|
+
waitForModalAuthResponse(_dialog, iframe, cleanup, requestId) {
|
|
2600
4613
|
const dialogOrigin = this.getDialogOrigin();
|
|
4614
|
+
const modalNonce = generateModalNonce();
|
|
2601
4615
|
return new Promise((resolve) => {
|
|
2602
|
-
let dialogReady = false;
|
|
2603
4616
|
const handleMessage = (event) => {
|
|
2604
4617
|
if (event.origin !== dialogOrigin) return;
|
|
2605
4618
|
const data = event.data;
|
|
4619
|
+
const matchesSource = event.source === iframe.contentWindow;
|
|
4620
|
+
const matchesRequest = typeof requestId === "string" && data?.requestId === requestId;
|
|
4621
|
+
const matchesNonce = data?.modalNonce === modalNonce;
|
|
4622
|
+
const isCurrentFrameClose = data?.type === "PASSKEY_CLOSE" && matchesSource;
|
|
4623
|
+
if (data?.type === "PASSKEY_CLOSE" && (matchesRequest || matchesNonce || isCurrentFrameClose)) {
|
|
4624
|
+
window.removeEventListener("message", handleMessage);
|
|
4625
|
+
cleanup();
|
|
4626
|
+
resolve({
|
|
4627
|
+
success: false,
|
|
4628
|
+
error: {
|
|
4629
|
+
code: "USER_CANCELLED",
|
|
4630
|
+
message: "Authentication was cancelled"
|
|
4631
|
+
}
|
|
4632
|
+
});
|
|
4633
|
+
return;
|
|
4634
|
+
}
|
|
2606
4635
|
if (data?.type === "PASSKEY_READY") {
|
|
2607
|
-
dialogReady = true;
|
|
2608
4636
|
iframe.contentWindow?.postMessage({
|
|
2609
4637
|
type: "PASSKEY_INIT",
|
|
2610
4638
|
mode: "iframe",
|
|
2611
|
-
fullViewport: true
|
|
4639
|
+
fullViewport: true,
|
|
4640
|
+
modalNonce
|
|
2612
4641
|
}, dialogOrigin);
|
|
2613
4642
|
return;
|
|
2614
4643
|
}
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
4644
|
+
const hasModalNonce = typeof data?.modalNonce === "string";
|
|
4645
|
+
const isCurrentDialogMessage = matchesSource || matchesNonce || !hasModalNonce;
|
|
4646
|
+
if (!isCurrentDialogMessage) return;
|
|
4647
|
+
if (data?.modalNonce && data.modalNonce !== modalNonce) return;
|
|
2618
4648
|
if (data?.type === "PASSKEY_LOGIN_RESULT") {
|
|
2619
4649
|
window.removeEventListener("message", handleMessage);
|
|
2620
4650
|
cleanup();
|
|
@@ -2625,7 +4655,8 @@ var OneAuthClient = class {
|
|
|
2625
4655
|
id: data.data?.user?.id,
|
|
2626
4656
|
username: data.data?.username,
|
|
2627
4657
|
address: data.data?.address
|
|
2628
|
-
}
|
|
4658
|
+
},
|
|
4659
|
+
signerType: data.data?.signerType ?? "passkey"
|
|
2629
4660
|
});
|
|
2630
4661
|
} else {
|
|
2631
4662
|
resolve({
|
|
@@ -2638,16 +4669,6 @@ var OneAuthClient = class {
|
|
|
2638
4669
|
cleanup();
|
|
2639
4670
|
const popupUrl = data.data?.url?.replace("mode=iframe", "mode=popup") || `${this.getDialogUrl()}/dialog/auth?mode=popup${this.config.clientId ? `&clientId=${this.config.clientId}` : ""}`;
|
|
2640
4671
|
this.waitForPopupAuthResponse(popupUrl).then(resolve);
|
|
2641
|
-
} else if (data?.type === "PASSKEY_CLOSE") {
|
|
2642
|
-
window.removeEventListener("message", handleMessage);
|
|
2643
|
-
cleanup();
|
|
2644
|
-
resolve({
|
|
2645
|
-
success: false,
|
|
2646
|
-
error: {
|
|
2647
|
-
code: "USER_CANCELLED",
|
|
2648
|
-
message: "Authentication was cancelled"
|
|
2649
|
-
}
|
|
2650
|
-
});
|
|
2651
4672
|
}
|
|
2652
4673
|
};
|
|
2653
4674
|
window.addEventListener("message", handleMessage);
|
|
@@ -2688,7 +4709,8 @@ var OneAuthClient = class {
|
|
|
2688
4709
|
id: data.data?.user?.id,
|
|
2689
4710
|
username: data.data?.username,
|
|
2690
4711
|
address: data.data?.address
|
|
2691
|
-
}
|
|
4712
|
+
},
|
|
4713
|
+
signerType: data.data?.signerType ?? "passkey"
|
|
2692
4714
|
});
|
|
2693
4715
|
} else {
|
|
2694
4716
|
resolve({
|
|
@@ -2712,63 +4734,6 @@ var OneAuthClient = class {
|
|
|
2712
4734
|
window.addEventListener("message", handleMessage);
|
|
2713
4735
|
});
|
|
2714
4736
|
}
|
|
2715
|
-
/**
|
|
2716
|
-
* Listen for the authenticate result from the modal dialog.
|
|
2717
|
-
*
|
|
2718
|
-
* The authenticate flow combines sign-in/sign-up with optional off-chain
|
|
2719
|
-
* challenge signing. The dialog sends `PASSKEY_AUTHENTICATE_RESULT` on
|
|
2720
|
-
* completion with both user details and, if a challenge was requested, the
|
|
2721
|
-
* WebAuthn signature and the hashed challenge value for server-side
|
|
2722
|
-
* verification.
|
|
2723
|
-
*
|
|
2724
|
-
* @param _dialog - Unused; kept for signature consistency.
|
|
2725
|
-
* @param _iframe - Unused; kept for signature consistency.
|
|
2726
|
-
* @param cleanup - Idempotent teardown for the modal dialog.
|
|
2727
|
-
* @returns An `AuthenticateResult` discriminated union.
|
|
2728
|
-
*/
|
|
2729
|
-
waitForAuthenticateResponse(_dialog, _iframe, cleanup) {
|
|
2730
|
-
const dialogOrigin = this.getDialogOrigin();
|
|
2731
|
-
return new Promise((resolve) => {
|
|
2732
|
-
const handleMessage = (event) => {
|
|
2733
|
-
if (event.origin !== dialogOrigin) return;
|
|
2734
|
-
const data = event.data;
|
|
2735
|
-
if (data?.type === "PASSKEY_AUTHENTICATE_RESULT") {
|
|
2736
|
-
window.removeEventListener("message", handleMessage);
|
|
2737
|
-
cleanup();
|
|
2738
|
-
if (data.success) {
|
|
2739
|
-
resolve({
|
|
2740
|
-
success: true,
|
|
2741
|
-
user: {
|
|
2742
|
-
id: data.data?.user?.id,
|
|
2743
|
-
username: data.data?.username,
|
|
2744
|
-
address: data.data?.accountAddress
|
|
2745
|
-
},
|
|
2746
|
-
challenge: data.data?.signature ? {
|
|
2747
|
-
signature: data.data.signature,
|
|
2748
|
-
signedHash: data.data.signedHash
|
|
2749
|
-
} : void 0
|
|
2750
|
-
});
|
|
2751
|
-
} else {
|
|
2752
|
-
resolve({
|
|
2753
|
-
success: false,
|
|
2754
|
-
error: data.error
|
|
2755
|
-
});
|
|
2756
|
-
}
|
|
2757
|
-
} else if (data?.type === "PASSKEY_CLOSE") {
|
|
2758
|
-
window.removeEventListener("message", handleMessage);
|
|
2759
|
-
cleanup();
|
|
2760
|
-
resolve({
|
|
2761
|
-
success: false,
|
|
2762
|
-
error: {
|
|
2763
|
-
code: "USER_CANCELLED",
|
|
2764
|
-
message: "Authentication was cancelled"
|
|
2765
|
-
}
|
|
2766
|
-
});
|
|
2767
|
-
}
|
|
2768
|
-
};
|
|
2769
|
-
window.addEventListener("message", handleMessage);
|
|
2770
|
-
});
|
|
2771
|
-
}
|
|
2772
4737
|
/**
|
|
2773
4738
|
* Listen for the connect result from the connect dialog.
|
|
2774
4739
|
*
|
|
@@ -2827,48 +4792,100 @@ var OneAuthClient = class {
|
|
|
2827
4792
|
});
|
|
2828
4793
|
}
|
|
2829
4794
|
/**
|
|
2830
|
-
* Listen for
|
|
2831
|
-
*
|
|
2832
|
-
* The consent dialog shows the user which data fields an app is requesting
|
|
2833
|
-
* access to and lets them approve or deny. On approval, `data` contains the
|
|
2834
|
-
* consented field values (e.g. email address, device names) and `grantedAt`
|
|
2835
|
-
* records when consent was given.
|
|
4795
|
+
* Listen for grant-permission results and bridge sponsorship token requests.
|
|
2836
4796
|
*
|
|
2837
|
-
*
|
|
2838
|
-
*
|
|
2839
|
-
*
|
|
4797
|
+
* The grant iframe owns the passkey ceremony and install intent submission.
|
|
4798
|
+
* Extension-token minting stays in the app origin, so the SDK exposes a
|
|
4799
|
+
* narrow postMessage round-trip that never gives the iframe app secrets.
|
|
2840
4800
|
*
|
|
2841
4801
|
* @param _dialog - Unused; kept for signature consistency.
|
|
2842
|
-
* @param
|
|
4802
|
+
* @param iframe - The grant iframe that receives extension-token responses.
|
|
2843
4803
|
* @param cleanup - Idempotent teardown for the modal dialog.
|
|
2844
|
-
* @
|
|
4804
|
+
* @param sponsor - Whether this grant flow should request an extension token.
|
|
4805
|
+
* @returns A `GrantPermissionsResult` discriminated union.
|
|
2845
4806
|
*/
|
|
2846
|
-
|
|
4807
|
+
waitForGrantPermissionResponse(dialog, iframe, cleanup, sponsor, initPayload) {
|
|
2847
4808
|
const dialogOrigin = this.getDialogOrigin();
|
|
2848
4809
|
return new Promise((resolve) => {
|
|
2849
|
-
|
|
4810
|
+
let readySeen = false;
|
|
4811
|
+
const teardown = () => {
|
|
4812
|
+
clearTimeout(readyTimeout);
|
|
4813
|
+
window.removeEventListener("message", handleMessage);
|
|
4814
|
+
dialog.removeEventListener("close", handleClose);
|
|
4815
|
+
};
|
|
4816
|
+
const postInit = () => {
|
|
4817
|
+
iframe.contentWindow?.postMessage(
|
|
4818
|
+
{ type: "PASSKEY_INIT", ...initPayload, fullViewport: true },
|
|
4819
|
+
dialogOrigin
|
|
4820
|
+
);
|
|
4821
|
+
};
|
|
4822
|
+
const handleClose = () => {
|
|
4823
|
+
teardown();
|
|
4824
|
+
resolve({
|
|
4825
|
+
success: false,
|
|
4826
|
+
error: {
|
|
4827
|
+
code: "USER_CANCELLED",
|
|
4828
|
+
message: "User closed the dialog"
|
|
4829
|
+
}
|
|
4830
|
+
});
|
|
4831
|
+
};
|
|
4832
|
+
const handleMessage = async (event) => {
|
|
2850
4833
|
if (event.origin !== dialogOrigin) return;
|
|
2851
4834
|
const data = event.data;
|
|
2852
|
-
if (data?.type === "
|
|
2853
|
-
|
|
4835
|
+
if (data?.type === "PASSKEY_READY") {
|
|
4836
|
+
readySeen = true;
|
|
4837
|
+
clearTimeout(readyTimeout);
|
|
4838
|
+
postInit();
|
|
4839
|
+
return;
|
|
4840
|
+
}
|
|
4841
|
+
if (data?.type === "PASSKEY_GRANT_EXTENSION_TOKEN_REQUEST") {
|
|
4842
|
+
const requestId = data.requestId;
|
|
4843
|
+
const intentOp = data.intentOp;
|
|
4844
|
+
const postResponse = (payload) => {
|
|
4845
|
+
iframe.contentWindow?.postMessage(
|
|
4846
|
+
{ type: "PASSKEY_GRANT_EXTENSION_TOKEN_RESPONSE", requestId, ...payload },
|
|
4847
|
+
dialogOrigin
|
|
4848
|
+
);
|
|
4849
|
+
};
|
|
4850
|
+
if (!requestId || !intentOp) {
|
|
4851
|
+
postResponse({ success: false, error: "Invalid extension token request" });
|
|
4852
|
+
return;
|
|
4853
|
+
}
|
|
4854
|
+
if (!sponsor) {
|
|
4855
|
+
postResponse({ success: true });
|
|
4856
|
+
return;
|
|
4857
|
+
}
|
|
4858
|
+
if (!this.sponsorship) {
|
|
4859
|
+
postResponse({ success: false, error: MISSING_APP_CREDENTIALS_MESSAGE });
|
|
4860
|
+
return;
|
|
4861
|
+
}
|
|
4862
|
+
try {
|
|
4863
|
+
const token = await this.sponsorship.getExtensionToken(intentOp);
|
|
4864
|
+
postResponse({ success: true, token });
|
|
4865
|
+
} catch (err) {
|
|
4866
|
+
postResponse({
|
|
4867
|
+
success: false,
|
|
4868
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4869
|
+
});
|
|
4870
|
+
}
|
|
4871
|
+
return;
|
|
4872
|
+
}
|
|
4873
|
+
if (data?.type === "PASSKEY_GRANT_PERMISSION_RESULT") {
|
|
4874
|
+
teardown();
|
|
2854
4875
|
cleanup();
|
|
2855
4876
|
if (data.success) {
|
|
2856
|
-
resolve({
|
|
2857
|
-
success: true,
|
|
2858
|
-
data: data.data,
|
|
2859
|
-
grantedAt: data.data?.grantedAt
|
|
2860
|
-
});
|
|
4877
|
+
resolve({ success: true, ...data.data ?? {} });
|
|
2861
4878
|
} else {
|
|
2862
4879
|
resolve({
|
|
2863
4880
|
success: false,
|
|
2864
|
-
error: data.error ?? {
|
|
2865
|
-
code: "
|
|
2866
|
-
message: "
|
|
4881
|
+
error: data.error ?? data.data?.error ?? {
|
|
4882
|
+
code: "GRANT_PERMISSION_FAILED",
|
|
4883
|
+
message: "Permission grant failed"
|
|
2867
4884
|
}
|
|
2868
4885
|
});
|
|
2869
4886
|
}
|
|
2870
4887
|
} else if (data?.type === "PASSKEY_CLOSE") {
|
|
2871
|
-
|
|
4888
|
+
teardown();
|
|
2872
4889
|
cleanup();
|
|
2873
4890
|
resolve({
|
|
2874
4891
|
success: false,
|
|
@@ -2879,7 +4896,20 @@ var OneAuthClient = class {
|
|
|
2879
4896
|
});
|
|
2880
4897
|
}
|
|
2881
4898
|
};
|
|
4899
|
+
const readyTimeout = setTimeout(() => {
|
|
4900
|
+
if (readySeen) return;
|
|
4901
|
+
teardown();
|
|
4902
|
+
cleanup();
|
|
4903
|
+
resolve({
|
|
4904
|
+
success: false,
|
|
4905
|
+
error: {
|
|
4906
|
+
code: "USER_CANCELLED",
|
|
4907
|
+
message: "User closed the dialog"
|
|
4908
|
+
}
|
|
4909
|
+
});
|
|
4910
|
+
}, 1e4);
|
|
2882
4911
|
window.addEventListener("message", handleMessage);
|
|
4912
|
+
dialog.addEventListener("close", handleClose);
|
|
2883
4913
|
});
|
|
2884
4914
|
}
|
|
2885
4915
|
/**
|
|
@@ -3043,6 +5073,65 @@ var OneAuthClient = class {
|
|
|
3043
5073
|
}
|
|
3044
5074
|
};
|
|
3045
5075
|
|
|
5076
|
+
// src/permissions.ts
|
|
5077
|
+
function definePermissions(config) {
|
|
5078
|
+
const { name, ...permission } = config;
|
|
5079
|
+
return {
|
|
5080
|
+
permissions: [permission],
|
|
5081
|
+
contracts: [
|
|
5082
|
+
{
|
|
5083
|
+
address: config.address,
|
|
5084
|
+
name,
|
|
5085
|
+
abi: config.abi
|
|
5086
|
+
}
|
|
5087
|
+
]
|
|
5088
|
+
};
|
|
5089
|
+
}
|
|
5090
|
+
|
|
5091
|
+
// src/crossChainPermissions.ts
|
|
5092
|
+
import { isAddress } from "viem";
|
|
5093
|
+
function toUnixSecondsBigint(input) {
|
|
5094
|
+
if (typeof input === "bigint") return input;
|
|
5095
|
+
return BigInt(Math.floor(input.getTime() / 1e3));
|
|
5096
|
+
}
|
|
5097
|
+
function resolveTokenForChain(token, chainId) {
|
|
5098
|
+
return isAddress(token, { strict: false }) ? token : getTokenAddress(token, chainId);
|
|
5099
|
+
}
|
|
5100
|
+
function createCrossChainPermission(input) {
|
|
5101
|
+
const fromLegs = Array.isArray(input.from) ? input.from : [input.from];
|
|
5102
|
+
const toLegs = Array.isArray(input.to) ? input.to : [input.to];
|
|
5103
|
+
if (fromLegs.length === 0) {
|
|
5104
|
+
throw new Error("createCrossChainPermission: `from` must contain at least one leg");
|
|
5105
|
+
}
|
|
5106
|
+
if (toLegs.length === 0) {
|
|
5107
|
+
throw new Error("createCrossChainPermission: `to` must contain at least one leg");
|
|
5108
|
+
}
|
|
5109
|
+
const validUntil = input.validUntil !== void 0 ? toUnixSecondsBigint(input.validUntil) : void 0;
|
|
5110
|
+
const validAfter = input.validAfter !== void 0 ? toUnixSecondsBigint(input.validAfter) : void 0;
|
|
5111
|
+
if (validUntil !== void 0 && validAfter !== void 0 && validAfter > validUntil) {
|
|
5112
|
+
throw new Error(
|
|
5113
|
+
`createCrossChainPermission: validAfter (${validAfter}) is greater than validUntil (${validUntil})`
|
|
5114
|
+
);
|
|
5115
|
+
}
|
|
5116
|
+
return {
|
|
5117
|
+
from: fromLegs.map((leg) => ({
|
|
5118
|
+
chain: leg.chain,
|
|
5119
|
+
token: resolveTokenForChain(leg.token, leg.chain.id),
|
|
5120
|
+
maxAmount: leg.maxAmount
|
|
5121
|
+
})),
|
|
5122
|
+
to: toLegs.map((leg) => ({
|
|
5123
|
+
chain: leg.chain,
|
|
5124
|
+
token: resolveTokenForChain(leg.token, leg.chain.id),
|
|
5125
|
+
recipient: leg.recipient
|
|
5126
|
+
})),
|
|
5127
|
+
validUntil,
|
|
5128
|
+
validAfter,
|
|
5129
|
+
fillDeadline: input.fillDeadline,
|
|
5130
|
+
recipientIsAccount: !input.allowRecipientNotAccount,
|
|
5131
|
+
settlementLayers: input.settlementLayers
|
|
5132
|
+
};
|
|
5133
|
+
}
|
|
5134
|
+
|
|
3046
5135
|
// src/account.ts
|
|
3047
5136
|
import {
|
|
3048
5137
|
bytesToString,
|
|
@@ -3068,7 +5157,8 @@ function createPasskeyAccount(client, params) {
|
|
|
3068
5157
|
address,
|
|
3069
5158
|
signMessage: async ({ message }) => {
|
|
3070
5159
|
const result = await client.signMessage({
|
|
3071
|
-
username,
|
|
5160
|
+
username: username || void 0,
|
|
5161
|
+
accountAddress: address,
|
|
3072
5162
|
message: normalizeMessage(message)
|
|
3073
5163
|
});
|
|
3074
5164
|
if (!result.success || !result.signature) {
|
|
@@ -3103,7 +5193,8 @@ function createPasskeyAccount(client, params) {
|
|
|
3103
5193
|
])
|
|
3104
5194
|
);
|
|
3105
5195
|
const result = await client.signTypedData({
|
|
3106
|
-
username,
|
|
5196
|
+
username: username || void 0,
|
|
5197
|
+
accountAddress: address,
|
|
3107
5198
|
domain,
|
|
3108
5199
|
types: normalizedTypes,
|
|
3109
5200
|
primaryType: typedData.primaryType,
|
|
@@ -3117,29 +5208,37 @@ function createPasskeyAccount(client, params) {
|
|
|
3117
5208
|
});
|
|
3118
5209
|
return {
|
|
3119
5210
|
...account,
|
|
3120
|
-
username
|
|
5211
|
+
...username ? { username } : {},
|
|
5212
|
+
accountAddress: address
|
|
3121
5213
|
};
|
|
3122
5214
|
}
|
|
3123
5215
|
|
|
3124
5216
|
// src/walletClient/index.ts
|
|
3125
5217
|
import {
|
|
3126
5218
|
createWalletClient,
|
|
3127
|
-
hashMessage,
|
|
5219
|
+
hashMessage as hashMessage2,
|
|
3128
5220
|
hashTypedData as hashTypedData2
|
|
3129
5221
|
} from "viem";
|
|
3130
5222
|
import { toAccount as toAccount2 } from "viem/accounts";
|
|
3131
5223
|
function createPasskeyWalletClient(config) {
|
|
3132
|
-
const
|
|
5224
|
+
const identity = {
|
|
5225
|
+
...config.username ? { username: config.username } : {},
|
|
5226
|
+
accountAddress: config.accountAddress
|
|
5227
|
+
};
|
|
3133
5228
|
const provider = new OneAuthClient({
|
|
3134
5229
|
providerUrl: config.providerUrl,
|
|
3135
5230
|
clientId: config.clientId,
|
|
3136
|
-
dialogUrl: config.dialogUrl
|
|
5231
|
+
dialogUrl: config.dialogUrl,
|
|
5232
|
+
theme: config.theme,
|
|
5233
|
+
blind_signing: config.blind_signing,
|
|
5234
|
+
testnets: config.testnets,
|
|
5235
|
+
sponsorship: config.sponsorship
|
|
3137
5236
|
});
|
|
3138
5237
|
const signMessageImpl = async (message) => {
|
|
3139
|
-
const hash =
|
|
5238
|
+
const hash = hashMessage2(message);
|
|
3140
5239
|
const result = await provider.signWithModal({
|
|
3141
5240
|
challenge: hash,
|
|
3142
|
-
|
|
5241
|
+
...identity,
|
|
3143
5242
|
description: "Sign message",
|
|
3144
5243
|
transaction: {
|
|
3145
5244
|
actions: [
|
|
@@ -3170,7 +5269,7 @@ function createPasskeyWalletClient(config) {
|
|
|
3170
5269
|
const hash = hashCalls(calls);
|
|
3171
5270
|
const result = await provider.signWithModal({
|
|
3172
5271
|
challenge: hash,
|
|
3173
|
-
|
|
5272
|
+
...identity,
|
|
3174
5273
|
description: "Sign transaction",
|
|
3175
5274
|
transaction: buildTransactionReview(calls)
|
|
3176
5275
|
});
|
|
@@ -3186,7 +5285,7 @@ function createPasskeyWalletClient(config) {
|
|
|
3186
5285
|
const hash = hashTypedData2(typedData);
|
|
3187
5286
|
const result = await provider.signWithModal({
|
|
3188
5287
|
challenge: hash,
|
|
3189
|
-
|
|
5288
|
+
...identity,
|
|
3190
5289
|
description: "Sign typed data",
|
|
3191
5290
|
transaction: {
|
|
3192
5291
|
actions: [
|
|
@@ -3213,10 +5312,12 @@ function createPasskeyWalletClient(config) {
|
|
|
3213
5312
|
data: call.data || "0x",
|
|
3214
5313
|
value: call.value !== void 0 ? call.value.toString() : "0",
|
|
3215
5314
|
label: call.label,
|
|
3216
|
-
sublabel: call.sublabel
|
|
5315
|
+
sublabel: call.sublabel,
|
|
5316
|
+
icon: call.icon,
|
|
5317
|
+
abi: call.abi
|
|
3217
5318
|
}));
|
|
3218
5319
|
return {
|
|
3219
|
-
username:
|
|
5320
|
+
...config.username ? { username: config.username } : {},
|
|
3220
5321
|
accountAddress: config.accountAddress,
|
|
3221
5322
|
targetChain,
|
|
3222
5323
|
calls: intentCalls
|
|
@@ -3246,11 +5347,9 @@ function createPasskeyWalletClient(config) {
|
|
|
3246
5347
|
value: transaction.value
|
|
3247
5348
|
}
|
|
3248
5349
|
];
|
|
3249
|
-
const closeOn = config.waitForHash ?? true ? "completed" : "preconfirmed";
|
|
3250
5350
|
const intentPayload = await buildIntentPayload(calls, targetChain);
|
|
3251
5351
|
const result = await provider.sendIntent({
|
|
3252
5352
|
...intentPayload,
|
|
3253
|
-
closeOn,
|
|
3254
5353
|
waitForHash: config.waitForHash ?? true,
|
|
3255
5354
|
hashTimeoutMs: config.hashTimeoutMs,
|
|
3256
5355
|
hashIntervalMs: config.hashIntervalMs
|
|
@@ -3265,14 +5364,12 @@ function createPasskeyWalletClient(config) {
|
|
|
3265
5364
|
*/
|
|
3266
5365
|
async sendCalls(params) {
|
|
3267
5366
|
const { calls, chainId: targetChain, tokenRequests, sourceAssets, sourceChainId } = params;
|
|
3268
|
-
const closeOn = config.waitForHash ?? true ? "completed" : "preconfirmed";
|
|
3269
5367
|
const intentPayload = await buildIntentPayload(calls, targetChain, { tokenRequests, sourceAssets });
|
|
3270
5368
|
const result = await provider.sendIntent({
|
|
3271
5369
|
...intentPayload,
|
|
3272
5370
|
tokenRequests,
|
|
3273
5371
|
sourceAssets,
|
|
3274
5372
|
sourceChainId,
|
|
3275
|
-
closeOn,
|
|
3276
5373
|
waitForHash: config.waitForHash ?? true,
|
|
3277
5374
|
hashTimeoutMs: config.hashTimeoutMs,
|
|
3278
5375
|
hashIntervalMs: config.hashIntervalMs
|
|
@@ -3303,21 +5400,36 @@ function useBatchQueue() {
|
|
|
3303
5400
|
function generateId() {
|
|
3304
5401
|
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
3305
5402
|
}
|
|
3306
|
-
function getStorageKey(
|
|
3307
|
-
|
|
5403
|
+
function getStorageKey(identity) {
|
|
5404
|
+
const key = identity?.accountAddress || identity?.username;
|
|
5405
|
+
return key ? `1auth_batch_queue_${key.toLowerCase()}` : "1auth_batch_queue_anonymous";
|
|
5406
|
+
}
|
|
5407
|
+
function resolveIdentity(passedIdentity, providerIdentity) {
|
|
5408
|
+
if (typeof passedIdentity === "string") {
|
|
5409
|
+
return { username: passedIdentity, accountAddress: providerIdentity.accountAddress };
|
|
5410
|
+
}
|
|
5411
|
+
return {
|
|
5412
|
+
username: passedIdentity?.username ?? providerIdentity.username,
|
|
5413
|
+
accountAddress: passedIdentity?.accountAddress ?? providerIdentity.accountAddress
|
|
5414
|
+
};
|
|
3308
5415
|
}
|
|
3309
5416
|
function BatchQueueProvider({
|
|
3310
5417
|
client,
|
|
3311
5418
|
username,
|
|
5419
|
+
accountAddress,
|
|
3312
5420
|
children
|
|
3313
5421
|
}) {
|
|
3314
5422
|
const [queue, setQueue] = React.useState([]);
|
|
3315
5423
|
const [isExpanded, setExpanded] = React.useState(false);
|
|
3316
5424
|
const [isSigning, setIsSigning] = React.useState(false);
|
|
3317
5425
|
const [animationTrigger, setAnimationTrigger] = React.useState(0);
|
|
5426
|
+
const providerIdentity = React.useMemo(
|
|
5427
|
+
() => ({ username, accountAddress }),
|
|
5428
|
+
[username, accountAddress]
|
|
5429
|
+
);
|
|
3318
5430
|
const batchChainId = queue.length > 0 ? queue[0].targetChain : null;
|
|
3319
5431
|
React.useEffect(() => {
|
|
3320
|
-
const storageKey = getStorageKey(
|
|
5432
|
+
const storageKey = getStorageKey(providerIdentity);
|
|
3321
5433
|
try {
|
|
3322
5434
|
const stored = localStorage.getItem(storageKey);
|
|
3323
5435
|
if (stored) {
|
|
@@ -3330,9 +5442,9 @@ function BatchQueueProvider({
|
|
|
3330
5442
|
}
|
|
3331
5443
|
} catch {
|
|
3332
5444
|
}
|
|
3333
|
-
}, [
|
|
5445
|
+
}, [providerIdentity]);
|
|
3334
5446
|
React.useEffect(() => {
|
|
3335
|
-
const storageKey = getStorageKey(
|
|
5447
|
+
const storageKey = getStorageKey(providerIdentity);
|
|
3336
5448
|
try {
|
|
3337
5449
|
if (queue.length > 0) {
|
|
3338
5450
|
localStorage.setItem(storageKey, JSON.stringify(queue));
|
|
@@ -3341,7 +5453,7 @@ function BatchQueueProvider({
|
|
|
3341
5453
|
}
|
|
3342
5454
|
} catch {
|
|
3343
5455
|
}
|
|
3344
|
-
}, [queue,
|
|
5456
|
+
}, [queue, providerIdentity]);
|
|
3345
5457
|
const addToBatch = React.useCallback((call, targetChain) => {
|
|
3346
5458
|
if (batchChainId !== null && batchChainId !== targetChain) {
|
|
3347
5459
|
return {
|
|
@@ -3366,7 +5478,7 @@ function BatchQueueProvider({
|
|
|
3366
5478
|
setQueue([]);
|
|
3367
5479
|
setExpanded(false);
|
|
3368
5480
|
}, []);
|
|
3369
|
-
const signAll = React.useCallback(async (
|
|
5481
|
+
const signAll = React.useCallback(async (identity) => {
|
|
3370
5482
|
if (queue.length === 0) {
|
|
3371
5483
|
return {
|
|
3372
5484
|
success: false,
|
|
@@ -3378,12 +5490,25 @@ function BatchQueueProvider({
|
|
|
3378
5490
|
}
|
|
3379
5491
|
};
|
|
3380
5492
|
}
|
|
5493
|
+
const signer = resolveIdentity(identity, providerIdentity);
|
|
5494
|
+
if (!signer.username && !signer.accountAddress) {
|
|
5495
|
+
return {
|
|
5496
|
+
success: false,
|
|
5497
|
+
intentId: "",
|
|
5498
|
+
status: "failed",
|
|
5499
|
+
error: {
|
|
5500
|
+
code: "MISSING_IDENTITY",
|
|
5501
|
+
message: "Batch signing requires an accountAddress or username"
|
|
5502
|
+
}
|
|
5503
|
+
};
|
|
5504
|
+
}
|
|
3381
5505
|
const targetChain = queue[0].targetChain;
|
|
3382
5506
|
const calls = queue.map((item) => item.call);
|
|
3383
5507
|
setIsSigning(true);
|
|
3384
5508
|
try {
|
|
3385
5509
|
const result = await client.sendIntent({
|
|
3386
|
-
username:
|
|
5510
|
+
username: signer.username,
|
|
5511
|
+
accountAddress: signer.accountAddress,
|
|
3387
5512
|
targetChain,
|
|
3388
5513
|
calls
|
|
3389
5514
|
});
|
|
@@ -3394,7 +5519,7 @@ function BatchQueueProvider({
|
|
|
3394
5519
|
} finally {
|
|
3395
5520
|
setIsSigning(false);
|
|
3396
5521
|
}
|
|
3397
|
-
}, [queue, client, clearBatch]);
|
|
5522
|
+
}, [queue, client, clearBatch, providerIdentity]);
|
|
3398
5523
|
const value = {
|
|
3399
5524
|
queue,
|
|
3400
5525
|
batchChainId,
|
|
@@ -3712,28 +5837,16 @@ function BatchQueueWidget({ onSignAll }) {
|
|
|
3712
5837
|
] })
|
|
3713
5838
|
] });
|
|
3714
5839
|
}
|
|
3715
|
-
|
|
3716
|
-
// src/verify.ts
|
|
3717
|
-
import { keccak256, toBytes } from "viem";
|
|
3718
|
-
var ETHEREUM_MESSAGE_PREFIX = "Ethereum Signed Message:\n";
|
|
3719
|
-
function hashMessage2(message) {
|
|
3720
|
-
const messageBytes = toBytes(message);
|
|
3721
|
-
const prefixed = ETHEREUM_MESSAGE_PREFIX + messageBytes.length.toString() + message;
|
|
3722
|
-
return keccak256(toBytes(prefixed));
|
|
3723
|
-
}
|
|
3724
|
-
function verifyMessageHash(message, signedHash) {
|
|
3725
|
-
if (!signedHash) return false;
|
|
3726
|
-
const expectedHash = hashMessage2(message);
|
|
3727
|
-
return expectedHash.toLowerCase() === signedHash.toLowerCase();
|
|
3728
|
-
}
|
|
3729
5840
|
export {
|
|
3730
5841
|
BatchQueueProvider,
|
|
3731
5842
|
BatchQueueWidget,
|
|
3732
5843
|
ETHEREUM_MESSAGE_PREFIX,
|
|
3733
5844
|
OneAuthClient,
|
|
5845
|
+
createCrossChainPermission,
|
|
3734
5846
|
createOneAuthProvider,
|
|
3735
5847
|
createPasskeyAccount,
|
|
3736
5848
|
createPasskeyWalletClient,
|
|
5849
|
+
definePermissions,
|
|
3737
5850
|
encodeWebAuthnSignature,
|
|
3738
5851
|
getAllSupportedChainsAndTokens,
|
|
3739
5852
|
getChainById,
|
|
@@ -3748,7 +5861,7 @@ export {
|
|
|
3748
5861
|
getTokenDecimals,
|
|
3749
5862
|
getTokenSymbol,
|
|
3750
5863
|
hashCalls,
|
|
3751
|
-
|
|
5864
|
+
hashMessage,
|
|
3752
5865
|
isTestnet,
|
|
3753
5866
|
isTokenAddressSupported,
|
|
3754
5867
|
resolveTokenAddress,
|