@vex-chat/libvex 6.1.9 → 6.2.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/dist/Client.d.ts +111 -10
- package/dist/Client.d.ts.map +1 -1
- package/dist/Client.js +165 -8
- package/dist/Client.js.map +1 -1
- package/dist/codecs.d.ts +60 -0
- package/dist/codecs.d.ts.map +1 -1
- package/dist/codecs.js +22 -1
- package/dist/codecs.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/transport/websocket.d.ts +31 -0
- package/dist/transport/websocket.d.ts.map +1 -1
- package/dist/transport/websocket.js +56 -1
- package/dist/transport/websocket.js.map +1 -1
- package/package.json +3 -3
- package/src/Client.ts +322 -19
- package/src/codecs.ts +30 -0
- package/src/index.ts +2 -1
- package/src/transport/websocket.ts +59 -1
package/src/codecs.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
FileSQLSchema,
|
|
22
22
|
InviteSchema,
|
|
23
23
|
KeyBundleSchema,
|
|
24
|
+
PasskeySchema,
|
|
24
25
|
PermissionSchema,
|
|
25
26
|
ServerSchema,
|
|
26
27
|
UserSchema,
|
|
@@ -156,6 +157,35 @@ export const WhoamiCodec = createCodec(
|
|
|
156
157
|
|
|
157
158
|
export const OtkCountCodec = createCodec(z.object({ count: z.number() }));
|
|
158
159
|
|
|
160
|
+
// ── Passkey response codecs ────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
export const PasskeyCodec = createCodec(PasskeySchema);
|
|
163
|
+
export const PasskeyArrayCodec = createCodec(z.array(PasskeySchema));
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The shape of `/user/:id/passkeys/register/begin` and
|
|
167
|
+
* `/auth/passkey/begin` responses. `options` is the WebAuthn JSON
|
|
168
|
+
* the host hands straight to `navigator.credentials.create()` /
|
|
169
|
+
* `.get()` (via `@simplewebauthn/browser`); we don't validate its
|
|
170
|
+
* inner shape because both ends of the wire (`@simplewebauthn/server`
|
|
171
|
+
* on spire, `@simplewebauthn/browser` on the host) already do.
|
|
172
|
+
*/
|
|
173
|
+
export const PasskeyOptionsCodec = createCodec(
|
|
174
|
+
z.object({
|
|
175
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- WebAuthn options shape varies by simplewebauthn version
|
|
176
|
+
options: z.unknown() as z.ZodType<any>,
|
|
177
|
+
requestID: z.string(),
|
|
178
|
+
}),
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
export const PasskeyAuthFinishResponseCodec = createCodec(
|
|
182
|
+
z.object({
|
|
183
|
+
passkeyID: z.string(),
|
|
184
|
+
token: z.string(),
|
|
185
|
+
user: UserSchema,
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
|
|
159
189
|
// ── Helper: decode axios response buffer ────────────────────────────────────
|
|
160
190
|
|
|
161
191
|
/**
|
package/src/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type {
|
|
|
23
23
|
Message,
|
|
24
24
|
Messages,
|
|
25
25
|
Moderation,
|
|
26
|
+
Passkeys,
|
|
26
27
|
PendingDeviceApprovalStatus,
|
|
27
28
|
PendingDeviceRegistration,
|
|
28
29
|
PendingDeviceRequest,
|
|
@@ -47,4 +48,4 @@ export type {
|
|
|
47
48
|
UnsavedPreKey,
|
|
48
49
|
} from "./types/index.js";
|
|
49
50
|
// Re-export app-facing types
|
|
50
|
-
export type { Invite } from "@vex-chat/types";
|
|
51
|
+
export type { Invite, Passkey } from "@vex-chat/types";
|
|
@@ -102,11 +102,69 @@ export class WebSocketAdapter implements WebSocketLike {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Forward `data` to the underlying socket if and only if it's
|
|
107
|
+
* OPEN. Throws `WebSocketNotOpenError` (a typed, named error)
|
|
108
|
+
* otherwise, so callers can distinguish a teardown race from a
|
|
109
|
+
* protocol error and either retry on the next socket or drop
|
|
110
|
+
* the frame.
|
|
111
|
+
*
|
|
112
|
+
* Without this guard a transient teardown surfaces as
|
|
113
|
+
* `DOMException("INVALID_STATE_ERR")` from the platform WebSocket
|
|
114
|
+
* — opaque, hard to catch by name, and surfaced by React Native
|
|
115
|
+
* as an unhandled promise rejection (red box / "frozen UI") any
|
|
116
|
+
* time a `void this.send(...)` callsite (ping / pong / queued
|
|
117
|
+
* notify reply) is in flight when the close event lands.
|
|
118
|
+
*/
|
|
105
119
|
send(data: Uint8Array) {
|
|
106
|
-
this.ws.
|
|
120
|
+
if (this.ws.readyState !== 1) {
|
|
121
|
+
throw new WebSocketNotOpenError(this.ws.readyState);
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
this.ws.send(new Uint8Array(data));
|
|
125
|
+
} catch (err: unknown) {
|
|
126
|
+
// Handles the TOCTOU between the readyState check above
|
|
127
|
+
// and the actual `ws.send` call. React Native's bridge
|
|
128
|
+
// can dispatch a `websocketMessage` and a
|
|
129
|
+
// `websocketClosed` back-to-back in the same JS turn:
|
|
130
|
+
// our message listener observes readyState=1 because
|
|
131
|
+
// the close event hasn't been processed yet, but the
|
|
132
|
+
// underlying WebSocket has already transitioned native-
|
|
133
|
+
// side and `send` throws INVALID_STATE_ERR.
|
|
134
|
+
if (
|
|
135
|
+
err instanceof Error &&
|
|
136
|
+
/invalid_state|INVALID_STATE_ERR/i.test(err.message)
|
|
137
|
+
) {
|
|
138
|
+
throw new WebSocketNotOpenError(this.ws.readyState);
|
|
139
|
+
}
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
107
142
|
}
|
|
108
143
|
|
|
109
144
|
terminate() {
|
|
110
145
|
this.ws.close();
|
|
111
146
|
}
|
|
112
147
|
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Thrown when `send()` is called on a socket that's not in the OPEN
|
|
151
|
+
* state. Surfaced as a named, recognisable type so callers can
|
|
152
|
+
* distinguish "transient teardown race" from a real protocol error
|
|
153
|
+
* and either drop the frame (pings) or wait for reconnect (real
|
|
154
|
+
* payloads).
|
|
155
|
+
*
|
|
156
|
+
* Replaces the bare `DOMException("INVALID_STATE_ERR")` that the
|
|
157
|
+
* underlying WebSocket throws — that one is opaque, gets reported
|
|
158
|
+
* by RN's dev console as a red unhandled rejection, and freezes the
|
|
159
|
+
* passkey/foreground/network-swap recovery flow because every code
|
|
160
|
+
* path that voids the resulting promise leaks the rejection.
|
|
161
|
+
*/
|
|
162
|
+
export class WebSocketNotOpenError extends Error {
|
|
163
|
+
public readonly readyState: number;
|
|
164
|
+
|
|
165
|
+
constructor(readyState: number) {
|
|
166
|
+
super(`WebSocket is not open (readyState=${readyState.toString()})`);
|
|
167
|
+
this.name = "WebSocketNotOpenError";
|
|
168
|
+
this.readyState = readyState;
|
|
169
|
+
}
|
|
170
|
+
}
|