@icp-sdk/signer 5.2.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/LICENSE +190 -0
- package/README.md +165 -0
- package/lib/esm/agent/agent.d.ts +112 -0
- package/lib/esm/agent/agent.js +288 -0
- package/lib/esm/agent/agent.js.map +1 -0
- package/lib/esm/agent/index.d.ts +1 -0
- package/lib/esm/agent/index.js +2 -0
- package/lib/esm/agent/index.js.map +1 -0
- package/lib/esm/extension/browserExtensionChannel.d.ts +37 -0
- package/lib/esm/extension/browserExtensionChannel.js +80 -0
- package/lib/esm/extension/browserExtensionChannel.js.map +1 -0
- package/lib/esm/extension/browserExtensionTransport.d.ts +67 -0
- package/lib/esm/extension/browserExtensionTransport.js +70 -0
- package/lib/esm/extension/browserExtensionTransport.js.map +1 -0
- package/lib/esm/extension/index.d.ts +3 -0
- package/lib/esm/extension/index.js +3 -0
- package/lib/esm/extension/index.js.map +1 -0
- package/lib/esm/extension/types.d.ts +32 -0
- package/lib/esm/extension/types.js +2 -0
- package/lib/esm/extension/types.js.map +1 -0
- package/lib/esm/index.d.ts +2 -0
- package/lib/esm/index.js +2 -0
- package/lib/esm/index.js.map +1 -0
- package/lib/esm/signer.d.ts +180 -0
- package/lib/esm/signer.js +427 -0
- package/lib/esm/signer.js.map +1 -0
- package/lib/esm/transport.d.ts +32 -0
- package/lib/esm/transport.js +11 -0
- package/lib/esm/transport.js.map +1 -0
- package/lib/esm/web/heartbeat/client.d.ts +60 -0
- package/lib/esm/web/heartbeat/client.js +112 -0
- package/lib/esm/web/heartbeat/client.js.map +1 -0
- package/lib/esm/web/heartbeat/server.d.ts +43 -0
- package/lib/esm/web/heartbeat/server.js +82 -0
- package/lib/esm/web/heartbeat/server.js.map +1 -0
- package/lib/esm/web/index.d.ts +4 -0
- package/lib/esm/web/index.js +5 -0
- package/lib/esm/web/index.js.map +1 -0
- package/lib/esm/web/postMessageChannel.d.ts +55 -0
- package/lib/esm/web/postMessageChannel.js +109 -0
- package/lib/esm/web/postMessageChannel.js.map +1 -0
- package/lib/esm/web/postMessageTransport.d.ts +96 -0
- package/lib/esm/web/postMessageTransport.js +113 -0
- package/lib/esm/web/postMessageTransport.js.map +1 -0
- package/package.json +122 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { Cbor, Certificate, Expiry, HttpAgent, IC_ROOT_KEY, JSON_KEY_EXPIRY, LookupPathStatus, requestIdOf, SubmitRequestType, } from '@icp-sdk/core/agent';
|
|
2
|
+
import { uint8Equals } from '@icp-sdk/core/candid';
|
|
3
|
+
import { Principal } from '@icp-sdk/core/principal';
|
|
4
|
+
// Hex helpers — use native Uint8Array methods when available
|
|
5
|
+
const fromHex = (hex) => {
|
|
6
|
+
if ('fromHex' in Uint8Array && typeof Uint8Array.fromHex === 'function') {
|
|
7
|
+
return Uint8Array.fromHex(hex);
|
|
8
|
+
}
|
|
9
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
10
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
11
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
12
|
+
}
|
|
13
|
+
return bytes;
|
|
14
|
+
};
|
|
15
|
+
const toHex = (bytes) => {
|
|
16
|
+
if ('toHex' in bytes && typeof bytes.toHex === 'function') {
|
|
17
|
+
return bytes.toHex();
|
|
18
|
+
}
|
|
19
|
+
let hex = '';
|
|
20
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
21
|
+
hex += bytes[i].toString(16).padStart(2, '0');
|
|
22
|
+
}
|
|
23
|
+
return hex;
|
|
24
|
+
};
|
|
25
|
+
// IC root key as bytes, used as fallback when agent has no root key
|
|
26
|
+
const ROOT_KEY = fromHex(IC_ROOT_KEY);
|
|
27
|
+
const MAX_AGE_IN_MINUTES = 5;
|
|
28
|
+
const INVALID_RESPONSE_MESSAGE = 'Received invalid response from signer';
|
|
29
|
+
/**
|
|
30
|
+
* Error thrown by {@link SignerAgent} when a signer returns an invalid
|
|
31
|
+
* response or certificate validation fails.
|
|
32
|
+
*/
|
|
33
|
+
export class SignerAgentError extends Error {
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* An {@link Agent} implementation that routes canister calls through a
|
|
37
|
+
* {@link Signer} for user approval. Drop-in replacement for {@link HttpAgent}
|
|
38
|
+
* when canister calls need to be signed by an external signer.
|
|
39
|
+
*
|
|
40
|
+
* Calls are sent to the signer via ICRC-49, and the returned content map
|
|
41
|
+
* and certificate are validated before being returned to the caller.
|
|
42
|
+
*
|
|
43
|
+
* Use {@link SignerAgent.create} or {@link SignerAgent.createSync} to
|
|
44
|
+
* construct an instance — the constructor is private.
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* const agent = await SignerAgent.create({ signer, account });
|
|
48
|
+
* const result = await agent.update(canisterId, { methodName: "transfer", arg, effectiveCanisterId: canisterId });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export class SignerAgent {
|
|
52
|
+
static #isInternalConstructing = false;
|
|
53
|
+
#options;
|
|
54
|
+
#certificates = new Map();
|
|
55
|
+
#pending = Promise.resolve();
|
|
56
|
+
constructor(options) {
|
|
57
|
+
const throwError = !SignerAgent.#isInternalConstructing;
|
|
58
|
+
SignerAgent.#isInternalConstructing = false;
|
|
59
|
+
if (throwError) {
|
|
60
|
+
throw new SignerAgentError('SignerAgent is not constructable');
|
|
61
|
+
}
|
|
62
|
+
this.#options = options;
|
|
63
|
+
}
|
|
64
|
+
/** The root key used for certificate verification. */
|
|
65
|
+
get rootKey() {
|
|
66
|
+
return this.#options.agent.rootKey ?? ROOT_KEY;
|
|
67
|
+
}
|
|
68
|
+
/** The signer this agent routes calls through. */
|
|
69
|
+
get signer() {
|
|
70
|
+
return this.#options.signer;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Creates a new SignerAgent, asynchronously initializing the
|
|
74
|
+
* underlying HttpAgent if one is not provided.
|
|
75
|
+
* @param options - The signer agent options.
|
|
76
|
+
*/
|
|
77
|
+
static async create(options) {
|
|
78
|
+
SignerAgent.#isInternalConstructing = true;
|
|
79
|
+
return new SignerAgent({
|
|
80
|
+
...options,
|
|
81
|
+
agent: options.agent ?? (await HttpAgent.create()),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Creates a new SignerAgent synchronously.
|
|
86
|
+
* Use this when you already have an HttpAgent or don't need async initialization.
|
|
87
|
+
* @param options - The signer agent options.
|
|
88
|
+
*/
|
|
89
|
+
static createSync(options) {
|
|
90
|
+
SignerAgent.#isInternalConstructing = true;
|
|
91
|
+
return new SignerAgent({
|
|
92
|
+
...options,
|
|
93
|
+
agent: options.agent ?? HttpAgent.createSync(),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Sends a canister call through the signer, validates the content map
|
|
98
|
+
* and certificate, and returns the verified result.
|
|
99
|
+
*
|
|
100
|
+
* Calls are queued to ensure sequential execution — the signer's
|
|
101
|
+
* transport channel is opened first to avoid blocking popups.
|
|
102
|
+
* @param canisterId - The target canister principal.
|
|
103
|
+
* @param fields - The call options including method name and arguments.
|
|
104
|
+
*/
|
|
105
|
+
async #sendAndVerify(canisterId, fields) {
|
|
106
|
+
await this.#options.signer.openChannel();
|
|
107
|
+
// Queue calls to ensure sequential execution through the signer
|
|
108
|
+
const response = await new Promise((resolve, reject) => {
|
|
109
|
+
this.#pending = this.#pending.finally(() => this.signer
|
|
110
|
+
.callCanister({
|
|
111
|
+
canisterId,
|
|
112
|
+
sender: this.#options.account,
|
|
113
|
+
method: fields.methodName,
|
|
114
|
+
arg: fields.arg,
|
|
115
|
+
nonce: fields.nonce,
|
|
116
|
+
})
|
|
117
|
+
.then(resolve, reject));
|
|
118
|
+
});
|
|
119
|
+
// Decode CBOR content map and reconstruct the Expiry
|
|
120
|
+
// (Expiry has a private constructor, so we use the JSON round-trip)
|
|
121
|
+
const decoded = Cbor.decode(response.contentMap);
|
|
122
|
+
const requestBody = {
|
|
123
|
+
...decoded,
|
|
124
|
+
canister_id: Principal.from(decoded.canister_id),
|
|
125
|
+
ingress_expiry: Expiry.fromJSON(JSON.stringify({ [JSON_KEY_EXPIRY]: String(decoded.ingress_expiry) })),
|
|
126
|
+
};
|
|
127
|
+
// Verify the content map matches what we requested
|
|
128
|
+
const contentMapMatchesRequest = SubmitRequestType.Call === requestBody.request_type &&
|
|
129
|
+
canisterId.toText() === requestBody.canister_id.toText() &&
|
|
130
|
+
fields.methodName === requestBody.method_name &&
|
|
131
|
+
uint8Equals(fields.arg, requestBody.arg) &&
|
|
132
|
+
this.#options.account.toText() === Principal.from(requestBody.sender).toText();
|
|
133
|
+
if (!contentMapMatchesRequest) {
|
|
134
|
+
throw new SignerAgentError(INVALID_RESPONSE_MESSAGE);
|
|
135
|
+
}
|
|
136
|
+
// Validate the certificate against the IC root key
|
|
137
|
+
const requestId = requestIdOf(requestBody);
|
|
138
|
+
const certificate = await Certificate.create({
|
|
139
|
+
certificate: response.certificate,
|
|
140
|
+
rootKey: this.rootKey,
|
|
141
|
+
principal: { canisterId },
|
|
142
|
+
maxAgeInMinutes: MAX_AGE_IN_MINUTES,
|
|
143
|
+
}).catch(cause => {
|
|
144
|
+
throw new SignerAgentError(INVALID_RESPONSE_MESSAGE, { cause });
|
|
145
|
+
});
|
|
146
|
+
// Extract the reply from the certified state tree
|
|
147
|
+
const replyLookup = certificate.lookup_path(['request_status', requestId, 'reply']);
|
|
148
|
+
if (replyLookup.status !== LookupPathStatus.Found) {
|
|
149
|
+
throw new SignerAgentError(INVALID_RESPONSE_MESSAGE);
|
|
150
|
+
}
|
|
151
|
+
// Store raw certificate for readState lookups, deleted on first read
|
|
152
|
+
this.#certificates.set(toHex(requestId), response.certificate);
|
|
153
|
+
return {
|
|
154
|
+
requestId,
|
|
155
|
+
requestBody,
|
|
156
|
+
certificate,
|
|
157
|
+
rawCertificate: response.certificate,
|
|
158
|
+
reply: replyLookup.value,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Sends a canister call through the signer.
|
|
163
|
+
* Returns the request ID and a synthetic HTTP response.
|
|
164
|
+
* @param canisterId - The target canister principal or its text representation.
|
|
165
|
+
* @param fields - The call options including method name and arguments.
|
|
166
|
+
*/
|
|
167
|
+
async call(canisterId, fields) {
|
|
168
|
+
canisterId = Principal.from(canisterId);
|
|
169
|
+
const { requestId, requestBody } = await this.#sendAndVerify(canisterId, fields);
|
|
170
|
+
return {
|
|
171
|
+
requestId,
|
|
172
|
+
response: {
|
|
173
|
+
ok: true,
|
|
174
|
+
status: 202,
|
|
175
|
+
statusText: 'Call has been sent over ICRC-25 JSON-RPC',
|
|
176
|
+
body: null,
|
|
177
|
+
headers: [],
|
|
178
|
+
},
|
|
179
|
+
requestDetails: requestBody,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Executes a canister update call and returns the certified result.
|
|
184
|
+
* Combines {@link call} with certificate validation and reply extraction.
|
|
185
|
+
* @param canisterId - The target canister principal or its text representation.
|
|
186
|
+
* @param fields - The call options including method name and arguments.
|
|
187
|
+
* @param _pollingOptions - Ignored. The signer already returns the
|
|
188
|
+
* certificate with the reply in a single round-trip.
|
|
189
|
+
*/
|
|
190
|
+
async update(canisterId, fields, _pollingOptions) {
|
|
191
|
+
canisterId = Principal.from(canisterId);
|
|
192
|
+
const { requestBody, certificate, rawCertificate, reply } = await this.#sendAndVerify(canisterId, fields);
|
|
193
|
+
return {
|
|
194
|
+
certificate,
|
|
195
|
+
reply,
|
|
196
|
+
rawCertificate,
|
|
197
|
+
requestDetails: requestBody,
|
|
198
|
+
callResponse: {
|
|
199
|
+
ok: true,
|
|
200
|
+
status: 202,
|
|
201
|
+
statusText: 'Call has been sent over ICRC-25 JSON-RPC',
|
|
202
|
+
body: null,
|
|
203
|
+
headers: [],
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Executes a query by upgrading it to a canister call through the signer.
|
|
209
|
+
* The signer signs and submits the call, and the reply is extracted
|
|
210
|
+
* from the certified response.
|
|
211
|
+
* @param canisterId - The target canister principal or its text representation.
|
|
212
|
+
* @param options - The query fields including method name and arguments.
|
|
213
|
+
* @param _identity - Ignored. The signer manages identity internally.
|
|
214
|
+
*/
|
|
215
|
+
async query(canisterId, options, _identity) {
|
|
216
|
+
canisterId = Principal.from(canisterId);
|
|
217
|
+
const { requestId, reply } = await this.#sendAndVerify(canisterId, {
|
|
218
|
+
methodName: options.methodName,
|
|
219
|
+
arg: options.arg,
|
|
220
|
+
effectiveCanisterId: canisterId,
|
|
221
|
+
});
|
|
222
|
+
return {
|
|
223
|
+
requestId,
|
|
224
|
+
status: 'replied',
|
|
225
|
+
reply: { arg: reply },
|
|
226
|
+
httpDetails: {
|
|
227
|
+
ok: true,
|
|
228
|
+
status: 202,
|
|
229
|
+
statusText: 'Certificate with reply has been received over ICRC-25 JSON-RPC',
|
|
230
|
+
headers: [],
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/** Fetches the IC root key via the underlying HttpAgent. */
|
|
235
|
+
async fetchRootKey() {
|
|
236
|
+
return await this.#options.agent.fetchRootKey();
|
|
237
|
+
}
|
|
238
|
+
/** Returns the account principal this agent makes calls on behalf of. */
|
|
239
|
+
getPrincipal() {
|
|
240
|
+
return Promise.resolve(this.#options.account);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* @internal
|
|
244
|
+
* @param _options - The read state options.
|
|
245
|
+
* @param _identity - The identity to use for the request.
|
|
246
|
+
*/
|
|
247
|
+
createReadStateRequest(_options, _identity) {
|
|
248
|
+
return Promise.resolve({ body: { content: {} } });
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Returns the raw certificate for a previously completed call.
|
|
252
|
+
* The certificate is deleted after being read (single-use).
|
|
253
|
+
*
|
|
254
|
+
* Only supports `request_status` paths for request IDs that were
|
|
255
|
+
* returned by a prior {@link call}, {@link update}, or {@link query}.
|
|
256
|
+
* @param _canisterId - The target canister principal (unused).
|
|
257
|
+
* @param options - The read state options containing paths to look up.
|
|
258
|
+
* @param _identity - The identity to use (unused).
|
|
259
|
+
* @param _request - The request object (unused).
|
|
260
|
+
*/
|
|
261
|
+
readState(_canisterId, options, _identity, _request) {
|
|
262
|
+
if (options.paths.length !== 1 ||
|
|
263
|
+
options.paths[0].length !== 2 ||
|
|
264
|
+
new TextDecoder().decode(options.paths[0][0]) !== 'request_status') {
|
|
265
|
+
return Promise.reject(new SignerAgentError('Given paths are not supported'));
|
|
266
|
+
}
|
|
267
|
+
const requestId = options.paths[0][1];
|
|
268
|
+
const key = toHex(requestId);
|
|
269
|
+
const certificate = this.#certificates.get(key);
|
|
270
|
+
if (!certificate) {
|
|
271
|
+
return Promise.reject(new SignerAgentError('Certificate could not be found'));
|
|
272
|
+
}
|
|
273
|
+
this.#certificates.delete(key);
|
|
274
|
+
return Promise.resolve({ certificate });
|
|
275
|
+
}
|
|
276
|
+
/** Queries the IC replica status via the underlying HttpAgent. */
|
|
277
|
+
async status() {
|
|
278
|
+
return await this.#options.agent.status();
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Replaces the account principal used for subsequent calls.
|
|
282
|
+
* @param account - The new account principal to use for subsequent calls.
|
|
283
|
+
*/
|
|
284
|
+
replaceAccount(account) {
|
|
285
|
+
this.#options.account = account;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../src/agent/agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SignerAgent, SignerAgentError, type SignerAgentOptions } from './agent.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/agent/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAA2B,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Channel, type JsonRpcRequest, type JsonRpcResponse } from '../transport.js';
|
|
2
|
+
import type { ProviderDetail } from './types.js';
|
|
3
|
+
/** Options for creating a {@link BrowserExtensionChannel}. */
|
|
4
|
+
export interface BrowserExtensionChannelOptions {
|
|
5
|
+
/** The provider details obtained during extension discovery. */
|
|
6
|
+
providerDetail: ProviderDetail;
|
|
7
|
+
/**
|
|
8
|
+
* The window to listen for extension events on.
|
|
9
|
+
* @default globalThis.window
|
|
10
|
+
*/
|
|
11
|
+
window?: Window;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A {@link Channel} implementation that communicates with a browser
|
|
15
|
+
* extension signer via the ICRC-94 provider API.
|
|
16
|
+
*
|
|
17
|
+
* Messages are sent through `providerDetail.sendMessage` and responses
|
|
18
|
+
* are validated as JSON-RPC before being dispatched to listeners.
|
|
19
|
+
* The channel is automatically closed if the extension fires an
|
|
20
|
+
* `icrc94:unexpectedlyClosed` event.
|
|
21
|
+
*/
|
|
22
|
+
export declare class BrowserExtensionChannel implements Channel {
|
|
23
|
+
#private;
|
|
24
|
+
constructor(options: BrowserExtensionChannelOptions);
|
|
25
|
+
/** Whether this channel has been closed. */
|
|
26
|
+
get closed(): boolean;
|
|
27
|
+
addEventListener(...[event, listener]: [event: 'close', listener: () => void] | [event: 'response', listener: (response: JsonRpcResponse) => void]): () => void;
|
|
28
|
+
/**
|
|
29
|
+
* Sends a JSON-RPC request to the extension via `providerDetail.sendMessage`.
|
|
30
|
+
* The response is validated as JSON-RPC before being dispatched.
|
|
31
|
+
* Non-JSON-RPC responses are silently ignored.
|
|
32
|
+
* @param request - The JSON-RPC request to send to the extension.
|
|
33
|
+
*/
|
|
34
|
+
send(request: JsonRpcRequest): Promise<void>;
|
|
35
|
+
/** Dismisses the extension and notifies all close listeners. */
|
|
36
|
+
close(): Promise<void>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { isJsonRpcResponse, } from '../transport.js';
|
|
2
|
+
import { BrowserExtensionTransportError } from './browserExtensionTransport.js';
|
|
3
|
+
/**
|
|
4
|
+
* A {@link Channel} implementation that communicates with a browser
|
|
5
|
+
* extension signer via the ICRC-94 provider API.
|
|
6
|
+
*
|
|
7
|
+
* Messages are sent through `providerDetail.sendMessage` and responses
|
|
8
|
+
* are validated as JSON-RPC before being dispatched to listeners.
|
|
9
|
+
* The channel is automatically closed if the extension fires an
|
|
10
|
+
* `icrc94:unexpectedlyClosed` event.
|
|
11
|
+
*/
|
|
12
|
+
export class BrowserExtensionChannel {
|
|
13
|
+
#closeListeners = new Set();
|
|
14
|
+
#responseListeners = new Set();
|
|
15
|
+
#options;
|
|
16
|
+
#closed = false;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.#options = {
|
|
19
|
+
window: globalThis.window,
|
|
20
|
+
...options,
|
|
21
|
+
};
|
|
22
|
+
// Listen for unexpected extension closure
|
|
23
|
+
const closeListener = () => {
|
|
24
|
+
this.#options.window.removeEventListener('icrc94:unexpectedlyClosed', closeListener);
|
|
25
|
+
this.#closed = true;
|
|
26
|
+
for (const listener of this.#closeListeners) {
|
|
27
|
+
listener();
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
this.#options.window.addEventListener('icrc94:unexpectedlyClosed', closeListener);
|
|
31
|
+
}
|
|
32
|
+
/** Whether this channel has been closed. */
|
|
33
|
+
get closed() {
|
|
34
|
+
return this.#closed;
|
|
35
|
+
}
|
|
36
|
+
addEventListener(...[event, listener]) {
|
|
37
|
+
switch (event) {
|
|
38
|
+
case 'close':
|
|
39
|
+
this.#closeListeners.add(listener);
|
|
40
|
+
return () => {
|
|
41
|
+
this.#closeListeners.delete(listener);
|
|
42
|
+
};
|
|
43
|
+
case 'response':
|
|
44
|
+
this.#responseListeners.add(listener);
|
|
45
|
+
return () => {
|
|
46
|
+
this.#responseListeners.delete(listener);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Sends a JSON-RPC request to the extension via `providerDetail.sendMessage`.
|
|
52
|
+
* The response is validated as JSON-RPC before being dispatched.
|
|
53
|
+
* Non-JSON-RPC responses are silently ignored.
|
|
54
|
+
* @param request - The JSON-RPC request to send to the extension.
|
|
55
|
+
*/
|
|
56
|
+
async send(request) {
|
|
57
|
+
if (this.#closed) {
|
|
58
|
+
throw new BrowserExtensionTransportError('Communication channel is closed');
|
|
59
|
+
}
|
|
60
|
+
const response = await this.#options.providerDetail.sendMessage(request);
|
|
61
|
+
if (!isJsonRpcResponse(response)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
for (const listener of this.#responseListeners) {
|
|
65
|
+
listener(response);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Dismisses the extension and notifies all close listeners. */
|
|
69
|
+
async close() {
|
|
70
|
+
if (this.#closed) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
this.#closed = true;
|
|
74
|
+
await this.#options.providerDetail.dismiss();
|
|
75
|
+
for (const listener of this.#closeListeners) {
|
|
76
|
+
listener();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=browserExtensionChannel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browserExtensionChannel.js","sourceRoot":"","sources":["../../../src/extension/browserExtensionChannel.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,iBAAiB,GAGlB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,8BAA8B,EAAE,MAAM,gCAAgC,CAAC;AAchF;;;;;;;;GAQG;AACH,MAAM,OAAO,uBAAuB;IACzB,eAAe,GAAG,IAAI,GAAG,EAAc,CAAC;IACxC,kBAAkB,GAAG,IAAI,GAAG,EAAuC,CAAC;IACpE,QAAQ,CAA2C;IAC5D,OAAO,GAAG,KAAK,CAAC;IAEhB,YAAY,OAAuC;QACjD,IAAI,CAAC,QAAQ,GAAG;YACd,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,GAAG,OAAO;SACX,CAAC;QAEF,0CAA0C;QAC1C,MAAM,aAAa,GAAG,GAAG,EAAE;YACzB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,mBAAmB,CAAC,2BAA2B,EAAE,aAAa,CAAC,CAAC;YACrF,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;gBAC5C,QAAQ,EAAE,CAAC;YACb,CAAC;QACH,CAAC,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,gBAAgB,CAAC,2BAA2B,EAAE,aAAa,CAAC,CAAC;IACpF,CAAC;IAED,4CAA4C;IAC5C,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,gBAAgB,CACd,GAAG,CAAC,KAAK,EAAE,QAAQ,CAEmD;QAEtE,QAAQ,KAAK,EAAE,CAAC;YACd,KAAK,OAAO;gBACV,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBACnC,OAAO,GAAG,EAAE;oBACV,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBACxC,CAAC,CAAC;YACJ,KAAK,UAAU;gBACb,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBACtC,OAAO,GAAG,EAAE;oBACV,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAC3C,CAAC,CAAC;QACN,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,IAAI,CAAC,OAAuB;QAChC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,IAAI,8BAA8B,CAAC,iCAAiC,CAAC,CAAC;QAC9E,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QACzE,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjC,OAAO;QACT,CAAC;QACD,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC/C,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAED,gEAAgE;IAChE,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,MAAM,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC;QAC7C,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YAC5C,QAAQ,EAAE,CAAC;QACb,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Transport } from '../transport.js';
|
|
2
|
+
import { BrowserExtensionChannel, type BrowserExtensionChannelOptions } from './browserExtensionChannel.js';
|
|
3
|
+
import type { ProviderDetail } from './types.js';
|
|
4
|
+
/** Error thrown by {@link BrowserExtensionTransport} for transport-level failures. */
|
|
5
|
+
export declare class BrowserExtensionTransportError extends Error {
|
|
6
|
+
}
|
|
7
|
+
/** Options for creating a {@link BrowserExtensionTransport}. */
|
|
8
|
+
export type BrowserExtensionTransportOptions = BrowserExtensionChannelOptions;
|
|
9
|
+
/** Options for {@link BrowserExtensionTransport.discover}. */
|
|
10
|
+
export interface DiscoverBrowserExtensionOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Time in milliseconds to wait for browser extensions to announce themselves
|
|
13
|
+
* via `icrc94:announceProvider` events.
|
|
14
|
+
* @default 100
|
|
15
|
+
*/
|
|
16
|
+
discoveryDuration?: number;
|
|
17
|
+
/**
|
|
18
|
+
* The window to listen for extension events on.
|
|
19
|
+
* @default globalThis.window
|
|
20
|
+
*/
|
|
21
|
+
window?: Window;
|
|
22
|
+
}
|
|
23
|
+
/** Options for {@link BrowserExtensionTransport.findTransport}. */
|
|
24
|
+
export interface EstablishBrowserExtensionTransportOptions extends DiscoverBrowserExtensionOptions, Omit<BrowserExtensionTransportOptions, 'providerDetail'> {
|
|
25
|
+
/** The UUID of the browser extension to connect to. */
|
|
26
|
+
uuid: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* ICRC-94 transport for communicating with browser extension signers.
|
|
30
|
+
*
|
|
31
|
+
* Browser extensions announce themselves via `icrc94:announceProvider`
|
|
32
|
+
* window events. Use {@link BrowserExtensionTransport.discover} to find installed extensions, or
|
|
33
|
+
* {@link BrowserExtensionTransport.findTransport} to connect to a specific one by UUID.
|
|
34
|
+
* @see https://github.com/dfinity/wg-identity-authentication/blob/main/topics/icrc_94_multi_injected_provider_discovery.md
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* // Discover all installed extensions
|
|
38
|
+
* const providers = await BrowserExtensionTransport.discover();
|
|
39
|
+
*
|
|
40
|
+
* // Or connect to a specific extension by UUID
|
|
41
|
+
* const transport = await BrowserExtensionTransport.findTransport({ uuid: "..." });
|
|
42
|
+
* const signer = new Signer({ transport });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare class BrowserExtensionTransport implements Transport {
|
|
46
|
+
#private;
|
|
47
|
+
constructor(options: BrowserExtensionTransportOptions);
|
|
48
|
+
/**
|
|
49
|
+
* Discovers all installed browser extension signers by dispatching
|
|
50
|
+
* an `icrc94:requestProvider` event and collecting `icrc94:announceProvider`
|
|
51
|
+
* responses. Waits for `discoveryDuration` ms before returning.
|
|
52
|
+
* @param root0 - The discovery options.
|
|
53
|
+
* @param root0.discoveryDuration - Time in milliseconds to wait for announcements.
|
|
54
|
+
* @param root0.window - The window to listen for extension events on.
|
|
55
|
+
* @returns The discovered extension providers, deduplicated by UUID.
|
|
56
|
+
*/
|
|
57
|
+
static discover({ discoveryDuration, window, }?: DiscoverBrowserExtensionOptions): Promise<ProviderDetail[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Discovers extensions and connects to the one matching the given UUID.
|
|
60
|
+
* @param options - The options including UUID and discovery settings.
|
|
61
|
+
* @throws {BrowserExtensionTransportError} If no extension with the given
|
|
62
|
+
* UUID is found.
|
|
63
|
+
*/
|
|
64
|
+
static findTransport(options: EstablishBrowserExtensionTransportOptions): Promise<BrowserExtensionTransport>;
|
|
65
|
+
/** Creates a new {@link BrowserExtensionChannel} for this extension. */
|
|
66
|
+
establishChannel(): Promise<BrowserExtensionChannel>;
|
|
67
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { BrowserExtensionChannel, } from './browserExtensionChannel.js';
|
|
2
|
+
/** Error thrown by {@link BrowserExtensionTransport} for transport-level failures. */
|
|
3
|
+
export class BrowserExtensionTransportError extends Error {
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* ICRC-94 transport for communicating with browser extension signers.
|
|
7
|
+
*
|
|
8
|
+
* Browser extensions announce themselves via `icrc94:announceProvider`
|
|
9
|
+
* window events. Use {@link BrowserExtensionTransport.discover} to find installed extensions, or
|
|
10
|
+
* {@link BrowserExtensionTransport.findTransport} to connect to a specific one by UUID.
|
|
11
|
+
* @see https://github.com/dfinity/wg-identity-authentication/blob/main/topics/icrc_94_multi_injected_provider_discovery.md
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* // Discover all installed extensions
|
|
15
|
+
* const providers = await BrowserExtensionTransport.discover();
|
|
16
|
+
*
|
|
17
|
+
* // Or connect to a specific extension by UUID
|
|
18
|
+
* const transport = await BrowserExtensionTransport.findTransport({ uuid: "..." });
|
|
19
|
+
* const signer = new Signer({ transport });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export class BrowserExtensionTransport {
|
|
23
|
+
#options;
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this.#options = {
|
|
26
|
+
window: globalThis.window,
|
|
27
|
+
...options,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Discovers all installed browser extension signers by dispatching
|
|
32
|
+
* an `icrc94:requestProvider` event and collecting `icrc94:announceProvider`
|
|
33
|
+
* responses. Waits for `discoveryDuration` ms before returning.
|
|
34
|
+
* @param root0 - The discovery options.
|
|
35
|
+
* @param root0.discoveryDuration - Time in milliseconds to wait for announcements.
|
|
36
|
+
* @param root0.window - The window to listen for extension events on.
|
|
37
|
+
* @returns The discovered extension providers, deduplicated by UUID.
|
|
38
|
+
*/
|
|
39
|
+
static async discover({ discoveryDuration = 100, window = globalThis.window, } = {}) {
|
|
40
|
+
const providerDetails = [];
|
|
41
|
+
window.addEventListener('icrc94:announceProvider', ((event) => {
|
|
42
|
+
if (providerDetails.find(providerDetail => providerDetail.uuid === event.detail.uuid)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
providerDetails.push(event.detail);
|
|
46
|
+
}));
|
|
47
|
+
window.dispatchEvent(new CustomEvent('icrc94:requestProvider'));
|
|
48
|
+
await new Promise(resolve => setTimeout(resolve, discoveryDuration));
|
|
49
|
+
return providerDetails;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Discovers extensions and connects to the one matching the given UUID.
|
|
53
|
+
* @param options - The options including UUID and discovery settings.
|
|
54
|
+
* @throws {BrowserExtensionTransportError} If no extension with the given
|
|
55
|
+
* UUID is found.
|
|
56
|
+
*/
|
|
57
|
+
static async findTransport(options) {
|
|
58
|
+
const providerDetails = await BrowserExtensionTransport.discover(options);
|
|
59
|
+
const providerDetail = providerDetails.find(({ uuid }) => uuid === options.uuid);
|
|
60
|
+
if (!providerDetail) {
|
|
61
|
+
throw new BrowserExtensionTransportError("Browser extension couldn't be found, make sure it's installed and enabled for this page.");
|
|
62
|
+
}
|
|
63
|
+
return new BrowserExtensionTransport({ ...options, providerDetail });
|
|
64
|
+
}
|
|
65
|
+
/** Creates a new {@link BrowserExtensionChannel} for this extension. */
|
|
66
|
+
establishChannel() {
|
|
67
|
+
return Promise.resolve(new BrowserExtensionChannel(this.#options));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=browserExtensionTransport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browserExtensionTransport.js","sourceRoot":"","sources":["../../../src/extension/browserExtensionTransport.ts"],"names":[],"mappings":"AACA,OAAO,EACL,uBAAuB,GAExB,MAAM,8BAA8B,CAAC;AAGtC,sFAAsF;AACtF,MAAM,OAAO,8BAA+B,SAAQ,KAAK;CAAG;AA6B5D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,yBAAyB;IAC3B,QAAQ,CAA6C;IAE9D,YAAY,OAAyC;QACnD,IAAI,CAAC,QAAQ,GAAG;YACd,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,GAAG,OAAO;SACX,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EACpB,iBAAiB,GAAG,GAAG,EACvB,MAAM,GAAG,UAAU,CAAC,MAAM,MACS,EAAE;QACrC,MAAM,eAAe,GAAqB,EAAE,CAAC;QAC7C,MAAM,CAAC,gBAAgB,CAAC,yBAAyB,EAAE,CAAC,CAAC,KAAkC,EAAE,EAAE;YACzF,IAAI,eAAe,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC,cAAc,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;gBACtF,OAAO;YACT,CAAC;YACD,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACrC,CAAC,CAAkB,CAAC,CAAC;QACrB,MAAM,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,wBAAwB,CAAC,CAAC,CAAC;QAChE,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC;QACrE,OAAO,eAAe,CAAC;IACzB,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,KAAK,CAAC,aAAa,CACxB,OAAkD;QAElD,MAAM,eAAe,GAAG,MAAM,yBAAyB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC1E,MAAM,cAAc,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;QACjF,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,MAAM,IAAI,8BAA8B,CACtC,0FAA0F,CAC3F,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,yBAAyB,CAAC,EAAE,GAAG,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,wEAAwE;IACxE,gBAAgB;QACd,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,uBAAuB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrE,CAAC;CACF"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { BrowserExtensionChannel, type BrowserExtensionChannelOptions, } from './browserExtensionChannel.js';
|
|
2
|
+
export { BrowserExtensionTransport, BrowserExtensionTransportError, type BrowserExtensionTransportOptions, type DiscoverBrowserExtensionOptions, type EstablishBrowserExtensionTransportOptions, } from './browserExtensionTransport.js';
|
|
3
|
+
export type { ProviderDetail } from './types.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extension/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,GAExB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,yBAAyB,EACzB,8BAA8B,GAI/B,MAAM,gCAAgC,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { JsonRpcRequest } from '../transport.js';
|
|
2
|
+
/**
|
|
3
|
+
* Details about a browser extension signer, as announced via the
|
|
4
|
+
* ICRC-94 `icrc94:announceProvider` event.
|
|
5
|
+
* @see https://github.com/dfinity/wg-identity-authentication/blob/main/topics/icrc_94_multi_injected_provider_discovery.md
|
|
6
|
+
*/
|
|
7
|
+
export interface ProviderDetail {
|
|
8
|
+
/** Globally unique identifier (UUIDv4) for this extension. */
|
|
9
|
+
uuid: string;
|
|
10
|
+
/** Human-readable name of the signer. */
|
|
11
|
+
name: string;
|
|
12
|
+
/** Icon as a data URI (e.g. `data:image/svg+xml,...`). */
|
|
13
|
+
icon: `data:image/${string}`;
|
|
14
|
+
/** Reverse domain name identifier (e.g. `com.example.wallet`). */
|
|
15
|
+
rdns: string;
|
|
16
|
+
/** Sends a JSON-RPC request to the extension and returns the response. */
|
|
17
|
+
sendMessage: (message: JsonRpcRequest) => Promise<unknown>;
|
|
18
|
+
/** Dismisses the extension's UI. */
|
|
19
|
+
dismiss: () => Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
/** Fired by extensions to announce their presence. */
|
|
22
|
+
export interface AnnounceProviderEvent extends CustomEvent<ProviderDetail> {
|
|
23
|
+
type: 'icrc94:announceProvider';
|
|
24
|
+
}
|
|
25
|
+
/** Dispatched by relying parties to trigger extension announcements. */
|
|
26
|
+
export interface RequestProviderEvent extends Event {
|
|
27
|
+
type: 'icrc94:requestProvider';
|
|
28
|
+
}
|
|
29
|
+
/** Fired by extensions when they close unexpectedly. */
|
|
30
|
+
export interface UnexpectedlyClosedEvent extends Event {
|
|
31
|
+
type: 'icrc94:unexpectedlyClosed';
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/extension/types.ts"],"names":[],"mappings":""}
|
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,MAAM,EACN,WAAW,GAGZ,MAAM,aAAa,CAAC"}
|