snapreq 0.0.1
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/README.md +144 -0
- package/package.json +39 -0
- package/src/capabilities.js +31 -0
- package/src/errors.js +69 -0
- package/src/headers.js +92 -0
- package/src/index.js +22 -0
- package/src/request.js +104 -0
- package/src/response.js +156 -0
- package/src/retry.js +105 -0
- package/src/snap-req.js +248 -0
- package/src/transports/fetch-transport.js +128 -0
- package/src/transports/node-transport.js +323 -0
- package/src/transports/select.js +73 -0
- package/src/transports/xhr-transport.js +112 -0
- package/src/websocket/websocket-channel.js +176 -0
- package/src/websocket/websocket-client.js +1029 -0
- package/src/websocket/websocket-connection.js +154 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Client-side handle for a 1:1 connection opened via
|
|
5
|
+
* `SnapReqWebSocketClient.openConnection()`. Mirrors the server's connection
|
|
6
|
+
* lifecycle — `onConnect` / `onMessage` / `onClose` plus `sendMessage` /
|
|
7
|
+
* `close`.
|
|
8
|
+
*/
|
|
9
|
+
export default class SnapReqWebSocketConnection {
|
|
10
|
+
/**
|
|
11
|
+
* @param {object} args - Connection arguments.
|
|
12
|
+
* @param {import("./websocket-client.js").default} args.client - Owning client.
|
|
13
|
+
* @param {string} args.connectionId - Generated id unique within the session.
|
|
14
|
+
* @param {string} args.connectionType - Name the server registered the class under.
|
|
15
|
+
* @param {Record<string, any>} [args.params] - Opaque params forwarded to the server.
|
|
16
|
+
* @param {() => void} [args.onConnect] - Fired after the server confirms `connection-opened`.
|
|
17
|
+
* @param {(body: any) => void} [args.onMessage] - Fired on each `connection-message` from the server.
|
|
18
|
+
* @param {() => void} [args.onDisconnect] - Fired when the socket drops; connection is preserved pending resume.
|
|
19
|
+
* @param {() => void} [args.onResume] - Fired when the session successfully resumes after a drop.
|
|
20
|
+
* @param {(reason: string) => void} [args.onClose] - Fired exactly once when the handle closes permanently.
|
|
21
|
+
*/
|
|
22
|
+
constructor({client, connectionId, connectionType, params, onConnect, onMessage, onDisconnect, onResume, onClose}) {
|
|
23
|
+
this.client = client
|
|
24
|
+
this.connectionId = connectionId
|
|
25
|
+
this.connectionType = connectionType
|
|
26
|
+
this.params = params || {}
|
|
27
|
+
this._onConnect = onConnect
|
|
28
|
+
this._onMessage = onMessage
|
|
29
|
+
this._onDisconnect = onDisconnect
|
|
30
|
+
this._onResume = onResume
|
|
31
|
+
this._onClose = onClose
|
|
32
|
+
this._connected = false
|
|
33
|
+
this._closed = false
|
|
34
|
+
|
|
35
|
+
/** @type {Promise<void>} - Resolves once the server sends `connection-opened`. */
|
|
36
|
+
this.ready = new Promise((resolve, reject) => {
|
|
37
|
+
this._resolveReady = resolve
|
|
38
|
+
this._rejectReady = reject
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Called by the client dispatcher when `{type: "connection-opened"}` arrives.
|
|
44
|
+
* Fires the user's `onConnect` and resolves `ready`.
|
|
45
|
+
* @returns {void}
|
|
46
|
+
*/
|
|
47
|
+
_handleOpened() {
|
|
48
|
+
if (this._closed || this._connected) return
|
|
49
|
+
this._connected = true
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
this._onConnect?.()
|
|
53
|
+
} finally {
|
|
54
|
+
this._resolveReady?.()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Called by the client dispatcher for each `connection-message` targeted at
|
|
60
|
+
* this connection id.
|
|
61
|
+
* @param {any} body - Message payload.
|
|
62
|
+
* @returns {void}
|
|
63
|
+
*/
|
|
64
|
+
_handleMessage(body) {
|
|
65
|
+
if (this._closed) return
|
|
66
|
+
this._onMessage?.(body)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Called by the client when the underlying socket drops. The connection stays
|
|
71
|
+
* alive pending session resume.
|
|
72
|
+
* @returns {void}
|
|
73
|
+
*/
|
|
74
|
+
_handleDisconnected() {
|
|
75
|
+
if (this._closed) return
|
|
76
|
+
this._onDisconnect?.()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Called by the client after `session-resumed` confirms the server still has
|
|
81
|
+
* this connection.
|
|
82
|
+
* @returns {void}
|
|
83
|
+
*/
|
|
84
|
+
_handleResumed() {
|
|
85
|
+
if (this._closed) return
|
|
86
|
+
this._onResume?.()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Called by the client dispatcher when the connection closes for any reason.
|
|
91
|
+
* Fires `onClose(reason)` at most once.
|
|
92
|
+
* @param {string} reason - Why the connection closed.
|
|
93
|
+
* @returns {void}
|
|
94
|
+
*/
|
|
95
|
+
_handleClosed(reason) {
|
|
96
|
+
if (this._closed) return
|
|
97
|
+
this._closed = true
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
this._onClose?.(reason)
|
|
101
|
+
} finally {
|
|
102
|
+
if (!this._connected) {
|
|
103
|
+
this._rejectReady?.(new Error(`Connection closed before open: ${reason}`))
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Sends a message to the server side of this connection.
|
|
110
|
+
* @param {any} body - Message payload.
|
|
111
|
+
* @returns {void}
|
|
112
|
+
*/
|
|
113
|
+
sendMessage(body) {
|
|
114
|
+
if (this._closed) {
|
|
115
|
+
throw new Error(`Cannot sendMessage on closed connection ${this.connectionId}`)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.client._sendMessage({
|
|
119
|
+
type: "connection-message",
|
|
120
|
+
connectionId: this.connectionId,
|
|
121
|
+
body
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Closes the connection from the client side. Fires `onClose("client_close")`
|
|
127
|
+
* locally and notifies the server. No-op if already closed.
|
|
128
|
+
* @returns {void}
|
|
129
|
+
*/
|
|
130
|
+
close() {
|
|
131
|
+
if (this._closed) return
|
|
132
|
+
|
|
133
|
+
// Send the close frame BEFORE flipping _closed so _sendMessage doesn't
|
|
134
|
+
// refuse — and guard against a socket that's already gone so the local
|
|
135
|
+
// teardown still runs.
|
|
136
|
+
try {
|
|
137
|
+
if (this.client.isOpen()) {
|
|
138
|
+
this.client._sendMessage({type: "connection-close", connectionId: this.connectionId})
|
|
139
|
+
}
|
|
140
|
+
} catch {
|
|
141
|
+
// Socket may have closed between our check and the send; the server will
|
|
142
|
+
// see the session destroy and clean up regardless.
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.client._removeConnection(this.connectionId)
|
|
146
|
+
this._handleClosed("client_close")
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** @returns {boolean} - Whether the connection is closed. */
|
|
150
|
+
isClosed() { return this._closed }
|
|
151
|
+
|
|
152
|
+
/** @returns {boolean} - Whether the connection is open. */
|
|
153
|
+
isConnected() { return this._connected && !this._closed }
|
|
154
|
+
}
|