@lazyneoaz/testfca 1.0.4 → 1.0.6
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/index.js +7 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +38 -35
- package/src/apis/listenMqtt.js +106 -102
- package/src/e2ee/bridge.js +31 -13
- package/src/utils/goCerts.js +68 -0
- package/scripts/build-go.mjs +0 -61
- package/scripts/detect-platform.mjs +0 -36
- package/scripts/download-prebuilt.mjs +0 -108
- package/scripts/package.mjs +0 -6
- package/scripts/postinstall.mjs +0 -95
package/index.js
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
|
|
3
|
+
// Inject Node's Mozilla CA bundle into SSL_CERT_FILE *before* any module loads
|
|
4
|
+
// the Go native binary (meta-messenger.js / messagix.so). Go reads SSL_CERT_FILE
|
|
5
|
+
// lazily on the first TLS dial — setting it here, at library entry, guarantees
|
|
6
|
+
// it is always in place in every hosting environment (Railway, Docker, Render …).
|
|
7
|
+
require('./src/utils/goCerts').ensureGoCerts();
|
|
8
|
+
|
|
2
9
|
module.exports = require('./src/engine/client');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyneoaz/testfca",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"description": "Advanced Facebook Chat API client for building Messenger bots — supports real-time messaging, thread management, MQTT, session stability, anti-automation protection, and real E2EE via Signal Protocol.",
|
|
6
6
|
"main": "index.js",
|
package/scripts/postinstall.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Postinstall:
|
|
4
|
+
* Postinstall: ensure meta-messenger.js has a working native binary.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* are never established and sendE2EEMessage always times out.
|
|
6
|
+
* meta-messenger.js's own postinstall runs first (npm guarantees dependency
|
|
7
|
+
* scripts run before the dependent's postinstall). It places the binary via:
|
|
8
|
+
* 1. A prebuilt shipped in its npm tarball (if present)
|
|
9
|
+
* 2. A download from GitHub Releases
|
|
10
|
+
* 3. A local `go build` (if MESSAGIX_BUILD_FROM_SOURCE=true)
|
|
12
11
|
*
|
|
13
|
-
*
|
|
12
|
+
* This script validates that a binary was placed. If for any reason it was
|
|
13
|
+
* not (e.g. the GitHub release download failed, npm ci --ignore-scripts was
|
|
14
|
+
* used), we copy the prebuilt binary we ship in prebuilt/ as a fallback.
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* at the project root node_modules/ or nested — works in all npm versions.
|
|
16
|
+
* TLS NOTE:
|
|
17
|
+
* TLS certificate errors in containerized environments (Railway, Docker, etc.)
|
|
18
|
+
* are fixed at runtime by src/utils/goCerts.js — not by a patched binary.
|
|
19
|
+
* Go reads SSL_CERT_FILE lazily on the first TLS dial. We set it to Node's
|
|
20
|
+
* built-in Mozilla CA bundle before any connection is attempted.
|
|
21
|
+
* No binary modification is required for this fix.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
const fs = require('fs');
|
|
@@ -35,7 +35,7 @@ function platformKey() {
|
|
|
35
35
|
return null;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
function
|
|
38
|
+
function binaryExt() {
|
|
39
39
|
if (process.platform === 'win32') return '.dll';
|
|
40
40
|
if (process.platform === 'darwin') return '.dylib';
|
|
41
41
|
return '.so';
|
|
@@ -43,7 +43,6 @@ function ext() {
|
|
|
43
43
|
|
|
44
44
|
function findMetaMessengerBuildDir() {
|
|
45
45
|
try {
|
|
46
|
-
// resolve() finds the package regardless of hoisting depth
|
|
47
46
|
const pkgJson = require.resolve('meta-messenger.js/package.json');
|
|
48
47
|
return path.join(path.dirname(pkgJson), 'build');
|
|
49
48
|
} catch (_) {
|
|
@@ -59,39 +58,43 @@ function main() {
|
|
|
59
58
|
|
|
60
59
|
const key = platformKey();
|
|
61
60
|
if (!key) {
|
|
62
|
-
console.log('[nkxfca] postinstall: unsupported platform (' + process.platform + '/' + process.arch + '), skipping
|
|
61
|
+
console.log('[nkxfca] postinstall: unsupported platform (' + process.platform + '/' + process.arch + '), skipping.');
|
|
63
62
|
return;
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
const
|
|
67
|
-
const
|
|
65
|
+
const ext = binaryExt();
|
|
66
|
+
const mmBuildDir = findMetaMessengerBuildDir();
|
|
68
67
|
|
|
69
|
-
if (!
|
|
70
|
-
console.log('[nkxfca] postinstall:
|
|
68
|
+
if (!mmBuildDir) {
|
|
69
|
+
console.log('[nkxfca] postinstall: meta-messenger.js not found — skipping binary check.');
|
|
71
70
|
return;
|
|
72
71
|
}
|
|
73
72
|
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
const mmBin = path.join(mmBuildDir, 'messagix' + ext);
|
|
74
|
+
|
|
75
|
+
// Happy path: meta-messenger.js's own postinstall already placed the binary
|
|
76
|
+
if (fs.existsSync(mmBin) && fs.statSync(mmBin).size > 0) {
|
|
77
|
+
console.log('[nkxfca] postinstall: meta-messenger.js binary present — OK.');
|
|
77
78
|
return;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
|
|
81
|
+
// Fallback: meta-messenger.js postinstall failed (download error, no internet, etc.)
|
|
82
|
+
// Use the prebuilt we ship as insurance.
|
|
83
|
+
const ourBin = path.join(__dirname, '..', 'prebuilt', key, 'messagix' + ext);
|
|
84
|
+
if (!fs.existsSync(ourBin)) {
|
|
85
|
+
console.warn('[nkxfca] postinstall: no prebuilt for ' + key + ' and meta-messenger.js binary is missing.');
|
|
86
|
+
console.warn('[nkxfca] E2EE will not work. Try: MESSAGIX_BUILD_FROM_SOURCE=true npm install');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
81
89
|
|
|
82
|
-
// Ensure the build directory exists (meta-messenger.js postinstall creates it,
|
|
83
|
-
// but guard against edge cases where it hasn't run yet)
|
|
84
90
|
try {
|
|
85
91
|
fs.mkdirSync(mmBuildDir, { recursive: true });
|
|
86
|
-
} catch (_) {}
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
92
|
fs.copyFileSync(ourBin, mmBin);
|
|
90
|
-
|
|
93
|
+
fs.chmodSync(mmBin, 0o755);
|
|
94
|
+
console.log('[nkxfca] postinstall: installed fallback prebuilt binary for ' + key);
|
|
91
95
|
} catch (err) {
|
|
92
|
-
|
|
93
|
-
console.warn('[nkxfca]
|
|
94
|
-
console.warn('[nkxfca] E2EE messaging may not work in sandboxed environments.');
|
|
96
|
+
console.warn('[nkxfca] postinstall: could not copy fallback binary: ' + err.message);
|
|
97
|
+
console.warn('[nkxfca] E2EE may not work. Try reinstalling meta-messenger.js manually.');
|
|
95
98
|
}
|
|
96
99
|
}
|
|
97
100
|
|
package/src/apis/listenMqtt.js
CHANGED
|
@@ -592,130 +592,134 @@ async function listenMqtt(defaultFuncs, api, ctx, globalCallback, scheduleReconn
|
|
|
592
592
|
}
|
|
593
593
|
while (ctx._e2eePollingActive) {
|
|
594
594
|
try {
|
|
595
|
-
// Yield to event loop before blocking poll
|
|
595
|
+
// Yield to event loop before blocking poll
|
|
596
596
|
await new Promise(resolve => setImmediate(resolve));
|
|
597
597
|
if (!ctx._e2eePollingActive) break;
|
|
598
598
|
|
|
599
|
-
//
|
|
600
|
-
const
|
|
599
|
+
// pollEvents returns { events: [...] } — iterate the array
|
|
600
|
+
const result = await api.e2ee.pollEvents(1000);
|
|
601
601
|
if (!ctx._e2eePollingActive) break;
|
|
602
602
|
|
|
603
|
-
// No
|
|
604
|
-
if (!
|
|
603
|
+
// No events returned (timeout or empty)
|
|
604
|
+
if (!result || !Array.isArray(result.events) || result.events.length === 0) continue;
|
|
605
605
|
|
|
606
|
-
const
|
|
606
|
+
for (const ev of result.events) {
|
|
607
|
+
if (!ctx._e2eePollingActive) break;
|
|
607
608
|
|
|
608
|
-
|
|
609
|
-
if (evType === 'timeout') continue;
|
|
609
|
+
const evType = (ev.type || '').toLowerCase();
|
|
610
610
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
utils.warn("E2EE", "Bridge closed. Stopping DM poll loop.");
|
|
614
|
-
ctx._e2eePollingActive = false;
|
|
615
|
-
break;
|
|
616
|
-
}
|
|
611
|
+
// ── timeout: no event arrived during the poll window ──
|
|
612
|
+
if (evType === 'timeout') continue;
|
|
617
613
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
utils.warn("E2EE", "Bridge error event:", msg);
|
|
622
|
-
if (ev.data && ev.data.code === 1) {
|
|
614
|
+
// ── closed: bridge shut down ──
|
|
615
|
+
if (evType === 'closed') {
|
|
616
|
+
utils.warn("E2EE", "Bridge closed. Stopping DM poll loop.");
|
|
623
617
|
ctx._e2eePollingActive = false;
|
|
624
618
|
break;
|
|
625
619
|
}
|
|
626
|
-
continue;
|
|
627
|
-
}
|
|
628
620
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
621
|
+
// ── permanent error (e.g. session invalid) ──
|
|
622
|
+
if (evType === 'error') {
|
|
623
|
+
const msg = ev.data && ev.data.message ? ev.data.message : JSON.stringify(ev.data);
|
|
624
|
+
utils.warn("E2EE", "Bridge error event:", msg);
|
|
625
|
+
if (ev.data && ev.data.code === 1) {
|
|
626
|
+
ctx._e2eePollingActive = false;
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
634
631
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
if (!d) continue;
|
|
639
|
-
// threadId may be 0 for pure-JID threads — fall back to chatJid
|
|
640
|
-
const chatJid = d.chatJid || '';
|
|
641
|
-
const senderJid = d.senderJid || '';
|
|
642
|
-
const threadID = String(d.threadId || chatJid.replace(/@.*/, '') || '');
|
|
643
|
-
const senderID = String(d.senderId || senderJid.replace(/@.*/, '') || '');
|
|
644
|
-
if (!threadID || !senderID) {
|
|
645
|
-
utils.log("E2EE", "e2eeMessage missing IDs — raw:", JSON.stringify(d).slice(0, 300));
|
|
632
|
+
// ── E2EE connection confirmed by the bridge ──
|
|
633
|
+
if (evType === 'e2eeconnected') {
|
|
634
|
+
utils.log("E2EE", "E2EE fully connected (bridge event).");
|
|
646
635
|
continue;
|
|
647
636
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
threadID
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
637
|
+
|
|
638
|
+
// ── Incoming E2EE DM ─────────────────────────────────
|
|
639
|
+
if (evType === 'e2eemessage') {
|
|
640
|
+
const d = ev.data;
|
|
641
|
+
if (!d) continue;
|
|
642
|
+
// threadId may be 0 for pure-JID threads — fall back to chatJid
|
|
643
|
+
const chatJid = d.chatJid || '';
|
|
644
|
+
const senderJid = d.senderJid || '';
|
|
645
|
+
const threadID = String(d.threadId || chatJid.replace(/@.*/, '') || '');
|
|
646
|
+
const senderID = String(d.senderId || senderJid.replace(/@.*/, '') || '');
|
|
647
|
+
if (!threadID || !senderID) {
|
|
648
|
+
utils.log("E2EE", "e2eeMessage missing IDs — raw:", JSON.stringify(d).slice(0, 300));
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
if (senderID === ctx.userID.toString() && !ctx.globalOptions.selfListen) continue;
|
|
652
|
+
|
|
653
|
+
utils.log("E2EE", "Incoming E2EE DM from", senderID, "→ thread", threadID, "body:", JSON.stringify(d.text || '').slice(0, 80));
|
|
654
|
+
|
|
655
|
+
const fmtMsg = {
|
|
656
|
+
type: 'message',
|
|
657
|
+
senderID: utils.formatID(senderID),
|
|
658
|
+
body: d.text || '',
|
|
659
|
+
threadID: utils.formatID(threadID),
|
|
660
|
+
messageID: d.id || '',
|
|
661
|
+
isGroup: false,
|
|
662
|
+
attachments: [],
|
|
663
|
+
mentions: {},
|
|
664
|
+
timestamp: Number(d.timestampMs || Date.now()),
|
|
665
|
+
isUnread: true,
|
|
666
|
+
};
|
|
667
|
+
if (ctx.globalOptions.autoMarkDelivery) {
|
|
668
|
+
try { api.markAsDelivered(fmtMsg.threadID, fmtMsg.messageID); } catch (_) {}
|
|
669
|
+
}
|
|
670
|
+
if (ctx.globalOptions.autoMarkRead) {
|
|
671
|
+
try { api.markAsRead(fmtMsg.threadID); } catch (_) {}
|
|
672
|
+
}
|
|
673
|
+
globalCallback(null, fmtMsg);
|
|
674
|
+
continue;
|
|
666
675
|
}
|
|
667
|
-
|
|
668
|
-
|
|
676
|
+
|
|
677
|
+
// ── Incoming regular (non-E2EE) message via bridge ───
|
|
678
|
+
if (evType === 'message') {
|
|
679
|
+
const d = ev.data;
|
|
680
|
+
if (!d) continue;
|
|
681
|
+
const threadID = String(d.threadId || '');
|
|
682
|
+
const senderID = String(d.senderId || '');
|
|
683
|
+
if (!threadID || !senderID) continue;
|
|
684
|
+
if (senderID === ctx.userID.toString() && !ctx.globalOptions.selfListen) continue;
|
|
685
|
+
globalCallback(null, {
|
|
686
|
+
type: 'message',
|
|
687
|
+
senderID: utils.formatID(senderID),
|
|
688
|
+
body: d.text || '',
|
|
689
|
+
threadID: utils.formatID(threadID),
|
|
690
|
+
messageID: d.id || '',
|
|
691
|
+
isGroup: !!(d.isGroup),
|
|
692
|
+
attachments: [],
|
|
693
|
+
mentions: {},
|
|
694
|
+
timestamp: Number(d.timestampMs || Date.now()),
|
|
695
|
+
isUnread: true,
|
|
696
|
+
});
|
|
697
|
+
continue;
|
|
669
698
|
}
|
|
670
|
-
globalCallback(null, fmtMsg);
|
|
671
|
-
continue;
|
|
672
|
-
}
|
|
673
699
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
});
|
|
694
|
-
continue;
|
|
695
|
-
}
|
|
700
|
+
// ── E2EE reaction ────────────────────────────────────
|
|
701
|
+
if (evType === 'e2eereaction') {
|
|
702
|
+
if (!ctx.globalOptions.listenEvents) continue;
|
|
703
|
+
const d = ev.data;
|
|
704
|
+
if (!d) continue;
|
|
705
|
+
const chatJid = d.chatJid || '';
|
|
706
|
+
const senderJid = d.senderJid || '';
|
|
707
|
+
globalCallback(null, {
|
|
708
|
+
type: 'message_reaction',
|
|
709
|
+
threadID: utils.formatID(chatJid.replace(/@.*/, '')),
|
|
710
|
+
messageID: d.messageId || '',
|
|
711
|
+
reaction: d.reaction || '',
|
|
712
|
+
senderID: utils.formatID(senderJid.replace(/@.*/, '')),
|
|
713
|
+
offlineThreadingId: null,
|
|
714
|
+
timestamp: Date.now(),
|
|
715
|
+
isGroup: false,
|
|
716
|
+
});
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
696
719
|
|
|
697
|
-
|
|
698
|
-
if (evType === 'e2eereaction') {
|
|
699
|
-
if (!ctx.globalOptions.listenEvents) continue;
|
|
700
|
-
const d = ev.data;
|
|
701
|
-
if (!d) continue;
|
|
702
|
-
const chatJid = d.chatJid || '';
|
|
703
|
-
const senderJid = d.senderJid || '';
|
|
704
|
-
globalCallback(null, {
|
|
705
|
-
type: 'message_reaction',
|
|
706
|
-
threadID: utils.formatID(chatJid.replace(/@.*/, '')),
|
|
707
|
-
messageID: d.messageId || '',
|
|
708
|
-
reaction: d.reaction || '',
|
|
709
|
-
senderID: utils.formatID(senderJid.replace(/@.*/, '')),
|
|
710
|
-
offlineThreadingId: null,
|
|
711
|
-
timestamp: Date.now(),
|
|
712
|
-
isGroup: false,
|
|
713
|
-
});
|
|
714
|
-
continue;
|
|
720
|
+
// ready / reconnected / disconnected / deviceDataChanged / raw → ignore silently
|
|
715
721
|
}
|
|
716
722
|
|
|
717
|
-
// ready / reconnected / disconnected / deviceDataChanged / raw → ignore silently
|
|
718
|
-
|
|
719
723
|
} catch (pollErr) {
|
|
720
724
|
if (!ctx._e2eePollingActive) break;
|
|
721
725
|
utils.warn("E2EE", "pollEvents error:", pollErr && pollErr.message ? pollErr.message : pollErr);
|
package/src/e2ee/bridge.js
CHANGED
|
@@ -3,16 +3,34 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Lightweight E2EE bridge — delegates to the `meta-messenger.js` npm package.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* meta-messenger.js ships a native Go shared library (messagix.so) compiled
|
|
7
|
+
* from bridge-go/ Go source. The library uses Go's standard TLS stack
|
|
8
|
+
* (x509.SystemCertPool) which requires OS CA certificates to verify Facebook's
|
|
9
|
+
* TLS chain. Many hosting environments (Railway, Docker Alpine, Render, etc.)
|
|
10
|
+
* ship no CA bundle, causing every outbound TLS connection to fail with:
|
|
11
|
+
* "x509: certificate signed by unknown authority"
|
|
12
|
+
*
|
|
13
|
+
* THE FIX — SSL_CERT_FILE (see src/utils/goCerts.js):
|
|
14
|
+
* We set SSL_CERT_FILE to a temp file containing Node's built-in Mozilla CA
|
|
15
|
+
* bundle BEFORE the Go binary is ever loaded. ensureGoCerts() is called from
|
|
16
|
+
* index.js (library entry point) so it fires on the very first require().
|
|
17
|
+
* It is also called here at module evaluation time as a second guarantee.
|
|
8
18
|
*
|
|
9
19
|
* Key design decisions:
|
|
10
20
|
* - enableE2EE: false → we call connectE2EE() explicitly; no auto-start race
|
|
11
|
-
* - e2eeMemoryOnly: false → Signal sessions
|
|
21
|
+
* - e2eeMemoryOnly: false → Signal sessions persisted to devicePath on disk
|
|
12
22
|
* - devicePath defaults to ".nkxfca_e2ee_device.json" in cwd when not provided
|
|
13
23
|
*/
|
|
14
24
|
|
|
15
|
-
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
|
|
28
|
+
// Ensure Go TLS certs are ready — second guarantee (index.js is the primary)
|
|
29
|
+
require('../utils/goCerts').ensureGoCerts();
|
|
30
|
+
|
|
31
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
let _Client = null;
|
|
16
34
|
let _loadError = null;
|
|
17
35
|
|
|
18
36
|
function loadClient() {
|
|
@@ -49,9 +67,9 @@ function newClient(cfg) {
|
|
|
49
67
|
const cookies = cfg.cookies || {};
|
|
50
68
|
|
|
51
69
|
const opts = {
|
|
52
|
-
platform:
|
|
53
|
-
logLevel:
|
|
54
|
-
enableE2EE:
|
|
70
|
+
platform: cfg.platform || 'facebook',
|
|
71
|
+
logLevel: cfg.logLevel || 'warn',
|
|
72
|
+
enableE2EE: false, // we call connectE2EE() explicitly
|
|
55
73
|
e2eeMemoryOnly: false, // always persist sessions to disk
|
|
56
74
|
autoReconnect: cfg.autoReconnect !== false,
|
|
57
75
|
};
|
|
@@ -65,7 +83,7 @@ function newClient(cfg) {
|
|
|
65
83
|
|
|
66
84
|
const client = new Client(cookies, opts);
|
|
67
85
|
const handle = String(seq++);
|
|
68
|
-
const entry
|
|
86
|
+
const entry = { client, eventBuffer: [], resolveWaiter: null, lastDeviceData: null };
|
|
69
87
|
|
|
70
88
|
for (const type of EVENT_TYPES) {
|
|
71
89
|
client.on(type, (data) => {
|
|
@@ -154,7 +172,7 @@ function toBigInt(val) {
|
|
|
154
172
|
return BigInt(String(val).split('@')[0]);
|
|
155
173
|
}
|
|
156
174
|
|
|
157
|
-
// ── E2EE operations
|
|
175
|
+
// ── E2EE operations ───────────────────────────────────────────────────────────
|
|
158
176
|
|
|
159
177
|
async function sendE2EEMessage(handle, chatJid, text, replyToId, replyToSenderJid) {
|
|
160
178
|
const opts = replyToId ? { replyToId, replyToSenderJid } : undefined;
|
|
@@ -218,7 +236,7 @@ async function uploadMedia(handle, options) {
|
|
|
218
236
|
async function sendImage(handle, options) {
|
|
219
237
|
const { chatJid, data, filename, caption, replyToId } = options;
|
|
220
238
|
const opts = {};
|
|
221
|
-
if (caption)
|
|
239
|
+
if (caption) opts.caption = caption;
|
|
222
240
|
if (replyToId) opts.replyToId = replyToId;
|
|
223
241
|
return getEntry(handle).client.sendImage(
|
|
224
242
|
toBigInt(chatJid), toBuffer(data), filename || 'image.jpg', Object.keys(opts).length ? opts : undefined
|
|
@@ -228,7 +246,7 @@ async function sendImage(handle, options) {
|
|
|
228
246
|
async function sendVideo(handle, options) {
|
|
229
247
|
const { chatJid, data, filename, caption, replyToId } = options;
|
|
230
248
|
const opts = {};
|
|
231
|
-
if (caption)
|
|
249
|
+
if (caption) opts.caption = caption;
|
|
232
250
|
if (replyToId) opts.replyToId = replyToId;
|
|
233
251
|
return getEntry(handle).client.sendVideo(
|
|
234
252
|
toBigInt(chatJid), toBuffer(data), filename || 'video.mp4', Object.keys(opts).length ? opts : undefined
|
|
@@ -246,7 +264,7 @@ async function sendVoice(handle, options) {
|
|
|
246
264
|
async function sendFile(handle, options) {
|
|
247
265
|
const { chatJid, data, filename, mimeType, caption, replyToId } = options;
|
|
248
266
|
const opts = {};
|
|
249
|
-
if (caption)
|
|
267
|
+
if (caption) opts.caption = caption;
|
|
250
268
|
if (replyToId) opts.replyToId = replyToId;
|
|
251
269
|
return getEntry(handle).client.sendFile(
|
|
252
270
|
toBigInt(chatJid), toBuffer(data), filename || 'file', mimeType || 'application/octet-stream',
|
|
@@ -287,7 +305,7 @@ function unload() {
|
|
|
287
305
|
try { entry.client.disconnect(); } catch (_) {}
|
|
288
306
|
}
|
|
289
307
|
registry.clear();
|
|
290
|
-
_Client
|
|
308
|
+
_Client = null;
|
|
291
309
|
_loadError = null;
|
|
292
310
|
}
|
|
293
311
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ensureGoCerts — inject Node's built-in Mozilla CA bundle into the process
|
|
5
|
+
* environment so that the Go binary inside meta-messenger.js can verify TLS
|
|
6
|
+
* certificates in any hosting environment.
|
|
7
|
+
*
|
|
8
|
+
* WHY this is needed:
|
|
9
|
+
* The Go binary (messagix.so) uses Go's standard TLS stack, which calls
|
|
10
|
+
* x509.SystemCertPool() to verify server certificates. In many hosting
|
|
11
|
+
* environments — Railway, Render, Docker Alpine, Heroku, etc. — the OS
|
|
12
|
+
* ships no CA certificate bundle (no /etc/ssl/certs), so SystemCertPool()
|
|
13
|
+
* returns an empty pool. Every outbound TLS connection the binary makes
|
|
14
|
+
* (Facebook's E2EE WebSocket, prekey HTTPS requests) then fails with:
|
|
15
|
+
* "x509: certificate signed by unknown authority"
|
|
16
|
+
*
|
|
17
|
+
* THE FIX:
|
|
18
|
+
* Go reads the SSL_CERT_FILE environment variable when calling
|
|
19
|
+
* x509.SystemCertPool(). If we set it to a valid PEM file before the first
|
|
20
|
+
* TLS connection, Go finds a full CA bundle. Node.js ships Mozilla's CA
|
|
21
|
+
* bundle as tls.rootCertificates — we write that to a temp file and point
|
|
22
|
+
* SSL_CERT_FILE at it. DigiCert (Facebook), Let's Encrypt, and all major
|
|
23
|
+
* CAs are included.
|
|
24
|
+
*
|
|
25
|
+
* TIMING:
|
|
26
|
+
* Must run before any TLS dial in the Go binary. We call it from index.js
|
|
27
|
+
* (library entry point) so it fires the moment anyone requires nkxfca,
|
|
28
|
+
* long before the Go binary is loaded or any connection is attempted.
|
|
29
|
+
*
|
|
30
|
+
* IDEMPOTENT:
|
|
31
|
+
* Safe to call multiple times — exits immediately on subsequent calls or
|
|
32
|
+
* when the host already provides SSL_CERT_FILE / SSL_CERT_DIR.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
const fs = require('fs');
|
|
36
|
+
const path = require('path');
|
|
37
|
+
const os = require('os');
|
|
38
|
+
|
|
39
|
+
let _done = false;
|
|
40
|
+
|
|
41
|
+
function ensureGoCerts() {
|
|
42
|
+
if (_done) return;
|
|
43
|
+
|
|
44
|
+
// Respect host-provided cert configuration — don't override it
|
|
45
|
+
if (process.env.SSL_CERT_FILE || process.env.SSL_CERT_DIR) {
|
|
46
|
+
_done = true;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const { rootCertificates } = require('tls');
|
|
52
|
+
if (!rootCertificates || !rootCertificates.length) return;
|
|
53
|
+
|
|
54
|
+
const certFile = path.join(os.tmpdir(), '.nkxfca-cacert.pem');
|
|
55
|
+
|
|
56
|
+
// Write the bundle once per container/OS session (not per process start)
|
|
57
|
+
if (!fs.existsSync(certFile) || fs.statSync(certFile).size === 0) {
|
|
58
|
+
fs.writeFileSync(certFile, rootCertificates.join('\n'), 'utf8');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
process.env.SSL_CERT_FILE = certFile;
|
|
62
|
+
_done = true;
|
|
63
|
+
} catch (_) {
|
|
64
|
+
// Never crash — worst case, Go falls back to the system cert store
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { ensureGoCerts };
|
package/scripts/build-go.mjs
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
import { detectPlatform } from "./detect-platform.mjs";
|
|
7
|
-
import { packageJson } from "./package.mjs";
|
|
8
|
-
|
|
9
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
-
const { ext } = detectPlatform();
|
|
11
|
-
const { name } = packageJson;
|
|
12
|
-
const bridgeDir = join(__dirname, "..", "bridge-e2ee", "bridge-go");
|
|
13
|
-
const vendorDir = join(bridgeDir, "vendor");
|
|
14
|
-
const hasVendor = existsSync(vendorDir);
|
|
15
|
-
|
|
16
|
-
function runGo(args) {
|
|
17
|
-
const res = spawnSync(process.env.GO_BIN || "go", args, {
|
|
18
|
-
cwd: bridgeDir,
|
|
19
|
-
stdio: "inherit",
|
|
20
|
-
env: {
|
|
21
|
-
...process.env,
|
|
22
|
-
CGO_ENABLED: "1",
|
|
23
|
-
// Prevent Go from downloading a newer toolchain when go.mod specifies
|
|
24
|
-
// a version higher than what is locally installed. The local compiler
|
|
25
|
-
// should always be used in sandboxed / offline environments.
|
|
26
|
-
GOTOOLCHAIN: "local",
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
if (res.error) {
|
|
30
|
-
console.error(`[${name}] Failed to spawn Go: ${res.error.message}`);
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
if (res.status !== 0) process.exit(res.status || 1);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const buildDir = join(__dirname, "..", "build");
|
|
37
|
-
if (!existsSync(buildDir)) mkdirSync(buildDir, { recursive: true });
|
|
38
|
-
|
|
39
|
-
// Skip mod tidy when vendor directory is present — tidy can break vendor/go.mod consistency
|
|
40
|
-
// and would overwrite the patched prekeys.go with the unpatched upstream version.
|
|
41
|
-
if (!hasVendor) {
|
|
42
|
-
console.log(`[${name}] Tidying Go modules...`);
|
|
43
|
-
runGo(["mod", "tidy"]);
|
|
44
|
-
} else {
|
|
45
|
-
console.log(`[${name}] Vendor directory present — skipping mod tidy to preserve patches.`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const buildArgs = [
|
|
49
|
-
"build",
|
|
50
|
-
...(hasVendor ? ["-mod=vendor"] : []),
|
|
51
|
-
"-buildmode=c-shared",
|
|
52
|
-
"-ldflags=-s -w",
|
|
53
|
-
"-o",
|
|
54
|
-
join("..", "..", "build", `messagix.${ext}`),
|
|
55
|
-
".",
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
console.log(`[${name}] Building native library (release mode)...`);
|
|
59
|
-
runGo(buildArgs);
|
|
60
|
-
|
|
61
|
-
console.log(`[${name}] Built native: build/messagix.${ext}`);
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { execSync } from "node:child_process";
|
|
2
|
-
|
|
3
|
-
export function detectPlatform() {
|
|
4
|
-
const { platform } = process;
|
|
5
|
-
const { arch } = process;
|
|
6
|
-
const isMusl = detectMusl();
|
|
7
|
-
|
|
8
|
-
const libc = platform === "linux" ? (isMusl ? "musl" : "gnu") : "";
|
|
9
|
-
const triplet = platform === "linux" ? `${platform}-${arch}-${libc}` : `${platform}-${arch}`;
|
|
10
|
-
|
|
11
|
-
const ext = platform === "win32" ? "dll" : platform === "darwin" ? "dylib" : "so";
|
|
12
|
-
|
|
13
|
-
return { platform, arch, libc, triplet, ext };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function detectMusl() {
|
|
17
|
-
try {
|
|
18
|
-
if (process.platform !== "linux") return false;
|
|
19
|
-
if (process.report && typeof process.report.getReport === "function") {
|
|
20
|
-
const rep = process.report.getReport();
|
|
21
|
-
const glibc = rep.header && rep.header.glibcVersionRuntime;
|
|
22
|
-
return !glibc;
|
|
23
|
-
}
|
|
24
|
-
} catch {
|
|
25
|
-
//
|
|
26
|
-
}
|
|
27
|
-
try {
|
|
28
|
-
const out = execSync("ldd --version 2>&1 || true", { encoding: "utf8" });
|
|
29
|
-
return /musl/i.test(out);
|
|
30
|
-
} catch {
|
|
31
|
-
//
|
|
32
|
-
}
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export default detectPlatform;
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { createWriteStream } from "node:fs";
|
|
2
|
-
import { mkdir, rename, unlink } from "node:fs/promises";
|
|
3
|
-
import https from "node:https";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
|
|
7
|
-
import { detectPlatform } from "./detect-platform.mjs";
|
|
8
|
-
import { packageJson as pkg } from "./package.mjs";
|
|
9
|
-
|
|
10
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
|
|
12
|
-
function buildBaseURL() {
|
|
13
|
-
// yumi-team/meta-messenger.js hosts prebuilt messagix binaries for all
|
|
14
|
-
// platforms except linux-x64-gnu, which ships directly in this package's
|
|
15
|
-
// prebuilt/ directory and is therefore never fetched from here.
|
|
16
|
-
const repo = "yumi-team/meta-messenger.js";
|
|
17
|
-
const tag = "v1.1.3";
|
|
18
|
-
return `https://github.com/${repo}/releases/download/${tag}`;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function httpGet(url, redirectCount = 0) {
|
|
22
|
-
console.log(`[${pkg.name}] HTTP GET: ${url}${redirectCount > 0 ? ` (redirect #${redirectCount})` : ""}`);
|
|
23
|
-
const reqStart = Date.now();
|
|
24
|
-
return new Promise((resolve, reject) => {
|
|
25
|
-
https
|
|
26
|
-
.get(url, res => {
|
|
27
|
-
console.log(`[${pkg.name}] Response: HTTP ${res.statusCode} in ${Date.now() - reqStart}ms`);
|
|
28
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
29
|
-
console.log(`[${pkg.name}] Following redirect to: ${res.headers.location}`);
|
|
30
|
-
return resolve(httpGet(res.headers.location, redirectCount + 1));
|
|
31
|
-
}
|
|
32
|
-
if (res.statusCode !== 200) {
|
|
33
|
-
return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
34
|
-
}
|
|
35
|
-
resolve(res);
|
|
36
|
-
})
|
|
37
|
-
.on("error", err => {
|
|
38
|
-
console.log(`[${pkg.name}] HTTP error after ${Date.now() - reqStart}ms:`, err?.message);
|
|
39
|
-
reject(err);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function downloadTo(url, dstPath) {
|
|
45
|
-
console.log(`[${pkg.name}] Downloading to: ${dstPath}`);
|
|
46
|
-
await mkdir(dirname(dstPath), { recursive: true });
|
|
47
|
-
const tmp = `${dstPath}.download`;
|
|
48
|
-
try {
|
|
49
|
-
await unlink(tmp);
|
|
50
|
-
} catch {
|
|
51
|
-
//
|
|
52
|
-
}
|
|
53
|
-
const res = await httpGet(url);
|
|
54
|
-
console.log(`[${pkg.name}] Starting file write...`);
|
|
55
|
-
const writeStart = Date.now();
|
|
56
|
-
let bytesWritten = 0;
|
|
57
|
-
await new Promise((resolve, reject) => {
|
|
58
|
-
const out = createWriteStream(tmp);
|
|
59
|
-
res.on("data", chunk => {
|
|
60
|
-
bytesWritten += chunk.length;
|
|
61
|
-
});
|
|
62
|
-
res.pipe(out);
|
|
63
|
-
res.on("error", reject);
|
|
64
|
-
out.on("error", reject);
|
|
65
|
-
out.on("finish", () => {
|
|
66
|
-
console.log(`[${pkg.name}] File write completed: ${bytesWritten} bytes in ${Date.now() - writeStart}ms`);
|
|
67
|
-
res.destroy();
|
|
68
|
-
resolve();
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
await rename(tmp, dstPath);
|
|
72
|
-
console.log(`[${pkg.name}] File renamed to final destination`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export async function downloadPrebuilt() {
|
|
76
|
-
console.log(`[${pkg.name}] downloadPrebuilt() started`);
|
|
77
|
-
const { triplet, ext } = detectPlatform();
|
|
78
|
-
const baseURL = buildBaseURL();
|
|
79
|
-
const filename = `messagix-${triplet}.${ext}`;
|
|
80
|
-
const url = `${baseURL}/${filename}`;
|
|
81
|
-
console.log(`[${pkg.name}] Target URL: ${url}`);
|
|
82
|
-
|
|
83
|
-
const out = join(__dirname, "..", "build", `messagix.${ext}`);
|
|
84
|
-
const totalStart = Date.now();
|
|
85
|
-
try {
|
|
86
|
-
await downloadTo(url, out);
|
|
87
|
-
console.log(`[${pkg.name}] Downloaded prebuilt from ${url} in ${Date.now() - totalStart}ms`);
|
|
88
|
-
return true;
|
|
89
|
-
} catch (err) {
|
|
90
|
-
console.warn(
|
|
91
|
-
`[${pkg.name}] No remote prebuilt found at ${url} (after ${Date.now() - totalStart}ms):`,
|
|
92
|
-
err?.message || String(err),
|
|
93
|
-
);
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const isMain = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
|
|
99
|
-
if (isMain) {
|
|
100
|
-
downloadPrebuilt()
|
|
101
|
-
.then(ok => {
|
|
102
|
-
if (!ok) process.exit(1);
|
|
103
|
-
})
|
|
104
|
-
.catch(err => {
|
|
105
|
-
console.error(`[${pkg.name}] download-prebuilt failed:`, err?.message || String(err));
|
|
106
|
-
process.exit(1);
|
|
107
|
-
});
|
|
108
|
-
}
|
package/scripts/package.mjs
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
|
|
5
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
export const packageJson = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
package/scripts/postinstall.mjs
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { copyFile, mkdir } from "node:fs/promises";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
import { detectPlatform } from "./detect-platform.mjs";
|
|
7
|
-
import { downloadPrebuilt } from "./download-prebuilt.mjs";
|
|
8
|
-
import { packageJson as pkg } from "./package.mjs";
|
|
9
|
-
|
|
10
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
|
|
12
|
-
async function copyIfExists(src, dst) {
|
|
13
|
-
try {
|
|
14
|
-
await mkdir(dirname(dst), { recursive: true });
|
|
15
|
-
await copyFile(src, dst);
|
|
16
|
-
return true;
|
|
17
|
-
} catch (err) {
|
|
18
|
-
if (err?.code === "ENOENT") return false;
|
|
19
|
-
throw err;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function run() {
|
|
24
|
-
console.log(`[${pkg.name}] postinstall started`);
|
|
25
|
-
const startTime = Date.now();
|
|
26
|
-
|
|
27
|
-
if (process.env.MESSAGIX_SKIP_POSTINSTALL === "true") {
|
|
28
|
-
console.log(`[${pkg.name}] Skipping postinstall (MESSAGIX_SKIP_POSTINSTALL=true)`);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
console.log(`[${pkg.name}] Detecting platform...`);
|
|
33
|
-
const { triplet, ext } = detectPlatform();
|
|
34
|
-
console.log(`[${pkg.name}] Platform: ${triplet}, ext: ${ext}`);
|
|
35
|
-
|
|
36
|
-
const buildOut = join(__dirname, "..", "build", `messagix.${ext}`);
|
|
37
|
-
console.log(`[${pkg.name}] Build output path: ${buildOut}`);
|
|
38
|
-
|
|
39
|
-
if (existsSync(buildOut)) {
|
|
40
|
-
console.log(`[${pkg.name}] Native library already present at build/messagix.${ext}`);
|
|
41
|
-
console.log(`[${pkg.name}] postinstall completed in ${Date.now() - startTime}ms`);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// 1) Prefer local prebuilt shipped in npm tarball
|
|
46
|
-
const prebuiltDir = join(__dirname, "..", "prebuilt", triplet);
|
|
47
|
-
const prebuilt = join(prebuiltDir, `messagix.${ext}`);
|
|
48
|
-
console.log(`[${pkg.name}] Checking local prebuilt at: ${prebuilt}`);
|
|
49
|
-
if (await copyIfExists(prebuilt, buildOut)) {
|
|
50
|
-
console.log(`[${pkg.name}] Using local prebuilt for ${triplet}`);
|
|
51
|
-
// Keep the prebuilt copy so reinstalls (e.g. npm rebuild, CI caches) don't
|
|
52
|
-
// lose the patched binary and fall back to downloading an unpatched one.
|
|
53
|
-
console.log(`[${pkg.name}] postinstall completed in ${Date.now() - startTime}ms`);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
console.log(`[${pkg.name}] No local prebuilt found`);
|
|
57
|
-
|
|
58
|
-
// 2) Try remote prebuilt from GitHub Releases
|
|
59
|
-
console.log(`[${pkg.name}] Attempting to download remote prebuilt...`);
|
|
60
|
-
const downloadStart = Date.now();
|
|
61
|
-
try {
|
|
62
|
-
if (await downloadPrebuilt()) {
|
|
63
|
-
console.log(`[${pkg.name}] Download completed in ${Date.now() - downloadStart}ms`);
|
|
64
|
-
console.log(`[${pkg.name}] postinstall completed in ${Date.now() - startTime}ms`);
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
} catch (err) {
|
|
68
|
-
console.log(
|
|
69
|
-
`[${pkg.name}] Download failed after ${Date.now() - downloadStart}ms:`,
|
|
70
|
-
err?.message || String(err),
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
console.warn(`[${pkg.name}] ──────────────────────────────────────────────────────────`);
|
|
75
|
-
console.warn(`[${pkg.name}] No prebuilt E2EE bridge found for your platform.`);
|
|
76
|
-
console.warn(`[${pkg.name}] Platform detected: ${triplet}`);
|
|
77
|
-
console.warn(`[${pkg.name}]`);
|
|
78
|
-
console.warn(`[${pkg.name}] Supported out-of-the-box:`);
|
|
79
|
-
console.warn(`[${pkg.name}] linux-x64-gnu (ships inside the npm package)`);
|
|
80
|
-
console.warn(`[${pkg.name}] linux-x64-musl / darwin-x64 / darwin-arm64 / win32-x64`);
|
|
81
|
-
console.warn(`[${pkg.name}] (downloaded automatically from yumi-team/meta-messenger.js)`);
|
|
82
|
-
console.warn(`[${pkg.name}]`);
|
|
83
|
-
console.warn(`[${pkg.name}] If the download failed, check your internet connection and retry:`);
|
|
84
|
-
console.warn(`[${pkg.name}] npm install`);
|
|
85
|
-
console.warn(`[${pkg.name}]`);
|
|
86
|
-
console.warn(`[${pkg.name}] Non-E2EE features (sendMessage, listen, etc.) work without the bridge.`);
|
|
87
|
-
console.warn(`[${pkg.name}] ──────────────────────────────────────────────────────────`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
run()
|
|
91
|
-
.then(() => process.exit(0))
|
|
92
|
-
.catch(err => {
|
|
93
|
-
console.error(`[${pkg.name}] postinstall failed:`, err?.message || String(err));
|
|
94
|
-
process.exit(1);
|
|
95
|
-
});
|