@unicitylabs/sphere-sdk 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/connect/index.cjs +802 -110
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.d.cts +305 -16
- package/dist/connect/index.d.ts +305 -16
- package/dist/connect/index.js +802 -110
- package/dist/connect/index.js.map +1 -1
- package/dist/core/index.cjs +20 -1
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +4 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +20 -1
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/connect/index.cjs +208 -41
- package/dist/impl/browser/connect/index.cjs.map +1 -1
- package/dist/impl/browser/connect/index.d.cts +33 -1
- package/dist/impl/browser/connect/index.d.ts +33 -1
- package/dist/impl/browser/connect/index.js +208 -41
- package/dist/impl/browser/connect/index.js.map +1 -1
- package/dist/impl/nodejs/connect/index.cjs +101 -1
- package/dist/impl/nodejs/connect/index.cjs.map +1 -1
- package/dist/impl/nodejs/connect/index.d.cts +6 -1
- package/dist/impl/nodejs/connect/index.d.ts +6 -1
- package/dist/impl/nodejs/connect/index.js +101 -1
- package/dist/impl/nodejs/connect/index.js.map +1 -1
- package/dist/index.cjs +20 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +20 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
|
@@ -14,7 +14,7 @@ interface NetworkInfo {
|
|
|
14
14
|
* JSON-RPC-like message types for wallet ↔ dApp communication.
|
|
15
15
|
*/
|
|
16
16
|
declare const SPHERE_CONNECT_NAMESPACE = "sphere-connect";
|
|
17
|
-
declare const SPHERE_CONNECT_VERSION = "2.
|
|
17
|
+
declare const SPHERE_CONNECT_VERSION = "2.1";
|
|
18
18
|
|
|
19
19
|
interface SphereMessageBase {
|
|
20
20
|
readonly ns: typeof SPHERE_CONNECT_NAMESPACE;
|
|
@@ -72,6 +72,11 @@ interface SphereHandshake extends SphereMessageBase {
|
|
|
72
72
|
readonly error?: SphereRpcError;
|
|
73
73
|
/** Response: non-fatal deprecation notice (does not block the connection). */
|
|
74
74
|
readonly warning?: SphereRpcError;
|
|
75
|
+
/** Response only: the wallet is LOCKED and the session is ALIVE. The dApp is connected
|
|
76
|
+
* and must not re-handshake; it will receive `wallet:unlocked` on the same session.
|
|
77
|
+
* Additive and safe for old clients: handleHandshakeResponse reads only sessionId,
|
|
78
|
+
* permissions, identity, network, warning and error and ignores unknown fields. */
|
|
79
|
+
readonly locked?: boolean;
|
|
75
80
|
}
|
|
76
81
|
interface SphereRpcError {
|
|
77
82
|
readonly code: number;
|
|
@@ -152,6 +157,9 @@ interface ConnectResult {
|
|
|
152
157
|
readonly sessionId: string;
|
|
153
158
|
readonly permissions: PermissionScope[];
|
|
154
159
|
readonly identity: PublicIdentity;
|
|
160
|
+
/** True when the wallet is locked but the session is alive — a resume during a lock,
|
|
161
|
+
* the most common entry into this feature. Requests answer 4009 until wallet:unlocked. */
|
|
162
|
+
readonly locked?: boolean;
|
|
155
163
|
}
|
|
156
164
|
type ConnectEventHandler = (data: unknown) => void;
|
|
157
165
|
|
|
@@ -178,6 +186,8 @@ declare class ConnectClient {
|
|
|
178
186
|
private grantedPermissions;
|
|
179
187
|
private identity;
|
|
180
188
|
private walletNet;
|
|
189
|
+
private walletProto;
|
|
190
|
+
private locked;
|
|
181
191
|
private connected;
|
|
182
192
|
private pendingRequests;
|
|
183
193
|
private eventHandlers;
|
|
@@ -198,6 +208,27 @@ declare class ConnectClient {
|
|
|
198
208
|
get walletIdentity(): PublicIdentity | null;
|
|
199
209
|
/** Wallet's active network, received during handshake. */
|
|
200
210
|
get walletNetwork(): NetworkInfo | null;
|
|
211
|
+
/**
|
|
212
|
+
* The wallet's Connect protocol version, captured from the handshake response `v`.
|
|
213
|
+
* Null before the first handshake response and after a disconnect.
|
|
214
|
+
*
|
|
215
|
+
* Feature-detect with it: compare against '2.1' to decide whether the wallet can be
|
|
216
|
+
* trusted to send wallet:unlocked / wallet:disconnected. A Connect 2.0 wallet destroys
|
|
217
|
+
* the session on lock and never emits either, so a dApp waiting for them against one
|
|
218
|
+
* waits forever.
|
|
219
|
+
*
|
|
220
|
+
* CAVEAT: on an ERROR response the host echoes the dApp's own `v` back
|
|
221
|
+
* (ConnectHost.sendHandshakeResponse), so after a refused connection this may be the
|
|
222
|
+
* dApp's version rather than the wallet's. Only trust it after a successful handshake.
|
|
223
|
+
*/
|
|
224
|
+
get walletProtocol(): string | null;
|
|
225
|
+
/**
|
|
226
|
+
* Whether the wallet was locked at the last handshake or lifecycle event.
|
|
227
|
+
* A locked client is still CONNECTED: `isConnected` stays true and `session` stays valid.
|
|
228
|
+
* Requests answer WALLET_LOCKED (4009) until `wallet:unlocked` arrives on the SAME
|
|
229
|
+
* session — do not disconnect, do not clear the session, do not re-handshake.
|
|
230
|
+
*/
|
|
231
|
+
get walletLocked(): boolean;
|
|
201
232
|
/** Send a query request and return the result */
|
|
202
233
|
query<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T>;
|
|
203
234
|
/** Send an intent request. The wallet will open its UI for user confirmation. */
|
|
@@ -205,6 +236,7 @@ declare class ConnectClient {
|
|
|
205
236
|
/** Subscribe to a wallet event. Returns unsubscribe function. */
|
|
206
237
|
on(event: string, handler: ConnectEventHandler): () => void;
|
|
207
238
|
private handleMessage;
|
|
239
|
+
private dispatchEvent;
|
|
208
240
|
private handleHandshakeResponse;
|
|
209
241
|
private handlePendingResponse;
|
|
210
242
|
private cleanup;
|
|
@@ -14,7 +14,7 @@ interface NetworkInfo {
|
|
|
14
14
|
* JSON-RPC-like message types for wallet ↔ dApp communication.
|
|
15
15
|
*/
|
|
16
16
|
declare const SPHERE_CONNECT_NAMESPACE = "sphere-connect";
|
|
17
|
-
declare const SPHERE_CONNECT_VERSION = "2.
|
|
17
|
+
declare const SPHERE_CONNECT_VERSION = "2.1";
|
|
18
18
|
|
|
19
19
|
interface SphereMessageBase {
|
|
20
20
|
readonly ns: typeof SPHERE_CONNECT_NAMESPACE;
|
|
@@ -72,6 +72,11 @@ interface SphereHandshake extends SphereMessageBase {
|
|
|
72
72
|
readonly error?: SphereRpcError;
|
|
73
73
|
/** Response: non-fatal deprecation notice (does not block the connection). */
|
|
74
74
|
readonly warning?: SphereRpcError;
|
|
75
|
+
/** Response only: the wallet is LOCKED and the session is ALIVE. The dApp is connected
|
|
76
|
+
* and must not re-handshake; it will receive `wallet:unlocked` on the same session.
|
|
77
|
+
* Additive and safe for old clients: handleHandshakeResponse reads only sessionId,
|
|
78
|
+
* permissions, identity, network, warning and error and ignores unknown fields. */
|
|
79
|
+
readonly locked?: boolean;
|
|
75
80
|
}
|
|
76
81
|
interface SphereRpcError {
|
|
77
82
|
readonly code: number;
|
|
@@ -152,6 +157,9 @@ interface ConnectResult {
|
|
|
152
157
|
readonly sessionId: string;
|
|
153
158
|
readonly permissions: PermissionScope[];
|
|
154
159
|
readonly identity: PublicIdentity;
|
|
160
|
+
/** True when the wallet is locked but the session is alive — a resume during a lock,
|
|
161
|
+
* the most common entry into this feature. Requests answer 4009 until wallet:unlocked. */
|
|
162
|
+
readonly locked?: boolean;
|
|
155
163
|
}
|
|
156
164
|
type ConnectEventHandler = (data: unknown) => void;
|
|
157
165
|
|
|
@@ -178,6 +186,8 @@ declare class ConnectClient {
|
|
|
178
186
|
private grantedPermissions;
|
|
179
187
|
private identity;
|
|
180
188
|
private walletNet;
|
|
189
|
+
private walletProto;
|
|
190
|
+
private locked;
|
|
181
191
|
private connected;
|
|
182
192
|
private pendingRequests;
|
|
183
193
|
private eventHandlers;
|
|
@@ -198,6 +208,27 @@ declare class ConnectClient {
|
|
|
198
208
|
get walletIdentity(): PublicIdentity | null;
|
|
199
209
|
/** Wallet's active network, received during handshake. */
|
|
200
210
|
get walletNetwork(): NetworkInfo | null;
|
|
211
|
+
/**
|
|
212
|
+
* The wallet's Connect protocol version, captured from the handshake response `v`.
|
|
213
|
+
* Null before the first handshake response and after a disconnect.
|
|
214
|
+
*
|
|
215
|
+
* Feature-detect with it: compare against '2.1' to decide whether the wallet can be
|
|
216
|
+
* trusted to send wallet:unlocked / wallet:disconnected. A Connect 2.0 wallet destroys
|
|
217
|
+
* the session on lock and never emits either, so a dApp waiting for them against one
|
|
218
|
+
* waits forever.
|
|
219
|
+
*
|
|
220
|
+
* CAVEAT: on an ERROR response the host echoes the dApp's own `v` back
|
|
221
|
+
* (ConnectHost.sendHandshakeResponse), so after a refused connection this may be the
|
|
222
|
+
* dApp's version rather than the wallet's. Only trust it after a successful handshake.
|
|
223
|
+
*/
|
|
224
|
+
get walletProtocol(): string | null;
|
|
225
|
+
/**
|
|
226
|
+
* Whether the wallet was locked at the last handshake or lifecycle event.
|
|
227
|
+
* A locked client is still CONNECTED: `isConnected` stays true and `session` stays valid.
|
|
228
|
+
* Requests answer WALLET_LOCKED (4009) until `wallet:unlocked` arrives on the SAME
|
|
229
|
+
* session — do not disconnect, do not clear the session, do not re-handshake.
|
|
230
|
+
*/
|
|
231
|
+
get walletLocked(): boolean;
|
|
201
232
|
/** Send a query request and return the result */
|
|
202
233
|
query<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T>;
|
|
203
234
|
/** Send an intent request. The wallet will open its UI for user confirmation. */
|
|
@@ -205,6 +236,7 @@ declare class ConnectClient {
|
|
|
205
236
|
/** Subscribe to a wallet event. Returns unsubscribe function. */
|
|
206
237
|
on(event: string, handler: ConnectEventHandler): () => void;
|
|
207
238
|
private handleMessage;
|
|
239
|
+
private dispatchEvent;
|
|
208
240
|
private handleHandshakeResponse;
|
|
209
241
|
private handlePendingResponse;
|
|
210
242
|
private cleanup;
|
|
@@ -90,26 +90,6 @@ var logger = {
|
|
|
90
90
|
}
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
-
// core/errors.ts
|
|
94
|
-
var SphereError = class extends Error {
|
|
95
|
-
code;
|
|
96
|
-
cause;
|
|
97
|
-
/**
|
|
98
|
-
* #441 deferred-paid linkage: the transferId of the possibly-committed send,
|
|
99
|
-
* stamped by `sendOnce` before the error leaves so a payment-request consumer
|
|
100
|
-
* can durably journal request→transfer and resolve 'paid' only when that
|
|
101
|
-
* transfer actually completes (never optimistically). Present ONLY on
|
|
102
|
-
* possibly-committed outcomes ({@link isPossiblyCommittedSendOutcome}).
|
|
103
|
-
*/
|
|
104
|
-
transferId;
|
|
105
|
-
constructor(message, code, cause) {
|
|
106
|
-
super(message);
|
|
107
|
-
this.name = "SphereError";
|
|
108
|
-
this.code = code;
|
|
109
|
-
this.cause = cause;
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
|
|
113
93
|
// connect/semver.ts
|
|
114
94
|
function majorOf(v) {
|
|
115
95
|
return parseInt(String(v).split(".")[0], 10);
|
|
@@ -308,7 +288,7 @@ var HOST_READY_TIMEOUT = 3e4;
|
|
|
308
288
|
|
|
309
289
|
// connect/protocol.ts
|
|
310
290
|
var SPHERE_CONNECT_NAMESPACE = "sphere-connect";
|
|
311
|
-
var SPHERE_CONNECT_VERSION = "2.
|
|
291
|
+
var SPHERE_CONNECT_VERSION = "2.1";
|
|
312
292
|
var RPC_METHODS = {
|
|
313
293
|
GET_IDENTITY: "sphere_getIdentity",
|
|
314
294
|
GET_BALANCE: "sphere_getBalance",
|
|
@@ -344,6 +324,78 @@ var INTENT_ACTIONS = {
|
|
|
344
324
|
SET_AUTO_RETURN: "set_auto_return",
|
|
345
325
|
MINT: "mint"
|
|
346
326
|
};
|
|
327
|
+
var ERROR_CODES = {
|
|
328
|
+
// Standard JSON-RPC
|
|
329
|
+
PARSE_ERROR: -32700,
|
|
330
|
+
INVALID_REQUEST: -32600,
|
|
331
|
+
METHOD_NOT_FOUND: -32601,
|
|
332
|
+
INVALID_PARAMS: -32602,
|
|
333
|
+
INTERNAL_ERROR: -32603,
|
|
334
|
+
// Sphere Connect (4xxx)
|
|
335
|
+
NOT_CONNECTED: 4001,
|
|
336
|
+
PERMISSION_DENIED: 4002,
|
|
337
|
+
USER_REJECTED: 4003,
|
|
338
|
+
SESSION_EXPIRED: 4004,
|
|
339
|
+
ORIGIN_BLOCKED: 4005,
|
|
340
|
+
RATE_LIMITED: 4006,
|
|
341
|
+
UNSUPPORTED_PROTOCOL_VERSION: 4007,
|
|
342
|
+
// Connect MAJOR mismatch (incompatible era)
|
|
343
|
+
INCOMPATIBLE_NETWORK: 4008,
|
|
344
|
+
// dApp targets a different network than the wallet
|
|
345
|
+
// Wallet locked; THE SESSION IS STILL ALIVE. A QUERY may be retried after wallet:unlocked.
|
|
346
|
+
// An INTENT already delegated to the wallet is NEVER answered with this code — it gets
|
|
347
|
+
// INTENT_OUTCOME_UNKNOWN (4201) instead, because a retry could double-spend.
|
|
348
|
+
WALLET_LOCKED: 4009,
|
|
349
|
+
INSUFFICIENT_BALANCE: 4100,
|
|
350
|
+
INVALID_RECIPIENT: 4101,
|
|
351
|
+
TRANSFER_FAILED: 4102,
|
|
352
|
+
INTENT_CANCELLED: 4200,
|
|
353
|
+
/**
|
|
354
|
+
* The intent was DELEGATED to the wallet and the host lost track of the answer — a host
|
|
355
|
+
* deadline fired, or the wallet locked / logged out mid-flight. **The outcome is UNKNOWN:
|
|
356
|
+
* the money may or may not have moved.**
|
|
357
|
+
*
|
|
358
|
+
* A dApp MUST NOT retry on this code. Reconcile out of band (poll the recipient, the
|
|
359
|
+
* aggregator, or your own backend) and only then decide.
|
|
360
|
+
*
|
|
361
|
+
* This code exists because every other answer would be a lie. `INTENT_CANCELLED` (4200)
|
|
362
|
+
* asserts the user declined and nothing happened; `WALLET_LOCKED` (4009) invites a retry
|
|
363
|
+
* after the unlock. Sending either for an intent the wallet had already submitted is how a
|
|
364
|
+
* paid-but-not-credited order — and then a double spend on retry — happens.
|
|
365
|
+
*/
|
|
366
|
+
INTENT_OUTCOME_UNKNOWN: 4201
|
|
367
|
+
};
|
|
368
|
+
var WALLET_EVENTS = {
|
|
369
|
+
/** Wallet is LOCKED — the session is STILL ALIVE. Requests are answered
|
|
370
|
+
* WALLET_LOCKED (4009) until `wallet:unlocked`. The dApp must NOT disconnect,
|
|
371
|
+
* must NOT clear its sessionId, and must NOT re-handshake.
|
|
372
|
+
* Payload: {@link WalletLockedPayload}. Pushed by ConnectHost.setLocked() and
|
|
373
|
+
* immediately after a handshake response carrying `locked: true`. */
|
|
374
|
+
LOCKED: "wallet:locked",
|
|
375
|
+
/** Wallet was unlocked — the SAME session continues: no re-handshake, no re-approval,
|
|
376
|
+
* no re-subscribe (the host re-arms the dApp's subscriptions before pushing this).
|
|
377
|
+
* Payload: {@link WalletUnlockedPayload} — carries the CURRENT identity, which may
|
|
378
|
+
* differ from the one the dApp connected with. Pushed by ConnectHost.updateSphere()
|
|
379
|
+
* on the locked -> live edge only. */
|
|
380
|
+
UNLOCKED: "wallet:unlocked",
|
|
381
|
+
/** The session is GONE (logout, wallet deleted, dApp sphere_disconnect, expiry seen at
|
|
382
|
+
* unlock, a different seed behind the lock screen, host destroy).
|
|
383
|
+
* The dApp must clear its session and re-handshake to continue. Unlocking does not cure it.
|
|
384
|
+
* Payload: {@link WalletDisconnectedPayload}. Pushed by ConnectHost.revokeSession(). */
|
|
385
|
+
DISCONNECTED: "wallet:disconnected",
|
|
386
|
+
/** Active wallet address changed. dApp should update displayed identity.
|
|
387
|
+
* Pushed automatically by ConnectHost — no sphere_subscribe needed. */
|
|
388
|
+
IDENTITY_CHANGED: "identity:changed"
|
|
389
|
+
};
|
|
390
|
+
var AUTO_PUSHED_EVENTS = [
|
|
391
|
+
WALLET_EVENTS.LOCKED,
|
|
392
|
+
WALLET_EVENTS.UNLOCKED,
|
|
393
|
+
WALLET_EVENTS.DISCONNECTED,
|
|
394
|
+
WALLET_EVENTS.IDENTITY_CHANGED
|
|
395
|
+
];
|
|
396
|
+
function isAutoPushedEvent(event) {
|
|
397
|
+
return AUTO_PUSHED_EVENTS.includes(event);
|
|
398
|
+
}
|
|
347
399
|
function isSphereConnectMessage(msg) {
|
|
348
400
|
if (!msg || typeof msg !== "object") return false;
|
|
349
401
|
const m = msg;
|
|
@@ -360,7 +412,7 @@ function createRequestId() {
|
|
|
360
412
|
}
|
|
361
413
|
|
|
362
414
|
// connect/version.ts
|
|
363
|
-
var SDK_VERSION = "0.
|
|
415
|
+
var SDK_VERSION = "0.13.0";
|
|
364
416
|
|
|
365
417
|
// connect/permissions.ts
|
|
366
418
|
var PERMISSION_SCOPES = {
|
|
@@ -419,6 +471,37 @@ var INTENT_PERMISSIONS = {
|
|
|
419
471
|
[INTENT_ACTIONS.MINT]: PERMISSION_SCOPES.MINT_REQUEST
|
|
420
472
|
};
|
|
421
473
|
|
|
474
|
+
// connect/host/host-state.ts
|
|
475
|
+
var WALLET_LOCKED_MESSAGE = "Wallet is locked";
|
|
476
|
+
var NOT_CONNECTED_MESSAGE = "Not connected";
|
|
477
|
+
var LOCKED_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
478
|
+
RPC_METHODS.GET_IDENTITY,
|
|
479
|
+
RPC_METHODS.SUBSCRIBE,
|
|
480
|
+
RPC_METHODS.UNSUBSCRIBE,
|
|
481
|
+
RPC_METHODS.DISCONNECT
|
|
482
|
+
]);
|
|
483
|
+
var REFUSE_NOT_CONNECTED = {
|
|
484
|
+
kind: "refuse",
|
|
485
|
+
error: { code: ERROR_CODES.NOT_CONNECTED, message: NOT_CONNECTED_MESSAGE }
|
|
486
|
+
};
|
|
487
|
+
var REFUSE_LOCKED = {
|
|
488
|
+
kind: "refuse",
|
|
489
|
+
error: {
|
|
490
|
+
code: ERROR_CODES.WALLET_LOCKED,
|
|
491
|
+
message: WALLET_LOCKED_MESSAGE,
|
|
492
|
+
data: { reason: "locked" }
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
// connect/host/WalletSnapshot.ts
|
|
497
|
+
var EMPTY_WALLET_SNAPSHOT = Object.freeze({ capturedAt: 0 });
|
|
498
|
+
|
|
499
|
+
// connect/host/ConnectHost.ts
|
|
500
|
+
var CHANNEL_ONLY_CODES = /* @__PURE__ */ new Set([
|
|
501
|
+
ERROR_CODES.WALLET_LOCKED,
|
|
502
|
+
ERROR_CODES.NOT_CONNECTED
|
|
503
|
+
]);
|
|
504
|
+
|
|
422
505
|
// connect/client/ConnectClient.ts
|
|
423
506
|
var ConnectError = class extends Error {
|
|
424
507
|
constructor(message, code, data) {
|
|
@@ -443,6 +526,8 @@ var ConnectClient = class {
|
|
|
443
526
|
grantedPermissions = [];
|
|
444
527
|
identity = null;
|
|
445
528
|
walletNet = null;
|
|
529
|
+
walletProto = null;
|
|
530
|
+
locked = false;
|
|
446
531
|
connected = false;
|
|
447
532
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
448
533
|
eventHandlers = /* @__PURE__ */ new Map();
|
|
@@ -515,12 +600,37 @@ var ConnectClient = class {
|
|
|
515
600
|
get walletNetwork() {
|
|
516
601
|
return this.walletNet;
|
|
517
602
|
}
|
|
603
|
+
/**
|
|
604
|
+
* The wallet's Connect protocol version, captured from the handshake response `v`.
|
|
605
|
+
* Null before the first handshake response and after a disconnect.
|
|
606
|
+
*
|
|
607
|
+
* Feature-detect with it: compare against '2.1' to decide whether the wallet can be
|
|
608
|
+
* trusted to send wallet:unlocked / wallet:disconnected. A Connect 2.0 wallet destroys
|
|
609
|
+
* the session on lock and never emits either, so a dApp waiting for them against one
|
|
610
|
+
* waits forever.
|
|
611
|
+
*
|
|
612
|
+
* CAVEAT: on an ERROR response the host echoes the dApp's own `v` back
|
|
613
|
+
* (ConnectHost.sendHandshakeResponse), so after a refused connection this may be the
|
|
614
|
+
* dApp's version rather than the wallet's. Only trust it after a successful handshake.
|
|
615
|
+
*/
|
|
616
|
+
get walletProtocol() {
|
|
617
|
+
return this.walletProto;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Whether the wallet was locked at the last handshake or lifecycle event.
|
|
621
|
+
* A locked client is still CONNECTED: `isConnected` stays true and `session` stays valid.
|
|
622
|
+
* Requests answer WALLET_LOCKED (4009) until `wallet:unlocked` arrives on the SAME
|
|
623
|
+
* session — do not disconnect, do not clear the session, do not re-handshake.
|
|
624
|
+
*/
|
|
625
|
+
get walletLocked() {
|
|
626
|
+
return this.locked;
|
|
627
|
+
}
|
|
518
628
|
// ===========================================================================
|
|
519
629
|
// Query (read data)
|
|
520
630
|
// ===========================================================================
|
|
521
631
|
/** Send a query request and return the result */
|
|
522
632
|
async query(method, params) {
|
|
523
|
-
if (!this.connected) throw new
|
|
633
|
+
if (!this.connected) throw new ConnectError("Not connected", ERROR_CODES.NOT_CONNECTED);
|
|
524
634
|
const id = createRequestId();
|
|
525
635
|
return new Promise((resolve, reject) => {
|
|
526
636
|
const timer = setTimeout(() => {
|
|
@@ -530,7 +640,8 @@ var ConnectClient = class {
|
|
|
530
640
|
this.pendingRequests.set(id, {
|
|
531
641
|
resolve,
|
|
532
642
|
reject,
|
|
533
|
-
timer
|
|
643
|
+
timer,
|
|
644
|
+
kind: "query"
|
|
534
645
|
});
|
|
535
646
|
this.transport.send({
|
|
536
647
|
ns: SPHERE_CONNECT_NAMESPACE,
|
|
@@ -547,17 +658,23 @@ var ConnectClient = class {
|
|
|
547
658
|
// ===========================================================================
|
|
548
659
|
/** Send an intent request. The wallet will open its UI for user confirmation. */
|
|
549
660
|
async intent(action, params) {
|
|
550
|
-
if (!this.connected) throw new
|
|
661
|
+
if (!this.connected) throw new ConnectError("Not connected", ERROR_CODES.NOT_CONNECTED);
|
|
551
662
|
const id = createRequestId();
|
|
552
663
|
return new Promise((resolve, reject) => {
|
|
553
664
|
const timer = setTimeout(() => {
|
|
554
665
|
this.pendingRequests.delete(id);
|
|
555
|
-
reject(
|
|
666
|
+
reject(
|
|
667
|
+
new ConnectError(
|
|
668
|
+
`Intent outcome unknown \u2014 do not retry; reconcile before acting: ${action}`,
|
|
669
|
+
ERROR_CODES.INTENT_OUTCOME_UNKNOWN
|
|
670
|
+
)
|
|
671
|
+
);
|
|
556
672
|
}, this.intentTimeout);
|
|
557
673
|
this.pendingRequests.set(id, {
|
|
558
674
|
resolve,
|
|
559
675
|
reject,
|
|
560
|
-
timer
|
|
676
|
+
timer,
|
|
677
|
+
kind: "intent"
|
|
561
678
|
});
|
|
562
679
|
this.transport.send({
|
|
563
680
|
ns: SPHERE_CONNECT_NAMESPACE,
|
|
@@ -576,7 +693,7 @@ var ConnectClient = class {
|
|
|
576
693
|
on(event, handler) {
|
|
577
694
|
if (!this.eventHandlers.has(event)) {
|
|
578
695
|
this.eventHandlers.set(event, /* @__PURE__ */ new Set());
|
|
579
|
-
if (this.connected) {
|
|
696
|
+
if (this.connected && !isAutoPushedEvent(event)) {
|
|
580
697
|
this.query(RPC_METHODS.SUBSCRIBE, { event }).catch((err) => logger.debug("Connect", "Event subscription failed", err));
|
|
581
698
|
}
|
|
582
699
|
}
|
|
@@ -587,7 +704,7 @@ var ConnectClient = class {
|
|
|
587
704
|
handlers.delete(handler);
|
|
588
705
|
if (handlers.size === 0) {
|
|
589
706
|
this.eventHandlers.delete(event);
|
|
590
|
-
if (this.connected) {
|
|
707
|
+
if (this.connected && !isAutoPushedEvent(event)) {
|
|
591
708
|
this.query(RPC_METHODS.UNSUBSCRIBE, { event }).catch((err) => logger.debug("Connect", "Event unsubscription failed", err));
|
|
592
709
|
}
|
|
593
710
|
}
|
|
@@ -611,15 +728,36 @@ var ConnectClient = class {
|
|
|
611
728
|
return;
|
|
612
729
|
}
|
|
613
730
|
if (msg.type === "event") {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
731
|
+
if (!this.connected || !this.sessionId) {
|
|
732
|
+
logger.warn("Connect", `Ignoring wallet event before a session exists: ${msg.event}`);
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
if (msg.event === WALLET_EVENTS.LOCKED) {
|
|
736
|
+
this.locked = true;
|
|
737
|
+
} else if (msg.event === WALLET_EVENTS.UNLOCKED) {
|
|
738
|
+
this.locked = false;
|
|
739
|
+
const identity = msg.data?.identity;
|
|
740
|
+
if (identity) this.identity = identity;
|
|
741
|
+
} else if (msg.event === WALLET_EVENTS.DISCONNECTED) {
|
|
742
|
+
this.connected = false;
|
|
743
|
+
} else if (msg.event === WALLET_EVENTS.IDENTITY_CHANGED) {
|
|
744
|
+
const data = msg.data;
|
|
745
|
+
if (data && typeof data.chainPubkey === "string") this.identity = data;
|
|
746
|
+
}
|
|
747
|
+
this.dispatchEvent(msg.event, msg.data);
|
|
748
|
+
if (msg.event === WALLET_EVENTS.DISCONNECTED) {
|
|
749
|
+
this.cleanup();
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
dispatchEvent(event, data) {
|
|
754
|
+
const handlers = this.eventHandlers.get(event);
|
|
755
|
+
if (!handlers) return;
|
|
756
|
+
for (const handler of handlers) {
|
|
757
|
+
try {
|
|
758
|
+
handler(data);
|
|
759
|
+
} catch (err) {
|
|
760
|
+
logger.debug("Connect", "Event handler error", err);
|
|
623
761
|
}
|
|
624
762
|
}
|
|
625
763
|
}
|
|
@@ -627,6 +765,7 @@ var ConnectClient = class {
|
|
|
627
765
|
if (!this.handshakeResolver) return;
|
|
628
766
|
clearTimeout(this.handshakeResolver.timer);
|
|
629
767
|
const m = msg;
|
|
768
|
+
this.walletProto = msg.v ?? null;
|
|
630
769
|
if (m.error) {
|
|
631
770
|
this.handshakeResolver.reject(new ConnectError(m.error.message, m.error.code, m.error.data));
|
|
632
771
|
this.handshakeResolver = null;
|
|
@@ -637,12 +776,16 @@ var ConnectClient = class {
|
|
|
637
776
|
this.grantedPermissions = msg.permissions;
|
|
638
777
|
this.identity = msg.identity;
|
|
639
778
|
this.walletNet = m.network ?? null;
|
|
779
|
+
this.locked = m.locked === true;
|
|
640
780
|
this.connected = true;
|
|
641
781
|
if (m.warning) logger.warn("Connect", "Wallet deprecation notice", m.warning.message);
|
|
642
782
|
this.handshakeResolver.resolve({
|
|
643
783
|
sessionId: msg.sessionId,
|
|
644
784
|
permissions: this.grantedPermissions,
|
|
645
|
-
identity: msg.identity
|
|
785
|
+
identity: msg.identity,
|
|
786
|
+
// A resume DURING a lock succeeds: the dApp is connected on the same session and
|
|
787
|
+
// must not re-handshake. It will get wallet:unlocked when the user unlocks.
|
|
788
|
+
...this.locked ? { locked: true } : {}
|
|
646
789
|
});
|
|
647
790
|
} else {
|
|
648
791
|
this.handshakeResolver.reject(new Error("Connection rejected by wallet"));
|
|
@@ -668,9 +811,19 @@ var ConnectClient = class {
|
|
|
668
811
|
this.unsubscribeTransport();
|
|
669
812
|
this.unsubscribeTransport = null;
|
|
670
813
|
}
|
|
814
|
+
if (this.handshakeResolver) {
|
|
815
|
+
clearTimeout(this.handshakeResolver.timer);
|
|
816
|
+
this.handshakeResolver.reject(new ConnectError("Disconnected", ERROR_CODES.NOT_CONNECTED));
|
|
817
|
+
this.handshakeResolver = null;
|
|
818
|
+
}
|
|
671
819
|
for (const [, pending] of this.pendingRequests) {
|
|
672
820
|
clearTimeout(pending.timer);
|
|
673
|
-
pending.reject(
|
|
821
|
+
pending.reject(
|
|
822
|
+
pending.kind === "intent" ? new ConnectError(
|
|
823
|
+
"Intent outcome unknown \u2014 do not retry; reconcile before acting",
|
|
824
|
+
ERROR_CODES.INTENT_OUTCOME_UNKNOWN
|
|
825
|
+
) : new ConnectError("Disconnected", ERROR_CODES.NOT_CONNECTED)
|
|
826
|
+
);
|
|
674
827
|
}
|
|
675
828
|
this.pendingRequests.clear();
|
|
676
829
|
this.eventHandlers.clear();
|
|
@@ -679,11 +832,21 @@ var ConnectClient = class {
|
|
|
679
832
|
this.grantedPermissions = [];
|
|
680
833
|
this.identity = null;
|
|
681
834
|
this.walletNet = null;
|
|
835
|
+
this.walletProto = null;
|
|
836
|
+
this.locked = false;
|
|
682
837
|
}
|
|
683
838
|
};
|
|
684
839
|
|
|
685
840
|
// impl/browser/connect/PostMessageTransport.ts
|
|
686
841
|
var POPUP_CLOSE_CHECK_INTERVAL = 1e3;
|
|
842
|
+
function originOf(targetOrigin) {
|
|
843
|
+
if (targetOrigin === "*") return null;
|
|
844
|
+
try {
|
|
845
|
+
return [new URL(targetOrigin).origin];
|
|
846
|
+
} catch {
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
687
850
|
var PostMessageTransport = class _PostMessageTransport {
|
|
688
851
|
targetWindow;
|
|
689
852
|
targetOrigin;
|
|
@@ -697,6 +860,9 @@ var PostMessageTransport = class _PostMessageTransport {
|
|
|
697
860
|
this.targetOrigin = targetOrigin;
|
|
698
861
|
this.allowedOrigins = allowedOrigins ? new Set(allowedOrigins) : null;
|
|
699
862
|
this.listener = (event) => {
|
|
863
|
+
if (event.source && event.source !== this.targetWindow) {
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
700
866
|
if (this.allowedOrigins && !this.allowedOrigins.has("*") && !this.allowedOrigins.has(event.origin)) {
|
|
701
867
|
return;
|
|
702
868
|
}
|
|
@@ -735,7 +901,8 @@ var PostMessageTransport = class _PostMessageTransport {
|
|
|
735
901
|
static forClient(options) {
|
|
736
902
|
const target = options?.target ?? window.parent;
|
|
737
903
|
const targetOrigin = options?.targetOrigin ?? "*";
|
|
738
|
-
const
|
|
904
|
+
const allowedOrigins = originOf(targetOrigin);
|
|
905
|
+
const transport = new _PostMessageTransport(target, targetOrigin, allowedOrigins);
|
|
739
906
|
if (options?.target && options.target !== window.parent) {
|
|
740
907
|
transport.startPopupCloseDetection(options.target);
|
|
741
908
|
}
|