pubblue 0.4.2 → 0.4.4
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/chunk-AIEPM67G.js +659 -0
- package/dist/chunk-MW35LBNH.js +60 -0
- package/dist/index.js +88 -33
- package/dist/tunnel-daemon-DR4A65ME.js +11 -0
- package/dist/tunnel-daemon-entry.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-3RFMAQOM.js +0 -349
- package/dist/chunk-56IKFMJ2.js +0 -40
- package/dist/tunnel-daemon-K7Z7FUFN.js +0 -9
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// src/lib/bridge-protocol.ts
|
|
2
|
+
var CONTROL_CHANNEL = "_control";
|
|
3
|
+
var CHANNELS = {
|
|
4
|
+
CHAT: "chat",
|
|
5
|
+
CANVAS: "canvas",
|
|
6
|
+
AUDIO: "audio",
|
|
7
|
+
MEDIA: "media",
|
|
8
|
+
FILE: "file"
|
|
9
|
+
};
|
|
10
|
+
var idCounter = 0;
|
|
11
|
+
function generateMessageId() {
|
|
12
|
+
const ts = Date.now().toString(36);
|
|
13
|
+
const seq = (idCounter++).toString(36);
|
|
14
|
+
const rand = Math.random().toString(36).slice(2, 6);
|
|
15
|
+
return `${ts}-${seq}-${rand}`;
|
|
16
|
+
}
|
|
17
|
+
function encodeMessage(msg) {
|
|
18
|
+
return JSON.stringify(msg);
|
|
19
|
+
}
|
|
20
|
+
function decodeMessage(raw) {
|
|
21
|
+
try {
|
|
22
|
+
const parsed = JSON.parse(raw);
|
|
23
|
+
if (parsed && typeof parsed.id === "string" && typeof parsed.type === "string") {
|
|
24
|
+
return parsed;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function makeEventMessage(event, meta) {
|
|
32
|
+
return { id: generateMessageId(), type: "event", data: event, meta };
|
|
33
|
+
}
|
|
34
|
+
function makeAckMessage(messageId, channel) {
|
|
35
|
+
return makeEventMessage("ack", { messageId, channel, receivedAt: Date.now() });
|
|
36
|
+
}
|
|
37
|
+
function parseAckMessage(msg) {
|
|
38
|
+
if (msg.type !== "event" || msg.data !== "ack" || !msg.meta) return null;
|
|
39
|
+
const messageId = typeof msg.meta.messageId === "string" ? msg.meta.messageId : null;
|
|
40
|
+
const channel = typeof msg.meta.channel === "string" ? msg.meta.channel : null;
|
|
41
|
+
if (!messageId || !channel) return null;
|
|
42
|
+
const receivedAt = typeof msg.meta.receivedAt === "number" ? msg.meta.receivedAt : void 0;
|
|
43
|
+
return { messageId, channel, receivedAt };
|
|
44
|
+
}
|
|
45
|
+
function shouldAcknowledgeMessage(channel, msg) {
|
|
46
|
+
return channel !== CONTROL_CHANNEL && parseAckMessage(msg) === null;
|
|
47
|
+
}
|
|
48
|
+
var MAX_TUNNEL_EXPIRY_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
49
|
+
var DEFAULT_TUNNEL_EXPIRY_MS = 24 * 60 * 60 * 1e3;
|
|
50
|
+
|
|
51
|
+
export {
|
|
52
|
+
CONTROL_CHANNEL,
|
|
53
|
+
CHANNELS,
|
|
54
|
+
generateMessageId,
|
|
55
|
+
encodeMessage,
|
|
56
|
+
decodeMessage,
|
|
57
|
+
makeAckMessage,
|
|
58
|
+
parseAckMessage,
|
|
59
|
+
shouldAcknowledgeMessage
|
|
60
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -3,8 +3,9 @@ import {
|
|
|
3
3
|
TunnelApiClient
|
|
4
4
|
} from "./chunk-BV423NLA.js";
|
|
5
5
|
import {
|
|
6
|
+
CHANNELS,
|
|
6
7
|
generateMessageId
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-MW35LBNH.js";
|
|
8
9
|
|
|
9
10
|
// src/index.ts
|
|
10
11
|
import * as fs3 from "fs";
|
|
@@ -185,6 +186,9 @@ function tunnelInfoDir() {
|
|
|
185
186
|
function tunnelInfoPath(tunnelId) {
|
|
186
187
|
return path2.join(tunnelInfoDir(), `${tunnelId}.json`);
|
|
187
188
|
}
|
|
189
|
+
function tunnelLogPath(tunnelId) {
|
|
190
|
+
return path2.join(tunnelInfoDir(), `${tunnelId}.log`);
|
|
191
|
+
}
|
|
188
192
|
function createApiClient() {
|
|
189
193
|
const config = getConfig();
|
|
190
194
|
return new TunnelApiClient(config.baseUrl, config.apiKey);
|
|
@@ -215,19 +219,25 @@ function isDaemonRunning(tunnelId) {
|
|
|
215
219
|
return false;
|
|
216
220
|
}
|
|
217
221
|
}
|
|
222
|
+
function getFollowReadDelayMs(disconnected, consecutiveFailures) {
|
|
223
|
+
if (!disconnected) return 1e3;
|
|
224
|
+
return Math.min(5e3, 1e3 * 2 ** Math.min(consecutiveFailures, 3));
|
|
225
|
+
}
|
|
226
|
+
function resolveTunnelIdSelection(tunnelIdArg, tunnelOpt) {
|
|
227
|
+
return tunnelOpt || tunnelIdArg;
|
|
228
|
+
}
|
|
218
229
|
function registerTunnelCommands(program2) {
|
|
219
230
|
const tunnel = program2.command("tunnel").description("P2P encrypted tunnel to browser");
|
|
220
|
-
tunnel.command("start").description("Start a new tunnel (spawns background daemon)").option("--
|
|
231
|
+
tunnel.command("start").description("Start a new tunnel (spawns background daemon)").option("--expires <duration>", "Auto-close after duration (e.g. 4h, 1d)", "24h").option("--foreground", "Run in foreground (don't fork)").action(async (opts) => {
|
|
221
232
|
await ensureNodeDatachannelAvailable();
|
|
222
233
|
const apiClient = createApiClient();
|
|
223
234
|
const result = await apiClient.create({
|
|
224
|
-
title: opts.title,
|
|
225
235
|
expiresIn: opts.expires
|
|
226
236
|
});
|
|
227
237
|
const socketPath = getSocketPath(result.tunnelId);
|
|
228
238
|
const infoPath = tunnelInfoPath(result.tunnelId);
|
|
229
239
|
if (opts.foreground) {
|
|
230
|
-
const { startDaemon } = await import("./tunnel-daemon-
|
|
240
|
+
const { startDaemon } = await import("./tunnel-daemon-DR4A65ME.js");
|
|
231
241
|
console.log(`Tunnel started: ${result.url}`);
|
|
232
242
|
console.log(`Tunnel ID: ${result.tunnelId}`);
|
|
233
243
|
console.log(`Expires: ${new Date(result.expiresAt).toISOString()}`);
|
|
@@ -247,9 +257,11 @@ function registerTunnelCommands(program2) {
|
|
|
247
257
|
} else {
|
|
248
258
|
const daemonScript = path2.join(import.meta.dirname, "tunnel-daemon-entry.js");
|
|
249
259
|
const config = getConfig();
|
|
260
|
+
const logPath = tunnelLogPath(result.tunnelId);
|
|
261
|
+
const daemonLogFd = fs2.openSync(logPath, "a");
|
|
250
262
|
const child = fork(daemonScript, [], {
|
|
251
263
|
detached: true,
|
|
252
|
-
stdio: "ignore",
|
|
264
|
+
stdio: ["ignore", daemonLogFd, daemonLogFd],
|
|
253
265
|
env: {
|
|
254
266
|
...process.env,
|
|
255
267
|
PUBBLUE_DAEMON_TUNNEL_ID: result.tunnelId,
|
|
@@ -259,10 +271,12 @@ function registerTunnelCommands(program2) {
|
|
|
259
271
|
PUBBLUE_DAEMON_INFO: infoPath
|
|
260
272
|
}
|
|
261
273
|
});
|
|
274
|
+
fs2.closeSync(daemonLogFd);
|
|
262
275
|
child.unref();
|
|
263
276
|
const ready = await waitForDaemonReady(infoPath, child, 5e3);
|
|
264
277
|
if (!ready) {
|
|
265
278
|
console.error("Daemon failed to start. Cleaning up tunnel...");
|
|
279
|
+
console.error(`Daemon log: ${logPath}`);
|
|
266
280
|
await apiClient.close(result.tunnelId).catch(() => {
|
|
267
281
|
});
|
|
268
282
|
process.exit(1);
|
|
@@ -270,6 +284,7 @@ function registerTunnelCommands(program2) {
|
|
|
270
284
|
console.log(`Tunnel started: ${result.url}`);
|
|
271
285
|
console.log(`Tunnel ID: ${result.tunnelId}`);
|
|
272
286
|
console.log(`Expires: ${new Date(result.expiresAt).toISOString()}`);
|
|
287
|
+
console.log(`Daemon log: ${logPath}`);
|
|
273
288
|
}
|
|
274
289
|
});
|
|
275
290
|
tunnel.command("write").description("Write data to a channel").argument("[message]", "Text message (or use --file)").option("-t, --tunnel <tunnelId>", "Tunnel ID (auto-detected if one active)").option("-c, --channel <channel>", "Channel name", "chat").option("-f, --file <file>", "Read content from file").action(
|
|
@@ -330,31 +345,50 @@ function registerTunnelCommands(program2) {
|
|
|
330
345
|
}
|
|
331
346
|
}
|
|
332
347
|
);
|
|
333
|
-
tunnel.command("read").description("Read buffered messages from channels").argument("[tunnelId]", "Tunnel ID (auto-detected if one active)").option("-c, --channel <channel>", "Filter by channel").option("--follow", "Stream messages continuously").action(
|
|
348
|
+
tunnel.command("read").description("Read buffered messages from channels").argument("[tunnelId]", "Tunnel ID (auto-detected if one active)").option("-t, --tunnel <tunnelId>", "Tunnel ID (alternative to positional arg)").option("-c, --channel <channel>", "Filter by channel").option("--follow", "Stream messages continuously").option("--all", "With --follow, include all channels instead of chat-only default").action(
|
|
334
349
|
async (tunnelIdArg, opts) => {
|
|
335
|
-
const tunnelId = tunnelIdArg || await resolveActiveTunnel();
|
|
350
|
+
const tunnelId = resolveTunnelIdSelection(tunnelIdArg, opts.tunnel) || await resolveActiveTunnel();
|
|
336
351
|
const socketPath = getSocketPath(tunnelId);
|
|
352
|
+
const readChannel = opts.channel || (opts.follow && !opts.all ? CHANNELS.CHAT : void 0);
|
|
337
353
|
if (opts.follow) {
|
|
354
|
+
if (!opts.channel && !opts.all) {
|
|
355
|
+
console.error(
|
|
356
|
+
"Following chat channel by default. Use `--all` to include binary/file channels."
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
let consecutiveFailures = 0;
|
|
360
|
+
let warnedDisconnected = false;
|
|
338
361
|
while (true) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
362
|
+
try {
|
|
363
|
+
const response = await ipcCall(socketPath, {
|
|
364
|
+
method: "read",
|
|
365
|
+
params: { channel: readChannel }
|
|
366
|
+
});
|
|
367
|
+
if (warnedDisconnected) {
|
|
368
|
+
console.error("Daemon reconnected.");
|
|
369
|
+
warnedDisconnected = false;
|
|
370
|
+
}
|
|
371
|
+
consecutiveFailures = 0;
|
|
372
|
+
if (response.messages && response.messages.length > 0) {
|
|
373
|
+
for (const m of response.messages) {
|
|
374
|
+
console.log(JSON.stringify(m));
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} catch (error) {
|
|
378
|
+
consecutiveFailures += 1;
|
|
379
|
+
if (!warnedDisconnected) {
|
|
380
|
+
const detail = error instanceof Error ? ` ${error.message}` : "";
|
|
381
|
+
console.error(`Daemon disconnected. Waiting for recovery...${detail}`);
|
|
382
|
+
warnedDisconnected = true;
|
|
350
383
|
}
|
|
351
384
|
}
|
|
352
|
-
|
|
385
|
+
const delayMs = getFollowReadDelayMs(warnedDisconnected, consecutiveFailures);
|
|
386
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
353
387
|
}
|
|
354
388
|
} else {
|
|
355
389
|
const response = await ipcCall(socketPath, {
|
|
356
390
|
method: "read",
|
|
357
|
-
params: { channel:
|
|
391
|
+
params: { channel: readChannel }
|
|
358
392
|
});
|
|
359
393
|
if (!response.ok) {
|
|
360
394
|
console.error(`Failed: ${response.error}`);
|
|
@@ -364,8 +398,8 @@ function registerTunnelCommands(program2) {
|
|
|
364
398
|
}
|
|
365
399
|
}
|
|
366
400
|
);
|
|
367
|
-
tunnel.command("channels").description("List active channels").argument("[tunnelId]", "Tunnel ID").action(async (tunnelIdArg) => {
|
|
368
|
-
const tunnelId = tunnelIdArg || await resolveActiveTunnel();
|
|
401
|
+
tunnel.command("channels").description("List active channels").argument("[tunnelId]", "Tunnel ID").option("-t, --tunnel <tunnelId>", "Tunnel ID (alternative to positional arg)").action(async (tunnelIdArg, opts) => {
|
|
402
|
+
const tunnelId = resolveTunnelIdSelection(tunnelIdArg, opts.tunnel) || await resolveActiveTunnel();
|
|
369
403
|
const socketPath = getSocketPath(tunnelId);
|
|
370
404
|
const response = await ipcCall(socketPath, { method: "channels", params: {} });
|
|
371
405
|
if (response.channels) {
|
|
@@ -374,8 +408,8 @@ function registerTunnelCommands(program2) {
|
|
|
374
408
|
}
|
|
375
409
|
}
|
|
376
410
|
});
|
|
377
|
-
tunnel.command("status").description("Check tunnel connection status").argument("[tunnelId]", "Tunnel ID").action(async (tunnelIdArg) => {
|
|
378
|
-
const tunnelId = tunnelIdArg || await resolveActiveTunnel();
|
|
411
|
+
tunnel.command("status").description("Check tunnel connection status").argument("[tunnelId]", "Tunnel ID").option("-t, --tunnel <tunnelId>", "Tunnel ID (alternative to positional arg)").action(async (tunnelIdArg, opts) => {
|
|
412
|
+
const tunnelId = resolveTunnelIdSelection(tunnelIdArg, opts.tunnel) || await resolveActiveTunnel();
|
|
379
413
|
const socketPath = getSocketPath(tunnelId);
|
|
380
414
|
const response = await ipcCall(socketPath, { method: "status", params: {} });
|
|
381
415
|
console.log(` Status: ${response.connected ? "connected" : "waiting"}`);
|
|
@@ -383,6 +417,13 @@ function registerTunnelCommands(program2) {
|
|
|
383
417
|
const chNames = Array.isArray(response.channels) ? response.channels.map((c) => typeof c === "string" ? c : String(c)) : [];
|
|
384
418
|
console.log(` Channels: ${chNames.join(", ")}`);
|
|
385
419
|
console.log(` Buffered: ${response.bufferedMessages ?? 0} messages`);
|
|
420
|
+
if (typeof response.lastError === "string" && response.lastError.length > 0) {
|
|
421
|
+
console.log(` Last error: ${response.lastError}`);
|
|
422
|
+
}
|
|
423
|
+
const logPath = tunnelLogPath(tunnelId);
|
|
424
|
+
if (fs2.existsSync(logPath)) {
|
|
425
|
+
console.log(` Log: ${logPath}`);
|
|
426
|
+
}
|
|
386
427
|
});
|
|
387
428
|
tunnel.command("list").description("List active tunnels").action(async () => {
|
|
388
429
|
const apiClient = createApiClient();
|
|
@@ -395,9 +436,7 @@ function registerTunnelCommands(program2) {
|
|
|
395
436
|
const age = Math.floor((Date.now() - t.createdAt) / 6e4);
|
|
396
437
|
const running = isDaemonRunning(t.tunnelId) ? "running" : "no daemon";
|
|
397
438
|
const conn = t.hasConnection ? "connected" : "waiting";
|
|
398
|
-
console.log(
|
|
399
|
-
` ${t.tunnelId} ${t.title || "(untitled)"} ${conn} ${running} ${age}m ago`
|
|
400
|
-
);
|
|
439
|
+
console.log(` ${t.tunnelId} ${conn} ${running} ${age}m ago`);
|
|
401
440
|
}
|
|
402
441
|
});
|
|
403
442
|
tunnel.command("close").description("Close a tunnel and stop its daemon").argument("<tunnelId>", "Tunnel ID").action(async (tunnelId) => {
|
|
@@ -568,6 +607,14 @@ async function resolveConfigureApiKey(opts) {
|
|
|
568
607
|
}
|
|
569
608
|
return readApiKeyFromPrompt();
|
|
570
609
|
}
|
|
610
|
+
function resolveVisibilityFlags(opts) {
|
|
611
|
+
if (opts.public && opts.private) {
|
|
612
|
+
throw new Error(`Use only one of --public or --private for ${opts.commandName}.`);
|
|
613
|
+
}
|
|
614
|
+
if (opts.public) return true;
|
|
615
|
+
if (opts.private) return false;
|
|
616
|
+
return void 0;
|
|
617
|
+
}
|
|
571
618
|
function readFile(filePath) {
|
|
572
619
|
const resolved = path3.resolve(filePath);
|
|
573
620
|
if (!fs3.existsSync(resolved)) {
|
|
@@ -579,7 +626,7 @@ function readFile(filePath) {
|
|
|
579
626
|
basename: path3.basename(resolved)
|
|
580
627
|
};
|
|
581
628
|
}
|
|
582
|
-
program.name("pubblue").description("Publish static content and get shareable URLs").version("0.4.
|
|
629
|
+
program.name("pubblue").description("Publish static content and get shareable URLs").version("0.4.4");
|
|
583
630
|
program.command("configure").description("Configure the CLI with your API key").option("--api-key <key>", "Your API key (less secure: appears in shell history)").option("--api-key-stdin", "Read API key from stdin").action(async (opts) => {
|
|
584
631
|
try {
|
|
585
632
|
const apiKey = await resolveConfigureApiKey(opts);
|
|
@@ -591,7 +638,7 @@ program.command("configure").description("Configure the CLI with your API key").
|
|
|
591
638
|
process.exit(1);
|
|
592
639
|
}
|
|
593
640
|
});
|
|
594
|
-
program.command("create").description("Create a new publication").argument("[file]", "Path to the file (reads stdin if omitted)").option("--slug <slug>", "Custom slug for the URL").option("--title <title>", "Title for the publication").option("--private", "Make the publication private (default)").option("--expires <duration>", "Auto-delete after duration (e.g. 1h, 24h, 7d)").action(
|
|
641
|
+
program.command("create").description("Create a new publication").argument("[file]", "Path to the file (reads stdin if omitted)").option("--slug <slug>", "Custom slug for the URL").option("--title <title>", "Title for the publication").option("--public", "Make the publication public").option("--private", "Make the publication private (default)").option("--expires <duration>", "Auto-delete after duration (e.g. 1h, 24h, 7d)").action(
|
|
595
642
|
async (fileArg, opts) => {
|
|
596
643
|
const client = createClient();
|
|
597
644
|
let content;
|
|
@@ -603,12 +650,17 @@ program.command("create").description("Create a new publication").argument("[fil
|
|
|
603
650
|
} else {
|
|
604
651
|
content = await readFromStdin();
|
|
605
652
|
}
|
|
653
|
+
const resolvedVisibility = resolveVisibilityFlags({
|
|
654
|
+
public: opts.public,
|
|
655
|
+
private: opts.private,
|
|
656
|
+
commandName: "create"
|
|
657
|
+
});
|
|
606
658
|
const result = await client.create({
|
|
607
659
|
content,
|
|
608
660
|
filename,
|
|
609
661
|
title: opts.title,
|
|
610
662
|
slug: opts.slug,
|
|
611
|
-
isPublic: false,
|
|
663
|
+
isPublic: resolvedVisibility ?? false,
|
|
612
664
|
expiresIn: opts.expires
|
|
613
665
|
});
|
|
614
666
|
console.log(`Created: ${result.url}`);
|
|
@@ -633,7 +685,7 @@ program.command("get").description("Get details of a publication").argument("<sl
|
|
|
633
685
|
console.log(` Updated: ${new Date(pub.updatedAt).toLocaleDateString()}`);
|
|
634
686
|
console.log(` Size: ${pub.content.length} bytes`);
|
|
635
687
|
});
|
|
636
|
-
program.command("update").description("Update a publication's content and/or metadata").argument("<slug>", "Slug of the publication to update").option("--file <file>", "New content from file").option("--title <title>", "New title").option("--private", "Make the publication private").option("--slug <newSlug>", "Rename the slug").action(
|
|
688
|
+
program.command("update").description("Update a publication's content and/or metadata").argument("<slug>", "Slug of the publication to update").option("--file <file>", "New content from file").option("--title <title>", "New title").option("--public", "Make the publication public").option("--private", "Make the publication private").option("--slug <newSlug>", "Rename the slug").action(
|
|
637
689
|
async (slug, opts) => {
|
|
638
690
|
const client = createClient();
|
|
639
691
|
let content;
|
|
@@ -643,8 +695,11 @@ program.command("update").description("Update a publication's content and/or met
|
|
|
643
695
|
content = file.content;
|
|
644
696
|
filename = file.basename;
|
|
645
697
|
}
|
|
646
|
-
|
|
647
|
-
|
|
698
|
+
const isPublic = resolveVisibilityFlags({
|
|
699
|
+
public: opts.public,
|
|
700
|
+
private: opts.private,
|
|
701
|
+
commandName: "update"
|
|
702
|
+
});
|
|
648
703
|
const result = await client.update({
|
|
649
704
|
slug,
|
|
650
705
|
content,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getTunnelWriteReadinessError,
|
|
3
|
+
shouldRecoverForBrowserAnswerChange,
|
|
4
|
+
startDaemon
|
|
5
|
+
} from "./chunk-AIEPM67G.js";
|
|
6
|
+
import "./chunk-MW35LBNH.js";
|
|
7
|
+
export {
|
|
8
|
+
getTunnelWriteReadinessError,
|
|
9
|
+
shouldRecoverForBrowserAnswerChange,
|
|
10
|
+
startDaemon
|
|
11
|
+
};
|
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
} from "./chunk-BV423NLA.js";
|
|
4
4
|
import {
|
|
5
5
|
startDaemon
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-AIEPM67G.js";
|
|
7
|
+
import "./chunk-MW35LBNH.js";
|
|
8
8
|
|
|
9
9
|
// src/tunnel-daemon-entry.ts
|
|
10
10
|
var tunnelId = process.env.PUBBLUE_DAEMON_TUNNEL_ID;
|