@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,60 @@
|
|
|
1
|
+
export interface HeartbeatClientOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Signer window to send and receive heartbeat messages from
|
|
4
|
+
*/
|
|
5
|
+
signerWindow: Window;
|
|
6
|
+
/**
|
|
7
|
+
* Callback when first heartbeat has been received
|
|
8
|
+
*/
|
|
9
|
+
onEstablish: (origin: string, status: 'pending' | 'ready') => void;
|
|
10
|
+
/**
|
|
11
|
+
* Reasonable time in milliseconds in which the communication channel needs to be established
|
|
12
|
+
* @default 10000
|
|
13
|
+
*/
|
|
14
|
+
establishTimeout?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Callback when no heartbeats have been received for `establishTimeout` milliseconds
|
|
17
|
+
*/
|
|
18
|
+
onEstablishTimeout: () => void;
|
|
19
|
+
/**
|
|
20
|
+
* Callback when status response has changed
|
|
21
|
+
*/
|
|
22
|
+
onStatusChange: (status: 'pending' | 'ready') => void;
|
|
23
|
+
/**
|
|
24
|
+
* Reasonable time in milliseconds in which the communication channel can be pending
|
|
25
|
+
* @default 300000
|
|
26
|
+
*/
|
|
27
|
+
pendingTimeout?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Callback when no heartbeats have been received for `pendingTimeout` milliseconds
|
|
30
|
+
*/
|
|
31
|
+
onPendingTimeout: () => void;
|
|
32
|
+
/**
|
|
33
|
+
* Time in milliseconds of not receiving heartbeat responses after which the communication channel is disconnected
|
|
34
|
+
* @default 5000
|
|
35
|
+
*/
|
|
36
|
+
disconnectTimeout?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Callback when no heartbeats have been received for `disconnectTimeout` milliseconds
|
|
39
|
+
*/
|
|
40
|
+
onDisconnect: () => void;
|
|
41
|
+
/**
|
|
42
|
+
* Status polling rate in ms
|
|
43
|
+
* @default 300
|
|
44
|
+
*/
|
|
45
|
+
statusPollingRate?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Relying party window, used to listen for incoming message events
|
|
48
|
+
* @default globalThis.window
|
|
49
|
+
*/
|
|
50
|
+
window?: Window;
|
|
51
|
+
/**
|
|
52
|
+
* Get random uuid implementation for status messages
|
|
53
|
+
* @default globalThis.crypto
|
|
54
|
+
*/
|
|
55
|
+
crypto?: Pick<Crypto, 'randomUUID'>;
|
|
56
|
+
}
|
|
57
|
+
export declare class HeartbeatClient {
|
|
58
|
+
#private;
|
|
59
|
+
constructor(options: HeartbeatClientOptions);
|
|
60
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { isJsonRpcResponse } from '../../transport.js';
|
|
2
|
+
export class HeartbeatClient {
|
|
3
|
+
#options;
|
|
4
|
+
#status;
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.#options = {
|
|
7
|
+
establishTimeout: 10000,
|
|
8
|
+
pendingTimeout: 300000,
|
|
9
|
+
disconnectTimeout: 5000,
|
|
10
|
+
statusPollingRate: 300,
|
|
11
|
+
window: globalThis.window,
|
|
12
|
+
crypto: globalThis.crypto,
|
|
13
|
+
...options,
|
|
14
|
+
};
|
|
15
|
+
this.#establish();
|
|
16
|
+
}
|
|
17
|
+
#establish() {
|
|
18
|
+
let pending = [];
|
|
19
|
+
// Create new pending entry that's waiting for a response
|
|
20
|
+
const create = () => {
|
|
21
|
+
const id = this.#options.crypto.randomUUID();
|
|
22
|
+
pending.push(id);
|
|
23
|
+
return id;
|
|
24
|
+
};
|
|
25
|
+
// Establish communication channel if a response is received for any pending id
|
|
26
|
+
const listener = this.#receiveStatusResponse(response => {
|
|
27
|
+
if ('result' in response.data &&
|
|
28
|
+
response.data.id !== null &&
|
|
29
|
+
pending.includes(response.data.id)) {
|
|
30
|
+
pending = [];
|
|
31
|
+
listener();
|
|
32
|
+
clearInterval(interval);
|
|
33
|
+
clearTimeout(timeout);
|
|
34
|
+
this.#status = response.data.result;
|
|
35
|
+
this.#options.onEstablish(response.origin, response.data.result);
|
|
36
|
+
this.#maintain(response.origin, response.data.result);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
// Init timeout
|
|
40
|
+
const timeout = setTimeout(() => {
|
|
41
|
+
listener();
|
|
42
|
+
clearInterval(interval);
|
|
43
|
+
this.#options.onEstablishTimeout();
|
|
44
|
+
}, this.#options.establishTimeout);
|
|
45
|
+
// Start sending requests
|
|
46
|
+
const interval = setInterval(() => this.#sendStatusRequest(create()), this.#options.statusPollingRate);
|
|
47
|
+
}
|
|
48
|
+
#maintain(origin, status) {
|
|
49
|
+
let timeout;
|
|
50
|
+
let pending = [];
|
|
51
|
+
// Consume a pending entry if it exists
|
|
52
|
+
const consume = (id) => {
|
|
53
|
+
const index = pending.findIndex(entry => entry.id === id);
|
|
54
|
+
if (index > -1) {
|
|
55
|
+
pending.splice(index, 1);
|
|
56
|
+
}
|
|
57
|
+
return index > -1;
|
|
58
|
+
};
|
|
59
|
+
// Create new pending entry that's waiting for a response
|
|
60
|
+
const create = () => {
|
|
61
|
+
const id = this.#options.crypto.randomUUID();
|
|
62
|
+
const time = Date.now();
|
|
63
|
+
// Cleanup ids outside disconnect window
|
|
64
|
+
pending = pending.filter(entry => time - this.#options.disconnectTimeout > entry.time);
|
|
65
|
+
// Insert and return new id
|
|
66
|
+
pending.push({ id, time });
|
|
67
|
+
return id;
|
|
68
|
+
};
|
|
69
|
+
// Clear existing timeout (if any) and create a new one
|
|
70
|
+
const resetTimeout = (status) => {
|
|
71
|
+
clearTimeout(timeout);
|
|
72
|
+
timeout = setTimeout(() => {
|
|
73
|
+
listener();
|
|
74
|
+
this.#options.onDisconnect();
|
|
75
|
+
}, status === 'pending' ? this.#options.pendingTimeout : this.#options.disconnectTimeout);
|
|
76
|
+
};
|
|
77
|
+
// Reset disconnect timeout if a response is received to an id within disconnect window
|
|
78
|
+
const listener = this.#receiveStatusResponse(response => {
|
|
79
|
+
if ('result' in response.data &&
|
|
80
|
+
response.data.id !== null &&
|
|
81
|
+
response.origin === origin &&
|
|
82
|
+
consume(response.data.id)) {
|
|
83
|
+
resetTimeout(response.data.result);
|
|
84
|
+
if (this.#status !== response.data.result) {
|
|
85
|
+
this.#status = response.data.result;
|
|
86
|
+
this.#options.onStatusChange(response.data.result);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
setTimeout(() => this.#sendStatusRequest(create()), this.#options.statusPollingRate);
|
|
90
|
+
});
|
|
91
|
+
// Init timeout and start sending requests
|
|
92
|
+
resetTimeout(status);
|
|
93
|
+
this.#sendStatusRequest(create());
|
|
94
|
+
}
|
|
95
|
+
#receiveStatusResponse(handler) {
|
|
96
|
+
const listener = (event) => {
|
|
97
|
+
if ((event.source === this.#options.signerWindow &&
|
|
98
|
+
isJsonRpcResponse(event.data) &&
|
|
99
|
+
'result' in event.data &&
|
|
100
|
+
event.data.result === 'pending') ||
|
|
101
|
+
event.data.result === 'ready') {
|
|
102
|
+
handler(event);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
this.#options.window.addEventListener('message', listener);
|
|
106
|
+
return () => this.#options.window.removeEventListener('message', listener);
|
|
107
|
+
}
|
|
108
|
+
#sendStatusRequest(id) {
|
|
109
|
+
this.#options.signerWindow.postMessage({ jsonrpc: '2.0', id, method: 'icrc29_status' }, '*');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../../src/web/heartbeat/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface HeartbeatServerOptions {
|
|
2
|
+
/**
|
|
3
|
+
* The initial server status to return to the client
|
|
4
|
+
* @default "ready"
|
|
5
|
+
*/
|
|
6
|
+
status?: 'pending' | 'ready';
|
|
7
|
+
/**
|
|
8
|
+
* The allowed origin that the communication channel can be established with, recommended for secure re-establishment
|
|
9
|
+
*/
|
|
10
|
+
allowedOrigin?: string | null;
|
|
11
|
+
/**
|
|
12
|
+
* Callback when first heartbeat has been received
|
|
13
|
+
*/
|
|
14
|
+
onEstablish: (origin: string, source: WindowProxy) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Reasonable time in milliseconds in which the communication channel needs to be established
|
|
17
|
+
* @default 10000
|
|
18
|
+
*/
|
|
19
|
+
establishTimeout?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Callback when no heartbeats have been received for `establishTimeout` milliseconds
|
|
22
|
+
*/
|
|
23
|
+
onEstablishTimeout: () => void;
|
|
24
|
+
/**
|
|
25
|
+
* Time in milliseconds of not receiving heartbeat requests after which the communication channel is disconnected
|
|
26
|
+
* @default 2000
|
|
27
|
+
*/
|
|
28
|
+
disconnectTimeout?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Callback when no heartbeats have been received for `disconnectTimeout` milliseconds
|
|
31
|
+
*/
|
|
32
|
+
onDisconnect: () => void;
|
|
33
|
+
/**
|
|
34
|
+
* Signer window, used to listen for incoming message events
|
|
35
|
+
* @default globalThis.window
|
|
36
|
+
*/
|
|
37
|
+
window?: Window;
|
|
38
|
+
}
|
|
39
|
+
export declare class HeartbeatServer {
|
|
40
|
+
#private;
|
|
41
|
+
constructor(options: HeartbeatServerOptions);
|
|
42
|
+
changeStatus(status: 'pending' | 'ready'): void;
|
|
43
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { isJsonRpcRequest } from '../../transport.js';
|
|
2
|
+
export class HeartbeatServer {
|
|
3
|
+
#options;
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this.#options = {
|
|
6
|
+
status: 'ready',
|
|
7
|
+
establishTimeout: 10000,
|
|
8
|
+
disconnectTimeout: 2000,
|
|
9
|
+
window: globalThis.window,
|
|
10
|
+
...options,
|
|
11
|
+
allowedOrigin: options.allowedOrigin ?? null,
|
|
12
|
+
};
|
|
13
|
+
this.#establish();
|
|
14
|
+
}
|
|
15
|
+
changeStatus(status) {
|
|
16
|
+
this.#options.status = status;
|
|
17
|
+
}
|
|
18
|
+
#establish() {
|
|
19
|
+
// Establish communication channel if a request is received
|
|
20
|
+
const listener = this.#receiveStatusRequest(request => {
|
|
21
|
+
if (request.source === null || request.data.id === undefined) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
listener();
|
|
25
|
+
clearTimeout(timeout);
|
|
26
|
+
this.#options.onEstablish(request.origin, request.source);
|
|
27
|
+
this.#sendStatusResponse(request.data.id, request.origin, request.source);
|
|
28
|
+
this.#maintain(request.origin, request.source);
|
|
29
|
+
});
|
|
30
|
+
// Send initial status response to kickstart the process
|
|
31
|
+
// in case the client is already waiting for responses.
|
|
32
|
+
//
|
|
33
|
+
// This resumes the client page from sleep in the case
|
|
34
|
+
// of browsers like Safari that unload background pages
|
|
35
|
+
// after a certain idle time and requiring an event to
|
|
36
|
+
// wake them up again (e.g. by sending a message to them).
|
|
37
|
+
if (this.#options.allowedOrigin !== null && this.#options.window.opener !== null) {
|
|
38
|
+
this.#sendStatusResponse('wake-up-client', this.#options.allowedOrigin, this.#options.window.opener);
|
|
39
|
+
}
|
|
40
|
+
// Init timeout
|
|
41
|
+
const timeout = setTimeout(() => {
|
|
42
|
+
listener();
|
|
43
|
+
this.#options.onEstablishTimeout();
|
|
44
|
+
}, this.#options.establishTimeout);
|
|
45
|
+
}
|
|
46
|
+
#maintain(origin, source) {
|
|
47
|
+
let timeout;
|
|
48
|
+
// Clear existing timeout (if any) and create a new one
|
|
49
|
+
const resetTimeout = () => {
|
|
50
|
+
clearTimeout(timeout);
|
|
51
|
+
timeout = setTimeout(() => {
|
|
52
|
+
listener();
|
|
53
|
+
this.#options.onDisconnect();
|
|
54
|
+
}, this.#options.disconnectTimeout);
|
|
55
|
+
};
|
|
56
|
+
// Init timeout and start sending messages
|
|
57
|
+
resetTimeout();
|
|
58
|
+
// Reset disconnect timeout and send response if a request is received
|
|
59
|
+
const listener = this.#receiveStatusRequest(request => {
|
|
60
|
+
if (request.origin === origin && request.source === source && request.data.id !== undefined) {
|
|
61
|
+
resetTimeout();
|
|
62
|
+
this.#sendStatusResponse(request.data.id, request.origin, request.source);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
#receiveStatusRequest(handler) {
|
|
67
|
+
const listener = (event) => {
|
|
68
|
+
if (!isJsonRpcRequest(event.data) ||
|
|
69
|
+
event.data.method !== 'icrc29_status' ||
|
|
70
|
+
(this.#options.allowedOrigin !== null && event.origin !== this.#options.allowedOrigin)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
handler(event);
|
|
74
|
+
};
|
|
75
|
+
this.#options.window.addEventListener('message', listener);
|
|
76
|
+
return () => this.#options.window.removeEventListener('message', listener);
|
|
77
|
+
}
|
|
78
|
+
#sendStatusResponse(id, origin, source) {
|
|
79
|
+
source.postMessage({ jsonrpc: '2.0', id, result: this.#options.status }, origin);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../../src/web/heartbeat/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { HeartbeatClient, type HeartbeatClientOptions } from './heartbeat/client.js';
|
|
2
|
+
export { HeartbeatServer, type HeartbeatServerOptions } from './heartbeat/server.js';
|
|
3
|
+
export { PostMessageChannel, type PostMessageChannelOptions } from './postMessageChannel.js';
|
|
4
|
+
export { PostMessageTransport, PostMessageTransportError, type PostMessageTransportOptions, } from './postMessageTransport.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { HeartbeatClient } from './heartbeat/client.js';
|
|
2
|
+
export { HeartbeatServer } from './heartbeat/server.js';
|
|
3
|
+
export { PostMessageChannel } from './postMessageChannel.js';
|
|
4
|
+
export { PostMessageTransport, PostMessageTransportError, } from './postMessageTransport.js';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/web/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAA+B,MAAM,uBAAuB,CAAC;AACrF,OAAO,EAAE,eAAe,EAA+B,MAAM,uBAAuB,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAkC,MAAM,yBAAyB,CAAC;AAC7F,OAAO,EACL,oBAAoB,EACpB,yBAAyB,GAE1B,MAAM,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type Channel, type JsonRpcRequest, type JsonRpcResponse } from '../transport.js';
|
|
2
|
+
/** Options for creating a {@link PostMessageChannel}. */
|
|
3
|
+
export interface PostMessageChannelOptions {
|
|
4
|
+
/** The signer window that this channel communicates with. */
|
|
5
|
+
signerWindow: Window;
|
|
6
|
+
/** The verified origin of the signer window. */
|
|
7
|
+
signerOrigin: string;
|
|
8
|
+
/**
|
|
9
|
+
* Initial status of the signer. When `"pending"`, messages are queued
|
|
10
|
+
* until the status changes to `"ready"`.
|
|
11
|
+
* @default "ready"
|
|
12
|
+
*/
|
|
13
|
+
signerStatus?: 'pending' | 'ready';
|
|
14
|
+
/**
|
|
15
|
+
* The relying party window, used to listen for incoming `postMessage` events.
|
|
16
|
+
* @default globalThis.window
|
|
17
|
+
*/
|
|
18
|
+
window?: Window;
|
|
19
|
+
/**
|
|
20
|
+
* Manage focus between the relying party and signer windows.
|
|
21
|
+
* @default true
|
|
22
|
+
*/
|
|
23
|
+
manageFocus?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A {@link Channel} implementation that communicates with a signer
|
|
27
|
+
* via `window.postMessage`. Created by {@link PostMessageTransport}
|
|
28
|
+
* after the ICRC-29 heartbeat handshake completes.
|
|
29
|
+
*
|
|
30
|
+
* Messages are filtered by source window and origin to prevent
|
|
31
|
+
* cross-origin interference. When the signer status is `"pending"`,
|
|
32
|
+
* outgoing messages are queued and flushed when it becomes `"ready"`.
|
|
33
|
+
*/
|
|
34
|
+
export declare class PostMessageChannel implements Channel {
|
|
35
|
+
#private;
|
|
36
|
+
constructor(options: PostMessageChannelOptions);
|
|
37
|
+
/** Whether this channel has been closed. */
|
|
38
|
+
get closed(): boolean;
|
|
39
|
+
addEventListener(...[event, listener]: [event: 'close', listener: () => void] | [event: 'response', listener: (response: JsonRpcResponse) => void]): () => void;
|
|
40
|
+
/**
|
|
41
|
+
* Sends a JSON-RPC request to the signer. If the signer status is
|
|
42
|
+
* `"pending"`, the request is queued until {@link changeStatus} is
|
|
43
|
+
* called with `"ready"`.
|
|
44
|
+
* @param request - The JSON-RPC request to send.
|
|
45
|
+
*/
|
|
46
|
+
send(request: JsonRpcRequest): Promise<void>;
|
|
47
|
+
/** Closes the signer window and notifies all close listeners. */
|
|
48
|
+
close(): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Updates the signer status. When transitioning to `"ready"`,
|
|
51
|
+
* all queued messages are flushed to the signer window.
|
|
52
|
+
* @param status - The new signer status.
|
|
53
|
+
*/
|
|
54
|
+
changeStatus(status: 'pending' | 'ready'): void;
|
|
55
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { isJsonRpcResponse, } from '../transport.js';
|
|
2
|
+
import { PostMessageTransportError } from './postMessageTransport.js';
|
|
3
|
+
/**
|
|
4
|
+
* A {@link Channel} implementation that communicates with a signer
|
|
5
|
+
* via `window.postMessage`. Created by {@link PostMessageTransport}
|
|
6
|
+
* after the ICRC-29 heartbeat handshake completes.
|
|
7
|
+
*
|
|
8
|
+
* Messages are filtered by source window and origin to prevent
|
|
9
|
+
* cross-origin interference. When the signer status is `"pending"`,
|
|
10
|
+
* outgoing messages are queued and flushed when it becomes `"ready"`.
|
|
11
|
+
*/
|
|
12
|
+
export class PostMessageChannel {
|
|
13
|
+
#closeListeners = new Set();
|
|
14
|
+
#options;
|
|
15
|
+
#closed = false;
|
|
16
|
+
#pendingQueue = [];
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.#options = {
|
|
19
|
+
signerStatus: 'ready',
|
|
20
|
+
window: globalThis.window,
|
|
21
|
+
manageFocus: true,
|
|
22
|
+
...options,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Whether this channel has been closed. */
|
|
26
|
+
get closed() {
|
|
27
|
+
return this.#closed;
|
|
28
|
+
}
|
|
29
|
+
addEventListener(...[event, listener]) {
|
|
30
|
+
switch (event) {
|
|
31
|
+
case 'close':
|
|
32
|
+
this.#closeListeners.add(listener);
|
|
33
|
+
return () => {
|
|
34
|
+
this.#closeListeners.delete(listener);
|
|
35
|
+
};
|
|
36
|
+
case 'response': {
|
|
37
|
+
const messageListener = (event) => {
|
|
38
|
+
// Only accept messages from the signer's window and origin
|
|
39
|
+
if (event.source !== this.#options.signerWindow ||
|
|
40
|
+
event.origin !== this.#options.signerOrigin ||
|
|
41
|
+
!isJsonRpcResponse(event.data)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
listener(event.data);
|
|
45
|
+
};
|
|
46
|
+
this.#options.window.addEventListener('message', messageListener);
|
|
47
|
+
return () => {
|
|
48
|
+
this.#options.window.removeEventListener('message', messageListener);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Sends a JSON-RPC request to the signer. If the signer status is
|
|
55
|
+
* `"pending"`, the request is queued until {@link changeStatus} is
|
|
56
|
+
* called with `"ready"`.
|
|
57
|
+
* @param request - The JSON-RPC request to send.
|
|
58
|
+
*/
|
|
59
|
+
send(request) {
|
|
60
|
+
if (this.#closed) {
|
|
61
|
+
return Promise.reject(new PostMessageTransportError('Communication channel is closed'));
|
|
62
|
+
}
|
|
63
|
+
if (this.#options.signerStatus === 'pending') {
|
|
64
|
+
this.#pendingQueue.push(request);
|
|
65
|
+
return Promise.resolve();
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
this.#options.signerWindow.postMessage(request, this.#options.signerOrigin);
|
|
69
|
+
if (this.#options.manageFocus) {
|
|
70
|
+
this.#options.signerWindow.focus();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return Promise.reject(error);
|
|
75
|
+
}
|
|
76
|
+
return Promise.resolve();
|
|
77
|
+
}
|
|
78
|
+
/** Closes the signer window and notifies all close listeners. */
|
|
79
|
+
close() {
|
|
80
|
+
if (this.#closed) {
|
|
81
|
+
return Promise.resolve();
|
|
82
|
+
}
|
|
83
|
+
this.#closed = true;
|
|
84
|
+
this.#options.signerWindow.close();
|
|
85
|
+
if (this.#options.manageFocus) {
|
|
86
|
+
this.#options.window.focus();
|
|
87
|
+
}
|
|
88
|
+
for (const listener of this.#closeListeners) {
|
|
89
|
+
listener();
|
|
90
|
+
}
|
|
91
|
+
return Promise.resolve();
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Updates the signer status. When transitioning to `"ready"`,
|
|
95
|
+
* all queued messages are flushed to the signer window.
|
|
96
|
+
* @param status - The new signer status.
|
|
97
|
+
*/
|
|
98
|
+
changeStatus(status) {
|
|
99
|
+
this.#options.signerStatus = status;
|
|
100
|
+
if (status === 'ready') {
|
|
101
|
+
const requests = this.#pendingQueue;
|
|
102
|
+
this.#pendingQueue = [];
|
|
103
|
+
requests.forEach(request => {
|
|
104
|
+
this.#options.signerWindow.postMessage(request, this.#options.signerOrigin);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=postMessageChannel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postMessageChannel.js","sourceRoot":"","sources":["../../../src/web/postMessageChannel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Transport } from '../transport.js';
|
|
2
|
+
import { PostMessageChannel } from './postMessageChannel.js';
|
|
3
|
+
/** Error thrown by {@link PostMessageTransport} for transport-level failures. */
|
|
4
|
+
export declare class PostMessageTransportError extends Error {
|
|
5
|
+
}
|
|
6
|
+
/** Options for creating a {@link PostMessageTransport}. */
|
|
7
|
+
export interface PostMessageTransportOptions {
|
|
8
|
+
/** The signer's RPC URL. Must be a secure context (HTTPS, localhost, or 127.0.0.1). */
|
|
9
|
+
url: string;
|
|
10
|
+
/**
|
|
11
|
+
* Window features string passed to `window.open()`.
|
|
12
|
+
* @example "toolbar=0,location=0,menubar=0,width=500,height=500,left=100,top=100"
|
|
13
|
+
*/
|
|
14
|
+
windowOpenerFeatures?: string;
|
|
15
|
+
/**
|
|
16
|
+
* The relying party window, used to listen for incoming `postMessage` events.
|
|
17
|
+
* @default globalThis.window
|
|
18
|
+
*/
|
|
19
|
+
window?: Window;
|
|
20
|
+
/**
|
|
21
|
+
* Time in milliseconds to wait for the ICRC-29 heartbeat handshake to complete.
|
|
22
|
+
* @default 120000
|
|
23
|
+
*/
|
|
24
|
+
establishTimeout?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Time in milliseconds the channel can remain in "pending" status
|
|
27
|
+
* before the connection is considered failed.
|
|
28
|
+
* @default 300000
|
|
29
|
+
*/
|
|
30
|
+
pendingTimeout?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Time in milliseconds without a heartbeat response after which
|
|
33
|
+
* the channel is considered disconnected.
|
|
34
|
+
* @default 2000
|
|
35
|
+
*/
|
|
36
|
+
disconnectTimeout?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Interval in milliseconds between ICRC-29 heartbeat status polls.
|
|
39
|
+
* @default 300
|
|
40
|
+
*/
|
|
41
|
+
statusPollingRate?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Source of random UUIDs for heartbeat request IDs.
|
|
44
|
+
* @default globalThis.crypto
|
|
45
|
+
*/
|
|
46
|
+
crypto?: Pick<Crypto, 'randomUUID'>;
|
|
47
|
+
/**
|
|
48
|
+
* Manage focus between the relying party and signer windows.
|
|
49
|
+
* When true, the signer window is focused on send and the relying
|
|
50
|
+
* party window is focused on close.
|
|
51
|
+
* @default true
|
|
52
|
+
*/
|
|
53
|
+
manageFocus?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Close the signer window if the heartbeat handshake times out.
|
|
56
|
+
* @default true
|
|
57
|
+
*/
|
|
58
|
+
closeOnEstablishTimeout?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Close the signer window if it stays in "pending" status too long.
|
|
61
|
+
* @default true
|
|
62
|
+
*/
|
|
63
|
+
closeOnPendingTimeout?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Detect and reject attempts to open the signer window outside a
|
|
66
|
+
* click handler. Browsers like Safari block popups opened without
|
|
67
|
+
* user interaction.
|
|
68
|
+
* @default true
|
|
69
|
+
*/
|
|
70
|
+
detectNonClickEstablishment?: boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* ICRC-29 post message transport for communicating with web-based signers.
|
|
74
|
+
*
|
|
75
|
+
* Opens a window to the signer's URL and establishes a communication channel
|
|
76
|
+
* using the ICRC-29 heartbeat protocol (`icrc29_status` polling). Messages
|
|
77
|
+
* are exchanged via `window.postMessage`.
|
|
78
|
+
* @see https://github.com/dfinity/wg-identity-authentication/blob/main/topics/icrc_29_window_post_message_transport.md
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* const transport = new PostMessageTransport({ url: "https://oisy.com/sign" });
|
|
82
|
+
* const signer = new Signer({ transport });
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export declare class PostMessageTransport implements Transport {
|
|
86
|
+
#private;
|
|
87
|
+
constructor(options: PostMessageTransportOptions);
|
|
88
|
+
/**
|
|
89
|
+
* Opens the signer window and establishes a communication channel
|
|
90
|
+
* via the ICRC-29 heartbeat handshake.
|
|
91
|
+
* @throws {PostMessageTransportError} If called outside a click handler
|
|
92
|
+
* (when `detectNonClickEstablishment` is enabled), if the window
|
|
93
|
+
* cannot be opened, or if the handshake times out.
|
|
94
|
+
*/
|
|
95
|
+
establishChannel(): Promise<PostMessageChannel>;
|
|
96
|
+
}
|