neoagent 2.3.1-beta.52 → 2.3.1-beta.54
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/flutter_app/android/app/src/main/res/values/arrays.xml +8 -0
- package/package.json +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/routes/auth.js +6 -1
- package/server/services/messaging/meshtastic.js +11 -3
- package/server/services/messaging/meshtastic_protocol.js +25 -2
- package/server/services/messaging/meshtastic_protocol.test.js +33 -0
- package/server/services/messaging/meshtastic_tcp_transport.js +2 -2
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<resources>
|
|
3
|
+
<!-- Hosts allowed to connect to the Android Auto app. Update as needed. -->
|
|
4
|
+
<string-array name="hosts_allowlist">
|
|
5
|
+
<item>androidx.car.app</item>
|
|
6
|
+
<item>com.google.android.projection.gearhead</item>
|
|
7
|
+
</string-array>
|
|
8
|
+
</resources>
|
package/package.json
CHANGED
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"42d3d75a56efe1a2e9902f52dc8006099c45d9
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "3319853538" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|
package/server/routes/auth.js
CHANGED
|
@@ -330,7 +330,12 @@ router.get('/api/auth/providers/complete', async (req, res) => {
|
|
|
330
330
|
if (!user) {
|
|
331
331
|
return res.status(404).json({ error: 'User not found' });
|
|
332
332
|
}
|
|
333
|
-
|
|
333
|
+
// Skip secondary two-factor when signing in via certain social providers.
|
|
334
|
+
// Currently skip for Google (`provider === 'google`).
|
|
335
|
+
const providerKey = String(completion.provider || '').trim();
|
|
336
|
+
const twoFactor = getTwoFactorStatus(user.id);
|
|
337
|
+
const skipTwoFactorForProviders = new Set(['google']);
|
|
338
|
+
if (twoFactor.enabled && !skipTwoFactorForProviders.has(providerKey)) {
|
|
334
339
|
return establishPendingTwoFactorSession(req, res, user);
|
|
335
340
|
}
|
|
336
341
|
updateLastLogin(user.id);
|
|
@@ -76,6 +76,7 @@ class MeshtasticPlatform extends BasePlatform {
|
|
|
76
76
|
this._connectPromise = null;
|
|
77
77
|
this._disconnecting = false;
|
|
78
78
|
this._reconnectTimer = null;
|
|
79
|
+
this._reconnectDelayMs = 10000;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
async connect() {
|
|
@@ -109,7 +110,11 @@ class MeshtasticPlatform extends BasePlatform {
|
|
|
109
110
|
await staleTransport.disconnect().catch(() => {});
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
const transport = await MeshtasticTcpTransport.create(this.host, this.port, 60000
|
|
113
|
+
const transport = await MeshtasticTcpTransport.create(this.host, this.port, 60000, {
|
|
114
|
+
// This service only needs the local node info plus live packets. Pulling
|
|
115
|
+
// the full node DB on each reconnect adds avoidable load to Wi-Fi radios.
|
|
116
|
+
noNodes: true,
|
|
117
|
+
});
|
|
113
118
|
this._transport = transport;
|
|
114
119
|
|
|
115
120
|
const conn = transport.connection;
|
|
@@ -189,6 +194,7 @@ class MeshtasticPlatform extends BasePlatform {
|
|
|
189
194
|
});
|
|
190
195
|
|
|
191
196
|
this.status = 'connected';
|
|
197
|
+
this._reconnectDelayMs = 10000;
|
|
192
198
|
this.authInfo = this._buildAuthInfo(conn);
|
|
193
199
|
this.emit('connected');
|
|
194
200
|
return { status: this.status };
|
|
@@ -196,7 +202,8 @@ class MeshtasticPlatform extends BasePlatform {
|
|
|
196
202
|
|
|
197
203
|
_scheduleReconnect() {
|
|
198
204
|
if (this._disconnecting || this._reconnectTimer) return;
|
|
199
|
-
|
|
205
|
+
const delayMs = this._reconnectDelayMs;
|
|
206
|
+
console.log(`[Meshtastic] Connection lost. Reconnecting in ${Math.round(delayMs / 1000)} seconds...`);
|
|
200
207
|
this._reconnectTimer = setTimeout(() => {
|
|
201
208
|
this._reconnectTimer = null;
|
|
202
209
|
if (this._disconnecting) return;
|
|
@@ -204,7 +211,8 @@ class MeshtasticPlatform extends BasePlatform {
|
|
|
204
211
|
console.error(`[Meshtastic] Auto-reconnect failed:`, err.message);
|
|
205
212
|
this._scheduleReconnect();
|
|
206
213
|
});
|
|
207
|
-
},
|
|
214
|
+
}, delayMs);
|
|
215
|
+
this._reconnectDelayMs = Math.min(delayMs * 2, 60000);
|
|
208
216
|
}
|
|
209
217
|
|
|
210
218
|
_buildAuthInfo(conn) {
|
|
@@ -8,6 +8,7 @@ const FRAME_START_1 = 0x94;
|
|
|
8
8
|
const FRAME_START_2 = 0xC3;
|
|
9
9
|
|
|
10
10
|
const BROADCAST_NUM = 0xFFFFFFFF;
|
|
11
|
+
const NODELESS_WANT_CONFIG_ID = 69420;
|
|
11
12
|
|
|
12
13
|
// PortNum values from public protocol specification
|
|
13
14
|
const PortNum = Object.freeze({
|
|
@@ -174,6 +175,21 @@ function encodeToRadioWantConfig(configId) {
|
|
|
174
175
|
return new Uint8Array(encodeVarintField(3, configId));
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
function resolveWantConfigId(options = {}) {
|
|
179
|
+
if (Number.isInteger(options.wantConfigId) && options.wantConfigId >= 0) {
|
|
180
|
+
return options.wantConfigId >>> 0;
|
|
181
|
+
}
|
|
182
|
+
if (options.noNodes) {
|
|
183
|
+
return NODELESS_WANT_CONFIG_ID;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let configId = (Math.random() * 0xFFFFFFFF) >>> 0;
|
|
187
|
+
if (configId === NODELESS_WANT_CONFIG_ID) {
|
|
188
|
+
configId = (configId + 1) >>> 0;
|
|
189
|
+
}
|
|
190
|
+
return configId;
|
|
191
|
+
}
|
|
192
|
+
|
|
177
193
|
function decodeUser(buf) {
|
|
178
194
|
const fields = decodeFields(buf);
|
|
179
195
|
return {
|
|
@@ -288,10 +304,10 @@ function createFrameParser(onPacket) {
|
|
|
288
304
|
// --------------------------------------------------------------------------
|
|
289
305
|
|
|
290
306
|
class MeshtasticConnection extends EventEmitter {
|
|
291
|
-
constructor() {
|
|
307
|
+
constructor(options = {}) {
|
|
292
308
|
super();
|
|
293
309
|
this._socket = null;
|
|
294
|
-
this._configId = (
|
|
310
|
+
this._configId = resolveWantConfigId(options);
|
|
295
311
|
this._myNodeNum = 0;
|
|
296
312
|
this._configured = false;
|
|
297
313
|
this._closing = false;
|
|
@@ -316,6 +332,9 @@ class MeshtasticConnection extends EventEmitter {
|
|
|
316
332
|
let settled = false;
|
|
317
333
|
let timer = null;
|
|
318
334
|
|
|
335
|
+
socket.setKeepAlive(true, 15000);
|
|
336
|
+
socket.setNoDelay(true);
|
|
337
|
+
|
|
319
338
|
const cleanupHandshake = () => {
|
|
320
339
|
if (timer) {
|
|
321
340
|
clearTimeout(timer);
|
|
@@ -487,6 +506,8 @@ class MeshtasticConnection extends EventEmitter {
|
|
|
487
506
|
this._socket = null;
|
|
488
507
|
if (socket) {
|
|
489
508
|
socket.removeAllListeners();
|
|
509
|
+
socket.end();
|
|
510
|
+
socket.destroySoon?.();
|
|
490
511
|
socket.destroy();
|
|
491
512
|
}
|
|
492
513
|
}
|
|
@@ -496,12 +517,14 @@ module.exports = {
|
|
|
496
517
|
MeshtasticConnection,
|
|
497
518
|
PortNum,
|
|
498
519
|
BROADCAST_NUM,
|
|
520
|
+
NODELESS_WANT_CONFIG_ID,
|
|
499
521
|
encodeVarint,
|
|
500
522
|
decodeVarint,
|
|
501
523
|
decodeFields,
|
|
502
524
|
decodeFromRadio,
|
|
503
525
|
decodeMeshPacket,
|
|
504
526
|
decodeUser,
|
|
527
|
+
encodeToRadioWantConfig,
|
|
505
528
|
framePacket,
|
|
506
529
|
createFrameParser,
|
|
507
530
|
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const test = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
MeshtasticConnection,
|
|
6
|
+
NODELESS_WANT_CONFIG_ID,
|
|
7
|
+
decodeFields,
|
|
8
|
+
encodeToRadioWantConfig,
|
|
9
|
+
} = require('./meshtastic_protocol');
|
|
10
|
+
|
|
11
|
+
test('nodeless startup uses Meshtastic special want_config_id', () => {
|
|
12
|
+
const connection = new MeshtasticConnection({ noNodes: true });
|
|
13
|
+
assert.equal(connection._configId, NODELESS_WANT_CONFIG_ID);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('default startup never collides with nodeless want_config_id', () => {
|
|
17
|
+
const originalRandom = Math.random;
|
|
18
|
+
Math.random = () => NODELESS_WANT_CONFIG_ID / 0xFFFFFFFF;
|
|
19
|
+
try {
|
|
20
|
+
const connection = new MeshtasticConnection();
|
|
21
|
+
assert.notEqual(connection._configId, NODELESS_WANT_CONFIG_ID);
|
|
22
|
+
} finally {
|
|
23
|
+
Math.random = originalRandom;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('want_config packets encode the requested config id in field 3', () => {
|
|
28
|
+
const packet = Buffer.from(encodeToRadioWantConfig(NODELESS_WANT_CONFIG_ID));
|
|
29
|
+
const fields = decodeFields(packet);
|
|
30
|
+
assert.deepEqual(fields, [
|
|
31
|
+
{ field: 3, wire: 0, value: NODELESS_WANT_CONFIG_ID },
|
|
32
|
+
]);
|
|
33
|
+
});
|
|
@@ -7,8 +7,8 @@ class MeshtasticTcpTransport {
|
|
|
7
7
|
this._connection = connection;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
static async create(hostname, port = 4403, timeout = 60000) {
|
|
11
|
-
const connection = new MeshtasticConnection();
|
|
10
|
+
static async create(hostname, port = 4403, timeout = 60000, options = {}) {
|
|
11
|
+
const connection = new MeshtasticConnection(options);
|
|
12
12
|
await connection.connect(hostname, port, timeout);
|
|
13
13
|
return new MeshtasticTcpTransport(connection);
|
|
14
14
|
}
|