@sigx/lynx-websocket 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Andreas Ekdahl
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,147 @@
1
+ # @sigx/lynx-websocket
2
+
3
+ Browser-standard `WebSocket` client for sigx-lynx. Wraps
4
+ `URLSessionWebSocketTask` on iOS and OkHttp `WebSocket` on Android, and
5
+ installs a global `WebSocket` class that matches the WHATWG / MDN API so
6
+ portable web code works unchanged.
7
+
8
+ > Note: this is **only for WebSockets**. Plain HTTP works out of the box —
9
+ > Lynx already ships a global `fetch()`. See [Networking](#networking)
10
+ > below.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ pnpm add @sigx/lynx-websocket
16
+ ```
17
+
18
+ ```ts
19
+ // sigx.lynx.config.ts
20
+ export default defineLynxConfig({
21
+ modules: ['@sigx/lynx-websocket'],
22
+ });
23
+ ```
24
+
25
+ Then run `sigx-lynx prebuild` to regenerate the iOS / Android projects.
26
+ No permissions are required on either platform (OkHttp piggy-backs on
27
+ the app's standard `INTERNET` permission, which the host app already
28
+ declares).
29
+
30
+ ## Usage
31
+
32
+ Listing the package in `modules:` registers a global `WebSocket` — you don't
33
+ need to `import` anything to use it:
34
+
35
+ ```ts
36
+ const ws = new WebSocket('wss://ws.postman-echo.com/raw');
37
+
38
+ ws.onopen = () => ws.send('hello');
39
+ ws.onmessage = (event) => {
40
+ console.log('received', event.data);
41
+ };
42
+ ws.onclose = (event) => {
43
+ console.log('closed', event.code, event.reason);
44
+ };
45
+ ```
46
+
47
+ If you prefer an explicit import (e.g. for TypeScript clarity, or to use
48
+ inside a library that shouldn't assume the global is set):
49
+
50
+ ```ts
51
+ import { WebSocket } from '@sigx/lynx-websocket';
52
+
53
+ const ws = new WebSocket('wss://example.com/socket');
54
+ ```
55
+
56
+ ### Binary frames
57
+
58
+ `binaryType` is fixed to `'arraybuffer'` — `'blob'` is not supported (Lynx
59
+ doesn't ship a `Blob` polyfill, matching upstream's `fetch` constraints).
60
+ Send `ArrayBuffer` / typed arrays directly:
61
+
62
+ ```ts
63
+ const ws = new WebSocket('wss://example.com/socket');
64
+ ws.binaryType = 'arraybuffer';
65
+
66
+ ws.onmessage = (event) => {
67
+ if (event.data instanceof ArrayBuffer) {
68
+ const bytes = new Uint8Array(event.data);
69
+ // ...
70
+ }
71
+ };
72
+
73
+ ws.onopen = () => {
74
+ ws.send(new Uint8Array([0x01, 0x02, 0x03]));
75
+ };
76
+ ```
77
+
78
+ ### Subprotocols
79
+
80
+ Pass a string or array of strings as the second arg to the constructor —
81
+ the negotiated value is available on `ws.protocol` after `open`:
82
+
83
+ ```ts
84
+ const ws = new WebSocket('wss://example.com/chat', ['v2.chat', 'v1.chat']);
85
+ ws.onopen = () => console.log('negotiated', ws.protocol);
86
+ ```
87
+
88
+ ## API
89
+
90
+ Matches [`WebSocket` on MDN](https://developer.mozilla.org/docs/Web/API/WebSocket):
91
+
92
+ | Member | Type | Notes |
93
+ |---|---|---|
94
+ | `new WebSocket(url, protocols?)` | constructor | `url` must be `ws:` / `wss:` (http/https accepted, normalised by native). |
95
+ | `readyState` | `0 \| 1 \| 2 \| 3` | `CONNECTING / OPEN / CLOSING / CLOSED`. |
96
+ | `url` | `string` | The URL passed to the constructor. |
97
+ | `protocol` | `string` | Negotiated subprotocol; `''` until `open`. |
98
+ | `extensions` | `string` | Negotiated `Sec-WebSocket-Extensions`. |
99
+ | `bufferedAmount` | `number` | Bytes handed to native, not yet acked. Approximate; updated on `send`. |
100
+ | `binaryType` | `'arraybuffer'` | Fixed — `'blob'` is unsupported. |
101
+ | `send(data)` | method | `string \| ArrayBuffer \| ArrayBufferView`. Throws if `CONNECTING`. |
102
+ | `close(code?, reason?)` | method | Browser semantics: `1000` or `3000–4999`; reason ≤123 UTF-8 bytes. |
103
+ | `onopen / onmessage / onerror / onclose` | listener slot | Standard WHATWG event shape. |
104
+ | `addEventListener(type, fn)` / `removeEventListener` / `dispatchEvent` | EventTarget | Multiple listeners per event. |
105
+ | Class constants | `CONNECTING (0) / OPEN (1) / CLOSING (2) / CLOSED (3)` | On both class and instance. |
106
+ | `isWebSocketAvailable()` | export | Whether the native module is registered (useful in tests / SSR). |
107
+
108
+ ## Caveats vs the browser
109
+
110
+ - **No `Blob`** binary type — use `ArrayBuffer`.
111
+ - **`bufferedAmount`** is a JS-side approximation (bytes handed off to
112
+ native), not the OS socket buffer level. Sufficient for backpressure
113
+ hinting; don't use it for exact accounting.
114
+ - **`Sec-WebSocket-Extensions`** negotiation is whatever URLSession (iOS)
115
+ or OkHttp (Android) offers — neither advertises `permessage-deflate` by
116
+ default. If you need it, configure the server to live without it or open
117
+ an issue.
118
+ - **No `Sec-WebSocket-Key` access** — handshake headers other than
119
+ `Sec-WebSocket-Protocol` aren't customizable from JS in this version.
120
+
121
+ ## How it works
122
+
123
+ JS → native is a 3-method bridge (`create / send / close`). Native → JS is
124
+ a single `__sigxWebSocketEvent` global event multiplexed by a monotonic
125
+ numeric id; the shim demultiplexes per-instance. A per-`LynxView`
126
+ publisher pumps events from a process-wide `WebSocketEventBus` into
127
+ `LynxView.sendGlobalEvent`. Sockets outlive any single LynxView so a
128
+ template reload doesn't drop in-flight connections that JS is
129
+ re-attaching to.
130
+
131
+ ## Related
132
+
133
+ - **HTTP**: just call `fetch()` — it's a built-in Lynx global. No package
134
+ needed. See [upstream Lynx fetch docs](https://lynxjs.org/api/lynx-api/global/fetch.html)
135
+ for the Lynx subset (no CORS / redirect / keepalive / FormData / Blob).
136
+ - **Connectivity**: [`@sigx/lynx-network`](../lynx-network) for online /
137
+ offline and connection-type state.
138
+
139
+ ## Reference app
140
+
141
+ Once wired in, point it at an echo service to smoke-test:
142
+
143
+ ```ts
144
+ const ws = new WebSocket('wss://ws.postman-echo.com/raw');
145
+ ws.onopen = () => ws.send('ping');
146
+ ws.onmessage = (e) => console.log(e.data); // → 'ping'
147
+ ```
@@ -0,0 +1,104 @@
1
+ package com.sigx.websocket
2
+
3
+ import com.lynx.react.bridge.JavaOnlyMap
4
+ import java.util.UUID
5
+ import java.util.concurrent.CopyOnWriteArrayList
6
+
7
+ /**
8
+ * Process-wide pub/sub bus that owns the conversion of native WebSocket
9
+ * callbacks into JS-side event payloads. OkHttp listeners write here;
10
+ * per-LynxView [WebSocketPublisher] instances read.
11
+ *
12
+ * Mirrors the publisher pattern used elsewhere in the repo
13
+ * (`LinkingState`, `SafeAreaPublisher`, …): native lifecycle is decoupled
14
+ * from any specific LynxView so events survive view recreation and
15
+ * per-view subscribers can fan out without holding onto the WS task.
16
+ *
17
+ * Payload keys match the `NativeEvent` interface in `src/websocket.ts`.
18
+ */
19
+ internal object WebSocketEventBus {
20
+
21
+ private val listeners = CopyOnWriteArrayList<Pair<UUID, (JavaOnlyMap) -> Unit>>()
22
+
23
+ fun addListener(fn: (JavaOnlyMap) -> Unit): UUID {
24
+ val token = UUID.randomUUID()
25
+ listeners.add(token to fn)
26
+ return token
27
+ }
28
+
29
+ fun removeListener(token: UUID) {
30
+ listeners.removeAll { it.first == token }
31
+ }
32
+
33
+ fun publishOpen(id: Int, protocol: String, extensions: String) {
34
+ emit(
35
+ mapOf(
36
+ "id" to id,
37
+ "type" to "open",
38
+ "protocol" to protocol,
39
+ "extensions" to extensions,
40
+ )
41
+ )
42
+ }
43
+
44
+ fun publishMessageText(id: Int, text: String) {
45
+ emit(
46
+ mapOf(
47
+ "id" to id,
48
+ "type" to "message",
49
+ "data" to text,
50
+ "isBinary" to false,
51
+ )
52
+ )
53
+ }
54
+
55
+ fun publishMessageBinary(id: Int, base64: String) {
56
+ emit(
57
+ mapOf(
58
+ "id" to id,
59
+ "type" to "message",
60
+ "binary" to base64,
61
+ "isBinary" to true,
62
+ )
63
+ )
64
+ }
65
+
66
+ fun publishError(id: Int, message: String) {
67
+ emit(
68
+ mapOf(
69
+ "id" to id,
70
+ "type" to "error",
71
+ "data" to message,
72
+ )
73
+ )
74
+ }
75
+
76
+ fun publishClose(id: Int, code: Int, reason: String, wasClean: Boolean) {
77
+ emit(
78
+ mapOf(
79
+ "id" to id,
80
+ "type" to "close",
81
+ "code" to code,
82
+ "reason" to reason,
83
+ "wasClean" to wasClean,
84
+ )
85
+ )
86
+ }
87
+
88
+ private fun emit(payload: Map<String, Any>) {
89
+ // JavaOnlyMap doesn't ship a `from(Map)` overload that handles
90
+ // mixed value types on every host, so build it explicitly to keep
91
+ // the bridge happy.
92
+ val map = JavaOnlyMap()
93
+ for ((k, v) in payload) {
94
+ when (v) {
95
+ is String -> map.putString(k, v)
96
+ is Int -> map.putInt(k, v)
97
+ is Double -> map.putDouble(k, v)
98
+ is Boolean -> map.putBoolean(k, v)
99
+ else -> map.putString(k, v.toString())
100
+ }
101
+ }
102
+ for ((_, fn) in listeners) fn(map)
103
+ }
104
+ }
@@ -0,0 +1,78 @@
1
+ package com.sigx.websocket
2
+
3
+ import android.content.Context
4
+ import android.util.Log
5
+ import com.lynx.jsbridge.LynxMethod
6
+ import com.lynx.jsbridge.LynxModule
7
+ import com.lynx.react.bridge.Callback
8
+ import com.lynx.react.bridge.JavaOnlyMap
9
+ import com.lynx.react.bridge.ReadableArray
10
+
11
+ /**
12
+ * Native WebSocket bridge — JS-callable side.
13
+ *
14
+ * JS usage (via the `@sigx/lynx-websocket` shim, not directly):
15
+ *
16
+ * NativeModules.WebSocket.create(id, url, protocols, cb)
17
+ * NativeModules.WebSocket.send(id, payload, isBinary, cb)
18
+ * NativeModules.WebSocket.close(id, code, reason, cb)
19
+ *
20
+ * Async lifecycle events (`open`, `message`, `error`, `close`) are pushed
21
+ * back via [WebSocketEventBus], which a per-LynxView [WebSocketPublisher]
22
+ * forwards to JS through `LynxView.sendGlobalEvent("__sigxWebSocketEvent",
23
+ * [...])`.
24
+ *
25
+ * Implemented with OkHttp's WebSocket. Each socket is stored in
26
+ * [WebSocketTaskStore] keyed by the JS-supplied numeric id; the same id is
27
+ * echoed back in every event so the JS shim can demultiplex.
28
+ */
29
+ class WebSocketModule(context: Context) : LynxModule(context) {
30
+
31
+ @LynxMethod
32
+ fun create(id: Int, url: String?, protocols: ReadableArray?, callback: Callback?) {
33
+ if (url.isNullOrEmpty()) {
34
+ WebSocketEventBus.publishError(id, "Invalid URL")
35
+ WebSocketEventBus.publishClose(id, 1006, "Invalid URL", wasClean = false)
36
+ callback?.invoke(JavaOnlyMap.from(emptyMap<String, Any>()))
37
+ return
38
+ }
39
+
40
+ val protoList = mutableListOf<String>()
41
+ if (protocols != null) {
42
+ for (i in 0 until protocols.size()) {
43
+ val v = protocols.getString(i)
44
+ if (!v.isNullOrEmpty()) protoList.add(v)
45
+ }
46
+ }
47
+
48
+ try {
49
+ WebSocketTaskStore.create(id, url, protoList)
50
+ callback?.invoke(JavaOnlyMap.from(emptyMap<String, Any>()))
51
+ } catch (e: Exception) {
52
+ Log.e(TAG, "create($id) failed: ${e.message}")
53
+ WebSocketEventBus.publishError(id, e.message ?: "create failed")
54
+ WebSocketEventBus.publishClose(id, 1006, e.message ?: "", wasClean = false)
55
+ callback?.invoke(JavaOnlyMap.from(mapOf("error" to (e.message ?: "create failed"))))
56
+ }
57
+ }
58
+
59
+ @LynxMethod
60
+ fun send(id: Int, payload: String?, isBinary: Boolean, callback: Callback?) {
61
+ val ok = WebSocketTaskStore.send(id, payload ?: "", isBinary)
62
+ if (!ok) {
63
+ callback?.invoke(JavaOnlyMap.from(mapOf("error" to "WebSocket $id not found or not open")))
64
+ return
65
+ }
66
+ callback?.invoke(JavaOnlyMap.from(emptyMap<String, Any>()))
67
+ }
68
+
69
+ @LynxMethod
70
+ fun close(id: Int, code: Int, reason: String?, callback: Callback?) {
71
+ WebSocketTaskStore.close(id, code, reason ?: "")
72
+ callback?.invoke(JavaOnlyMap.from(emptyMap<String, Any>()))
73
+ }
74
+
75
+ private companion object {
76
+ const val TAG = "SigxWebSocketModule"
77
+ }
78
+ }
@@ -0,0 +1,44 @@
1
+ package com.sigx.websocket
2
+
3
+ import android.util.Log
4
+ import com.lynx.react.bridge.JavaOnlyArray
5
+ import com.lynx.tasm.LynxView
6
+ import java.util.UUID
7
+
8
+ /**
9
+ * Per-[LynxView] publisher that pumps [WebSocketEventBus] payloads into JS
10
+ * via `LynxView.sendGlobalEvent("__sigxWebSocketEvent", [...])`.
11
+ *
12
+ * One instance per LynxView; instantiated by the generated
13
+ * `GeneratedLifecyclePublishers.attachAll(lynxView)` and retained for the
14
+ * LynxView's lifetime. The bus is global so opening a socket from one
15
+ * LynxView and reading it from another (via the JS shim) works, but in
16
+ * practice each LynxView holds its own JS heap so events are delivered to
17
+ * the matching view only.
18
+ */
19
+ class WebSocketPublisher(private val lynxView: LynxView) {
20
+
21
+ private var token: UUID? = null
22
+
23
+ fun attach() {
24
+ token = WebSocketEventBus.addListener { payload ->
25
+ try {
26
+ val params = JavaOnlyArray()
27
+ params.pushMap(payload)
28
+ lynxView.sendGlobalEvent(EVENT_NAME, params)
29
+ } catch (e: Throwable) {
30
+ Log.w(TAG, "publish failed: ${e.message}")
31
+ }
32
+ }
33
+ }
34
+
35
+ fun detach() {
36
+ token?.let { WebSocketEventBus.removeListener(it) }
37
+ token = null
38
+ }
39
+
40
+ private companion object {
41
+ const val TAG = "SigxWebSocketPublisher"
42
+ const val EVENT_NAME = "__sigxWebSocketEvent"
43
+ }
44
+ }
@@ -0,0 +1,115 @@
1
+ package com.sigx.websocket
2
+
3
+ import android.util.Base64
4
+ import android.util.Log
5
+ import okhttp3.OkHttpClient
6
+ import okhttp3.Request
7
+ import okhttp3.Response
8
+ import okhttp3.WebSocket
9
+ import okhttp3.WebSocketListener
10
+ import okio.ByteString
11
+ import okio.ByteString.Companion.toByteString
12
+ import java.util.concurrent.ConcurrentHashMap
13
+ import java.util.concurrent.TimeUnit
14
+
15
+ /**
16
+ * Process-wide registry of live OkHttp [WebSocket] instances keyed by the
17
+ * JS-supplied numeric id. Sockets outlive any single LynxView (a page
18
+ * reload should not drop in-flight sockets that the JS bundle is
19
+ * re-attaching to).
20
+ */
21
+ internal object WebSocketTaskStore {
22
+
23
+ private const val TAG = "SigxWebSocketStore"
24
+
25
+ private val sockets = ConcurrentHashMap<Int, WebSocket>()
26
+
27
+ private val client: OkHttpClient by lazy {
28
+ OkHttpClient.Builder()
29
+ // No read timeout on established sockets — close frames drive teardown.
30
+ .readTimeout(0, TimeUnit.MILLISECONDS)
31
+ .pingInterval(0, TimeUnit.MILLISECONDS)
32
+ .build()
33
+ }
34
+
35
+ fun create(id: Int, url: String, protocols: List<String>) {
36
+ // If JS re-uses an id (shouldn't — ids are monotonic), tear down
37
+ // the prior socket first to avoid leaks.
38
+ sockets.remove(id)?.cancel()
39
+
40
+ val builder = Request.Builder().url(url)
41
+ if (protocols.isNotEmpty()) {
42
+ builder.addHeader("Sec-WebSocket-Protocol", protocols.joinToString(", "))
43
+ }
44
+ val request = builder.build()
45
+ val ws = client.newWebSocket(request, Listener(id))
46
+ sockets[id] = ws
47
+ }
48
+
49
+ fun send(id: Int, payload: String, isBinary: Boolean): Boolean {
50
+ val ws = sockets[id] ?: return false
51
+ return if (isBinary) {
52
+ // Payload is base64-encoded on the JS side — decode to raw bytes.
53
+ val bytes = try {
54
+ Base64.decode(payload, Base64.DEFAULT)
55
+ } catch (e: IllegalArgumentException) {
56
+ Log.w(TAG, "send($id) invalid base64: ${e.message}")
57
+ return false
58
+ }
59
+ ws.send(bytes.toByteString())
60
+ } else {
61
+ ws.send(payload)
62
+ }
63
+ }
64
+
65
+ fun close(id: Int, code: Int, reason: String) {
66
+ val ws = sockets[id] ?: return
67
+ // OkHttp returns false if the socket is already closing/closed —
68
+ // either way the listener will fire and we'll clean up.
69
+ ws.close(code, reason)
70
+ }
71
+
72
+ private fun forget(id: Int) {
73
+ sockets.remove(id)
74
+ }
75
+
76
+ /**
77
+ * OkHttp WebSocketListener — converts OkHttp callbacks into
78
+ * [WebSocketEventBus] publishes. One listener per socket so we can
79
+ * capture the id in the closure.
80
+ */
81
+ private class Listener(private val id: Int) : WebSocketListener() {
82
+
83
+ override fun onOpen(webSocket: WebSocket, response: Response) {
84
+ val protocol = response.header("Sec-WebSocket-Protocol") ?: ""
85
+ val extensions = response.header("Sec-WebSocket-Extensions") ?: ""
86
+ WebSocketEventBus.publishOpen(id, protocol, extensions)
87
+ }
88
+
89
+ override fun onMessage(webSocket: WebSocket, text: String) {
90
+ WebSocketEventBus.publishMessageText(id, text)
91
+ }
92
+
93
+ override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
94
+ val b64 = Base64.encodeToString(bytes.toByteArray(), Base64.NO_WRAP)
95
+ WebSocketEventBus.publishMessageBinary(id, b64)
96
+ }
97
+
98
+ override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
99
+ // Echo the peer's close back — OkHttp does the responding close
100
+ // for us automatically here.
101
+ webSocket.close(code, reason)
102
+ }
103
+
104
+ override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
105
+ forget(id)
106
+ WebSocketEventBus.publishClose(id, code, reason, wasClean = true)
107
+ }
108
+
109
+ override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
110
+ forget(id)
111
+ WebSocketEventBus.publishError(id, t.message ?: t.javaClass.simpleName)
112
+ WebSocketEventBus.publishClose(id, 1006, t.message ?: "", wasClean = false)
113
+ }
114
+ }
115
+ }
@@ -0,0 +1,2 @@
1
+ export { WebSocket, isWebSocketAvailable } from './websocket.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `@sigx/lynx-websocket` — browser-standard `WebSocket` for sigx-lynx.
3
+ *
4
+ * Importing this module (or listing `@sigx/lynx-websocket` in
5
+ * `sigx.lynx.config.ts → modules:`) installs a global `WebSocket` class on
6
+ * `globalThis`, so portable web code that does `new WebSocket(url)` works
7
+ * unchanged.
8
+ */
9
+ import { WebSocket as SigxWebSocket } from './websocket.js';
10
+ export { WebSocket, isWebSocketAvailable } from './websocket.js';
11
+ // Side-effect: register on the global so consumers don't need an import
12
+ // site to call `new WebSocket(...)`. Mirrors the CLI plugin's auto-import
13
+ // behavior for native modules — listing the package in `modules: [...]`
14
+ // already causes this file to run at bundle init time.
15
+ {
16
+ const g = globalThis;
17
+ if (typeof g.WebSocket === 'undefined') {
18
+ g.WebSocket = SigxWebSocket;
19
+ }
20
+ }
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE5D,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEjE,wEAAwE;AACxE,0EAA0E;AAC1E,wEAAwE;AACxE,uDAAuD;AACvD,CAAC;IACG,MAAM,CAAC,GAAG,UAAgD,CAAC;IAC3D,IAAI,OAAO,CAAC,CAAC,SAAS,KAAK,WAAW,EAAE,CAAC;QACrC,CAAC,CAAC,SAAS,GAAG,aAAa,CAAC;IAChC,CAAC;AACL,CAAC"}
@@ -0,0 +1,91 @@
1
+ /** Bridge to lynx's `GlobalEventEmitter` for native → JS events. */
2
+ interface GlobalEventEmitterLike {
3
+ addListener: (name: string, fn: (...a: unknown[]) => void) => void;
4
+ removeListener: (name: string, fn: (...a: unknown[]) => void) => void;
5
+ }
6
+ /** Wire payload pushed by the native side. */
7
+ interface NativeEvent {
8
+ id: number;
9
+ type: 'open' | 'message' | 'error' | 'close';
10
+ /** Text body for message events, error message for error events. */
11
+ data?: string;
12
+ /** Base64-encoded binary payload (set when isBinary === true). */
13
+ binary?: string;
14
+ isBinary?: boolean;
15
+ /** Negotiated subprotocol — populated on the open event. */
16
+ protocol?: string;
17
+ /** Negotiated extensions — populated on the open event. */
18
+ extensions?: string;
19
+ /** Close frame fields. */
20
+ code?: number;
21
+ reason?: string;
22
+ wasClean?: boolean;
23
+ }
24
+ type ReadyState = 0 | 1 | 2 | 3;
25
+ type BinaryType = 'arraybuffer';
26
+ type EventListenerLike = ((ev: WebSocketEventLike) => void) | {
27
+ handleEvent(ev: WebSocketEventLike): void;
28
+ };
29
+ /** Minimal WHATWG `Event` shape — enough for portable WS code. */
30
+ interface WebSocketEventLike {
31
+ type: string;
32
+ target: WebSocket;
33
+ currentTarget: WebSocket;
34
+ data?: unknown;
35
+ code?: number;
36
+ reason?: string;
37
+ wasClean?: boolean;
38
+ message?: string;
39
+ }
40
+ /**
41
+ * WHATWG-compatible WebSocket. Drop-in for browser code.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * const ws = new WebSocket('wss://ws.postman-echo.com/raw');
46
+ * ws.onopen = () => ws.send('hello');
47
+ * ws.onmessage = e => console.log(e.data);
48
+ * ```
49
+ */
50
+ export declare class WebSocket {
51
+ static readonly CONNECTING: 0;
52
+ static readonly OPEN: 1;
53
+ static readonly CLOSING: 2;
54
+ static readonly CLOSED: 3;
55
+ readonly CONNECTING: 0;
56
+ readonly OPEN: 1;
57
+ readonly CLOSING: 2;
58
+ readonly CLOSED: 3;
59
+ readonly url: string;
60
+ protocol: string;
61
+ extensions: string;
62
+ bufferedAmount: number;
63
+ binaryType: BinaryType;
64
+ onopen: ((ev: WebSocketEventLike) => void) | null;
65
+ onmessage: ((ev: WebSocketEventLike) => void) | null;
66
+ onerror: ((ev: WebSocketEventLike) => void) | null;
67
+ onclose: ((ev: WebSocketEventLike) => void) | null;
68
+ private _readyState;
69
+ private readonly _id;
70
+ private readonly _listeners;
71
+ get readyState(): ReadyState;
72
+ constructor(url: string, protocols?: string | string[]);
73
+ send(data: string | ArrayBuffer | ArrayBufferView): void;
74
+ close(code?: number, reason?: string): void;
75
+ addEventListener(type: string, listener: EventListenerLike): void;
76
+ removeEventListener(type: string, listener: EventListenerLike): void;
77
+ dispatchEvent(event: WebSocketEventLike): boolean;
78
+ /** @internal — called by the shared global-event subscriber. */
79
+ private _dispatch;
80
+ private _invoke;
81
+ }
82
+ /** Whether the native WebSocket module is registered in this build. */
83
+ export declare function isWebSocketAvailable(): boolean;
84
+ /** @internal */
85
+ export declare const __internal: {
86
+ deliver(evt: NativeEvent): void;
87
+ reset(): void;
88
+ readonly cachedEmitter: GlobalEventEmitterLike | null;
89
+ };
90
+ export {};
91
+ //# sourceMappingURL=websocket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"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"}