@lazyneoaz/testfca 1.0.5 → 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/e2ee/bridge.js +25 -81
- 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/e2ee/bridge.js
CHANGED
|
@@ -3,95 +3,39 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Lightweight E2EE bridge — delegates to the `meta-messenger.js` npm package.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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.
|
|
11
18
|
*
|
|
12
19
|
* Key design decisions:
|
|
13
20
|
* - enableE2EE: false → we call connectE2EE() explicitly; no auto-start race
|
|
14
|
-
* - e2eeMemoryOnly: false → Signal sessions
|
|
21
|
+
* - e2eeMemoryOnly: false → Signal sessions persisted to devicePath on disk
|
|
15
22
|
* - devicePath defaults to ".nkxfca_e2ee_device.json" in cwd when not provided
|
|
16
23
|
*/
|
|
17
24
|
|
|
18
25
|
const fs = require('fs');
|
|
19
26
|
const path = require('path');
|
|
20
27
|
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
// Runs once; safe to call multiple times.
|
|
24
|
-
|
|
25
|
-
let _patchDone = false;
|
|
26
|
-
|
|
27
|
-
function _platformKey() {
|
|
28
|
-
const p = process.platform;
|
|
29
|
-
const a = process.arch;
|
|
30
|
-
if (p === 'linux' && a === 'x64') return 'linux-x64-gnu';
|
|
31
|
-
if (p === 'linux' && a === 'arm64') return 'linux-arm64-gnu';
|
|
32
|
-
if (p === 'darwin' && a === 'x64') return 'darwin-x64';
|
|
33
|
-
if (p === 'darwin' && a === 'arm64') return 'darwin-arm64';
|
|
34
|
-
if (p === 'win32' && a === 'x64') return 'win32-x64-msvc';
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function _binaryExt() {
|
|
39
|
-
if (process.platform === 'win32') return '.dll';
|
|
40
|
-
if (process.platform === 'darwin') return '.dylib';
|
|
41
|
-
return '.so';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function ensureTlsFixedBinary() {
|
|
45
|
-
if (_patchDone) return;
|
|
46
|
-
_patchDone = true;
|
|
47
|
-
|
|
48
|
-
if (process.env.NKXFCA_SKIP_POSTINSTALL === 'true') return;
|
|
49
|
-
|
|
50
|
-
const key = _platformKey();
|
|
51
|
-
if (!key) return;
|
|
52
|
-
|
|
53
|
-
const ext = _binaryExt();
|
|
54
|
-
// bridge.js lives at src/e2ee/bridge.js → prebuilt/ is two levels up
|
|
55
|
-
const ourBin = path.join(__dirname, '..', '..', 'prebuilt', key, 'messagix' + ext);
|
|
56
|
-
if (!fs.existsSync(ourBin)) return;
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
const pkgJson = require.resolve('meta-messenger.js/package.json');
|
|
60
|
-
const buildDir = path.join(path.dirname(pkgJson), 'build');
|
|
61
|
-
const mmBin = path.join(buildDir, 'messagix' + ext);
|
|
62
|
-
|
|
63
|
-
fs.mkdirSync(buildDir, { recursive: true });
|
|
64
|
-
|
|
65
|
-
// Only copy if sizes differ (avoids unnecessary writes on every boot)
|
|
66
|
-
let needsCopy = true;
|
|
67
|
-
if (fs.existsSync(mmBin)) {
|
|
68
|
-
needsCopy = fs.statSync(ourBin).size !== fs.statSync(mmBin).size;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (needsCopy) {
|
|
72
|
-
fs.copyFileSync(ourBin, mmBin);
|
|
73
|
-
fs.chmodSync(mmBin, 0o755);
|
|
74
|
-
console.log('[nkxfca] runtime patch: applied TLS-fixed binary (' + key + ')');
|
|
75
|
-
}
|
|
76
|
-
} catch (err) {
|
|
77
|
-
// Non-fatal — warn so the user knows why E2EE may not work
|
|
78
|
-
console.warn('[nkxfca] runtime patch failed (' + err.message + '). E2EE TLS errors may occur in sandboxed environments.');
|
|
79
|
-
}
|
|
80
|
-
}
|
|
28
|
+
// Ensure Go TLS certs are ready — second guarantee (index.js is the primary)
|
|
29
|
+
require('../utils/goCerts').ensureGoCerts();
|
|
81
30
|
|
|
82
31
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
83
32
|
|
|
84
|
-
let _Client
|
|
33
|
+
let _Client = null;
|
|
85
34
|
let _loadError = null;
|
|
86
35
|
|
|
87
36
|
function loadClient() {
|
|
88
37
|
if (_Client) return _Client;
|
|
89
38
|
if (_loadError) throw _loadError;
|
|
90
|
-
|
|
91
|
-
// Patch binary BEFORE the first require('meta-messenger.js') so the
|
|
92
|
-
// TLS-fixed .so/.dylib/.dll is loaded by Node rather than the original.
|
|
93
|
-
ensureTlsFixedBinary();
|
|
94
|
-
|
|
95
39
|
try {
|
|
96
40
|
const mm = require('meta-messenger.js');
|
|
97
41
|
_Client = mm.Client || mm.default?.Client;
|
|
@@ -123,9 +67,9 @@ function newClient(cfg) {
|
|
|
123
67
|
const cookies = cfg.cookies || {};
|
|
124
68
|
|
|
125
69
|
const opts = {
|
|
126
|
-
platform:
|
|
127
|
-
logLevel:
|
|
128
|
-
enableE2EE:
|
|
70
|
+
platform: cfg.platform || 'facebook',
|
|
71
|
+
logLevel: cfg.logLevel || 'warn',
|
|
72
|
+
enableE2EE: false, // we call connectE2EE() explicitly
|
|
129
73
|
e2eeMemoryOnly: false, // always persist sessions to disk
|
|
130
74
|
autoReconnect: cfg.autoReconnect !== false,
|
|
131
75
|
};
|
|
@@ -139,7 +83,7 @@ function newClient(cfg) {
|
|
|
139
83
|
|
|
140
84
|
const client = new Client(cookies, opts);
|
|
141
85
|
const handle = String(seq++);
|
|
142
|
-
const entry
|
|
86
|
+
const entry = { client, eventBuffer: [], resolveWaiter: null, lastDeviceData: null };
|
|
143
87
|
|
|
144
88
|
for (const type of EVENT_TYPES) {
|
|
145
89
|
client.on(type, (data) => {
|
|
@@ -228,7 +172,7 @@ function toBigInt(val) {
|
|
|
228
172
|
return BigInt(String(val).split('@')[0]);
|
|
229
173
|
}
|
|
230
174
|
|
|
231
|
-
// ── E2EE operations
|
|
175
|
+
// ── E2EE operations ───────────────────────────────────────────────────────────
|
|
232
176
|
|
|
233
177
|
async function sendE2EEMessage(handle, chatJid, text, replyToId, replyToSenderJid) {
|
|
234
178
|
const opts = replyToId ? { replyToId, replyToSenderJid } : undefined;
|
|
@@ -292,7 +236,7 @@ async function uploadMedia(handle, options) {
|
|
|
292
236
|
async function sendImage(handle, options) {
|
|
293
237
|
const { chatJid, data, filename, caption, replyToId } = options;
|
|
294
238
|
const opts = {};
|
|
295
|
-
if (caption)
|
|
239
|
+
if (caption) opts.caption = caption;
|
|
296
240
|
if (replyToId) opts.replyToId = replyToId;
|
|
297
241
|
return getEntry(handle).client.sendImage(
|
|
298
242
|
toBigInt(chatJid), toBuffer(data), filename || 'image.jpg', Object.keys(opts).length ? opts : undefined
|
|
@@ -302,7 +246,7 @@ async function sendImage(handle, options) {
|
|
|
302
246
|
async function sendVideo(handle, options) {
|
|
303
247
|
const { chatJid, data, filename, caption, replyToId } = options;
|
|
304
248
|
const opts = {};
|
|
305
|
-
if (caption)
|
|
249
|
+
if (caption) opts.caption = caption;
|
|
306
250
|
if (replyToId) opts.replyToId = replyToId;
|
|
307
251
|
return getEntry(handle).client.sendVideo(
|
|
308
252
|
toBigInt(chatJid), toBuffer(data), filename || 'video.mp4', Object.keys(opts).length ? opts : undefined
|
|
@@ -320,7 +264,7 @@ async function sendVoice(handle, options) {
|
|
|
320
264
|
async function sendFile(handle, options) {
|
|
321
265
|
const { chatJid, data, filename, mimeType, caption, replyToId } = options;
|
|
322
266
|
const opts = {};
|
|
323
|
-
if (caption)
|
|
267
|
+
if (caption) opts.caption = caption;
|
|
324
268
|
if (replyToId) opts.replyToId = replyToId;
|
|
325
269
|
return getEntry(handle).client.sendFile(
|
|
326
270
|
toBigInt(chatJid), toBuffer(data), filename || 'file', mimeType || 'application/octet-stream',
|
|
@@ -361,7 +305,7 @@ function unload() {
|
|
|
361
305
|
try { entry.client.disconnect(); } catch (_) {}
|
|
362
306
|
}
|
|
363
307
|
registry.clear();
|
|
364
|
-
_Client
|
|
308
|
+
_Client = null;
|
|
365
309
|
_loadError = null;
|
|
366
310
|
}
|
|
367
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
|
-
});
|