dsh-relay-phone 0.18.2 → 0.18.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/package.json +1 -1
- package/src/boot-source.js +18 -6
- package/src/pump.js +13 -5
- package/src/relay-client.js +5 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-relay-phone",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.4",
|
|
4
4
|
"description": "Remote-control your local DeepSeek Harness from a phone through your own zero-knowledge relay: phone-first page, QR pairing, end-to-end encryption, approvals, full desktop UI fallback.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/boot-source.js
CHANGED
|
@@ -472,32 +472,44 @@ export function buildBootSource(id, gate) {
|
|
|
472
472
|
try { userHandlers[i](ev) } catch (e) { console.error(e) }
|
|
473
473
|
}
|
|
474
474
|
}
|
|
475
|
-
|
|
475
|
+
// Binary is a real message kind here: the client sets binaryType="arraybuffer"
|
|
476
|
+
// and the account avatar arrives that way. Decoding those bytes as text both
|
|
477
|
+
// corrupts them and makes the host reject the frame, which is what killed the
|
|
478
|
+
// mux when the account panel opened.
|
|
479
|
+
function deliverSealed(b64, isBinary) {
|
|
476
480
|
var bin = atob(b64)
|
|
477
481
|
var bytes = new Uint8Array(bin.length)
|
|
478
482
|
for (var i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i)
|
|
479
483
|
var plain = openEnvelope(bytes)
|
|
480
484
|
if (!plain) return
|
|
485
|
+
if (isBinary) {
|
|
486
|
+
var copy = new Uint8Array(plain.length)
|
|
487
|
+
copy.set(plain)
|
|
488
|
+
return deliverRaw(copy.buffer)
|
|
489
|
+
}
|
|
481
490
|
deliverRaw(new TextDecoder().decode(plain))
|
|
482
491
|
}
|
|
483
492
|
target.addEventListener('message', function (ev) {
|
|
484
493
|
if (typeof ev.data !== 'string') return deliverRaw(ev.data)
|
|
485
494
|
try {
|
|
486
495
|
var parsed = JSON.parse(ev.data)
|
|
487
|
-
if (parsed && typeof parsed.__relay === 'string')
|
|
496
|
+
if (parsed && typeof parsed.__relay === 'string') {
|
|
497
|
+
return deliverSealed(parsed.__relay, parsed.__bin === 1)
|
|
498
|
+
}
|
|
488
499
|
} catch (e) {}
|
|
489
500
|
deliverRaw(ev.data)
|
|
490
501
|
})
|
|
491
502
|
|
|
492
503
|
var origSend = target.send.bind(target)
|
|
493
504
|
target.send = function (data) {
|
|
494
|
-
var
|
|
495
|
-
|
|
496
|
-
: new Uint8Array(data)
|
|
505
|
+
var isBinary = typeof data !== 'string'
|
|
506
|
+
var bytes = isBinary ? new Uint8Array(data) : new TextEncoder().encode(data)
|
|
497
507
|
var sealed = sealEnvelope(bytes)
|
|
498
508
|
var bin = ''
|
|
499
509
|
for (var i = 0; i < sealed.length; i++) bin += String.fromCharCode(sealed[i])
|
|
500
|
-
|
|
510
|
+
var wrapped = { __relay: btoa(bin) }
|
|
511
|
+
if (isBinary) wrapped.__bin = 1
|
|
512
|
+
origSend(JSON.stringify(wrapped))
|
|
501
513
|
}
|
|
502
514
|
Object.defineProperty(target, 'onmessage', {
|
|
503
515
|
get: function () { return userListener },
|
package/src/pump.js
CHANGED
|
@@ -1100,10 +1100,12 @@ export class PumpManager {
|
|
|
1100
1100
|
let out
|
|
1101
1101
|
if (isDataFrame(frame.opcode)) {
|
|
1102
1102
|
let plain = null
|
|
1103
|
+
let binary = false
|
|
1103
1104
|
try {
|
|
1104
1105
|
const json = JSON.parse(frame.payload.toString('utf8'))
|
|
1105
1106
|
if (json && typeof json.__relay === 'string') {
|
|
1106
1107
|
plain = this.cryptoBox.open(this.cryptoBox.envelopeKey, Buffer.from(json.__relay, 'base64'))
|
|
1108
|
+
binary = json.__bin === 1
|
|
1107
1109
|
}
|
|
1108
1110
|
} catch {}
|
|
1109
1111
|
if (!plain) {
|
|
@@ -1117,10 +1119,10 @@ export class PumpManager {
|
|
|
1117
1119
|
}
|
|
1118
1120
|
plain = frame.payload
|
|
1119
1121
|
}
|
|
1120
|
-
//
|
|
1121
|
-
//
|
|
1122
|
-
//
|
|
1123
|
-
out = buildFrame(OPCODES.OP_TEXT, plain, { mask: true })
|
|
1122
|
+
// Text stays text (the JSON protocol requires it) and binary stays binary:
|
|
1123
|
+
// an ArrayBuffer payload forwarded as text is rejected by the host, and a
|
|
1124
|
+
// text payload forwarded as binary is invisible to it.
|
|
1125
|
+
out = buildFrame(binary ? OPCODES.OP_BINARY : OPCODES.OP_TEXT, plain, { mask: true })
|
|
1124
1126
|
} else {
|
|
1125
1127
|
out = buildFrame(frame.opcode, frame.payload, { mask: true, fin: frame.fin })
|
|
1126
1128
|
}
|
|
@@ -1149,7 +1151,13 @@ export class PumpManager {
|
|
|
1149
1151
|
: frame.payload
|
|
1150
1152
|
stream.wsFragment = null
|
|
1151
1153
|
const sealed = this.cryptoBox.seal(this.cryptoBox.envelopeKey, payload)
|
|
1152
|
-
|
|
1154
|
+
// Carry the message kind across the seal. The client sets
|
|
1155
|
+
// binaryType="arraybuffer" and the account avatar arrives as binary;
|
|
1156
|
+
// decoding it as text corrupts the bytes and the app then breaks the
|
|
1157
|
+
// connection.
|
|
1158
|
+
const wrapped = { __relay: sealed.toString('base64') }
|
|
1159
|
+
if (frame.opcode === OPCODES.OP_BINARY) wrapped.__bin = 1
|
|
1160
|
+
const json = Buffer.from(JSON.stringify(wrapped))
|
|
1153
1161
|
this.client?.sendStream(sid, buildFrame(OPCODES.OP_TEXT, json, { mask: false }))
|
|
1154
1162
|
continue
|
|
1155
1163
|
}
|
package/src/relay-client.js
CHANGED
|
@@ -167,13 +167,13 @@ export class RelayClient extends EventTarget {
|
|
|
167
167
|
let msg
|
|
168
168
|
try { msg = JSON.parse(data.toString()) } catch { return }
|
|
169
169
|
if (msg.t === 'challenge') {
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
170
|
+
// A tenant proof is keyed by a hash of the relay key (the relay never
|
|
171
|
+
// stores the key itself); the legacy admin --key is keyed by the key.
|
|
172
|
+
// Version 2 domain-separates the secret from the key id, and an older
|
|
173
|
+
// relay sends no `v`, so the v1 derivation stays the fallback.
|
|
174
174
|
const key = String(this.relayKey)
|
|
175
175
|
const secret = msg.mode === 'tenant'
|
|
176
|
-
? crypto.createHash('sha256').update(key).digest('hex')
|
|
176
|
+
? crypto.createHash('sha256').update(msg.v >= 2 ? `dsh-relay:hmac:v2:${key}` : key).digest('hex')
|
|
177
177
|
: key
|
|
178
178
|
const proof = crypto.createHmac('sha256', secret)
|
|
179
179
|
.update(`${msg.nonce}|${this.identity.id}`).digest('hex')
|