-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathclient.ts
More file actions
109 lines (104 loc) · 3.04 KB
/
client.ts
File metadata and controls
109 lines (104 loc) · 3.04 KB
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import { makeWebSocket } from "./channel.ts";
import { CLIENT_VERSION_QUERY_STRING } from "./constants.ts";
import denoJSON from "./deno.json" with { type: "json" };
import { handleServerMessage } from "./handlers.client.ts";
import type { ClientMessage, ClientState, ServerMessage } from "./messages.ts";
import { dataViewerSerializer } from "./serializers.ts";
/**
* Options for establishing a connection.
* @typedef {Object} ConnectOptions
* @property {string} apiKey - The apiKey used for connecting to the server.
* @property {string} domain - The domain to register the connection with.
* @property {string} server - The WebSocket server URL.
* @property {string} localAddr - The local address for the WebSocket connection.
*/
export interface ConnectOptions {
apiKey: string;
domain: string;
server: string;
localAddr: string;
}
/**
* Represents a connection status object.
* @typedef {Object} Connected
* @property {Promise<void>} closed - A promise that resolves when the connection is closed.
* @property {Promise<void>} registered - A promise that resolves when the connection is registered.
*/
export interface Connected extends Disposable {
closed: Promise<Error | { intentional: boolean } | undefined>;
registered: Promise<void>;
close: () => void;
}
/**
* Establishes a WebSocket connection with the server.
* @param {ConnectOptions} opts - Options for establishing the connection.
* @returns {Promise<Connected>} A promise that resolves with the connection status.
*/
export const connect = async (opts: ConnectOptions): Promise<Connected> => {
const closed = Promise.withResolvers<
Error | undefined | { intentional: boolean }
>();
const registered = Promise.withResolvers<void>();
const client =
typeof Deno === "object" && typeof Deno.createHttpClient === "function"
? Deno.createHttpClient({
allowHost: true,
})
: undefined;
const socket = new WebSocket(
`${opts.server}/_connect?${CLIENT_VERSION_QUERY_STRING}=${denoJSON.version}`,
);
const ch = await makeWebSocket<ClientMessage, ServerMessage, ArrayBuffer>(
socket,
dataViewerSerializer(),
);
await ch.out.send({
id: crypto.randomUUID(),
type: "register",
apiKey: opts.apiKey,
domain: opts.domain,
});
const wsSockets: Record<string, WebSocket> = {};
(async () => {
let reason: undefined | Error | { intentional: boolean };
const state: ClientState = {
client,
localAddr: opts.localAddr,
live: false,
requests: {},
wsSockets,
ch,
};
try {
for await (const message of ch.in.recv()) {
await handleServerMessage(state, message);
if (state.live) {
registered.resolve();
}
}
} catch (err) {
reason = err as Error;
console.error(new Date(), "error handling message", err);
} finally {
closed.resolve(reason);
}
})();
const close = () => {
try {
socket.close();
ch.in.close();
ch.out.close();
reason = { intentional: true };
} catch {
// ignore
}
};
return {
closed: closed.promise,
registered: registered.promise,
close,
[Symbol.dispose]: () => {
close();
},
};
};