@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.
Files changed (45) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +165 -0
  3. package/lib/esm/agent/agent.d.ts +112 -0
  4. package/lib/esm/agent/agent.js +288 -0
  5. package/lib/esm/agent/agent.js.map +1 -0
  6. package/lib/esm/agent/index.d.ts +1 -0
  7. package/lib/esm/agent/index.js +2 -0
  8. package/lib/esm/agent/index.js.map +1 -0
  9. package/lib/esm/extension/browserExtensionChannel.d.ts +37 -0
  10. package/lib/esm/extension/browserExtensionChannel.js +80 -0
  11. package/lib/esm/extension/browserExtensionChannel.js.map +1 -0
  12. package/lib/esm/extension/browserExtensionTransport.d.ts +67 -0
  13. package/lib/esm/extension/browserExtensionTransport.js +70 -0
  14. package/lib/esm/extension/browserExtensionTransport.js.map +1 -0
  15. package/lib/esm/extension/index.d.ts +3 -0
  16. package/lib/esm/extension/index.js +3 -0
  17. package/lib/esm/extension/index.js.map +1 -0
  18. package/lib/esm/extension/types.d.ts +32 -0
  19. package/lib/esm/extension/types.js +2 -0
  20. package/lib/esm/extension/types.js.map +1 -0
  21. package/lib/esm/index.d.ts +2 -0
  22. package/lib/esm/index.js +2 -0
  23. package/lib/esm/index.js.map +1 -0
  24. package/lib/esm/signer.d.ts +180 -0
  25. package/lib/esm/signer.js +427 -0
  26. package/lib/esm/signer.js.map +1 -0
  27. package/lib/esm/transport.d.ts +32 -0
  28. package/lib/esm/transport.js +11 -0
  29. package/lib/esm/transport.js.map +1 -0
  30. package/lib/esm/web/heartbeat/client.d.ts +60 -0
  31. package/lib/esm/web/heartbeat/client.js +112 -0
  32. package/lib/esm/web/heartbeat/client.js.map +1 -0
  33. package/lib/esm/web/heartbeat/server.d.ts +43 -0
  34. package/lib/esm/web/heartbeat/server.js +82 -0
  35. package/lib/esm/web/heartbeat/server.js.map +1 -0
  36. package/lib/esm/web/index.d.ts +4 -0
  37. package/lib/esm/web/index.js +5 -0
  38. package/lib/esm/web/index.js.map +1 -0
  39. package/lib/esm/web/postMessageChannel.d.ts +55 -0
  40. package/lib/esm/web/postMessageChannel.js +109 -0
  41. package/lib/esm/web/postMessageChannel.js.map +1 -0
  42. package/lib/esm/web/postMessageTransport.d.ts +96 -0
  43. package/lib/esm/web/postMessageTransport.js +113 -0
  44. package/lib/esm/web/postMessageTransport.js.map +1 -0
  45. 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
+ }