lunel-cli 0.1.87 → 0.1.89

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.js CHANGED
@@ -931,7 +931,13 @@ async function handleGitDiscard(payload) {
931
931
  }
932
932
  function emitAppEvent(msg) {
933
933
  if (activeV2Transport) {
934
- void activeV2Transport.sendMessage(msg).catch((error) => {
934
+ if (!activeV2Transport.isSecure()) {
935
+ if (DEBUG_MODE) {
936
+ console.error("[transport:v2] dropped event before secure session:", `${msg.ns}.${msg.action}`);
937
+ }
938
+ return;
939
+ }
940
+ void activeV2Transport.sendEvent(msg).catch((error) => {
935
941
  if (DEBUG_MODE)
936
942
  console.error("[transport:v2] failed to send event:", error instanceof Error ? error.message : String(error));
937
943
  });
@@ -2551,11 +2557,15 @@ async function connectWebSocketV2() {
2551
2557
  if (!currentSessionPassword) {
2552
2558
  throw new Error("missing password for websocket connect");
2553
2559
  }
2560
+ if (!currentSessionCode) {
2561
+ throw new Error("missing session code for secure transport");
2562
+ }
2554
2563
  console.log(`Connecting to gateway ${gatewayUrl}...`);
2555
2564
  activeGatewayUrl = gatewayUrl;
2556
2565
  const transport = new V2SessionTransport({
2557
2566
  gatewayUrl,
2558
2567
  password: currentSessionPassword,
2568
+ sessionCode: currentSessionCode,
2559
2569
  role: "cli",
2560
2570
  debugLog: DEBUG_MODE ? debugLog : undefined,
2561
2571
  handlers: {
@@ -2564,7 +2574,6 @@ async function connectWebSocketV2() {
2564
2574
  return;
2565
2575
  if (message.type === "peer_connected") {
2566
2576
  console.log("App connected!\n");
2567
- startPortSync();
2568
2577
  return;
2569
2578
  }
2570
2579
  if (message.type === "peer_disconnected") {
@@ -2593,6 +2602,9 @@ async function connectWebSocketV2() {
2593
2602
  onProtocolResponse: async () => {
2594
2603
  // CLI does not currently await app responses outside request/reply routing.
2595
2604
  },
2605
+ onProtocolEvent: async (message) => {
2606
+ await processMessage(message);
2607
+ },
2596
2608
  onClose: (reason) => {
2597
2609
  if (shuttingDown)
2598
2610
  return;
@@ -2609,6 +2621,7 @@ async function connectWebSocketV2() {
2609
2621
  });
2610
2622
  activeV2Transport = transport;
2611
2623
  await transport.connect();
2624
+ startPortSync();
2612
2625
  console.log("Connected to gateway (single secure session).\n");
2613
2626
  }
2614
2627
  async function handleConnectionDrop(reason) {
@@ -17,12 +17,18 @@ export interface Response {
17
17
  message: string;
18
18
  };
19
19
  }
20
+ export interface EventMessage {
21
+ v: 1;
22
+ id: string;
23
+ ns: string;
24
+ action: string;
25
+ payload: Record<string, unknown>;
26
+ }
20
27
  export interface SystemMessage {
21
- type: "connected" | "peer_connected" | "peer_disconnected" | "error" | "app_disconnected" | "close_connection" | "e2ee_hello" | "e2ee_secure_ready";
28
+ type: "connected" | "peer_connected" | "peer_disconnected" | "error" | "app_disconnected" | "close_connection";
22
29
  role?: string;
23
30
  channel?: string;
24
31
  peer?: string;
25
- pubkey?: string;
26
32
  reconnectDeadline?: number;
27
33
  reason?: string;
28
34
  payload?: Record<string, unknown>;
@@ -35,11 +41,26 @@ export type V2HandshakeFrame = {
35
41
  t: "lunel_v2";
36
42
  kind: "server_hello";
37
43
  pubkey: string;
38
- header: string;
39
44
  } | {
40
45
  t: "lunel_v2";
41
- kind: "client_ready";
42
- header: string;
46
+ kind: "client_key";
47
+ nonce: string;
48
+ box: string;
49
+ auth: string;
50
+ } | {
51
+ t: "lunel_v2";
52
+ kind: "server_ready";
53
+ auth: string;
54
+ };
55
+ export type EncryptedProtocolEnvelope = {
56
+ kind: "request";
57
+ message: Message;
58
+ } | {
59
+ kind: "response";
60
+ message: Response;
61
+ } | {
62
+ kind: "event";
63
+ message: EventMessage;
43
64
  };
44
65
  export declare const V2_BINARY_MAGIC_0 = 76;
45
66
  export declare const V2_BINARY_MAGIC_1 = 50;
@@ -47,6 +68,7 @@ export declare const V2_FRAME_ENCRYPTED_MESSAGE = 1;
47
68
  export declare function isProtocolRequest(value: unknown): value is Message;
48
69
  export declare function isProtocolResponse(value: unknown): value is Response;
49
70
  export declare function isV2HandshakeFrame(value: unknown): value is V2HandshakeFrame;
71
+ export declare function isEncryptedProtocolEnvelope(value: unknown): value is EncryptedProtocolEnvelope;
50
72
  export declare function encodeV2EncryptedFrame(payload: Uint8Array): Uint8Array;
51
73
  export declare function decodeV2BinaryFrame(data: Uint8Array): {
52
74
  type: number;
@@ -28,9 +28,24 @@ export function isV2HandshakeFrame(value) {
28
28
  if (frame.kind === "client_hello")
29
29
  return typeof frame.pubkey === "string";
30
30
  if (frame.kind === "server_hello")
31
- return typeof frame.pubkey === "string" && typeof frame.header === "string";
32
- if (frame.kind === "client_ready")
33
- return typeof frame.header === "string";
31
+ return typeof frame.pubkey === "string";
32
+ if (frame.kind === "client_key") {
33
+ return typeof frame.nonce === "string" && typeof frame.box === "string" && typeof frame.auth === "string";
34
+ }
35
+ if (frame.kind === "server_ready")
36
+ return typeof frame.auth === "string";
37
+ return false;
38
+ }
39
+ export function isEncryptedProtocolEnvelope(value) {
40
+ if (!value || typeof value !== "object")
41
+ return false;
42
+ const envelope = value;
43
+ if (envelope.kind === "request")
44
+ return isProtocolRequest(envelope.message);
45
+ if (envelope.kind === "response")
46
+ return isProtocolResponse(envelope.message);
47
+ if (envelope.kind === "event")
48
+ return isProtocolRequest(envelope.message);
34
49
  return false;
35
50
  }
36
51
  export function encodeV2EncryptedFrame(payload) {
@@ -1,13 +1,15 @@
1
- import type { Message, Response, SystemMessage } from "./protocol.js";
1
+ import type { EventMessage, Message, Response, SystemMessage } from "./protocol.js";
2
2
  export interface V2TransportHandlers {
3
3
  onSystemMessage: (message: SystemMessage) => Promise<void> | void;
4
4
  onProtocolRequest: (message: Message) => Promise<Response>;
5
5
  onProtocolResponse?: (message: Response) => Promise<void> | void;
6
+ onProtocolEvent?: (message: EventMessage) => Promise<void> | void;
6
7
  onClose: (reason: string) => void;
7
8
  }
8
9
  export interface V2TransportOptions {
9
10
  gatewayUrl: string;
10
11
  password: string;
12
+ sessionCode: string;
11
13
  role: "cli" | "app";
12
14
  handlers: V2TransportHandlers;
13
15
  debugLog?: (message: string, ...args: unknown[]) => void;
@@ -18,9 +20,8 @@ export declare class V2SessionTransport {
18
20
  private closed;
19
21
  private state;
20
22
  private keyPair;
23
+ private remotePublicKey;
21
24
  private sessionKeys;
22
- private pushState;
23
- private pullState;
24
25
  private secureReadyResolve;
25
26
  private secureReadyReject;
26
27
  private secureReadyPromise;
@@ -28,12 +29,15 @@ export declare class V2SessionTransport {
28
29
  connect(): Promise<void>;
29
30
  sendMessage(message: Message): Promise<void>;
30
31
  sendResponse(response: Response): Promise<void>;
32
+ sendEvent(message: EventMessage): Promise<void>;
31
33
  close(): void;
34
+ isSecure(): boolean;
32
35
  private handleMessage;
33
36
  private maybeStartHandshake;
34
37
  private handleHandshakeFrame;
35
- private encryptMessage;
38
+ private encryptEnvelope;
36
39
  private ensureKeyPair;
40
+ private computeHandshakeAuth;
37
41
  private sendJsonFrame;
38
42
  private sendBinaryFrame;
39
43
  private markSecure;
@@ -1,6 +1,8 @@
1
1
  import { WebSocket } from "ws";
2
2
  import { createRequire } from "module";
3
- import { V2_FRAME_ENCRYPTED_MESSAGE, buildSessionV2WsUrl, decodeV2BinaryFrame, encodeV2EncryptedFrame, isProtocolRequest, isProtocolResponse, isV2HandshakeFrame, } from "./protocol.js";
3
+ import { V2_FRAME_ENCRYPTED_MESSAGE, buildSessionV2WsUrl, decodeV2BinaryFrame, encodeV2EncryptedFrame, isEncryptedProtocolEnvelope, isV2HandshakeFrame, } from "./protocol.js";
4
+ const encoder = new TextEncoder();
5
+ const decoder = new TextDecoder();
4
6
  const require = createRequire(import.meta.url);
5
7
  const sodium = require("libsodium-wrappers");
6
8
  function toUint8Array(data) {
@@ -10,15 +12,20 @@ function toUint8Array(data) {
10
12
  return new Uint8Array(Buffer.concat(data.map((chunk) => Buffer.from(chunk))));
11
13
  return new Uint8Array(data);
12
14
  }
15
+ function encodeUtf8(value) {
16
+ return encoder.encode(value);
17
+ }
18
+ function decodeUtf8(value) {
19
+ return decoder.decode(value);
20
+ }
13
21
  export class V2SessionTransport {
14
22
  options;
15
23
  ws = null;
16
24
  closed = false;
17
25
  state = "idle";
18
26
  keyPair = null;
27
+ remotePublicKey = null;
19
28
  sessionKeys = null;
20
- pushState = null;
21
- pullState = null;
22
29
  secureReadyResolve = null;
23
30
  secureReadyReject = null;
24
31
  secureReadyPromise = null;
@@ -86,11 +93,15 @@ export class V2SessionTransport {
86
93
  await this.secureReadyPromise;
87
94
  }
88
95
  async sendMessage(message) {
89
- const ciphertext = await this.encryptMessage(message);
96
+ const ciphertext = this.encryptEnvelope({ kind: "request", message });
90
97
  this.sendBinaryFrame(ciphertext);
91
98
  }
92
99
  async sendResponse(response) {
93
- const ciphertext = await this.encryptMessage(response);
100
+ const ciphertext = this.encryptEnvelope({ kind: "response", message: response });
101
+ this.sendBinaryFrame(ciphertext);
102
+ }
103
+ async sendEvent(message) {
104
+ const ciphertext = this.encryptEnvelope({ kind: "event", message });
94
105
  this.sendBinaryFrame(ciphertext);
95
106
  }
96
107
  close() {
@@ -103,6 +114,9 @@ export class V2SessionTransport {
103
114
  }
104
115
  this.ws = null;
105
116
  }
117
+ isSecure() {
118
+ return this.state === "secure";
119
+ }
106
120
  async handleMessage(data, isBinary) {
107
121
  if (!isBinary) {
108
122
  const text = typeof data === "string" ? data : Buffer.from(data).toString("utf-8");
@@ -118,19 +132,9 @@ export class V2SessionTransport {
118
132
  await this.handleHandshakeFrame(raw);
119
133
  return;
120
134
  }
121
- if (this.state !== "secure") {
122
- throw new Error("received plaintext app message before secure transport");
123
- }
124
- if (isProtocolResponse(raw)) {
125
- await this.options.handlers.onProtocolResponse?.(raw);
126
- return;
127
- }
128
- if (isProtocolRequest(raw)) {
129
- const response = await this.options.handlers.onProtocolRequest(raw);
130
- await this.sendResponse(response);
131
- return;
132
- }
133
- return;
135
+ throw new Error(this.state === "secure"
136
+ ? "received plaintext app message after secure transport"
137
+ : "received plaintext app message before secure transport");
134
138
  }
135
139
  const bytes = toUint8Array(data);
136
140
  const frame = decodeV2BinaryFrame(bytes);
@@ -140,24 +144,33 @@ export class V2SessionTransport {
140
144
  if (frame.type !== V2_FRAME_ENCRYPTED_MESSAGE) {
141
145
  throw new Error(`unsupported v2 frame type ${frame.type}`);
142
146
  }
143
- if (this.state !== "secure" || !this.pullState) {
147
+ if (this.state !== "secure" || !this.sessionKeys) {
144
148
  throw new Error("received encrypted frame before secure transport");
145
149
  }
146
- const pulled = sodium.crypto_secretstream_xchacha20poly1305_pull(this.pullState, frame.payload);
147
- if (!pulled.message) {
148
- throw new Error("failed to decrypt v2 frame");
150
+ if (frame.payload.length < sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES) {
151
+ throw new Error("encrypted frame missing nonce");
152
+ }
153
+ const nonce = frame.payload.subarray(0, sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
154
+ const ciphertext = frame.payload.subarray(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
155
+ const plaintext = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null, ciphertext, null, nonce, this.sessionKeys.rx);
156
+ const parsed = JSON.parse(decodeUtf8(plaintext));
157
+ if (!isEncryptedProtocolEnvelope(parsed)) {
158
+ throw new Error("invalid decrypted protocol envelope");
149
159
  }
150
- const parsed = JSON.parse(sodium.to_string(pulled.message));
151
- if (isProtocolResponse(parsed)) {
152
- await this.options.handlers.onProtocolResponse?.(parsed);
160
+ if (parsed.kind === "response") {
161
+ await this.options.handlers.onProtocolResponse?.(parsed.message);
153
162
  return;
154
163
  }
155
- if (isProtocolRequest(parsed)) {
156
- const response = await this.options.handlers.onProtocolRequest(parsed);
164
+ if (parsed.kind === "event") {
165
+ await this.options.handlers.onProtocolEvent?.(parsed.message);
166
+ return;
167
+ }
168
+ if (parsed.kind === "request") {
169
+ const response = await this.options.handlers.onProtocolRequest(parsed.message);
157
170
  await this.sendResponse(response);
158
171
  return;
159
172
  }
160
- throw new Error("invalid decrypted protocol message");
173
+ throw new Error("invalid decrypted protocol envelope");
161
174
  }
162
175
  async maybeStartHandshake() {
163
176
  if (this.state === "secure" || this.state === "handshaking")
@@ -179,72 +192,115 @@ export class V2SessionTransport {
179
192
  if (this.options.role !== "cli") {
180
193
  throw new Error("unexpected client_hello on app transport");
181
194
  }
182
- const clientPublicKey = sodium.from_base64(frame.pubkey, sodium.base64_variants.URLSAFE_NO_PADDING);
195
+ this.remotePublicKey = sodium.from_base64(frame.pubkey, sodium.base64_variants.URLSAFE_NO_PADDING);
183
196
  const keyPair = this.ensureKeyPair();
184
- const keys = sodium.crypto_kx_server_session_keys(keyPair.publicKey, keyPair.privateKey, clientPublicKey);
185
- this.sessionKeys = { rx: keys.sharedRx, tx: keys.sharedTx };
186
- const pushInit = sodium.crypto_secretstream_xchacha20poly1305_init_push(keys.sharedTx);
187
- this.pushState = pushInit.state;
188
- const response = {
197
+ this.sendJsonFrame({
189
198
  t: "lunel_v2",
190
199
  kind: "server_hello",
191
200
  pubkey: sodium.to_base64(keyPair.publicKey, sodium.base64_variants.URLSAFE_NO_PADDING),
192
- header: sodium.to_base64(pushInit.header, sodium.base64_variants.URLSAFE_NO_PADDING),
193
- };
194
- this.sendJsonFrame(response);
201
+ });
195
202
  return;
196
203
  }
197
204
  if (frame.kind === "server_hello") {
198
205
  if (this.options.role !== "app") {
199
206
  throw new Error("unexpected server_hello on cli transport");
200
207
  }
201
- const serverPublicKey = sodium.from_base64(frame.pubkey, sodium.base64_variants.URLSAFE_NO_PADDING);
202
- const serverHeader = sodium.from_base64(frame.header, sodium.base64_variants.URLSAFE_NO_PADDING);
208
+ this.remotePublicKey = sodium.from_base64(frame.pubkey, sodium.base64_variants.URLSAFE_NO_PADDING);
203
209
  const keyPair = this.ensureKeyPair();
204
- const keys = sodium.crypto_kx_client_session_keys(keyPair.publicKey, keyPair.privateKey, serverPublicKey);
205
- this.sessionKeys = { rx: keys.sharedRx, tx: keys.sharedTx };
206
- this.pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull(serverHeader, keys.sharedRx);
207
- const pushInit = sodium.crypto_secretstream_xchacha20poly1305_init_push(keys.sharedTx);
208
- this.pushState = pushInit.state;
209
- const ready = {
210
+ const c2sKey = sodium.crypto_aead_xchacha20poly1305_ietf_keygen();
211
+ const s2cKey = sodium.crypto_aead_xchacha20poly1305_ietf_keygen();
212
+ const nonce = sodium.randombytes_buf(sodium.crypto_box_NONCEBYTES);
213
+ const payload = {
214
+ c2s: sodium.to_base64(c2sKey, sodium.base64_variants.URLSAFE_NO_PADDING),
215
+ s2c: sodium.to_base64(s2cKey, sodium.base64_variants.URLSAFE_NO_PADDING),
216
+ };
217
+ const boxed = sodium.crypto_box_easy(encodeUtf8(JSON.stringify(payload)), nonce, this.remotePublicKey, keyPair.privateKey);
218
+ const auth = this.computeHandshakeAuth("client_key", "app", sodium.to_base64(keyPair.publicKey, sodium.base64_variants.URLSAFE_NO_PADDING), nonce, boxed);
219
+ this.sessionKeys = { rx: s2cKey, tx: c2sKey };
220
+ this.sendJsonFrame({
210
221
  t: "lunel_v2",
211
- kind: "client_ready",
212
- header: sodium.to_base64(pushInit.header, sodium.base64_variants.URLSAFE_NO_PADDING),
222
+ kind: "client_key",
223
+ nonce: sodium.to_base64(nonce, sodium.base64_variants.URLSAFE_NO_PADDING),
224
+ box: sodium.to_base64(boxed, sodium.base64_variants.URLSAFE_NO_PADDING),
225
+ auth,
226
+ });
227
+ return;
228
+ }
229
+ if (frame.kind === "client_key") {
230
+ if (this.options.role !== "cli") {
231
+ throw new Error("unexpected client_key on app transport");
232
+ }
233
+ if (!this.remotePublicKey) {
234
+ throw new Error("missing client public key before client_key");
235
+ }
236
+ const keyPair = this.ensureKeyPair();
237
+ const nonce = sodium.from_base64(frame.nonce, sodium.base64_variants.URLSAFE_NO_PADDING);
238
+ const boxed = sodium.from_base64(frame.box, sodium.base64_variants.URLSAFE_NO_PADDING);
239
+ const expectedAuth = this.computeHandshakeAuth("client_key", "app", sodium.to_base64(this.remotePublicKey, sodium.base64_variants.URLSAFE_NO_PADDING), nonce, boxed);
240
+ if (frame.auth !== expectedAuth) {
241
+ throw new Error("client_key authentication failed");
242
+ }
243
+ const opened = sodium.crypto_box_open_easy(boxed, nonce, this.remotePublicKey, keyPair.privateKey);
244
+ const payload = JSON.parse(decodeUtf8(opened));
245
+ this.sessionKeys = {
246
+ rx: sodium.from_base64(payload.c2s, sodium.base64_variants.URLSAFE_NO_PADDING),
247
+ tx: sodium.from_base64(payload.s2c, sodium.base64_variants.URLSAFE_NO_PADDING),
213
248
  };
214
- this.sendJsonFrame(ready);
249
+ this.sendJsonFrame({
250
+ t: "lunel_v2",
251
+ kind: "server_ready",
252
+ auth: this.computeHandshakeAuth("server_ready", "cli", sodium.to_base64(this.remotePublicKey, sodium.base64_variants.URLSAFE_NO_PADDING)),
253
+ });
215
254
  this.markSecure();
216
255
  return;
217
256
  }
218
- if (frame.kind === "client_ready") {
219
- if (this.options.role !== "cli") {
220
- throw new Error("unexpected client_ready on app transport");
257
+ if (frame.kind === "server_ready") {
258
+ if (this.options.role !== "app") {
259
+ throw new Error("unexpected server_ready on cli transport");
221
260
  }
222
261
  if (!this.sessionKeys) {
223
- throw new Error("missing session keys before client_ready");
262
+ throw new Error("missing session keys before server_ready");
263
+ }
264
+ const expectedAuth = this.computeHandshakeAuth("server_ready", "cli", sodium.to_base64(this.remotePublicKey, sodium.base64_variants.URLSAFE_NO_PADDING));
265
+ if (frame.auth !== expectedAuth) {
266
+ throw new Error("server_ready authentication failed");
224
267
  }
225
- const clientHeader = sodium.from_base64(frame.header, sodium.base64_variants.URLSAFE_NO_PADDING);
226
- this.pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull(clientHeader, this.sessionKeys.rx);
227
268
  this.markSecure();
228
269
  }
229
270
  }
230
- async encryptMessage(message) {
231
- await sodium.ready;
232
- if (this.state !== "secure" || !this.pushState) {
271
+ encryptEnvelope(envelope) {
272
+ if (this.state !== "secure" || !this.sessionKeys) {
233
273
  throw new Error("secure transport is not active");
234
274
  }
235
- const plaintext = sodium.from_string(JSON.stringify(message));
236
- return sodium.crypto_secretstream_xchacha20poly1305_push(this.pushState, plaintext, null, sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE);
275
+ const nonce = sodium.randombytes_buf(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
276
+ const ciphertext = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(encodeUtf8(JSON.stringify(envelope)), null, null, nonce, this.sessionKeys.tx);
277
+ const payload = new Uint8Array(nonce.length + ciphertext.length);
278
+ payload.set(nonce, 0);
279
+ payload.set(ciphertext, nonce.length);
280
+ return payload;
237
281
  }
238
282
  ensureKeyPair() {
239
283
  if (this.keyPair)
240
284
  return this.keyPair;
241
- const pair = sodium.crypto_kx_keypair();
285
+ const pair = sodium.crypto_box_keypair();
242
286
  this.keyPair = {
243
287
  publicKey: pair.publicKey,
244
288
  privateKey: pair.privateKey,
245
289
  };
246
290
  return this.keyPair;
247
291
  }
292
+ computeHandshakeAuth(phase, senderRole, peerPubkeyB64, nonce, boxed) {
293
+ const authKey = sodium.crypto_generichash(sodium.crypto_auth_KEYBYTES, encodeUtf8(this.options.sessionCode), undefined);
294
+ const parts = [
295
+ phase,
296
+ senderRole,
297
+ peerPubkeyB64,
298
+ nonce ? sodium.to_base64(nonce, sodium.base64_variants.URLSAFE_NO_PADDING) : "",
299
+ boxed ? sodium.to_base64(boxed, sodium.base64_variants.URLSAFE_NO_PADDING) : "",
300
+ ];
301
+ const tag = sodium.crypto_auth(parts.join(":"), authKey);
302
+ return sodium.to_base64(tag, sodium.base64_variants.URLSAFE_NO_PADDING);
303
+ }
248
304
  sendJsonFrame(frame) {
249
305
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
250
306
  throw new Error("v2 transport is not connected");
@@ -256,7 +312,7 @@ export class V2SessionTransport {
256
312
  throw new Error("v2 transport is not connected");
257
313
  }
258
314
  const framed = encodeV2EncryptedFrame(ciphertext);
259
- this.ws.send(Buffer.from(framed));
315
+ this.ws.send(framed);
260
316
  }
261
317
  markSecure() {
262
318
  this.state = "secure";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lunel-cli",
3
- "version": "0.1.87",
3
+ "version": "0.1.89",
4
4
  "author": [
5
5
  {
6
6
  "name": "Soham Bharambe",