@thru/wallet 0.2.25 → 0.2.28
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 +1 -0
- package/dist/{BrowserSDK-CpRFiJsW.d.ts → BrowserSDK-CRQTOT8S.d.ts} +178 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +376 -12
- package/dist/index.js.map +1 -1
- package/dist/native/react/transparent.d.ts +104 -0
- package/dist/native/react/transparent.js +2210 -0
- package/dist/native/react/transparent.js.map +1 -0
- package/dist/native/react.d.ts +5 -90
- package/dist/native/react.js +765 -32
- package/dist/native/react.js.map +1 -1
- package/dist/native.d.ts +105 -1
- package/dist/native.js +521 -31
- package/dist/native.js.map +1 -1
- package/dist/react-ui.js +5 -0
- package/dist/react-ui.js.map +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/react.js +376 -12
- package/dist/react.js.map +1 -1
- package/package.json +8 -2
- package/src/BrowserSDK.ts +32 -1
- package/src/encoding.ts +39 -0
- package/src/index.ts +5 -1
- package/src/interfaces/IThruChain.ts +50 -1
- package/src/interfaces/types.ts +52 -0
- package/src/native/NativeSDK.test.ts +200 -1
- package/src/native/NativeSDK.ts +124 -10
- package/src/native/index.ts +12 -0
- package/src/native/provider/NativeProvider.ts +106 -5
- package/src/native/provider/WebViewBridge.test.ts +22 -1
- package/src/native/provider/WebViewBridge.ts +17 -7
- package/src/native/provider/chains/ThruChain.ts +215 -5
- package/src/native/react/ThruContext.ts +3 -1
- package/src/native/react/ThruProvider.tsx +25 -0
- package/src/native/react/ThruTransparentWalletBridge.tsx +281 -0
- package/src/native/react/hooks/useWallet.ts +12 -1
- package/src/native/react/index.ts +11 -0
- package/src/native/react/transparent.ts +35 -0
- package/src/protocol/postMessage.ts +127 -2
- package/src/provider/EmbeddedProvider.ts +7 -1
- package/src/provider/IframeManager.test.ts +18 -0
- package/src/provider/IframeManager.ts +8 -1
- package/src/provider/chains/ThruChain.ts +210 -4
- package/src/provider/types/messages.ts +16 -0
- package/src/react/index.ts +6 -0
- package/src/signing-sessions.test.ts +182 -0
- package/src/signing-sessions.ts +204 -0
package/dist/native/react.js
CHANGED
|
@@ -49,14 +49,20 @@ var AddressType = {
|
|
|
49
49
|
// src/protocol/postMessage.ts
|
|
50
50
|
var POST_MESSAGE_REQUEST_TYPES = {
|
|
51
51
|
CONNECT: "connect",
|
|
52
|
+
CREATE_ACCOUNT: "createAccount",
|
|
52
53
|
DISCONNECT: "disconnect",
|
|
53
54
|
SIGN_MESSAGE: "signMessage",
|
|
54
55
|
SIGN_TRANSACTION: "signTransaction",
|
|
56
|
+
SIGN_PASSKEY_CHALLENGE: "signPasskeyChallenge",
|
|
55
57
|
GET_ACCOUNTS: "getAccounts",
|
|
56
58
|
GET_CONNECTION_STATE: "getConnectionState",
|
|
57
59
|
GET_SIGNING_CONTEXT: "getSigningContext",
|
|
58
60
|
SELECT_ACCOUNT: "selectAccount",
|
|
59
|
-
MANAGE_ACCOUNTS: "manageAccounts"
|
|
61
|
+
MANAGE_ACCOUNTS: "manageAccounts",
|
|
62
|
+
CREATE_SIGNING_SESSION: "createSigningSession",
|
|
63
|
+
CREATE_SIGNING_SESSION_INSTRUCTION: "createSigningSessionInstruction",
|
|
64
|
+
CONFIRM_SIGNING_SESSION: "confirmSigningSession",
|
|
65
|
+
REVOKE_SIGNING_SESSION: "revokeSigningSession"
|
|
60
66
|
};
|
|
61
67
|
var EMBEDDED_PROVIDER_EVENTS = {
|
|
62
68
|
CONNECT_START: "connect_start",
|
|
@@ -86,12 +92,195 @@ function normalizeConnectionStateResult(result) {
|
|
|
86
92
|
return normalizeWalletAccountResult(result);
|
|
87
93
|
}
|
|
88
94
|
|
|
95
|
+
// src/encoding.ts
|
|
96
|
+
var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
97
|
+
var BASE64_LOOKUP = new Map(
|
|
98
|
+
[...BASE64_ALPHABET].map((char, index) => [char, index])
|
|
99
|
+
);
|
|
100
|
+
function base64ToBytes(value) {
|
|
101
|
+
const normalized = value.replace(/\s+/g, "");
|
|
102
|
+
if (normalized.length === 0) return new Uint8Array();
|
|
103
|
+
if (normalized.length % 4 === 1) {
|
|
104
|
+
throw new Error("Invalid base64 data");
|
|
105
|
+
}
|
|
106
|
+
const padded = normalized.padEnd(
|
|
107
|
+
normalized.length + (4 - normalized.length % 4) % 4,
|
|
108
|
+
"="
|
|
109
|
+
);
|
|
110
|
+
const padding = padded.endsWith("==") ? 2 : padded.endsWith("=") ? 1 : 0;
|
|
111
|
+
const output = new Uint8Array(padded.length / 4 * 3 - padding);
|
|
112
|
+
let outIdx = 0;
|
|
113
|
+
for (let i = 0; i < padded.length; i += 4) {
|
|
114
|
+
const chars = padded.slice(i, i + 4);
|
|
115
|
+
const a = BASE64_LOOKUP.get(chars[0]);
|
|
116
|
+
const b = BASE64_LOOKUP.get(chars[1]);
|
|
117
|
+
const c = chars[2] === "=" ? 0 : BASE64_LOOKUP.get(chars[2]);
|
|
118
|
+
const d = chars[3] === "=" ? 0 : BASE64_LOOKUP.get(chars[3]);
|
|
119
|
+
if (a === void 0 || b === void 0 || c === void 0 || d === void 0) {
|
|
120
|
+
throw new Error("Invalid base64 data");
|
|
121
|
+
}
|
|
122
|
+
const chunk = a << 18 | b << 12 | c << 6 | d;
|
|
123
|
+
if (outIdx < output.length) output[outIdx++] = chunk >> 16 & 255;
|
|
124
|
+
if (outIdx < output.length) output[outIdx++] = chunk >> 8 & 255;
|
|
125
|
+
if (outIdx < output.length) output[outIdx++] = chunk & 255;
|
|
126
|
+
}
|
|
127
|
+
return output;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/signing-sessions.ts
|
|
131
|
+
var STORAGE_VERSION = 1;
|
|
132
|
+
var KEY_PREFIX = "thru.wallet.signing-sessions.v1";
|
|
133
|
+
function encodeKeyPart(input) {
|
|
134
|
+
return encodeURIComponent(input).replace(
|
|
135
|
+
/[!'()*]/g,
|
|
136
|
+
(char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
function nowSeconds() {
|
|
140
|
+
return Math.floor(Date.now() / 1e3);
|
|
141
|
+
}
|
|
142
|
+
function resolveSigningSessionStorageKey(params) {
|
|
143
|
+
if (params.storageKey) return params.storageKey;
|
|
144
|
+
return `${KEY_PREFIX}:${encodeKeyPart(params.walletOrigin)}:${encodeKeyPart(params.appOrigin)}`;
|
|
145
|
+
}
|
|
146
|
+
function normalizeExpiresAt(value, label = "expiresAt") {
|
|
147
|
+
if (value instanceof Date) {
|
|
148
|
+
const millis = value.getTime();
|
|
149
|
+
if (!Number.isFinite(millis)) throw new Error(`${label} must be a valid Date`);
|
|
150
|
+
return Math.floor(millis / 1e3);
|
|
151
|
+
}
|
|
152
|
+
if (typeof value === "bigint") {
|
|
153
|
+
if (value < 0n || value > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
154
|
+
throw new Error(`${label} must fit in a JavaScript safe integer`);
|
|
155
|
+
}
|
|
156
|
+
return Number(value);
|
|
157
|
+
}
|
|
158
|
+
if (typeof value === "string") {
|
|
159
|
+
const trimmed = value.trim();
|
|
160
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
161
|
+
throw new Error(`${label} must be a Unix timestamp in seconds`);
|
|
162
|
+
}
|
|
163
|
+
return normalizeExpiresAt(BigInt(trimmed), label);
|
|
164
|
+
}
|
|
165
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
166
|
+
throw new Error(`${label} must be a finite positive Unix timestamp`);
|
|
167
|
+
}
|
|
168
|
+
return Math.floor(value);
|
|
169
|
+
}
|
|
170
|
+
function resolveSessionExpirySeconds(options) {
|
|
171
|
+
const hasDuration = options.durationSeconds !== void 0;
|
|
172
|
+
const hasExpiresAt = options.expiresAt !== void 0;
|
|
173
|
+
if (hasDuration === hasExpiresAt) {
|
|
174
|
+
throw new Error("Provide exactly one of durationSeconds or expiresAt");
|
|
175
|
+
}
|
|
176
|
+
if (hasDuration) {
|
|
177
|
+
const duration = options.durationSeconds;
|
|
178
|
+
if (typeof duration !== "number" || !Number.isFinite(duration) || duration <= 0) {
|
|
179
|
+
throw new Error("durationSeconds must be a positive number");
|
|
180
|
+
}
|
|
181
|
+
return nowSeconds() + Math.floor(duration);
|
|
182
|
+
}
|
|
183
|
+
return normalizeExpiresAt(options.expiresAt, "expiresAt");
|
|
184
|
+
}
|
|
185
|
+
function assertSigningSessionWalletAccountIdx(walletAccountIdx) {
|
|
186
|
+
if (!Number.isInteger(walletAccountIdx) || walletAccountIdx < 2 || walletAccountIdx > 65535) {
|
|
187
|
+
throw new Error("walletAccountIdx must be an account index between 2 and 65535");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function normalizeDescriptor(descriptor) {
|
|
191
|
+
return {
|
|
192
|
+
id: descriptor.id,
|
|
193
|
+
walletAddress: descriptor.walletAddress,
|
|
194
|
+
publicKey: descriptor.publicKey,
|
|
195
|
+
authIdx: Number(descriptor.authIdx),
|
|
196
|
+
expiresAt: normalizeExpiresAt(descriptor.expiresAt, "descriptor.expiresAt"),
|
|
197
|
+
createdAt: normalizeExpiresAt(descriptor.createdAt, "descriptor.createdAt")
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function isActive(descriptor) {
|
|
201
|
+
return nowSeconds() < descriptor.expiresAt;
|
|
202
|
+
}
|
|
203
|
+
var SigningSessionDescriptorStore = class {
|
|
204
|
+
constructor(storage, key) {
|
|
205
|
+
this.storage = storage;
|
|
206
|
+
this.key = key;
|
|
207
|
+
}
|
|
208
|
+
async list() {
|
|
209
|
+
const sessions = await this.read();
|
|
210
|
+
const active = sessions.filter(isActive);
|
|
211
|
+
if (active.length !== sessions.length) {
|
|
212
|
+
await this.write(active);
|
|
213
|
+
}
|
|
214
|
+
return active;
|
|
215
|
+
}
|
|
216
|
+
async get(id) {
|
|
217
|
+
const sessions = await this.list();
|
|
218
|
+
return sessions.find((session) => session.id === id) ?? null;
|
|
219
|
+
}
|
|
220
|
+
async save(descriptor) {
|
|
221
|
+
const normalized = normalizeDescriptor(descriptor);
|
|
222
|
+
const sessions = (await this.list()).filter((session) => session.id !== normalized.id);
|
|
223
|
+
sessions.push(normalized);
|
|
224
|
+
await this.write(sessions);
|
|
225
|
+
}
|
|
226
|
+
async saveReplacingWalletSessions(descriptor) {
|
|
227
|
+
const normalized = normalizeDescriptor(descriptor);
|
|
228
|
+
const sessions = (await this.list()).filter(
|
|
229
|
+
(session) => session.id === normalized.id || session.walletAddress !== normalized.walletAddress
|
|
230
|
+
);
|
|
231
|
+
const withoutCurrent = sessions.filter((session) => session.id !== normalized.id);
|
|
232
|
+
withoutCurrent.push(normalized);
|
|
233
|
+
await this.write(withoutCurrent);
|
|
234
|
+
}
|
|
235
|
+
async remove(id) {
|
|
236
|
+
const sessions = (await this.list()).filter((session) => session.id !== id);
|
|
237
|
+
if (sessions.length === 0) {
|
|
238
|
+
await this.storage.removeItem(this.key);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
await this.write(sessions);
|
|
242
|
+
}
|
|
243
|
+
async read() {
|
|
244
|
+
const raw = await this.storage.getItem(this.key);
|
|
245
|
+
if (!raw) return [];
|
|
246
|
+
try {
|
|
247
|
+
const parsed = JSON.parse(raw);
|
|
248
|
+
if (parsed.version !== STORAGE_VERSION || !Array.isArray(parsed.sessions)) {
|
|
249
|
+
await this.storage.removeItem(this.key);
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
return parsed.sessions.map(normalizeDescriptor);
|
|
253
|
+
} catch {
|
|
254
|
+
await this.storage.removeItem(this.key);
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async write(sessions) {
|
|
259
|
+
const payload = {
|
|
260
|
+
version: STORAGE_VERSION,
|
|
261
|
+
sessions: sessions.map(normalizeDescriptor)
|
|
262
|
+
};
|
|
263
|
+
await this.storage.setItem(this.key, JSON.stringify(payload));
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
89
267
|
// src/native/provider/chains/ThruChain.ts
|
|
268
|
+
function descriptorFromWire(session) {
|
|
269
|
+
return {
|
|
270
|
+
id: session.id,
|
|
271
|
+
walletAddress: session.walletAddress,
|
|
272
|
+
publicKey: session.publicKey,
|
|
273
|
+
authIdx: session.authIdx,
|
|
274
|
+
expiresAt: Number(BigInt(session.expiresAt)),
|
|
275
|
+
createdAt: Number(BigInt(session.createdAt))
|
|
276
|
+
};
|
|
277
|
+
}
|
|
90
278
|
var NativeThruChain = class {
|
|
91
|
-
constructor(bridge, provider, origin) {
|
|
279
|
+
constructor(bridge, provider, origin, signingSessions) {
|
|
92
280
|
this.bridge = bridge;
|
|
93
281
|
this.provider = provider;
|
|
94
282
|
this.origin = origin;
|
|
283
|
+
this.signingSessions = signingSessions;
|
|
95
284
|
}
|
|
96
285
|
get connected() {
|
|
97
286
|
return this.provider.isConnected();
|
|
@@ -109,7 +298,7 @@ var NativeThruChain = class {
|
|
|
109
298
|
await this.provider.disconnect();
|
|
110
299
|
}
|
|
111
300
|
async getSigningContext() {
|
|
112
|
-
if (!this.provider.isConnected()) {
|
|
301
|
+
if (!this.provider.isConnected() && !this.provider.isTransparent()) {
|
|
113
302
|
throw new Error("Wallet not connected");
|
|
114
303
|
}
|
|
115
304
|
const response = await this.bridge.sendMessage({
|
|
@@ -120,33 +309,179 @@ var NativeThruChain = class {
|
|
|
120
309
|
return response.result.signingContext;
|
|
121
310
|
}
|
|
122
311
|
async signTransaction(transaction) {
|
|
123
|
-
|
|
312
|
+
const signingSessionId = transaction.signingSessionId;
|
|
313
|
+
if (!signingSessionId && !this.provider.isConnected() && !this.provider.isTransparent()) {
|
|
124
314
|
throw new Error("Wallet not connected");
|
|
125
315
|
}
|
|
126
|
-
this.
|
|
316
|
+
const session = signingSessionId ? await this.requireSigningSession(signingSessionId) : null;
|
|
317
|
+
const shouldShowWallet = !signingSessionId;
|
|
318
|
+
if (shouldShowWallet) {
|
|
319
|
+
await this.provider.requestShow();
|
|
320
|
+
}
|
|
127
321
|
try {
|
|
128
322
|
const response = await this.bridge.sendMessage({
|
|
129
323
|
id: createRequestId(),
|
|
130
324
|
type: POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION,
|
|
131
325
|
payload: {
|
|
132
|
-
walletAddress: transaction.walletAddress,
|
|
326
|
+
walletAddress: transaction.walletAddress ?? session?.walletAddress,
|
|
133
327
|
programAddress: transaction.programAddress,
|
|
134
328
|
instructionData: transaction.instructionData,
|
|
135
329
|
readWriteAddresses: transaction.readWriteAddresses,
|
|
136
330
|
readOnlyAddresses: transaction.readOnlyAddresses,
|
|
137
|
-
review: transaction.review
|
|
331
|
+
review: transaction.review,
|
|
332
|
+
signingSessionId
|
|
138
333
|
},
|
|
139
334
|
origin: this.origin
|
|
140
335
|
});
|
|
141
336
|
return response.result.signedTransaction;
|
|
337
|
+
} finally {
|
|
338
|
+
if (shouldShowWallet) {
|
|
339
|
+
this.provider.requestHide();
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
async signPasskeyChallenge(challenge) {
|
|
344
|
+
if (!this.provider.isConnected() && !this.provider.isTransparent()) {
|
|
345
|
+
throw new Error("Wallet not connected");
|
|
346
|
+
}
|
|
347
|
+
await this.provider.requestShow();
|
|
348
|
+
try {
|
|
349
|
+
const response = await this.bridge.sendMessage({
|
|
350
|
+
id: createRequestId(),
|
|
351
|
+
type: POST_MESSAGE_REQUEST_TYPES.SIGN_PASSKEY_CHALLENGE,
|
|
352
|
+
payload: {
|
|
353
|
+
challenge: challenge.challenge,
|
|
354
|
+
walletAddress: challenge.walletAddress
|
|
355
|
+
},
|
|
356
|
+
origin: this.origin
|
|
357
|
+
});
|
|
358
|
+
return response.result;
|
|
142
359
|
} finally {
|
|
143
360
|
this.provider.requestHide();
|
|
144
361
|
}
|
|
145
362
|
}
|
|
363
|
+
async createSigningSession(options) {
|
|
364
|
+
if (!this.provider.isConnected()) {
|
|
365
|
+
throw new Error("Wallet not connected");
|
|
366
|
+
}
|
|
367
|
+
if (!this.signingSessions) {
|
|
368
|
+
throw new Error("NativeSDKStorage is required for signing sessions");
|
|
369
|
+
}
|
|
370
|
+
const expiresAt = resolveSessionExpirySeconds(options);
|
|
371
|
+
await this.provider.requestShow();
|
|
372
|
+
try {
|
|
373
|
+
const response = await this.bridge.sendMessage({
|
|
374
|
+
id: createRequestId(),
|
|
375
|
+
type: POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION,
|
|
376
|
+
payload: {
|
|
377
|
+
walletAddress: options.walletAddress,
|
|
378
|
+
expiresAt: String(expiresAt),
|
|
379
|
+
review: options.review
|
|
380
|
+
},
|
|
381
|
+
origin: this.origin
|
|
382
|
+
});
|
|
383
|
+
const descriptor = descriptorFromWire(response.result.session);
|
|
384
|
+
await this.signingSessions.saveReplacingWalletSessions(descriptor);
|
|
385
|
+
return this.toSigningSession(descriptor);
|
|
386
|
+
} finally {
|
|
387
|
+
this.provider.requestHide();
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
async createSigningSessionInstruction(options) {
|
|
391
|
+
if (!this.provider.isConnected()) {
|
|
392
|
+
throw new Error("Wallet not connected");
|
|
393
|
+
}
|
|
394
|
+
if (!this.signingSessions) {
|
|
395
|
+
throw new Error("NativeSDKStorage is required for signing sessions");
|
|
396
|
+
}
|
|
397
|
+
const expiresAt = resolveSessionExpirySeconds(options);
|
|
398
|
+
assertSigningSessionWalletAccountIdx(options.walletAccountIdx);
|
|
399
|
+
const response = await this.bridge.sendMessage({
|
|
400
|
+
id: createRequestId(),
|
|
401
|
+
type: POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION_INSTRUCTION,
|
|
402
|
+
payload: {
|
|
403
|
+
walletAddress: options.walletAddress,
|
|
404
|
+
expiresAt: String(expiresAt),
|
|
405
|
+
walletAccountIdx: options.walletAccountIdx
|
|
406
|
+
},
|
|
407
|
+
origin: this.origin
|
|
408
|
+
});
|
|
409
|
+
const descriptor = descriptorFromWire(response.result.session);
|
|
410
|
+
return {
|
|
411
|
+
session: this.toSigningSession(descriptor),
|
|
412
|
+
programAddress: response.result.programAddress,
|
|
413
|
+
instructionData: base64ToBytes(response.result.instructionData)
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
async confirmSigningSession(id) {
|
|
417
|
+
if (!this.provider.isConnected()) {
|
|
418
|
+
throw new Error("Wallet not connected");
|
|
419
|
+
}
|
|
420
|
+
if (!this.signingSessions) {
|
|
421
|
+
throw new Error("NativeSDKStorage is required for signing sessions");
|
|
422
|
+
}
|
|
423
|
+
const response = await this.bridge.sendMessage({
|
|
424
|
+
id: createRequestId(),
|
|
425
|
+
type: POST_MESSAGE_REQUEST_TYPES.CONFIRM_SIGNING_SESSION,
|
|
426
|
+
payload: { sessionId: id },
|
|
427
|
+
origin: this.origin
|
|
428
|
+
});
|
|
429
|
+
const descriptor = descriptorFromWire(response.result.session);
|
|
430
|
+
await this.signingSessions.saveReplacingWalletSessions(descriptor);
|
|
431
|
+
return this.toSigningSession(descriptor);
|
|
432
|
+
}
|
|
433
|
+
async getSigningSession(id) {
|
|
434
|
+
if (!this.signingSessions) return null;
|
|
435
|
+
const descriptor = await this.signingSessions.get(id);
|
|
436
|
+
return descriptor ? this.toSigningSession(descriptor) : null;
|
|
437
|
+
}
|
|
438
|
+
async getSigningSessions() {
|
|
439
|
+
if (!this.signingSessions) return [];
|
|
440
|
+
return (await this.signingSessions.list()).map(
|
|
441
|
+
(descriptor) => this.toSigningSession(descriptor)
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
async revokeSigningSession(id) {
|
|
445
|
+
try {
|
|
446
|
+
await this.bridge.sendMessage({
|
|
447
|
+
id: createRequestId(),
|
|
448
|
+
type: POST_MESSAGE_REQUEST_TYPES.REVOKE_SIGNING_SESSION,
|
|
449
|
+
payload: { sessionId: id },
|
|
450
|
+
origin: this.origin
|
|
451
|
+
});
|
|
452
|
+
} finally {
|
|
453
|
+
await this.signingSessions?.remove(id);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
async requireSigningSession(id) {
|
|
457
|
+
if (!this.signingSessions) {
|
|
458
|
+
throw new Error("NativeSDKStorage is required for signing sessions");
|
|
459
|
+
}
|
|
460
|
+
const session = await this.signingSessions.get(id);
|
|
461
|
+
if (!session) {
|
|
462
|
+
throw new Error("Signing session is not known to this app");
|
|
463
|
+
}
|
|
464
|
+
return session;
|
|
465
|
+
}
|
|
466
|
+
toSigningSession(descriptor) {
|
|
467
|
+
return {
|
|
468
|
+
...descriptor,
|
|
469
|
+
signTransaction: (transaction) => this.signTransaction({
|
|
470
|
+
...transaction,
|
|
471
|
+
walletAddress: transaction.walletAddress ?? descriptor.walletAddress,
|
|
472
|
+
signingSessionId: descriptor.id
|
|
473
|
+
}),
|
|
474
|
+
revoke: () => this.revokeSigningSession(descriptor.id),
|
|
475
|
+
toJSON: () => ({ ...descriptor })
|
|
476
|
+
};
|
|
477
|
+
}
|
|
146
478
|
};
|
|
147
479
|
|
|
148
480
|
// src/native/provider/WebViewBridge.ts
|
|
149
|
-
var PRODUCTION_WALLET_ORIGINS = [
|
|
481
|
+
var PRODUCTION_WALLET_ORIGINS = [
|
|
482
|
+
"https://wallet.thru.org",
|
|
483
|
+
"https://wallet.tid.sh"
|
|
484
|
+
];
|
|
150
485
|
function isDevelopmentBuild() {
|
|
151
486
|
const runtime = globalThis;
|
|
152
487
|
const devFlag = runtime.__DEV__;
|
|
@@ -184,14 +519,23 @@ function validateWalletOrigin(walletUrl) {
|
|
|
184
519
|
);
|
|
185
520
|
}
|
|
186
521
|
}
|
|
522
|
+
function isNativeEmbeddedWalletPath(pathname) {
|
|
523
|
+
const normalized = pathname.replace(/\/+$/, "") || "/";
|
|
524
|
+
return normalized === "/embedded/native" || normalized.startsWith("/embedded/native/");
|
|
525
|
+
}
|
|
187
526
|
var READY_TIMEOUT_MS = 1e4;
|
|
188
527
|
var SLOW_REQUEST_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
189
528
|
var FAST_REQUEST_TIMEOUT_MS = 30 * 1e3;
|
|
190
529
|
var SLOW_REQUEST_TYPES = /* @__PURE__ */ new Set([
|
|
191
530
|
POST_MESSAGE_REQUEST_TYPES.CONNECT,
|
|
531
|
+
POST_MESSAGE_REQUEST_TYPES.CREATE_ACCOUNT,
|
|
192
532
|
POST_MESSAGE_REQUEST_TYPES.SIGN_MESSAGE,
|
|
193
533
|
POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION,
|
|
194
|
-
POST_MESSAGE_REQUEST_TYPES.
|
|
534
|
+
POST_MESSAGE_REQUEST_TYPES.SIGN_PASSKEY_CHALLENGE,
|
|
535
|
+
POST_MESSAGE_REQUEST_TYPES.MANAGE_ACCOUNTS,
|
|
536
|
+
POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION,
|
|
537
|
+
POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION_INSTRUCTION,
|
|
538
|
+
POST_MESSAGE_REQUEST_TYPES.CONFIRM_SIGNING_SESSION
|
|
195
539
|
]);
|
|
196
540
|
var WebViewBridge = class {
|
|
197
541
|
constructor(options) {
|
|
@@ -213,7 +557,7 @@ var WebViewBridge = class {
|
|
|
213
557
|
*/
|
|
214
558
|
getIframeSrc() {
|
|
215
559
|
const url = new URL(this.walletUrl);
|
|
216
|
-
if (!url.pathname
|
|
560
|
+
if (!isNativeEmbeddedWalletPath(url.pathname)) {
|
|
217
561
|
url.pathname = `${url.pathname.replace(/\/$/, "")}/native`;
|
|
218
562
|
}
|
|
219
563
|
url.searchParams.set("tn_frame_id", this.frameId);
|
|
@@ -293,14 +637,11 @@ var WebViewBridge = class {
|
|
|
293
637
|
}
|
|
294
638
|
});
|
|
295
639
|
const script = `try {
|
|
296
|
-
var msg = ${JSON.stringify(request)};
|
|
640
|
+
var msg = ${JSON.stringify({ ...request, frameId: this.frameId })};
|
|
297
641
|
if (window.__pushIn) {
|
|
298
642
|
window.__pushIn(msg);
|
|
299
643
|
} else {
|
|
300
|
-
window.
|
|
301
|
-
data: msg,
|
|
302
|
-
origin: msg.origin || ''
|
|
303
|
-
}));
|
|
644
|
+
window.postMessage(msg, window.location.origin);
|
|
304
645
|
}
|
|
305
646
|
} catch (e) {} ; true;`;
|
|
306
647
|
this.webView.injectJavaScript(script);
|
|
@@ -376,11 +717,13 @@ var WebViewBridge = class {
|
|
|
376
717
|
// src/native/provider/NativeProvider.ts
|
|
377
718
|
var DEFAULT_WALLET_URL = "https://wallet.thru.org/embedded/native";
|
|
378
719
|
var DEFAULT_ORIGIN = "thru-mobile://app";
|
|
720
|
+
var TRANSPARENT_FOCUS_SETTLE_MS = 500;
|
|
379
721
|
var NativeProvider = class {
|
|
380
722
|
constructor(config = {}) {
|
|
381
723
|
this.connected = false;
|
|
382
724
|
this.accounts = [];
|
|
383
725
|
this.selectedAccount = null;
|
|
726
|
+
this.isSurfaceShown = false;
|
|
384
727
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
385
728
|
/** Pass through the WebView's `onMessage` event handler. */
|
|
386
729
|
this.onMessage = (event) => {
|
|
@@ -388,8 +731,12 @@ var NativeProvider = class {
|
|
|
388
731
|
};
|
|
389
732
|
const walletUrl = config.walletUrl ?? DEFAULT_WALLET_URL;
|
|
390
733
|
this.origin = config.origin ?? DEFAULT_ORIGIN;
|
|
734
|
+
this.transparent = config.walletExperience === "transparent";
|
|
391
735
|
this.bridge = new WebViewBridge({ walletUrl });
|
|
392
736
|
this.bridge.onEvent = (eventType, payload) => {
|
|
737
|
+
if (this.transparent && eventType === EMBEDDED_PROVIDER_EVENTS.UI_SHOW) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
393
740
|
this.emit(eventType, payload);
|
|
394
741
|
if (eventType === EMBEDDED_PROVIDER_EVENTS.UI_SHOW) {
|
|
395
742
|
this.requestShow();
|
|
@@ -407,7 +754,12 @@ var NativeProvider = class {
|
|
|
407
754
|
};
|
|
408
755
|
const addressTypes = config.addressTypes ?? [AddressType.THRU];
|
|
409
756
|
if (addressTypes.includes(AddressType.THRU)) {
|
|
410
|
-
this._thruChain = new NativeThruChain(
|
|
757
|
+
this._thruChain = new NativeThruChain(
|
|
758
|
+
this.bridge,
|
|
759
|
+
this,
|
|
760
|
+
this.origin,
|
|
761
|
+
config.signingSessions
|
|
762
|
+
);
|
|
411
763
|
}
|
|
412
764
|
}
|
|
413
765
|
/** Hand the bridge a WebView ref. Required before connect/sign. */
|
|
@@ -432,12 +784,27 @@ var NativeProvider = class {
|
|
|
432
784
|
async initialize() {
|
|
433
785
|
await this.bridge.awaitReady();
|
|
434
786
|
}
|
|
435
|
-
/** Open the wallet
|
|
436
|
-
|
|
787
|
+
/** Open or focus the wallet host surface. Transparent hosts use this
|
|
788
|
+
to give WKWebView a focused document for WebAuthn without showing
|
|
789
|
+
wallet UI. */
|
|
790
|
+
async requestShow() {
|
|
791
|
+
if (this.transparent) {
|
|
792
|
+
if (!this.isSurfaceShown) {
|
|
793
|
+
this.isSurfaceShown = true;
|
|
794
|
+
this.onShowRequested?.();
|
|
795
|
+
}
|
|
796
|
+
await new Promise(
|
|
797
|
+
(resolve) => setTimeout(resolve, TRANSPARENT_FOCUS_SETTLE_MS)
|
|
798
|
+
);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
if (this.isSurfaceShown) return;
|
|
802
|
+
this.isSurfaceShown = true;
|
|
437
803
|
this.onShowRequested?.();
|
|
438
804
|
}
|
|
439
805
|
/** Close the wallet UI (called internally; also exposed for host). */
|
|
440
806
|
requestHide() {
|
|
807
|
+
this.isSurfaceShown = false;
|
|
441
808
|
this.onHideRequested?.();
|
|
442
809
|
}
|
|
443
810
|
/** Reject pending requests after a user-driven native sheet dismiss. */
|
|
@@ -447,7 +814,7 @@ var NativeProvider = class {
|
|
|
447
814
|
async connect(options) {
|
|
448
815
|
this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT_START, {});
|
|
449
816
|
try {
|
|
450
|
-
this.requestShow();
|
|
817
|
+
await this.requestShow();
|
|
451
818
|
const payload = {};
|
|
452
819
|
if (options?.metadata) payload.metadata = options.metadata;
|
|
453
820
|
if (options?.preferredAccountAddress) {
|
|
@@ -461,6 +828,9 @@ var NativeProvider = class {
|
|
|
461
828
|
origin: this.origin
|
|
462
829
|
});
|
|
463
830
|
const result = normalizeWalletAccountResult(response.result);
|
|
831
|
+
if (!result.selectedAccount) {
|
|
832
|
+
throw new Error("Wallet did not return an account");
|
|
833
|
+
}
|
|
464
834
|
this.connected = true;
|
|
465
835
|
this.accounts = result.accounts;
|
|
466
836
|
this.selectedAccount = result.selectedAccount;
|
|
@@ -473,6 +843,52 @@ var NativeProvider = class {
|
|
|
473
843
|
throw error;
|
|
474
844
|
}
|
|
475
845
|
}
|
|
846
|
+
async createAccount(options) {
|
|
847
|
+
try {
|
|
848
|
+
await this.requestShow();
|
|
849
|
+
const payload = {};
|
|
850
|
+
if (options?.accountName) payload.accountName = options.accountName;
|
|
851
|
+
if (options?.metadata) payload.metadata = options.metadata;
|
|
852
|
+
const response = await this.bridge.sendMessage({
|
|
853
|
+
id: createRequestId(),
|
|
854
|
+
type: POST_MESSAGE_REQUEST_TYPES.CREATE_ACCOUNT,
|
|
855
|
+
payload,
|
|
856
|
+
origin: this.origin
|
|
857
|
+
});
|
|
858
|
+
const normalized = normalizeWalletAccountResult(
|
|
859
|
+
response.result,
|
|
860
|
+
response.result.selectedAccount ?? response.result.account
|
|
861
|
+
);
|
|
862
|
+
const selectedAccount = normalized.selectedAccount ?? response.result.account;
|
|
863
|
+
if (!selectedAccount) {
|
|
864
|
+
throw new Error("Wallet did not return a created account");
|
|
865
|
+
}
|
|
866
|
+
const result = {
|
|
867
|
+
...response.result,
|
|
868
|
+
accounts: normalized.accounts,
|
|
869
|
+
selectedAccount,
|
|
870
|
+
account: selectedAccount
|
|
871
|
+
};
|
|
872
|
+
this.connected = true;
|
|
873
|
+
this.accounts = result.accounts;
|
|
874
|
+
this.selectedAccount = result.selectedAccount;
|
|
875
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.CONNECT, {
|
|
876
|
+
accounts: result.accounts,
|
|
877
|
+
selectedAccount: result.selectedAccount,
|
|
878
|
+
status: "completed",
|
|
879
|
+
metadata: options?.metadata
|
|
880
|
+
});
|
|
881
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.ACCOUNT_CHANGED, {
|
|
882
|
+
account: result.selectedAccount
|
|
883
|
+
});
|
|
884
|
+
this.requestHide();
|
|
885
|
+
return result;
|
|
886
|
+
} catch (error) {
|
|
887
|
+
this.requestHide();
|
|
888
|
+
this.emit(EMBEDDED_PROVIDER_EVENTS.ERROR, { error });
|
|
889
|
+
throw error;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
476
892
|
async getConnectionState(options) {
|
|
477
893
|
const payload = {};
|
|
478
894
|
if (options?.metadata) payload.metadata = options.metadata;
|
|
@@ -521,6 +937,9 @@ var NativeProvider = class {
|
|
|
521
937
|
isConnected() {
|
|
522
938
|
return this.connected;
|
|
523
939
|
}
|
|
940
|
+
isTransparent() {
|
|
941
|
+
return this.transparent;
|
|
942
|
+
}
|
|
524
943
|
hydrateConnection(result, selectedAccountAddress) {
|
|
525
944
|
const selectedAccount = resolveWalletAccountByAddress(result.accounts, selectedAccountAddress) ?? result.selectedAccount ?? null;
|
|
526
945
|
const normalized = normalizeWalletAccountResult(result, selectedAccount);
|
|
@@ -555,7 +974,7 @@ var NativeProvider = class {
|
|
|
555
974
|
async manageAccounts() {
|
|
556
975
|
if (!this.connected) throw new Error("Wallet not connected");
|
|
557
976
|
try {
|
|
558
|
-
this.requestShow();
|
|
977
|
+
await this.requestShow();
|
|
559
978
|
const response = await this.bridge.sendMessage({
|
|
560
979
|
id: createRequestId(),
|
|
561
980
|
type: POST_MESSAGE_REQUEST_TYPES.MANAGE_ACCOUNTS,
|
|
@@ -617,6 +1036,9 @@ var NativeProvider = class {
|
|
|
617
1036
|
};
|
|
618
1037
|
var DEFAULT_STORAGE_KEY = "thru.native-sdk.connection.v1";
|
|
619
1038
|
var SELECTED_ACCOUNT_STORAGE_KEY_SUFFIX = ".selected-account.v1";
|
|
1039
|
+
var SIGNING_SESSION_STORAGE_KEY_SUFFIX = ".signing-sessions.v1";
|
|
1040
|
+
var DEFAULT_NATIVE_WALLET_URL = "https://wallet.thru.org/embedded/native";
|
|
1041
|
+
var DEFAULT_TRANSPARENT_WALLET_URL = "https://wallet.thru.org/embedded/native/transparent";
|
|
620
1042
|
var CHECKING_WALLET_AVAILABILITY = {
|
|
621
1043
|
status: "checking",
|
|
622
1044
|
isAuthorized: false,
|
|
@@ -629,6 +1051,17 @@ var CHECKING_WALLET_AVAILABILITY = {
|
|
|
629
1051
|
metadata: null,
|
|
630
1052
|
error: null
|
|
631
1053
|
};
|
|
1054
|
+
function completeAppMetadata(metadata) {
|
|
1055
|
+
if (!metadata?.appId || !metadata.appName || !metadata.appUrl) {
|
|
1056
|
+
return void 0;
|
|
1057
|
+
}
|
|
1058
|
+
return {
|
|
1059
|
+
appId: metadata.appId,
|
|
1060
|
+
appName: metadata.appName,
|
|
1061
|
+
appUrl: metadata.appUrl,
|
|
1062
|
+
...metadata.imageUrl ? { imageUrl: metadata.imageUrl } : {}
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
632
1065
|
var NativeSDK = class {
|
|
633
1066
|
constructor(config = {}) {
|
|
634
1067
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
@@ -647,10 +1080,25 @@ var NativeSDK = class {
|
|
|
647
1080
|
this.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
648
1081
|
this.selectedAccountStorageKey = config.selectedAccountStorageKey ?? `${this.storageKey}${SELECTED_ACCOUNT_STORAGE_KEY_SUFFIX}`;
|
|
649
1082
|
this.iosWebViewMode = config.iosWebViewMode ?? "shell-iframe";
|
|
1083
|
+
this.walletExperience = config.walletExperience ?? "standard";
|
|
1084
|
+
this.defaultMetadata = config.metadata;
|
|
1085
|
+
const walletUrl = config.walletUrl ?? (this.walletExperience === "transparent" ? DEFAULT_TRANSPARENT_WALLET_URL : DEFAULT_NATIVE_WALLET_URL);
|
|
1086
|
+
const walletOrigin = new URL(walletUrl).origin;
|
|
1087
|
+
const signingSessions = this.storage ? new SigningSessionDescriptorStore(
|
|
1088
|
+
this.storage,
|
|
1089
|
+
resolveSigningSessionStorageKey({
|
|
1090
|
+
walletOrigin,
|
|
1091
|
+
appOrigin: this.origin,
|
|
1092
|
+
storageKey: config.signingSessionStorageKey ?? `${this.storageKey}${SIGNING_SESSION_STORAGE_KEY_SUFFIX}`
|
|
1093
|
+
})
|
|
1094
|
+
) : void 0;
|
|
650
1095
|
this.provider = new NativeProvider({
|
|
651
|
-
walletUrl
|
|
1096
|
+
walletUrl,
|
|
652
1097
|
origin: this.origin,
|
|
653
|
-
|
|
1098
|
+
metadata: this.defaultMetadata ? this.resolveMetadata(this.defaultMetadata) : void 0,
|
|
1099
|
+
addressTypes: config.addressTypes ?? [AddressType.THRU],
|
|
1100
|
+
signingSessions,
|
|
1101
|
+
walletExperience: this.walletExperience
|
|
654
1102
|
});
|
|
655
1103
|
this.setupEventForwarding();
|
|
656
1104
|
}
|
|
@@ -701,10 +1149,10 @@ var NativeSDK = class {
|
|
|
701
1149
|
this.emit("connect", { status: "connecting" });
|
|
702
1150
|
const inFlight = (async () => {
|
|
703
1151
|
try {
|
|
704
|
-
this.provider.requestShow();
|
|
1152
|
+
await this.provider.requestShow();
|
|
705
1153
|
if (!this.initialized) await this.initialize();
|
|
706
1154
|
const metadata = this.resolveMetadata(options?.metadata);
|
|
707
|
-
const preferredAccountAddress = isAccountSwitch ? null : await this.readSelectedAccountAddress();
|
|
1155
|
+
const preferredAccountAddress = isAccountSwitch ? null : options?.preferredAccountAddress ?? await this.readSelectedAccountAddress();
|
|
708
1156
|
const providerOptions = metadata || preferredAccountAddress || options?.intent ? {
|
|
709
1157
|
...metadata ? { metadata } : {},
|
|
710
1158
|
...preferredAccountAddress ? { preferredAccountAddress } : {},
|
|
@@ -757,11 +1205,52 @@ var NativeSDK = class {
|
|
|
757
1205
|
...options.intent ? { intent: options.intent } : {}
|
|
758
1206
|
});
|
|
759
1207
|
}
|
|
1208
|
+
async createAccount(options = {}) {
|
|
1209
|
+
this.emit("connect", { status: "connecting" });
|
|
1210
|
+
try {
|
|
1211
|
+
await this.provider.requestShow();
|
|
1212
|
+
if (!this.initialized) await this.initialize();
|
|
1213
|
+
const metadata = this.resolveMetadata(options.metadata);
|
|
1214
|
+
const result = await this.provider.createAccount({
|
|
1215
|
+
...options.accountName ? { accountName: options.accountName } : {},
|
|
1216
|
+
...metadata ? { metadata } : {}
|
|
1217
|
+
});
|
|
1218
|
+
const selectedAccount = result.selectedAccount ?? result.account;
|
|
1219
|
+
const activeResult = {
|
|
1220
|
+
...result,
|
|
1221
|
+
accounts: this.provider.getAccounts(),
|
|
1222
|
+
selectedAccount,
|
|
1223
|
+
account: selectedAccount
|
|
1224
|
+
};
|
|
1225
|
+
const completedResult = {
|
|
1226
|
+
accounts: activeResult.accounts,
|
|
1227
|
+
selectedAccount: activeResult.selectedAccount,
|
|
1228
|
+
status: "completed",
|
|
1229
|
+
metadata: completeAppMetadata(metadata)
|
|
1230
|
+
};
|
|
1231
|
+
this.lastConnectResult = completedResult;
|
|
1232
|
+
await this.persistSelectedAccountAddress(
|
|
1233
|
+
activeResult.selectedAccount.address
|
|
1234
|
+
);
|
|
1235
|
+
await this.clearPersistedConnection();
|
|
1236
|
+
this.setWalletAvailability(
|
|
1237
|
+
walletAvailabilityFromConnectResult(completedResult)
|
|
1238
|
+
);
|
|
1239
|
+
this.emit("connect", completedResult);
|
|
1240
|
+
this.emit("accountChanged", activeResult.selectedAccount);
|
|
1241
|
+
return activeResult;
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
this.provider.requestHide();
|
|
1244
|
+
this.emit("error", error);
|
|
1245
|
+
throw error;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
760
1248
|
async disconnect() {
|
|
761
1249
|
try {
|
|
762
1250
|
await this.provider.disconnect();
|
|
763
1251
|
this.emit("disconnect", {});
|
|
764
1252
|
this.lastConnectResult = null;
|
|
1253
|
+
await this.persistSelectedAccountAddress(null);
|
|
765
1254
|
await this.clearPersistedConnection();
|
|
766
1255
|
this.clearAuthorizedAvailability();
|
|
767
1256
|
} catch (error) {
|
|
@@ -906,9 +1395,9 @@ var NativeSDK = class {
|
|
|
906
1395
|
}
|
|
907
1396
|
async requestConnectionState(options) {
|
|
908
1397
|
if (!this.initialized) await this.initialize();
|
|
909
|
-
const metadata = options?.metadata ?? this.lastConnectResult?.metadata ?? void 0;
|
|
1398
|
+
const metadata = options?.metadata ?? this.lastConnectResult?.metadata ?? this.defaultMetadata ?? void 0;
|
|
910
1399
|
const providerOptions = metadata ? { metadata: this.resolveMetadata(metadata) } : void 0;
|
|
911
|
-
const preferredAccountAddress = await this.readSelectedAccountAddress();
|
|
1400
|
+
const preferredAccountAddress = options?.preferredAccountAddress ?? await this.readSelectedAccountAddress();
|
|
912
1401
|
const nextProviderOptions = providerOptions || preferredAccountAddress ? {
|
|
913
1402
|
...providerOptions ?? {},
|
|
914
1403
|
...preferredAccountAddress ? { preferredAccountAddress } : {}
|
|
@@ -961,15 +1450,16 @@ var NativeSDK = class {
|
|
|
961
1450
|
});
|
|
962
1451
|
}
|
|
963
1452
|
resolveMetadata(input) {
|
|
964
|
-
|
|
1453
|
+
const effectiveInput = input ?? this.defaultMetadata;
|
|
1454
|
+
if (!effectiveInput) {
|
|
965
1455
|
return { appId: this.origin };
|
|
966
1456
|
}
|
|
967
1457
|
const metadata = {
|
|
968
|
-
appId:
|
|
1458
|
+
appId: effectiveInput.appId ?? this.origin
|
|
969
1459
|
};
|
|
970
|
-
if (
|
|
971
|
-
if (
|
|
972
|
-
if (
|
|
1460
|
+
if (effectiveInput.appUrl) metadata.appUrl = effectiveInput.appUrl;
|
|
1461
|
+
if (effectiveInput.appName) metadata.appName = effectiveInput.appName;
|
|
1462
|
+
if (effectiveInput.imageUrl) metadata.imageUrl = effectiveInput.imageUrl;
|
|
973
1463
|
return metadata;
|
|
974
1464
|
}
|
|
975
1465
|
resolveSignInMetadata(options) {
|
|
@@ -1230,6 +1720,27 @@ function ThruProvider({ children, config }) {
|
|
|
1230
1720
|
throw err;
|
|
1231
1721
|
}
|
|
1232
1722
|
}, [sdk]);
|
|
1723
|
+
const createAccount = useCallback(
|
|
1724
|
+
async (options) => {
|
|
1725
|
+
if (!sdk) throw new Error("NativeSDK not initialized");
|
|
1726
|
+
try {
|
|
1727
|
+
const result = await sdk.createAccount(options);
|
|
1728
|
+
setSelectedAccount(result.selectedAccount);
|
|
1729
|
+
setAccounts(result.accounts);
|
|
1730
|
+
setIsConnected(true);
|
|
1731
|
+
setIsConnecting(false);
|
|
1732
|
+
setWalletAvailability(sdk.getWalletAvailability());
|
|
1733
|
+
return result;
|
|
1734
|
+
} catch (err) {
|
|
1735
|
+
setError(
|
|
1736
|
+
err instanceof Error ? err : new Error("createAccount failed")
|
|
1737
|
+
);
|
|
1738
|
+
setIsConnecting(false);
|
|
1739
|
+
throw err;
|
|
1740
|
+
}
|
|
1741
|
+
},
|
|
1742
|
+
[sdk]
|
|
1743
|
+
);
|
|
1233
1744
|
return /* @__PURE__ */ jsx(
|
|
1234
1745
|
ThruContext.Provider,
|
|
1235
1746
|
{
|
|
@@ -1243,6 +1754,7 @@ function ThruProvider({ children, config }) {
|
|
|
1243
1754
|
selectedAccount,
|
|
1244
1755
|
walletAvailability,
|
|
1245
1756
|
selectAccount,
|
|
1757
|
+
createAccount,
|
|
1246
1758
|
manageAccounts
|
|
1247
1759
|
},
|
|
1248
1760
|
children
|
|
@@ -2266,6 +2778,219 @@ var styles = StyleSheet.create({
|
|
|
2266
2778
|
},
|
|
2267
2779
|
webview: { flex: 1, backgroundColor: "transparent" }
|
|
2268
2780
|
});
|
|
2781
|
+
function ThruTransparentWalletBridge({
|
|
2782
|
+
wallet: walletProp,
|
|
2783
|
+
style,
|
|
2784
|
+
webViewProps
|
|
2785
|
+
}) {
|
|
2786
|
+
const thruContext = useContext(ThruContext);
|
|
2787
|
+
const wallet = walletProp ?? thruContext?.wallet ?? null;
|
|
2788
|
+
const webViewRef = useRef(null);
|
|
2789
|
+
const webViewNativeTagRef = useRef(null);
|
|
2790
|
+
const didRefreshWalletAvailabilityRef = useRef(false);
|
|
2791
|
+
const [isFocusSurfaceActive, setIsFocusSurfaceActive] = useState(false);
|
|
2792
|
+
const attachIfReady = useCallback(() => {
|
|
2793
|
+
if (!wallet || !webViewRef.current) return;
|
|
2794
|
+
const ref = {
|
|
2795
|
+
injectJavaScript: (script) => {
|
|
2796
|
+
webViewRef.current?.injectJavaScript(script);
|
|
2797
|
+
}
|
|
2798
|
+
};
|
|
2799
|
+
wallet.attachWebView(ref);
|
|
2800
|
+
}, [wallet]);
|
|
2801
|
+
const enableAndroidWebAuthnIfNeeded = useCallback(async () => {
|
|
2802
|
+
if (Platform.OS !== "android") return false;
|
|
2803
|
+
const enabled = await enableWebAuthnSupport(webViewNativeTagRef.current);
|
|
2804
|
+
webViewRef.current?.injectJavaScript(
|
|
2805
|
+
"window.dispatchEvent(new Event('thru:native-webauthn-ready')); true;"
|
|
2806
|
+
);
|
|
2807
|
+
return enabled;
|
|
2808
|
+
}, []);
|
|
2809
|
+
const focusWebViewDocument = useCallback(() => {
|
|
2810
|
+
const webView = webViewRef.current;
|
|
2811
|
+
webView?.requestFocus?.();
|
|
2812
|
+
webViewRef.current?.injectJavaScript(
|
|
2813
|
+
"try { window.focus(); document.body && document.body.focus && document.body.focus(); } catch (_) {} true;"
|
|
2814
|
+
);
|
|
2815
|
+
}, []);
|
|
2816
|
+
const refreshWalletAvailabilityIfReady = useCallback(() => {
|
|
2817
|
+
if (!wallet || didRefreshWalletAvailabilityRef.current) return;
|
|
2818
|
+
didRefreshWalletAvailabilityRef.current = true;
|
|
2819
|
+
void wallet.refreshWalletAvailability();
|
|
2820
|
+
}, [wallet]);
|
|
2821
|
+
useEffect(() => {
|
|
2822
|
+
if (!wallet) return;
|
|
2823
|
+
wallet.setUiHandlers({
|
|
2824
|
+
onShowRequested: () => {
|
|
2825
|
+
setIsFocusSurfaceActive(true);
|
|
2826
|
+
},
|
|
2827
|
+
onHideRequested: () => {
|
|
2828
|
+
setIsFocusSurfaceActive(false);
|
|
2829
|
+
}
|
|
2830
|
+
});
|
|
2831
|
+
return () => {
|
|
2832
|
+
wallet.clearUiHandlers();
|
|
2833
|
+
};
|
|
2834
|
+
}, [focusWebViewDocument, wallet]);
|
|
2835
|
+
useEffect(() => {
|
|
2836
|
+
if (!isFocusSurfaceActive) return;
|
|
2837
|
+
const timers = [0, 50, 120, 250, 500].map(
|
|
2838
|
+
(delay) => setTimeout(focusWebViewDocument, delay)
|
|
2839
|
+
);
|
|
2840
|
+
return () => {
|
|
2841
|
+
timers.forEach(clearTimeout);
|
|
2842
|
+
};
|
|
2843
|
+
}, [focusWebViewDocument, isFocusSurfaceActive]);
|
|
2844
|
+
const webViewSource = useMemo(() => {
|
|
2845
|
+
if (!wallet) return null;
|
|
2846
|
+
if (Platform.OS === "ios" && wallet.getIosWebViewMode() === "direct") {
|
|
2847
|
+
return { uri: wallet.getIframeSrc() };
|
|
2848
|
+
}
|
|
2849
|
+
return {
|
|
2850
|
+
html: getShellHtml({
|
|
2851
|
+
walletUrl: wallet.getIframeSrc(),
|
|
2852
|
+
walletOrigin: wallet.getWalletOrigin()
|
|
2853
|
+
}),
|
|
2854
|
+
baseUrl: wallet.getWalletOrigin()
|
|
2855
|
+
};
|
|
2856
|
+
}, [wallet]);
|
|
2857
|
+
const isDirectWalletSource = Boolean(
|
|
2858
|
+
wallet && Platform.OS === "ios" && wallet.getIosWebViewMode() === "direct"
|
|
2859
|
+
);
|
|
2860
|
+
useEffect(() => {
|
|
2861
|
+
didRefreshWalletAvailabilityRef.current = false;
|
|
2862
|
+
}, [webViewSource]);
|
|
2863
|
+
const handleWebViewLayout = useCallback(
|
|
2864
|
+
(event) => {
|
|
2865
|
+
const target = event.nativeEvent.target;
|
|
2866
|
+
webViewNativeTagRef.current = typeof target === "number" ? target : webViewNativeTagRef.current;
|
|
2867
|
+
void enableAndroidWebAuthnIfNeeded();
|
|
2868
|
+
webViewProps?.onLayout?.(event);
|
|
2869
|
+
},
|
|
2870
|
+
[enableAndroidWebAuthnIfNeeded, webViewProps]
|
|
2871
|
+
);
|
|
2872
|
+
const handleLoadEnd = useCallback(
|
|
2873
|
+
(event) => {
|
|
2874
|
+
attachIfReady();
|
|
2875
|
+
if (isDirectWalletSource) {
|
|
2876
|
+
void enableAndroidWebAuthnIfNeeded().finally(
|
|
2877
|
+
refreshWalletAvailabilityIfReady
|
|
2878
|
+
);
|
|
2879
|
+
} else {
|
|
2880
|
+
void enableAndroidWebAuthnIfNeeded();
|
|
2881
|
+
}
|
|
2882
|
+
webViewProps?.onLoadEnd?.(event);
|
|
2883
|
+
},
|
|
2884
|
+
[
|
|
2885
|
+
attachIfReady,
|
|
2886
|
+
enableAndroidWebAuthnIfNeeded,
|
|
2887
|
+
isDirectWalletSource,
|
|
2888
|
+
refreshWalletAvailabilityIfReady,
|
|
2889
|
+
webViewProps
|
|
2890
|
+
]
|
|
2891
|
+
);
|
|
2892
|
+
const handleMessage = useCallback(
|
|
2893
|
+
(event) => {
|
|
2894
|
+
let shouldRefreshAfterBridgeReady = false;
|
|
2895
|
+
let shouldCollapseFocusSurface = false;
|
|
2896
|
+
try {
|
|
2897
|
+
const data = JSON.parse(event.nativeEvent.data);
|
|
2898
|
+
shouldRefreshAfterBridgeReady = data.type === "iframe:ready";
|
|
2899
|
+
shouldCollapseFocusSurface = typeof data.id === "string" && typeof data.success === "boolean";
|
|
2900
|
+
} catch {
|
|
2901
|
+
}
|
|
2902
|
+
if (shouldCollapseFocusSurface) {
|
|
2903
|
+
setIsFocusSurfaceActive(false);
|
|
2904
|
+
}
|
|
2905
|
+
wallet?.onMessage({
|
|
2906
|
+
nativeEvent: { data: event.nativeEvent.data }
|
|
2907
|
+
});
|
|
2908
|
+
webViewProps?.onMessage?.(event);
|
|
2909
|
+
if (shouldRefreshAfterBridgeReady) {
|
|
2910
|
+
void enableAndroidWebAuthnIfNeeded().finally(
|
|
2911
|
+
refreshWalletAvailabilityIfReady
|
|
2912
|
+
);
|
|
2913
|
+
}
|
|
2914
|
+
},
|
|
2915
|
+
[
|
|
2916
|
+
enableAndroidWebAuthnIfNeeded,
|
|
2917
|
+
refreshWalletAvailabilityIfReady,
|
|
2918
|
+
wallet,
|
|
2919
|
+
webViewProps
|
|
2920
|
+
]
|
|
2921
|
+
);
|
|
2922
|
+
if (!webViewSource) return null;
|
|
2923
|
+
return /* @__PURE__ */ jsx(
|
|
2924
|
+
View,
|
|
2925
|
+
{
|
|
2926
|
+
collapsable: false,
|
|
2927
|
+
pointerEvents: isFocusSurfaceActive ? "auto" : "none",
|
|
2928
|
+
style: [
|
|
2929
|
+
styles2.container,
|
|
2930
|
+
isFocusSurfaceActive ? styles2.activeContainer : null,
|
|
2931
|
+
style
|
|
2932
|
+
],
|
|
2933
|
+
children: /* @__PURE__ */ jsx(
|
|
2934
|
+
WebView,
|
|
2935
|
+
{
|
|
2936
|
+
...webViewProps,
|
|
2937
|
+
ref: webViewRef,
|
|
2938
|
+
source: webViewSource,
|
|
2939
|
+
originWhitelist: ["*"],
|
|
2940
|
+
javaScriptEnabled: true,
|
|
2941
|
+
domStorageEnabled: true,
|
|
2942
|
+
webviewDebuggingEnabled: __DEV__,
|
|
2943
|
+
sharedCookiesEnabled: true,
|
|
2944
|
+
allowsInlineMediaPlayback: true,
|
|
2945
|
+
mediaPlaybackRequiresUserAction: false,
|
|
2946
|
+
limitsNavigationsToAppBoundDomains: isDirectWalletSource,
|
|
2947
|
+
onLoadStart: (event) => {
|
|
2948
|
+
attachIfReady();
|
|
2949
|
+
void enableAndroidWebAuthnIfNeeded();
|
|
2950
|
+
webViewProps?.onLoadStart?.(event);
|
|
2951
|
+
},
|
|
2952
|
+
onLoadEnd: handleLoadEnd,
|
|
2953
|
+
onLayout: handleWebViewLayout,
|
|
2954
|
+
onMessage: handleMessage,
|
|
2955
|
+
style: [
|
|
2956
|
+
styles2.webview,
|
|
2957
|
+
isFocusSurfaceActive ? styles2.activeWebview : null,
|
|
2958
|
+
webViewProps?.style
|
|
2959
|
+
]
|
|
2960
|
+
}
|
|
2961
|
+
)
|
|
2962
|
+
}
|
|
2963
|
+
);
|
|
2964
|
+
}
|
|
2965
|
+
var styles2 = StyleSheet.create({
|
|
2966
|
+
container: {
|
|
2967
|
+
height: 1,
|
|
2968
|
+
left: 0,
|
|
2969
|
+
opacity: 0,
|
|
2970
|
+
overflow: "hidden",
|
|
2971
|
+
position: "absolute",
|
|
2972
|
+
top: 0,
|
|
2973
|
+
width: 1
|
|
2974
|
+
},
|
|
2975
|
+
activeContainer: {
|
|
2976
|
+
bottom: 0,
|
|
2977
|
+
height: "100%",
|
|
2978
|
+
opacity: 1,
|
|
2979
|
+
right: 0,
|
|
2980
|
+
width: "100%",
|
|
2981
|
+
zIndex: 2147483647
|
|
2982
|
+
},
|
|
2983
|
+
webview: {
|
|
2984
|
+
backgroundColor: "transparent",
|
|
2985
|
+
height: 1,
|
|
2986
|
+
width: 1
|
|
2987
|
+
},
|
|
2988
|
+
activeWebview: {
|
|
2989
|
+
flex: 1,
|
|
2990
|
+
height: "100%",
|
|
2991
|
+
width: "100%"
|
|
2992
|
+
}
|
|
2993
|
+
});
|
|
2269
2994
|
|
|
2270
2995
|
// src/native/react/hooks/waitForWallet.ts
|
|
2271
2996
|
function waitForWallet(getWallet, timeout = 5e3, interval = 100) {
|
|
@@ -2307,6 +3032,13 @@ function useWallet() {
|
|
|
2307
3032
|
const ready = walletRef.current ?? await waitForWallet(() => walletRef.current);
|
|
2308
3033
|
return ready.signIn(options);
|
|
2309
3034
|
}, []);
|
|
3035
|
+
const createTransparentAccount = useCallback(
|
|
3036
|
+
async (options) => {
|
|
3037
|
+
const ready = walletRef.current ?? await waitForWallet(() => walletRef.current);
|
|
3038
|
+
return ready.createAccount(options);
|
|
3039
|
+
},
|
|
3040
|
+
[]
|
|
3041
|
+
);
|
|
2310
3042
|
const disconnect = useCallback(async () => {
|
|
2311
3043
|
const ready = walletRef.current ?? await waitForWallet(() => walletRef.current);
|
|
2312
3044
|
await ready.disconnect();
|
|
@@ -2321,6 +3053,7 @@ function useWallet() {
|
|
|
2321
3053
|
accounts,
|
|
2322
3054
|
connect,
|
|
2323
3055
|
signIn,
|
|
3056
|
+
createAccount: createTransparentAccount,
|
|
2324
3057
|
disconnect,
|
|
2325
3058
|
isConnected: isConnected && !!wallet,
|
|
2326
3059
|
isConnecting,
|
|
@@ -2376,6 +3109,6 @@ function useAccounts({ onAccountSelect } = {}) {
|
|
|
2376
3109
|
};
|
|
2377
3110
|
}
|
|
2378
3111
|
|
|
2379
|
-
export { ThruContext, ThruProvider, ThruWalletSheet, enableWebAuthnSupport, useAccounts, useThru, useWallet, useWalletAvailability };
|
|
3112
|
+
export { ThruContext, ThruProvider, ThruTransparentWalletBridge, ThruWalletSheet, enableWebAuthnSupport, useAccounts, useThru, useWallet, useWalletAvailability };
|
|
2380
3113
|
//# sourceMappingURL=react.js.map
|
|
2381
3114
|
//# sourceMappingURL=react.js.map
|