@abndnce/pulsar-client 0.0.7 → 0.0.8

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/dist/index.d.mts CHANGED
@@ -16,7 +16,8 @@ interface PulsarClientConnection {
16
16
  *
17
17
  * @param host Server IP address
18
18
  * @param port Server UDP port
19
- * @returns A connected PulsarClientConnection with an open keepalive channel.
19
+ * @returns A connected PulsarClientConnection with an open keepalive channel
20
+ * and a `connect()` helper for opening socket tunnels.
20
21
  */
21
22
  declare function connectDirect(host: string, port: number): Promise<PulsarClientConnection>;
22
23
  //#endregion
@@ -29,4 +30,75 @@ declare function connectDirect(host: string, port: number): Promise<PulsarClient
29
30
  */
30
31
  declare function connectNostr(_relay: string, _pubkey: string): Promise<PulsarClientConnection>;
31
32
  //#endregion
32
- export { type PulsarClientConnection, connectDirect, connectNostr };
33
+ //#region lib/socket-channel.d.ts
34
+ /**
35
+ * Shared utilities for opening Pulsar socket tunnel data channels.
36
+ *
37
+ * These work with any RTCPeerConnection, regardless of how it was
38
+ * established (direct, nostr, etc.).
39
+ */
40
+ /**
41
+ * Wait for an RTCDataChannel to enter the "open" state.
42
+ * Rejects if it closes or errors before opening.
43
+ */
44
+ declare function waitForDataChannelOpen(channel: RTCDataChannel, timeoutMs?: number): Promise<void>;
45
+ /**
46
+ * Create a data channel on the given `pc` with the Pulsar socket label
47
+ * convention (`socket/<hostname>:<port>`) and wait for it to open.
48
+ *
49
+ * This is the low-level primitive used by `connect()` and
50
+ * `libcurlTransport()`.
51
+ */
52
+ declare function openSocketChannel(pc: RTCPeerConnection, hostname: string, port: number, timeoutMs?: number): Promise<RTCDataChannel>;
53
+ //#endregion
54
+ //#region lib/tunnel.d.ts
55
+ /**
56
+ * A minimal WebSocket-like wrapper around an RTCDataChannel.
57
+ *
58
+ * libcurl.js expects its transport factory to return objects
59
+ * with `send()`, `close()`, `onopen`, `onmessage`, `onclose`,
60
+ * and `onerror` — which is exactly the RTCDataChannel API, so
61
+ * the wrapper is thin.
62
+ */
63
+ declare class DataChannelSocket {
64
+ private _channel;
65
+ private _closed;
66
+ onopen: (() => void) | null;
67
+ onmessage: ((event: {
68
+ data: ArrayBuffer | string;
69
+ }) => void) | null;
70
+ onclose: (() => void) | null;
71
+ onerror: ((event: {
72
+ error?: string;
73
+ }) => void) | null;
74
+ constructor(channel: RTCDataChannel);
75
+ get readyState(): string;
76
+ get binaryType(): string;
77
+ set binaryType(_: string);
78
+ send(data: ArrayBuffer | string | ArrayBufferView): void;
79
+ close(): void;
80
+ }
81
+ /**
82
+ * Create a libcurl.js transport factory from an existing WebRTC
83
+ * peer connection.
84
+ *
85
+ * Usage:
86
+ * ```ts
87
+ * import { connectDirect, libcurlTransport } from '@abndnce/pulsar-client';
88
+ * import { libcurl } from 'libcurl.js';
89
+ *
90
+ * const tunnel = await connectDirect('216.250.119.217', 42069);
91
+ * libcurl.transport = libcurlTransport(tunnel.pc);
92
+ * libcurl.set_websocket('wss://pulsar-tunnel.local/');
93
+ * ```
94
+ *
95
+ * libcurl.js calls the factory with URLs like:
96
+ * `wss://pulsar-tunnel.local/example.com:80`
97
+ * `wss://pulsar-tunnel.local/216.250.119.217:443`
98
+ *
99
+ * The factory parses `<hostname>:<port>` from the URL path, opens a
100
+ * Pulsar socket data channel, and returns a WebSocket-like adapter.
101
+ */
102
+ declare function libcurlTransport(pc: RTCPeerConnection): (url: string) => DataChannelSocket;
103
+ //#endregion
104
+ export { type PulsarClientConnection, connectDirect, connectNostr, libcurlTransport, openSocketChannel, waitForDataChannelOpen };
package/dist/index.mjs CHANGED
@@ -1,7 +1,11 @@
1
- //#region ../core/credentials.ts
1
+ //#region ../core/constants.ts
2
2
  const PULSAR_UFRAG = "pulsar";
3
3
  const PULSAR_PWD = "pulsarpulsarpulsarpuls";
4
4
  const PULSAR_FINGERPRINT = "F1:85:10:8F:36:FF:58:D8:D0:4B:52:D7:ED:DC:5C:28:AE:7D:DB:54:0E:2A:DD:C7:C3:94:EA:A1:27:D0:4E:78";
5
+ /** Label prefix for socket tunnel data channels. */
6
+ const SOCKET_PREFIX = "socket/";
7
+ /** Label for the mandatory keepalive data channel. */
8
+ const KEEPALIVE_LABEL = "keepalive";
5
9
  //#endregion
6
10
  //#region lib/connection/direct.ts
7
11
  /**
@@ -11,11 +15,12 @@ const PULSAR_FINGERPRINT = "F1:85:10:8F:36:FF:58:D8:D0:4B:52:D7:ED:DC:5C:28:AE:7
11
15
  *
12
16
  * @param host Server IP address
13
17
  * @param port Server UDP port
14
- * @returns A connected PulsarClientConnection with an open keepalive channel.
18
+ * @returns A connected PulsarClientConnection with an open keepalive channel
19
+ * and a `connect()` helper for opening socket tunnels.
15
20
  */
16
21
  async function connectDirect(host, port) {
17
22
  const pc = new RTCPeerConnection();
18
- const keepalive = pc.createDataChannel("keepalive", { ordered: true });
23
+ const keepalive = pc.createDataChannel(KEEPALIVE_LABEL, { ordered: true });
19
24
  const offer = await pc.createOffer();
20
25
  await pc.setLocalDescription(offer);
21
26
  const remoteSdp = [
@@ -77,4 +82,144 @@ async function connectNostr(_relay, _pubkey) {
77
82
  throw new Error("Nostr mode not yet implemented");
78
83
  }
79
84
  //#endregion
80
- export { connectDirect, connectNostr };
85
+ //#region lib/socket-channel.ts
86
+ /**
87
+ * Shared utilities for opening Pulsar socket tunnel data channels.
88
+ *
89
+ * These work with any RTCPeerConnection, regardless of how it was
90
+ * established (direct, nostr, etc.).
91
+ */
92
+ /**
93
+ * Wait for an RTCDataChannel to enter the "open" state.
94
+ * Rejects if it closes or errors before opening.
95
+ */
96
+ function waitForDataChannelOpen(channel, timeoutMs = 1e4) {
97
+ return new Promise((resolve, reject) => {
98
+ if (channel.readyState === "open") {
99
+ resolve();
100
+ return;
101
+ }
102
+ const timeout = setTimeout(() => {
103
+ cleanup();
104
+ reject(/* @__PURE__ */ new Error(`DataChannel open timed out after ${timeoutMs}ms`));
105
+ }, timeoutMs);
106
+ const cleanup = () => {
107
+ clearTimeout(timeout);
108
+ channel.onopen = null;
109
+ channel.onclose = null;
110
+ channel.onerror = null;
111
+ };
112
+ channel.onopen = () => {
113
+ cleanup();
114
+ resolve();
115
+ };
116
+ channel.onclose = () => {
117
+ cleanup();
118
+ reject(/* @__PURE__ */ new Error("DataChannel closed before opening"));
119
+ };
120
+ channel.onerror = (e) => {
121
+ cleanup();
122
+ reject(/* @__PURE__ */ new Error(`DataChannel error before opening: ${e}`));
123
+ };
124
+ });
125
+ }
126
+ /**
127
+ * Create a data channel on the given `pc` with the Pulsar socket label
128
+ * convention (`socket/<hostname>:<port>`) and wait for it to open.
129
+ *
130
+ * This is the low-level primitive used by `connect()` and
131
+ * `libcurlTransport()`.
132
+ */
133
+ function openSocketChannel(pc, hostname, port, timeoutMs) {
134
+ const label = `${SOCKET_PREFIX}${hostname}:${port}`;
135
+ const channel = pc.createDataChannel(label, { ordered: true });
136
+ return waitForDataChannelOpen(channel, timeoutMs).then(() => channel);
137
+ }
138
+ //#endregion
139
+ //#region lib/tunnel.ts
140
+ /**
141
+ * A minimal WebSocket-like wrapper around an RTCDataChannel.
142
+ *
143
+ * libcurl.js expects its transport factory to return objects
144
+ * with `send()`, `close()`, `onopen`, `onmessage`, `onclose`,
145
+ * and `onerror` — which is exactly the RTCDataChannel API, so
146
+ * the wrapper is thin.
147
+ */
148
+ var DataChannelSocket = class {
149
+ _channel;
150
+ _closed = false;
151
+ onopen = null;
152
+ onmessage = null;
153
+ onclose = null;
154
+ onerror = null;
155
+ constructor(channel) {
156
+ this._channel = channel;
157
+ channel.onopen = () => {
158
+ if (!this._closed) this.onopen?.();
159
+ };
160
+ channel.onmessage = (event) => {
161
+ if (!this._closed) this.onmessage?.({ data: event.data });
162
+ };
163
+ channel.onclose = () => {
164
+ this._closed = true;
165
+ this.onclose?.();
166
+ };
167
+ channel.onerror = (event) => {
168
+ this.onerror?.({ error: String(event) });
169
+ };
170
+ }
171
+ get readyState() {
172
+ return this._channel.readyState;
173
+ }
174
+ get binaryType() {
175
+ return "arraybuffer";
176
+ }
177
+ set binaryType(_) {}
178
+ send(data) {
179
+ if (this._closed || this._channel.readyState !== "open") return;
180
+ this._channel.send(data);
181
+ }
182
+ close() {
183
+ this._closed = true;
184
+ try {
185
+ this._channel.close();
186
+ } catch {}
187
+ }
188
+ };
189
+ /**
190
+ * Create a libcurl.js transport factory from an existing WebRTC
191
+ * peer connection.
192
+ *
193
+ * Usage:
194
+ * ```ts
195
+ * import { connectDirect, libcurlTransport } from '@abndnce/pulsar-client';
196
+ * import { libcurl } from 'libcurl.js';
197
+ *
198
+ * const tunnel = await connectDirect('216.250.119.217', 42069);
199
+ * libcurl.transport = libcurlTransport(tunnel.pc);
200
+ * libcurl.set_websocket('wss://pulsar-tunnel.local/');
201
+ * ```
202
+ *
203
+ * libcurl.js calls the factory with URLs like:
204
+ * `wss://pulsar-tunnel.local/example.com:80`
205
+ * `wss://pulsar-tunnel.local/216.250.119.217:443`
206
+ *
207
+ * The factory parses `<hostname>:<port>` from the URL path, opens a
208
+ * Pulsar socket data channel, and returns a WebSocket-like adapter.
209
+ */
210
+ function libcurlTransport(pc) {
211
+ return (url) => {
212
+ let dest;
213
+ try {
214
+ dest = new URL(url).pathname.replace(/^\//, "").replace(/\/$/, "");
215
+ } catch {
216
+ const slash = url.indexOf("/", url.indexOf("//") + 2);
217
+ dest = slash === -1 ? url : url.slice(slash + 1);
218
+ }
219
+ if (!dest) throw new Error(`libcurl transport: no destination found in URL "${url}"`);
220
+ if (dest.lastIndexOf(":") === -1) throw new Error(`libcurl transport: invalid destination "${dest}" — expected "hostname:port"`);
221
+ return new DataChannelSocket(pc.createDataChannel(`${SOCKET_PREFIX}${dest}`, { ordered: true }));
222
+ };
223
+ }
224
+ //#endregion
225
+ export { connectDirect, connectNostr, libcurlTransport, openSocketChannel, waitForDataChannelOpen };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@abndnce/pulsar-client",
3
3
  "type": "module",
4
- "version": "0.0.7",
4
+ "version": "0.0.8",
5
5
  "homepage": "https://github.com/abndnce/pulsar",
6
6
  "license": "MIT",
7
7
  "exports": {