-
Notifications
You must be signed in to change notification settings - Fork 88
/
index.ts
85 lines (79 loc) · 3.2 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import { Principal } from '@dfinity/principal';
import { Agent, RequestStatusResponseStatus } from '../agent';
import { Certificate, CreateCertificateOptions } from '../certificate';
import { RequestId } from '../request_id';
import { toHex } from '../utils/buffer';
export * as strategy from './strategy';
export { defaultStrategy } from './strategy';
export type PollStrategy = (
canisterId: Principal,
requestId: RequestId,
status: RequestStatusResponseStatus,
) => Promise<void>;
export type PollStrategyFactory = () => PollStrategy;
/**
* Polls the IC to check the status of the given request then
* returns the response bytes once the request has been processed.
* @param agent The agent to use to poll read_state.
* @param canisterId The effective canister ID.
* @param requestId The Request ID to poll status for.
* @param strategy A polling strategy.
* @param request Request for the readState call.
*/
export async function pollForResponse(
agent: Agent,
canisterId: Principal,
requestId: RequestId,
strategy: PollStrategy,
// eslint-disable-next-line
request?: any,
blsVerify?: CreateCertificateOptions['blsVerify'],
): Promise<ArrayBuffer> {
const path = [new TextEncoder().encode('request_status'), requestId];
const currentRequest = request ?? (await agent.createReadStateRequest?.({ paths: [path] }));
const state = await agent.readState(canisterId, { paths: [path] }, undefined, currentRequest);
if (agent.rootKey == null) throw new Error('Agent root key not initialized before polling');
const cert = await Certificate.create({
certificate: state.certificate,
rootKey: agent.rootKey,
canisterId: canisterId,
blsVerify,
});
const maybeBuf = cert.lookup([...path, new TextEncoder().encode('status')]);
let status;
if (typeof maybeBuf === 'undefined') {
// Missing requestId means we need to wait
status = RequestStatusResponseStatus.Unknown;
} else {
status = new TextDecoder().decode(maybeBuf);
}
switch (status) {
case RequestStatusResponseStatus.Replied: {
return cert.lookup([...path, 'reply'])!;
}
case RequestStatusResponseStatus.Received:
case RequestStatusResponseStatus.Unknown:
case RequestStatusResponseStatus.Processing:
// Execute the polling strategy, then retry.
await strategy(canisterId, requestId, status);
return pollForResponse(agent, canisterId, requestId, strategy, currentRequest);
case RequestStatusResponseStatus.Rejected: {
const rejectCode = new Uint8Array(cert.lookup([...path, 'reject_code'])!)[0];
const rejectMessage = new TextDecoder().decode(cert.lookup([...path, 'reject_message'])!);
throw new Error(
`Call was rejected:\n` +
` Request ID: ${toHex(requestId)}\n` +
` Reject code: ${rejectCode}\n` +
` Reject text: ${rejectMessage}\n`,
);
}
case RequestStatusResponseStatus.Done:
// This is _technically_ not an error, but we still didn't see the `Replied` status so
// we don't know the result and cannot decode it.
throw new Error(
`Call was marked as done but we never saw the reply:\n` +
` Request ID: ${toHex(requestId)}\n`,
);
}
throw new Error('unreachable');
}