@decentnetwork/beagle 0.1.80 → 0.1.82
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/carrier-node.d.ts +3 -1
- package/dist/cli.js +6 -0
- package/dist/desktop/app.js +2 -2
- package/dist/embedded-host.js +7 -4
- package/dist/identity-lock.d.ts +4 -0
- package/dist/identity-lock.js +33 -1
- package/dist/message-store.d.ts +12 -1
- package/dist/message-store.js +13 -1
- package/package.json +3 -3
package/dist/carrier-node.d.ts
CHANGED
|
@@ -90,7 +90,9 @@ export declare class CarrierNode extends EventEmitter {
|
|
|
90
90
|
isFriendOnline(userid: string): boolean;
|
|
91
91
|
sessionStatus(pubkey: string): unknown;
|
|
92
92
|
dhtHealth(): unknown;
|
|
93
|
-
sendText(userid: string, text: string): Promise<
|
|
93
|
+
sendText(userid: string, text: string): Promise<{
|
|
94
|
+
delivery: "acked" | "accepted" | "offline";
|
|
95
|
+
} | void>;
|
|
94
96
|
sendFriendRequest(address: string, hello?: string, opts?: {
|
|
95
97
|
force?: boolean;
|
|
96
98
|
}): Promise<void>;
|
package/dist/cli.js
CHANGED
|
@@ -12,6 +12,7 @@ import { startBeagleServer } from "./server.js";
|
|
|
12
12
|
import { openPeerHost, defaultConfigDir, decentlanCarrierDir, makeDaemonHost } from "./peer-host.js";
|
|
13
13
|
import { SwitchablePeerHost } from "./lan-handoff.js";
|
|
14
14
|
import { loadNodeConfig, saveAutoAccept } from "./node-config.js";
|
|
15
|
+
import { setUiPortHint } from "./identity-lock.js";
|
|
15
16
|
function parseArgs(argv) {
|
|
16
17
|
const get = (flag) => {
|
|
17
18
|
const i = argv.indexOf(flag);
|
|
@@ -134,6 +135,11 @@ async function main() {
|
|
|
134
135
|
onAutoAcceptChange: (enabled) => saveAutoAccept(args.configDir, enabled),
|
|
135
136
|
force: "embedded",
|
|
136
137
|
})).host;
|
|
138
|
+
// Publish the port before anything takes the identity lock, so the lock file
|
|
139
|
+
// and the way to reach us are written together. `agentnet restart` reads it:
|
|
140
|
+
// on a machine where beagle holds the identity there is no daemon for it to
|
|
141
|
+
// talk to, and without this the documented upgrade one-liner just failed.
|
|
142
|
+
setUiPortHint(args.port, decentlanCarrierDir(args.configDir));
|
|
137
143
|
const lanHost = new SwitchablePeerHost(peerHost, {
|
|
138
144
|
dataDir: decentlanCarrierDir(args.configDir),
|
|
139
145
|
makeEmbedded: openEmbedded,
|
package/dist/desktop/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__DK_UI_VERSION="0.1.
|
|
1
|
+
window.__DK_UI_VERSION="0.1.81";
|
|
2
2
|
"use strict";
|
|
3
3
|
(() => {
|
|
4
4
|
// packages/beagle-ui/dist/beagle-ui.js
|
|
@@ -1576,7 +1576,7 @@ window.__DK_UI_VERSION="0.1.79";
|
|
|
1576
1576
|
},
|
|
1577
1577
|
/* @__PURE__ */ React.createElement(Icon, { name: "clock", size: 11, stroke: 2.2, color: "var(--faint)" }),
|
|
1578
1578
|
/* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10, color: "var(--faint)" } }, m.status === "sending" ? T.sendingMsg || "sending" : T.queued || "queued")
|
|
1579
|
-
) : mine && m.status && /* @__PURE__ */ React.createElement(Icon, { name: "checkCheck", size: 12, stroke: 2.2, color: m.status === "read" ? "var(--accent)" : "var(--faint)" })))
|
|
1579
|
+
) : mine && m.status === "sent" && m.confirmed === false ? /* @__PURE__ */ React.createElement(Icon, { name: "check", size: 12, stroke: 2.2, color: "var(--faint)", title: T.sentUnconfirmed || "sent \u2014 not confirmed by the peer" }) : mine && m.status && /* @__PURE__ */ React.createElement(Icon, { name: "checkCheck", size: 12, stroke: 2.2, color: m.status === "read" ? "var(--accent)" : "var(--faint)" })))
|
|
1580
1580
|
);
|
|
1581
1581
|
}
|
|
1582
1582
|
function Conversation({ T, peer, lang, peers, onOpenChat, thread: threadProp, onSend, onSendFile, onSendRtcFile, onAlias, onRemove, onOpenNet, onCall, onReloadThread }) {
|
package/dist/embedded-host.js
CHANGED
|
@@ -326,8 +326,8 @@ export class EmbeddedHost {
|
|
|
326
326
|
// "queued" a terminal state for text: the clock icon never resolved.
|
|
327
327
|
if (!msg.file) {
|
|
328
328
|
try {
|
|
329
|
-
await this.#node.sendText(pubkey, msg.text);
|
|
330
|
-
this.#messages.setStatus(pubkey, msg.id, "sent");
|
|
329
|
+
const r = await this.#node.sendText(pubkey, msg.text);
|
|
330
|
+
this.#messages.setStatus(pubkey, msg.id, "sent", !r || r.delivery === "acked");
|
|
331
331
|
this.#events.emit("event", { type: "chat", userid: pubkey, dir: "out" });
|
|
332
332
|
}
|
|
333
333
|
catch (e) {
|
|
@@ -583,8 +583,11 @@ export class EmbeddedHost {
|
|
|
583
583
|
const job = prev.then(async () => {
|
|
584
584
|
await this.#flushOutbox(uid).catch(() => { });
|
|
585
585
|
try {
|
|
586
|
-
|
|
587
|
-
|
|
586
|
+
// "acked" is a confirmation from the far side; anything else is
|
|
587
|
+
// the transport taking the bytes. Still "sent" — resending on an
|
|
588
|
+
// unconfirmed message is how a phone ends up with two copies.
|
|
589
|
+
const r = await this.#node.sendText(uid, text);
|
|
590
|
+
this.#messages.setStatus(uid, msg.id, "sent", !r || r.delivery === "acked");
|
|
588
591
|
}
|
|
589
592
|
catch (e) {
|
|
590
593
|
// Offline peer or failed delivery: same recovery either way —
|
package/dist/identity-lock.d.ts
CHANGED
|
@@ -15,4 +15,8 @@ export declare class IdentityLockedError extends Error {
|
|
|
15
15
|
* A stale file (holder gone) is taken over silently: that is a crash we already
|
|
16
16
|
* survived, not a conflict.
|
|
17
17
|
*/
|
|
18
|
+
/** Where a holder publishes the port its local HTTP API is on. */
|
|
19
|
+
export declare function uiPortHintPath(dataDir: string): string;
|
|
20
|
+
/** Called by the server once it knows which port it actually bound. */
|
|
21
|
+
export declare function setUiPortHint(port: number, dataDir?: string): void;
|
|
18
22
|
export declare function acquireIdentityLock(dataDir: string): () => void;
|
package/dist/identity-lock.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// exclusion protocol whose other participant we cannot modify (decentlan is
|
|
14
14
|
// sealed) and must not fork.
|
|
15
15
|
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
16
|
-
import { resolve } from "node:path";
|
|
16
|
+
import { resolve, join } from "node:path";
|
|
17
17
|
/** Same path decentlan uses: `<carrier dataDir>/daemon.pid`. */
|
|
18
18
|
export function identityLockPath(dataDir) {
|
|
19
19
|
return resolve(dataDir, "daemon.pid");
|
|
@@ -55,6 +55,27 @@ export class IdentityLockedError extends Error {
|
|
|
55
55
|
* A stale file (holder gone) is taken over silently: that is a crash we already
|
|
56
56
|
* survived, not a conflict.
|
|
57
57
|
*/
|
|
58
|
+
/** Where a holder publishes the port its local HTTP API is on. */
|
|
59
|
+
export function uiPortHintPath(dataDir) {
|
|
60
|
+
return join(dataDir, "holder.http-port");
|
|
61
|
+
}
|
|
62
|
+
let uiPortForHint;
|
|
63
|
+
/** Called by the server once it knows which port it actually bound. */
|
|
64
|
+
export function setUiPortHint(port, dataDir) {
|
|
65
|
+
uiPortForHint = port;
|
|
66
|
+
if (dataDir)
|
|
67
|
+
writeUiPortHint(dataDir);
|
|
68
|
+
}
|
|
69
|
+
function writeUiPortHint(dataDir) {
|
|
70
|
+
if (!uiPortForHint)
|
|
71
|
+
return;
|
|
72
|
+
try {
|
|
73
|
+
writeFileSync(uiPortHintPath(dataDir), String(uiPortForHint), "utf-8");
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
/* best effort — the lock itself is what matters */
|
|
77
|
+
}
|
|
78
|
+
}
|
|
58
79
|
export function acquireIdentityLock(dataDir) {
|
|
59
80
|
const holder = identityLockHolder(dataDir);
|
|
60
81
|
if (holder !== null && holder !== process.pid)
|
|
@@ -65,11 +86,22 @@ export function acquireIdentityLock(dataDir) {
|
|
|
65
86
|
mkdirSync(dataDir, { recursive: true });
|
|
66
87
|
const file = identityLockPath(dataDir);
|
|
67
88
|
writeFileSync(file, String(process.pid), "utf-8");
|
|
89
|
+
// Also leave a way to REACH us. The documented upgrade is
|
|
90
|
+
// `npm i -g @decentnetwork/lan@latest && agentnet restart`, and on a machine
|
|
91
|
+
// where beagle holds the identity there is no daemon for that second half to
|
|
92
|
+
// talk to — it failed, and the person was left holding a new package and a
|
|
93
|
+
// process still running the old one. `agentnet restart` can drive our own
|
|
94
|
+
// /api/update-restart instead, but only if it can find the port.
|
|
95
|
+
writeUiPortHint(dataDir);
|
|
68
96
|
let released = false;
|
|
69
97
|
return () => {
|
|
70
98
|
if (released)
|
|
71
99
|
return;
|
|
72
100
|
released = true;
|
|
101
|
+
try {
|
|
102
|
+
unlinkSync(uiPortHintPath(dataDir));
|
|
103
|
+
}
|
|
104
|
+
catch { /* may not exist */ }
|
|
73
105
|
// Only remove it if it is still OURS. A daemon may have taken over after we
|
|
74
106
|
// released the peer during the LAN handoff, and deleting its lock would
|
|
75
107
|
// silently re-open the door this whole module exists to keep shut.
|
package/dist/message-store.d.ts
CHANGED
|
@@ -20,6 +20,9 @@ export interface ChatMessage {
|
|
|
20
20
|
* daemon will deliver it (in order) the moment the friend reconnects.
|
|
21
21
|
* Cleared once actually sent. */
|
|
22
22
|
status?: "sending" | "queued" | "sent" | "failed";
|
|
23
|
+
/** True when the far side confirmed receipt; false when "sent" is a guess.
|
|
24
|
+
* Absent on old records and on inbound messages. */
|
|
25
|
+
confirmed?: boolean;
|
|
23
26
|
/** How this message traversed the network, for the UI to distinguish at a
|
|
24
27
|
* glance (different colors). "online" = live net_crypto session (direct/relay);
|
|
25
28
|
* "offline" = express store-and-forward (the friend or we were offline). Lets
|
|
@@ -58,7 +61,15 @@ export declare class MessageStore {
|
|
|
58
61
|
/** Set (or, with undefined, clear) the delivery status on a text message.
|
|
59
62
|
* Used to flip a "queued" message to delivered once it's actually sent.
|
|
60
63
|
* No-op if the id isn't found or is a file entry. Returns true if patched. */
|
|
61
|
-
|
|
64
|
+
/**
|
|
65
|
+
* `confirmed` distinguishes a tick that is EVIDENCE from a tick that is a
|
|
66
|
+
* guess. sendText reports "acked" only when the far side answered — an
|
|
67
|
+
* app-level ACK from a JS peer, toxcore's own ACK from a phone. Anything
|
|
68
|
+
* else went to a live session and was never confirmed. The status stays
|
|
69
|
+
* "sent" either way, because the message is not queued and must not be
|
|
70
|
+
* resent; the flag is what the UI reads to stop over-claiming.
|
|
71
|
+
*/
|
|
72
|
+
setStatus(peer: string, id: string, status?: "sending" | "queued" | "sent" | "failed", confirmed?: boolean): boolean;
|
|
62
73
|
/** Has this peer ever sent us anything? Used as proof-of-friendship for
|
|
63
74
|
* records that predate the SDK's acceptedAt bookkeeping — a message can
|
|
64
75
|
* only arrive over an established session. */
|
package/dist/message-store.js
CHANGED
|
@@ -62,11 +62,23 @@ export class MessageStore {
|
|
|
62
62
|
/** Set (or, with undefined, clear) the delivery status on a text message.
|
|
63
63
|
* Used to flip a "queued" message to delivered once it's actually sent.
|
|
64
64
|
* No-op if the id isn't found or is a file entry. Returns true if patched. */
|
|
65
|
-
|
|
65
|
+
/**
|
|
66
|
+
* `confirmed` distinguishes a tick that is EVIDENCE from a tick that is a
|
|
67
|
+
* guess. sendText reports "acked" only when the far side answered — an
|
|
68
|
+
* app-level ACK from a JS peer, toxcore's own ACK from a phone. Anything
|
|
69
|
+
* else went to a live session and was never confirmed. The status stays
|
|
70
|
+
* "sent" either way, because the message is not queued and must not be
|
|
71
|
+
* resent; the flag is what the UI reads to stop over-claiming.
|
|
72
|
+
*/
|
|
73
|
+
setStatus(peer, id, status, confirmed) {
|
|
66
74
|
const arr = this.byPeer.get(peer);
|
|
67
75
|
const msg = arr?.find((m) => m.id === id);
|
|
68
76
|
if (!msg || msg.file)
|
|
69
77
|
return false;
|
|
78
|
+
if (confirmed === undefined)
|
|
79
|
+
delete msg.confirmed;
|
|
80
|
+
else
|
|
81
|
+
msg.confirmed = confirmed;
|
|
70
82
|
if (status)
|
|
71
83
|
msg.status = status;
|
|
72
84
|
else
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/beagle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.82",
|
|
4
4
|
"description": "Beagle — P2P chat, file transfer and calls for regular users, on the Decent Network. No admin privilege required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@decentnetwork/chat-components": "^0.1.3",
|
|
29
29
|
"@decentnetwork/lan": "^0.1.283",
|
|
30
|
-
"@decentnetwork/peer": "^0.1.
|
|
30
|
+
"@decentnetwork/peer": "^0.1.163",
|
|
31
31
|
"@decentnetwork/peer-webrtc": "^0.2.16",
|
|
32
32
|
"js-yaml": "^4.1.0",
|
|
33
33
|
"ws": "^8.21.1",
|
|
34
34
|
"yargs": "^17.7.2"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@decentnetwork/beagle-ui": "^0.2.
|
|
37
|
+
"@decentnetwork/beagle-ui": "^0.2.4",
|
|
38
38
|
"@types/js-yaml": "^4.0.9",
|
|
39
39
|
"@types/node": "^20.11.0",
|
|
40
40
|
"@types/yargs": "^17.0.32",
|