@thru/wallet 0.2.27 → 0.2.29
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 +3 -2
- package/app.plugin.cjs +1 -1
- 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 +768 -35
- package/dist/native/react.js.map +1 -1
- package/dist/native.d.ts +106 -2
- package/dist/native.js +524 -34
- 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 +125 -11
- package/src/native/index.ts +12 -0
- package/src/native/provider/NativeProvider.ts +109 -8
- package/src/native/provider/WebViewBridge.test.ts +24 -3
- package/src/native/provider/WebViewBridge.ts +18 -8
- package/src/native/provider/chains/ThruChain.ts +215 -5
- package/src/native/provider/shell.test.ts +3 -3
- package/src/native/provider/shell.ts +1 -1
- 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
|
@@ -16,7 +16,10 @@ import {
|
|
|
16
16
|
* Development builds additionally allow localhost, LAN, and Tailscale
|
|
17
17
|
* origins so local HTTPS RP-ID testing can use the hosted wallet path.
|
|
18
18
|
*/
|
|
19
|
-
const PRODUCTION_IFRAME_ORIGINS = [
|
|
19
|
+
const PRODUCTION_IFRAME_ORIGINS = [
|
|
20
|
+
'https://app.tid.sh',
|
|
21
|
+
'https://wallet.tid.sh',
|
|
22
|
+
];
|
|
20
23
|
|
|
21
24
|
const SLOW_REQUEST_TIMEOUT_MS = 5 * 60 * 1000;
|
|
22
25
|
const FAST_REQUEST_TIMEOUT_MS = 30 * 1000;
|
|
@@ -26,7 +29,11 @@ const SLOW_REQUEST_TYPES: ReadonlySet<string> = new Set([
|
|
|
26
29
|
POST_MESSAGE_REQUEST_TYPES.CONNECT,
|
|
27
30
|
POST_MESSAGE_REQUEST_TYPES.SIGN_MESSAGE,
|
|
28
31
|
POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION,
|
|
32
|
+
POST_MESSAGE_REQUEST_TYPES.SIGN_PASSKEY_CHALLENGE,
|
|
29
33
|
POST_MESSAGE_REQUEST_TYPES.MANAGE_ACCOUNTS,
|
|
34
|
+
POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION,
|
|
35
|
+
POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION_INSTRUCTION,
|
|
36
|
+
POST_MESSAGE_REQUEST_TYPES.CONFIRM_SIGNING_SESSION,
|
|
30
37
|
]);
|
|
31
38
|
|
|
32
39
|
function isPrivateIpv4Host(hostname: string): boolean {
|
|
@@ -2,11 +2,42 @@ import {
|
|
|
2
2
|
AddressType,
|
|
3
3
|
type IThruChain,
|
|
4
4
|
type ThruSigningContext,
|
|
5
|
+
type ThruSigningSession,
|
|
6
|
+
type ThruSigningSessionCreateOptions,
|
|
7
|
+
type ThruSigningSessionDescriptor,
|
|
8
|
+
type ThruSigningSessionInstruction,
|
|
9
|
+
type ThruSigningSessionInstructionCreateOptions,
|
|
10
|
+
type ThruPasskeyChallengeIntent,
|
|
11
|
+
type ThruPasskeyChallengeSignature,
|
|
5
12
|
type ThruTransactionIntent,
|
|
6
13
|
} from "../../interfaces";
|
|
7
14
|
import { POST_MESSAGE_REQUEST_TYPES, createRequestId } from "../../protocol";
|
|
15
|
+
import { base64ToBytes } from "../../encoding";
|
|
8
16
|
import type { EmbeddedProvider } from "../EmbeddedProvider";
|
|
9
17
|
import type { IframeManager } from "../IframeManager";
|
|
18
|
+
import {
|
|
19
|
+
SigningSessionDescriptorStore,
|
|
20
|
+
assertSigningSessionWalletAccountIdx,
|
|
21
|
+
resolveSessionExpirySeconds,
|
|
22
|
+
} from "../../signing-sessions";
|
|
23
|
+
|
|
24
|
+
function descriptorFromWire(session: {
|
|
25
|
+
id: string;
|
|
26
|
+
walletAddress: string;
|
|
27
|
+
publicKey: string;
|
|
28
|
+
authIdx: number;
|
|
29
|
+
expiresAt: string;
|
|
30
|
+
createdAt: string;
|
|
31
|
+
}): ThruSigningSessionDescriptor {
|
|
32
|
+
return {
|
|
33
|
+
id: session.id,
|
|
34
|
+
walletAddress: session.walletAddress,
|
|
35
|
+
publicKey: session.publicKey,
|
|
36
|
+
authIdx: session.authIdx,
|
|
37
|
+
expiresAt: Number(BigInt(session.expiresAt)),
|
|
38
|
+
createdAt: Number(BigInt(session.createdAt)),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
10
41
|
|
|
11
42
|
/**
|
|
12
43
|
* EmbeddedThruChain - postMessage-backed Thru chain adapter.
|
|
@@ -14,10 +45,16 @@ import type { IframeManager } from "../IframeManager";
|
|
|
14
45
|
export class EmbeddedThruChain implements IThruChain {
|
|
15
46
|
private readonly iframeManager: IframeManager;
|
|
16
47
|
private readonly provider: EmbeddedProvider;
|
|
48
|
+
private readonly signingSessions?: SigningSessionDescriptorStore;
|
|
17
49
|
|
|
18
|
-
constructor(
|
|
50
|
+
constructor(
|
|
51
|
+
iframeManager: IframeManager,
|
|
52
|
+
provider: EmbeddedProvider,
|
|
53
|
+
signingSessions?: SigningSessionDescriptorStore,
|
|
54
|
+
) {
|
|
19
55
|
this.iframeManager = iframeManager;
|
|
20
56
|
this.provider = provider;
|
|
57
|
+
this.signingSessions = signingSessions;
|
|
21
58
|
}
|
|
22
59
|
|
|
23
60
|
get connected(): boolean {
|
|
@@ -58,29 +95,198 @@ export class EmbeddedThruChain implements IThruChain {
|
|
|
58
95
|
}
|
|
59
96
|
|
|
60
97
|
async signTransaction(transaction: ThruTransactionIntent): Promise<string> {
|
|
61
|
-
|
|
98
|
+
const signingSessionId = transaction.signingSessionId;
|
|
99
|
+
if (!signingSessionId && !this.provider.isConnected()) {
|
|
62
100
|
throw new Error("Wallet not connected");
|
|
63
101
|
}
|
|
64
102
|
|
|
65
|
-
|
|
103
|
+
const session = signingSessionId
|
|
104
|
+
? await this.requireSigningSession(signingSessionId)
|
|
105
|
+
: null;
|
|
106
|
+
const shouldShowWallet = !signingSessionId;
|
|
107
|
+
if (shouldShowWallet) {
|
|
108
|
+
this.iframeManager.show();
|
|
109
|
+
}
|
|
66
110
|
|
|
67
111
|
try {
|
|
68
112
|
const response = await this.iframeManager.sendMessage({
|
|
69
113
|
id: createRequestId(),
|
|
70
114
|
type: POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION,
|
|
71
115
|
payload: {
|
|
72
|
-
walletAddress: transaction.walletAddress,
|
|
116
|
+
walletAddress: transaction.walletAddress ?? session?.walletAddress,
|
|
73
117
|
programAddress: transaction.programAddress,
|
|
74
118
|
instructionData: transaction.instructionData,
|
|
75
119
|
readWriteAddresses: transaction.readWriteAddresses,
|
|
76
120
|
readOnlyAddresses: transaction.readOnlyAddresses,
|
|
77
121
|
review: transaction.review,
|
|
122
|
+
signingSessionId,
|
|
78
123
|
},
|
|
79
124
|
origin: window.location.origin,
|
|
80
125
|
});
|
|
81
126
|
return response.result.signedTransaction;
|
|
127
|
+
} finally {
|
|
128
|
+
if (shouldShowWallet) {
|
|
129
|
+
this.iframeManager.hide();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async signPasskeyChallenge(
|
|
135
|
+
challenge: ThruPasskeyChallengeIntent,
|
|
136
|
+
): Promise<ThruPasskeyChallengeSignature> {
|
|
137
|
+
if (!this.provider.isConnected()) {
|
|
138
|
+
throw new Error("Wallet not connected");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.iframeManager.show();
|
|
142
|
+
try {
|
|
143
|
+
const response = await this.iframeManager.sendMessage({
|
|
144
|
+
id: createRequestId(),
|
|
145
|
+
type: POST_MESSAGE_REQUEST_TYPES.SIGN_PASSKEY_CHALLENGE,
|
|
146
|
+
payload: {
|
|
147
|
+
challenge: challenge.challenge,
|
|
148
|
+
walletAddress: challenge.walletAddress,
|
|
149
|
+
},
|
|
150
|
+
origin: window.location.origin,
|
|
151
|
+
});
|
|
152
|
+
return response.result;
|
|
153
|
+
} finally {
|
|
154
|
+
this.iframeManager.hide();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async createSigningSession(
|
|
159
|
+
options: ThruSigningSessionCreateOptions,
|
|
160
|
+
): Promise<ThruSigningSession> {
|
|
161
|
+
if (!this.provider.isConnected()) {
|
|
162
|
+
throw new Error("Wallet not connected");
|
|
163
|
+
}
|
|
164
|
+
if (!this.signingSessions) {
|
|
165
|
+
throw new Error("Signing session storage is not available");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const expiresAt = resolveSessionExpirySeconds(options);
|
|
169
|
+
this.iframeManager.show();
|
|
170
|
+
try {
|
|
171
|
+
const response = await this.iframeManager.sendMessage({
|
|
172
|
+
id: createRequestId(),
|
|
173
|
+
type: POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION,
|
|
174
|
+
payload: {
|
|
175
|
+
walletAddress: options.walletAddress,
|
|
176
|
+
expiresAt: String(expiresAt),
|
|
177
|
+
review: options.review,
|
|
178
|
+
},
|
|
179
|
+
origin: window.location.origin,
|
|
180
|
+
});
|
|
181
|
+
const descriptor = descriptorFromWire(response.result.session);
|
|
182
|
+
await this.signingSessions.saveReplacingWalletSessions(descriptor);
|
|
183
|
+
return this.toSigningSession(descriptor);
|
|
82
184
|
} finally {
|
|
83
185
|
this.iframeManager.hide();
|
|
84
186
|
}
|
|
85
187
|
}
|
|
188
|
+
|
|
189
|
+
async createSigningSessionInstruction(
|
|
190
|
+
options: ThruSigningSessionInstructionCreateOptions,
|
|
191
|
+
): Promise<ThruSigningSessionInstruction> {
|
|
192
|
+
if (!this.provider.isConnected()) {
|
|
193
|
+
throw new Error("Wallet not connected");
|
|
194
|
+
}
|
|
195
|
+
if (!this.signingSessions) {
|
|
196
|
+
throw new Error("Signing session storage is not available");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const expiresAt = resolveSessionExpirySeconds(options);
|
|
200
|
+
assertSigningSessionWalletAccountIdx(options.walletAccountIdx);
|
|
201
|
+
const response = await this.iframeManager.sendMessage({
|
|
202
|
+
id: createRequestId(),
|
|
203
|
+
type: POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION_INSTRUCTION,
|
|
204
|
+
payload: {
|
|
205
|
+
walletAddress: options.walletAddress,
|
|
206
|
+
expiresAt: String(expiresAt),
|
|
207
|
+
walletAccountIdx: options.walletAccountIdx,
|
|
208
|
+
},
|
|
209
|
+
origin: window.location.origin,
|
|
210
|
+
});
|
|
211
|
+
const descriptor = descriptorFromWire(response.result.session);
|
|
212
|
+
return {
|
|
213
|
+
session: this.toSigningSession(descriptor),
|
|
214
|
+
programAddress: response.result.programAddress,
|
|
215
|
+
instructionData: base64ToBytes(response.result.instructionData),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async confirmSigningSession(id: string): Promise<ThruSigningSession> {
|
|
220
|
+
if (!this.provider.isConnected()) {
|
|
221
|
+
throw new Error("Wallet not connected");
|
|
222
|
+
}
|
|
223
|
+
if (!this.signingSessions) {
|
|
224
|
+
throw new Error("Signing session storage is not available");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const response = await this.iframeManager.sendMessage({
|
|
228
|
+
id: createRequestId(),
|
|
229
|
+
type: POST_MESSAGE_REQUEST_TYPES.CONFIRM_SIGNING_SESSION,
|
|
230
|
+
payload: { sessionId: id },
|
|
231
|
+
origin: window.location.origin,
|
|
232
|
+
});
|
|
233
|
+
const descriptor = descriptorFromWire(response.result.session);
|
|
234
|
+
await this.signingSessions.saveReplacingWalletSessions(descriptor);
|
|
235
|
+
return this.toSigningSession(descriptor);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async getSigningSession(id: string): Promise<ThruSigningSession | null> {
|
|
239
|
+
if (!this.signingSessions) return null;
|
|
240
|
+
const descriptor = await this.signingSessions.get(id);
|
|
241
|
+
return descriptor ? this.toSigningSession(descriptor) : null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async getSigningSessions(): Promise<ThruSigningSession[]> {
|
|
245
|
+
if (!this.signingSessions) return [];
|
|
246
|
+
return (await this.signingSessions.list()).map((descriptor) =>
|
|
247
|
+
this.toSigningSession(descriptor),
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async revokeSigningSession(id: string): Promise<void> {
|
|
252
|
+
try {
|
|
253
|
+
await this.iframeManager.sendMessage({
|
|
254
|
+
id: createRequestId(),
|
|
255
|
+
type: POST_MESSAGE_REQUEST_TYPES.REVOKE_SIGNING_SESSION,
|
|
256
|
+
payload: { sessionId: id },
|
|
257
|
+
origin: window.location.origin,
|
|
258
|
+
});
|
|
259
|
+
} finally {
|
|
260
|
+
await this.signingSessions?.remove(id);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private async requireSigningSession(
|
|
265
|
+
id: string,
|
|
266
|
+
): Promise<ThruSigningSessionDescriptor> {
|
|
267
|
+
if (!this.signingSessions) {
|
|
268
|
+
throw new Error("Signing session storage is not available");
|
|
269
|
+
}
|
|
270
|
+
const session = await this.signingSessions.get(id);
|
|
271
|
+
if (!session) {
|
|
272
|
+
throw new Error("Signing session is not known to this app");
|
|
273
|
+
}
|
|
274
|
+
return session;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private toSigningSession(
|
|
278
|
+
descriptor: ThruSigningSessionDescriptor,
|
|
279
|
+
): ThruSigningSession {
|
|
280
|
+
return {
|
|
281
|
+
...descriptor,
|
|
282
|
+
signTransaction: (transaction) =>
|
|
283
|
+
this.signTransaction({
|
|
284
|
+
...transaction,
|
|
285
|
+
walletAddress: transaction.walletAddress ?? descriptor.walletAddress,
|
|
286
|
+
signingSessionId: descriptor.id,
|
|
287
|
+
}),
|
|
288
|
+
revoke: () => this.revokeSigningSession(descriptor.id),
|
|
289
|
+
toJSON: () => ({ ...descriptor }),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
86
292
|
}
|
|
@@ -10,13 +10,26 @@ export {
|
|
|
10
10
|
type EmbeddedProviderEvent,
|
|
11
11
|
type PostMessageRequest,
|
|
12
12
|
type ConnectRequestMessage,
|
|
13
|
+
type CreateSigningSessionPayload,
|
|
14
|
+
type CreateSigningSessionRequestMessage,
|
|
15
|
+
type CreateSigningSessionResult,
|
|
16
|
+
type ConfirmSigningSessionPayload,
|
|
17
|
+
type ConfirmSigningSessionRequestMessage,
|
|
18
|
+
type ConfirmSigningSessionResult,
|
|
19
|
+
type CreateSigningSessionInstructionPayload,
|
|
20
|
+
type CreateSigningSessionInstructionRequestMessage,
|
|
21
|
+
type CreateSigningSessionInstructionResult,
|
|
13
22
|
type DisconnectRequestMessage,
|
|
14
23
|
type SignMessageRequestMessage,
|
|
15
24
|
type SignTransactionRequestMessage,
|
|
25
|
+
type SignPasskeyChallengeRequestMessage,
|
|
16
26
|
type GetAccountsRequestMessage,
|
|
17
27
|
type GetSigningContextRequestMessage,
|
|
18
28
|
type ManageAccountsRequestMessage,
|
|
19
29
|
type SelectAccountRequestMessage,
|
|
30
|
+
type RevokeSigningSessionPayload,
|
|
31
|
+
type RevokeSigningSessionRequestMessage,
|
|
32
|
+
type RevokeSigningSessionResult,
|
|
20
33
|
type DisconnectResult,
|
|
21
34
|
type GetAccountsResult,
|
|
22
35
|
type GetSigningContextResult,
|
|
@@ -34,4 +47,7 @@ export {
|
|
|
34
47
|
type SignMessageResult,
|
|
35
48
|
type SignTransactionPayload,
|
|
36
49
|
type SignTransactionResult,
|
|
50
|
+
type SignPasskeyChallengePayload,
|
|
51
|
+
type SignPasskeyChallengeResult,
|
|
52
|
+
type SigningSessionDescriptorPayload,
|
|
37
53
|
} from "../../protocol";
|
package/src/react/index.ts
CHANGED
|
@@ -25,6 +25,12 @@ export type {
|
|
|
25
25
|
SignMessageParams,
|
|
26
26
|
SignMessageResult,
|
|
27
27
|
ThruSigningContext,
|
|
28
|
+
ThruSigningSession,
|
|
29
|
+
ThruSigningSessionCreateOptions,
|
|
30
|
+
ThruSigningSessionDescriptor,
|
|
31
|
+
ThruSigningSessionTimestamp,
|
|
28
32
|
ThruTransactionEncoding,
|
|
33
|
+
ThruTransactionIntent,
|
|
29
34
|
WalletAccount,
|
|
30
35
|
} from "../interfaces";
|
|
36
|
+
export type { SigningSessionStorage } from "../signing-sessions";
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
SigningSessionDescriptorStore,
|
|
4
|
+
assertSigningSessionWalletAccountIdx,
|
|
5
|
+
resolveSessionExpirySeconds,
|
|
6
|
+
resolveSigningSessionStorageKey,
|
|
7
|
+
} from "./signing-sessions";
|
|
8
|
+
|
|
9
|
+
class MemoryStorage {
|
|
10
|
+
values = new Map<string, string>();
|
|
11
|
+
|
|
12
|
+
getItem(key: string): string | null {
|
|
13
|
+
return this.values.get(key) ?? null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
setItem(key: string, value: string): void {
|
|
17
|
+
this.values.set(key, value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
removeItem(key: string): void {
|
|
21
|
+
this.values.delete(key);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("signing session descriptor storage", () => {
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
vi.useFakeTimers();
|
|
28
|
+
vi.setSystemTime(new Date("2026-06-11T12:00:00.000Z"));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
vi.useRealTimers();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("scopes default storage keys by wallet origin and app origin", () => {
|
|
36
|
+
const appA = resolveSigningSessionStorageKey({
|
|
37
|
+
walletOrigin: "https://wallet.example",
|
|
38
|
+
appOrigin: "https://app-a.example",
|
|
39
|
+
});
|
|
40
|
+
const appB = resolveSigningSessionStorageKey({
|
|
41
|
+
walletOrigin: "https://wallet.example",
|
|
42
|
+
appOrigin: "https://app-b.example",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(appA).not.toBe(appB);
|
|
46
|
+
expect(appA).toContain(encodeURIComponent("https://wallet.example"));
|
|
47
|
+
expect(appA).toContain(encodeURIComponent("https://app-a.example"));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("stores only active sessions and prunes expired descriptors locally", async () => {
|
|
51
|
+
const storage = new MemoryStorage();
|
|
52
|
+
const store = new SigningSessionDescriptorStore(storage, "sessions");
|
|
53
|
+
|
|
54
|
+
await store.save({
|
|
55
|
+
id: "expired",
|
|
56
|
+
walletAddress: "wallet",
|
|
57
|
+
publicKey: "expired-pubkey",
|
|
58
|
+
authIdx: 1,
|
|
59
|
+
expiresAt: Math.floor(Date.now() / 1000) - 1,
|
|
60
|
+
createdAt: Math.floor(Date.now() / 1000) - 10,
|
|
61
|
+
});
|
|
62
|
+
await store.save({
|
|
63
|
+
id: "active",
|
|
64
|
+
walletAddress: "wallet",
|
|
65
|
+
publicKey: "active-pubkey",
|
|
66
|
+
authIdx: 2,
|
|
67
|
+
expiresAt: Math.floor(Date.now() / 1000) + 60,
|
|
68
|
+
createdAt: Math.floor(Date.now() / 1000),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(await store.get("expired")).toBeNull();
|
|
72
|
+
expect(await store.get("active")).toMatchObject({
|
|
73
|
+
id: "active",
|
|
74
|
+
publicKey: "active-pubkey",
|
|
75
|
+
authIdx: 2,
|
|
76
|
+
});
|
|
77
|
+
expect(await store.list()).toHaveLength(1);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("replaces a descriptor with the same session id", async () => {
|
|
81
|
+
const store = new SigningSessionDescriptorStore(new MemoryStorage(), "sessions");
|
|
82
|
+
const expiresAt = Math.floor(Date.now() / 1000) + 60;
|
|
83
|
+
|
|
84
|
+
await store.save({
|
|
85
|
+
id: "session",
|
|
86
|
+
walletAddress: "wallet-a",
|
|
87
|
+
publicKey: "pubkey-a",
|
|
88
|
+
authIdx: 1,
|
|
89
|
+
expiresAt,
|
|
90
|
+
createdAt: Math.floor(Date.now() / 1000),
|
|
91
|
+
});
|
|
92
|
+
await store.save({
|
|
93
|
+
id: "session",
|
|
94
|
+
walletAddress: "wallet-b",
|
|
95
|
+
publicKey: "pubkey-b",
|
|
96
|
+
authIdx: 2,
|
|
97
|
+
expiresAt,
|
|
98
|
+
createdAt: Math.floor(Date.now() / 1000),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(await store.list()).toEqual([
|
|
102
|
+
expect.objectContaining({
|
|
103
|
+
id: "session",
|
|
104
|
+
walletAddress: "wallet-b",
|
|
105
|
+
publicKey: "pubkey-b",
|
|
106
|
+
authIdx: 2,
|
|
107
|
+
}),
|
|
108
|
+
]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("replaces older descriptors for the same wallet when saving a usable session", async () => {
|
|
112
|
+
const store = new SigningSessionDescriptorStore(new MemoryStorage(), "sessions");
|
|
113
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
114
|
+
|
|
115
|
+
await store.save({
|
|
116
|
+
id: "old-wallet-a",
|
|
117
|
+
walletAddress: "wallet-a",
|
|
118
|
+
publicKey: "old-pubkey",
|
|
119
|
+
authIdx: 1,
|
|
120
|
+
expiresAt: nowSeconds + 60,
|
|
121
|
+
createdAt: nowSeconds,
|
|
122
|
+
});
|
|
123
|
+
await store.save({
|
|
124
|
+
id: "wallet-b",
|
|
125
|
+
walletAddress: "wallet-b",
|
|
126
|
+
publicKey: "wallet-b-pubkey",
|
|
127
|
+
authIdx: 2,
|
|
128
|
+
expiresAt: nowSeconds + 60,
|
|
129
|
+
createdAt: nowSeconds,
|
|
130
|
+
});
|
|
131
|
+
await store.saveReplacingWalletSessions({
|
|
132
|
+
id: "new-wallet-a",
|
|
133
|
+
walletAddress: "wallet-a",
|
|
134
|
+
publicKey: "new-pubkey",
|
|
135
|
+
authIdx: 3,
|
|
136
|
+
expiresAt: nowSeconds + 120,
|
|
137
|
+
createdAt: nowSeconds + 1,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(await store.list()).toEqual([
|
|
141
|
+
expect.objectContaining({
|
|
142
|
+
id: "wallet-b",
|
|
143
|
+
walletAddress: "wallet-b",
|
|
144
|
+
}),
|
|
145
|
+
expect.objectContaining({
|
|
146
|
+
id: "new-wallet-a",
|
|
147
|
+
walletAddress: "wallet-a",
|
|
148
|
+
publicKey: "new-pubkey",
|
|
149
|
+
authIdx: 3,
|
|
150
|
+
}),
|
|
151
|
+
]);
|
|
152
|
+
expect(await store.get("old-wallet-a")).toBeNull();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("accepts exactly one of durationSeconds or expiresAt", () => {
|
|
156
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
157
|
+
|
|
158
|
+
expect(resolveSessionExpirySeconds({ durationSeconds: 30 })).toBe(nowSeconds + 30);
|
|
159
|
+
expect(resolveSessionExpirySeconds({ expiresAt: String(nowSeconds + 45) })).toBe(
|
|
160
|
+
nowSeconds + 45,
|
|
161
|
+
);
|
|
162
|
+
expect(() => resolveSessionExpirySeconds({})).toThrow(
|
|
163
|
+
"Provide exactly one of durationSeconds or expiresAt",
|
|
164
|
+
);
|
|
165
|
+
expect(() =>
|
|
166
|
+
resolveSessionExpirySeconds({ durationSeconds: 1, expiresAt: nowSeconds + 1 }),
|
|
167
|
+
).toThrow("Provide exactly one of durationSeconds or expiresAt");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("rejects invalid signing session wallet account indexes", () => {
|
|
171
|
+
expect(() => assertSigningSessionWalletAccountIdx(2)).not.toThrow();
|
|
172
|
+
expect(() => assertSigningSessionWalletAccountIdx(0)).toThrow(
|
|
173
|
+
"walletAccountIdx must be an account index between 2 and 65535",
|
|
174
|
+
);
|
|
175
|
+
expect(() => assertSigningSessionWalletAccountIdx(1.5)).toThrow(
|
|
176
|
+
"walletAccountIdx must be an account index between 2 and 65535",
|
|
177
|
+
);
|
|
178
|
+
expect(() => assertSigningSessionWalletAccountIdx(0x10000)).toThrow(
|
|
179
|
+
"walletAccountIdx must be an account index between 2 and 65535",
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
});
|