pinomad 0.3.0 → 0.4.0
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/{chunk-n60kkf1m.js → chunk-17p6bfvy.js} +4 -4
- package/dist/chunk-302y6by9.js +50 -0
- package/dist/{chunk-bggesvnj.js → chunk-374zqx5h.js} +15 -92
- package/dist/chunk-5785kwej.js +128 -0
- package/dist/{chunk-z4x88s0q.js → chunk-7gmkgyaz.js} +76 -3
- package/dist/chunk-837gh8nr.js +138 -0
- package/dist/{chunk-t9y2wfk5.js → chunk-akgxwg8f.js} +98 -6
- package/dist/chunk-b3f0ced1.js +147 -0
- package/dist/{chunk-syf0z68f.js → chunk-d4y99pfy.js} +2 -2
- package/dist/{chunk-3tf23vjq.js → chunk-fgx2sh7d.js} +1 -1
- package/dist/chunk-knaw1a9g.js +182 -0
- package/dist/{chunk-c155p0t0.js → chunk-myjp910s.js} +106 -7
- package/dist/{chunk-3qn8gbjb.js → chunk-n73t5xzw.js} +1 -1
- package/dist/{chunk-kxws8b83.js → chunk-prhvksh3.js} +4 -4
- package/dist/chunk-vw5yb5pd.js +1123 -0
- package/dist/chunk-xhwkx9bd.js +139 -0
- package/dist/{chunk-7fqn69ys.js → chunk-xrvmnymc.js} +2 -2
- package/dist/{chunk-tfqc1djy.js → chunk-z5pmcscx.js} +530 -242
- package/dist/pinomad.js +14 -9
- package/package.json +1 -1
- package/web/assets/index-8mNNPXPT.js +383 -0
- package/web/assets/{index-CG33GBxM.css → index-BFJJgGGo.css} +1 -1
- package/web/index.html +2 -2
- package/dist/chunk-4dj2vb8t.js +0 -284
- package/dist/chunk-fxaewke2.js +0 -113
- package/web/assets/index-CBXdQLua.js +0 -383
package/web/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>PiNomad</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-8mNNPXPT.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BFJJgGGo.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/dist/chunk-4dj2vb8t.js
DELETED
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
serveWebClient
|
|
4
|
-
} from "./chunk-r6w5aqkw.js";
|
|
5
|
-
import {
|
|
6
|
-
commandHint,
|
|
7
|
-
webRoot
|
|
8
|
-
} from "./chunk-3tf23vjq.js";
|
|
9
|
-
import {
|
|
10
|
-
RELAY_CLOSE,
|
|
11
|
-
decodeHostToRelay,
|
|
12
|
-
encodeRelayFrame,
|
|
13
|
-
verifyRelayChallenge
|
|
14
|
-
} from "./chunk-z4x88s0q.js";
|
|
15
|
-
import"./chunk-df32avw6.js";
|
|
16
|
-
|
|
17
|
-
// apps/relay/src/main.ts
|
|
18
|
-
import { parseArgs } from "node:util";
|
|
19
|
-
|
|
20
|
-
// apps/relay/src/relay.ts
|
|
21
|
-
import { randomBytes } from "node:crypto";
|
|
22
|
-
import { once } from "node:events";
|
|
23
|
-
import { createServer } from "node:http";
|
|
24
|
-
import { WebSocket, WebSocketServer } from "ws";
|
|
25
|
-
var MAX_PAYLOAD = 128 * 1024;
|
|
26
|
-
var MAX_BUFFERED = 64 * 1024;
|
|
27
|
-
var HIGH_WATER = 1024 * 1024;
|
|
28
|
-
var LOW_WATER = 256 * 1024;
|
|
29
|
-
var HOST_PATH = "/host";
|
|
30
|
-
var DEVICE_PATH = /^\/c\/([A-Za-z0-9_-]{43})$/;
|
|
31
|
-
var ACCEPT_PATH = /^\/accept\/([A-Za-z0-9_-]{22})$/;
|
|
32
|
-
var isSendableCloseCode = (code) => code >= 1000 && code <= 1003 || code >= 1007 && code <= 1014 || code >= 3000 && code <= 4999;
|
|
33
|
-
var rawLength = (data) => Array.isArray(data) ? data.reduce((sum, part) => sum + part.length, 0) : data.byteLength;
|
|
34
|
-
async function startRelay(options) {
|
|
35
|
-
const publicOrigin = options.publicOrigin.replace(/\/+$/, "");
|
|
36
|
-
if (publicOrigin === "")
|
|
37
|
-
throw new Error("publicOrigin is required");
|
|
38
|
-
const allowedHosts = new Set(options.allowedHosts);
|
|
39
|
-
const acceptTimeoutMs = options.acceptTimeoutMs ?? 1e4;
|
|
40
|
-
const registerTimeoutMs = options.registerTimeoutMs ?? 1e4;
|
|
41
|
-
const pingIntervalMs = options.pingIntervalMs ?? 30000;
|
|
42
|
-
const listenHost = options.listenHost ?? "127.0.0.1";
|
|
43
|
-
const controls = new Map;
|
|
44
|
-
const pending = new Map;
|
|
45
|
-
const sockets = new Set;
|
|
46
|
-
const alive = new WeakMap;
|
|
47
|
-
function track(socket) {
|
|
48
|
-
sockets.add(socket);
|
|
49
|
-
alive.set(socket, true);
|
|
50
|
-
socket.on("pong", () => alive.set(socket, true));
|
|
51
|
-
socket.on("close", () => sockets.delete(socket));
|
|
52
|
-
socket.on("error", () => socket.terminate());
|
|
53
|
-
}
|
|
54
|
-
function splice(a, b) {
|
|
55
|
-
const forward = (from, to) => {
|
|
56
|
-
from.on("message", (data, isBinary) => {
|
|
57
|
-
if (to.readyState !== WebSocket.OPEN)
|
|
58
|
-
return;
|
|
59
|
-
to.send(data, { binary: isBinary }, () => {
|
|
60
|
-
if (from.isPaused && to.bufferedAmount <= LOW_WATER)
|
|
61
|
-
from.resume();
|
|
62
|
-
});
|
|
63
|
-
if (to.bufferedAmount > HIGH_WATER)
|
|
64
|
-
from.pause();
|
|
65
|
-
});
|
|
66
|
-
from.on("close", (code, reason) => {
|
|
67
|
-
if (to.readyState !== WebSocket.OPEN)
|
|
68
|
-
return;
|
|
69
|
-
to.close(isSendableCloseCode(code) ? code : 1001, reason);
|
|
70
|
-
});
|
|
71
|
-
};
|
|
72
|
-
forward(a, b);
|
|
73
|
-
forward(b, a);
|
|
74
|
-
}
|
|
75
|
-
function onControl(socket) {
|
|
76
|
-
track(socket);
|
|
77
|
-
const nonce = randomBytes(32).toString("base64url");
|
|
78
|
-
socket.send(encodeRelayFrame({ t: "challenge", nonce }));
|
|
79
|
-
let hostId;
|
|
80
|
-
const deadline = setTimeout(() => socket.close(1008, "register timeout"), registerTimeoutMs);
|
|
81
|
-
socket.on("message", (data, isBinary) => {
|
|
82
|
-
if (hostId !== undefined || isBinary) {
|
|
83
|
-
socket.close(1008, "unexpected frame");
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
let frame;
|
|
87
|
-
try {
|
|
88
|
-
frame = decodeHostToRelay(data.toString());
|
|
89
|
-
} catch {
|
|
90
|
-
socket.close(1008, "malformed frame");
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
const verified = verifyRelayChallenge(frame.signature, { relayOrigin: publicOrigin, hostId: frame.hostId, nonce });
|
|
94
|
-
if (!verified) {
|
|
95
|
-
socket.close(RELAY_CLOSE.badSignature);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
if (!allowedHosts.has(frame.hostId)) {
|
|
99
|
-
socket.close(RELAY_CLOSE.hostNotAllowed);
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
hostId = frame.hostId;
|
|
103
|
-
clearTimeout(deadline);
|
|
104
|
-
const previous = controls.get(hostId);
|
|
105
|
-
if (previous !== undefined)
|
|
106
|
-
previous.close(RELAY_CLOSE.replaced);
|
|
107
|
-
controls.set(hostId, socket);
|
|
108
|
-
socket.send(encodeRelayFrame({ t: "registered" }));
|
|
109
|
-
});
|
|
110
|
-
socket.once("close", () => {
|
|
111
|
-
clearTimeout(deadline);
|
|
112
|
-
if (hostId !== undefined && controls.get(hostId) === socket)
|
|
113
|
-
controls.delete(hostId);
|
|
114
|
-
for (const [connId, entry] of pending) {
|
|
115
|
-
if (entry.control === socket) {
|
|
116
|
-
pending.delete(connId);
|
|
117
|
-
clearTimeout(entry.timer);
|
|
118
|
-
entry.socket.close(RELAY_CLOSE.hostOffline);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
function onDevice(socket, hostId) {
|
|
124
|
-
track(socket);
|
|
125
|
-
const control = controls.get(hostId);
|
|
126
|
-
if (control === undefined) {
|
|
127
|
-
socket.close(RELAY_CLOSE.hostOffline);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
const connId = randomBytes(16).toString("base64url");
|
|
131
|
-
const entry = {
|
|
132
|
-
control,
|
|
133
|
-
socket,
|
|
134
|
-
buffered: [],
|
|
135
|
-
bufferedBytes: 0,
|
|
136
|
-
timer: setTimeout(() => {
|
|
137
|
-
if (pending.delete(connId))
|
|
138
|
-
socket.close(RELAY_CLOSE.acceptTimeout);
|
|
139
|
-
}, acceptTimeoutMs),
|
|
140
|
-
onMessage(data, isBinary) {
|
|
141
|
-
entry.buffered.push({ data, isBinary });
|
|
142
|
-
entry.bufferedBytes += rawLength(data);
|
|
143
|
-
if (entry.bufferedBytes > MAX_BUFFERED && pending.delete(connId)) {
|
|
144
|
-
clearTimeout(entry.timer);
|
|
145
|
-
socket.close(1009);
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
|
-
onClose() {
|
|
149
|
-
if (pending.delete(connId))
|
|
150
|
-
clearTimeout(entry.timer);
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
pending.set(connId, entry);
|
|
154
|
-
socket.on("message", entry.onMessage);
|
|
155
|
-
socket.once("close", entry.onClose);
|
|
156
|
-
control.send(encodeRelayFrame({ t: "incoming", connId }));
|
|
157
|
-
}
|
|
158
|
-
function onAccept(socket, connId) {
|
|
159
|
-
track(socket);
|
|
160
|
-
const entry = pending.get(connId);
|
|
161
|
-
if (entry === undefined) {
|
|
162
|
-
socket.close(RELAY_CLOSE.unknownConnection);
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
pending.delete(connId);
|
|
166
|
-
clearTimeout(entry.timer);
|
|
167
|
-
const device = entry.socket;
|
|
168
|
-
device.removeListener("message", entry.onMessage);
|
|
169
|
-
device.removeListener("close", entry.onClose);
|
|
170
|
-
splice(socket, device);
|
|
171
|
-
for (const { data, isBinary } of entry.buffered)
|
|
172
|
-
socket.send(data, { binary: isBinary });
|
|
173
|
-
}
|
|
174
|
-
const httpServer = createServer((request, response) => {
|
|
175
|
-
if (options.webRoot !== undefined) {
|
|
176
|
-
serveWebClient(request, response, options.webRoot).catch(() => {
|
|
177
|
-
if (!response.headersSent)
|
|
178
|
-
response.writeHead(500);
|
|
179
|
-
response.end();
|
|
180
|
-
});
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
response.writeHead(404);
|
|
184
|
-
response.end();
|
|
185
|
-
});
|
|
186
|
-
const wss = new WebSocketServer({ noServer: true, maxPayload: MAX_PAYLOAD });
|
|
187
|
-
httpServer.on("upgrade", (request, socket, head) => {
|
|
188
|
-
let pathname;
|
|
189
|
-
try {
|
|
190
|
-
pathname = new URL(request.url ?? "/", "http://relay.invalid").pathname;
|
|
191
|
-
} catch {
|
|
192
|
-
pathname = "";
|
|
193
|
-
}
|
|
194
|
-
const upgrade = (handle) => {
|
|
195
|
-
wss.handleUpgrade(request, socket, head, handle);
|
|
196
|
-
};
|
|
197
|
-
const device = DEVICE_PATH.exec(pathname);
|
|
198
|
-
const accept = ACCEPT_PATH.exec(pathname);
|
|
199
|
-
if (pathname === HOST_PATH)
|
|
200
|
-
upgrade(onControl);
|
|
201
|
-
else if (device !== null)
|
|
202
|
-
upgrade((ws) => onDevice(ws, device[1]));
|
|
203
|
-
else if (accept !== null)
|
|
204
|
-
upgrade((ws) => onAccept(ws, accept[1]));
|
|
205
|
-
else {
|
|
206
|
-
socket.write(`HTTP/1.1 404 Not Found\r
|
|
207
|
-
\r
|
|
208
|
-
`);
|
|
209
|
-
socket.destroy();
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
await new Promise((resolveListen, reject) => {
|
|
213
|
-
httpServer.once("listening", resolveListen);
|
|
214
|
-
httpServer.once("error", reject);
|
|
215
|
-
httpServer.listen(options.port, listenHost);
|
|
216
|
-
});
|
|
217
|
-
const { port } = httpServer.address();
|
|
218
|
-
const pingTimer = setInterval(() => {
|
|
219
|
-
for (const socket of sockets) {
|
|
220
|
-
if (alive.get(socket) === true) {
|
|
221
|
-
alive.set(socket, false);
|
|
222
|
-
socket.ping();
|
|
223
|
-
} else {
|
|
224
|
-
socket.terminate();
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}, pingIntervalMs);
|
|
228
|
-
return {
|
|
229
|
-
port,
|
|
230
|
-
url: `ws://${listenHost.includes(":") ? `[${listenHost}]` : listenHost}:${port}`,
|
|
231
|
-
async close() {
|
|
232
|
-
clearInterval(pingTimer);
|
|
233
|
-
for (const entry of pending.values())
|
|
234
|
-
clearTimeout(entry.timer);
|
|
235
|
-
pending.clear();
|
|
236
|
-
for (const socket of sockets)
|
|
237
|
-
socket.terminate();
|
|
238
|
-
const closing = once(wss, "close");
|
|
239
|
-
wss.close();
|
|
240
|
-
await closing;
|
|
241
|
-
await new Promise((resolve) => httpServer.close(() => resolve()));
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// apps/relay/src/main.ts
|
|
247
|
-
async function main(argv) {
|
|
248
|
-
const { values } = parseArgs({
|
|
249
|
-
args: [...argv],
|
|
250
|
-
options: {
|
|
251
|
-
port: { type: "string", default: "7430" },
|
|
252
|
-
listen: { type: "string", default: "127.0.0.1" },
|
|
253
|
-
"public-origin": { type: "string" },
|
|
254
|
-
"allow-host": { type: "string", multiple: true }
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
const allowedHosts = values["allow-host"] ?? [];
|
|
258
|
-
if (values["public-origin"] === undefined || allowedHosts.length === 0) {
|
|
259
|
-
console.error(`usage: ${commandHint("relay")} --public-origin ORIGIN --allow-host HOST_ID [--allow-host ...] [--port 7430] [--listen 127.0.0.1]`);
|
|
260
|
-
process.exit(1);
|
|
261
|
-
}
|
|
262
|
-
const relay = await startRelay({
|
|
263
|
-
port: Number(values.port),
|
|
264
|
-
listenHost: values.listen,
|
|
265
|
-
publicOrigin: values["public-origin"],
|
|
266
|
-
allowedHosts,
|
|
267
|
-
webRoot
|
|
268
|
-
});
|
|
269
|
-
console.log(JSON.stringify({ event: "ready", url: relay.url, publicOrigin: values["public-origin"], allowedHosts: allowedHosts.length }));
|
|
270
|
-
let stopping = false;
|
|
271
|
-
const stop = () => {
|
|
272
|
-
if (stopping)
|
|
273
|
-
return;
|
|
274
|
-
stopping = true;
|
|
275
|
-
relay.close().finally(() => process.exit(0));
|
|
276
|
-
};
|
|
277
|
-
process.on("SIGINT", stop);
|
|
278
|
-
process.on("SIGTERM", stop);
|
|
279
|
-
}
|
|
280
|
-
if (false)
|
|
281
|
-
;
|
|
282
|
-
export {
|
|
283
|
-
main
|
|
284
|
-
};
|
package/dist/chunk-fxaewke2.js
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
fromBase64Url,
|
|
3
|
-
generateKeyPair,
|
|
4
|
-
keyPairFromPrivate,
|
|
5
|
-
toBase64Url
|
|
6
|
-
} from "./chunk-z4x88s0q.js";
|
|
7
|
-
|
|
8
|
-
// apps/host/src/devices.ts
|
|
9
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
12
|
-
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
13
|
-
import { defineDoc } from "@earendil-works/pi-durable";
|
|
14
|
-
var DevicesDoc = defineDoc({
|
|
15
|
-
kind: "pinomad.devices",
|
|
16
|
-
version: 1,
|
|
17
|
-
scope: "session",
|
|
18
|
-
initial: () => ({ devices: [] })
|
|
19
|
-
});
|
|
20
|
-
async function ensureDevices(harness, context) {
|
|
21
|
-
await harness.commit(async (tx) => void await tx.doc(DevicesDoc), context);
|
|
22
|
-
}
|
|
23
|
-
async function isRegistered(harness, publicKey, context) {
|
|
24
|
-
return await harness.commit(async (tx) => (await tx.doc(DevicesDoc)).devices.some((entry) => entry.publicKey === publicKey), context);
|
|
25
|
-
}
|
|
26
|
-
async function registerDevice(harness, device, context) {
|
|
27
|
-
return await harness.commit(async (tx) => {
|
|
28
|
-
const doc = await tx.doc(DevicesDoc);
|
|
29
|
-
const existing = doc.devices.find((entry2) => entry2.publicKey === device.publicKey);
|
|
30
|
-
if (existing !== undefined)
|
|
31
|
-
return { ...existing };
|
|
32
|
-
const name = device.name.trim().slice(0, 64) || "Unknown device";
|
|
33
|
-
const entry = { publicKey: device.publicKey, name, pairedAt: Date.now() };
|
|
34
|
-
doc.devices.push(entry);
|
|
35
|
-
return entry;
|
|
36
|
-
}, context);
|
|
37
|
-
}
|
|
38
|
-
async function revokeDevice(harness, publicKey, context) {
|
|
39
|
-
await harness.commit(async (tx) => {
|
|
40
|
-
const doc = await tx.doc(DevicesDoc);
|
|
41
|
-
doc.devices = doc.devices.filter((entry) => entry.publicKey !== publicKey);
|
|
42
|
-
}, context);
|
|
43
|
-
}
|
|
44
|
-
async function loadHostKey(dataDir) {
|
|
45
|
-
const path = join(dataDir, "host-key");
|
|
46
|
-
try {
|
|
47
|
-
const text = (await readFile(path, "utf8")).trim();
|
|
48
|
-
return keyPairFromPrivate(fromBase64Url(text));
|
|
49
|
-
} catch (error) {
|
|
50
|
-
if (error.code !== "ENOENT")
|
|
51
|
-
throw error;
|
|
52
|
-
const pair = generateKeyPair();
|
|
53
|
-
await writeFile(path, `${toBase64Url(pair.privateKey)}
|
|
54
|
-
`, { mode: 384 });
|
|
55
|
-
return pair;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
async function loadRelayKey(dataDir) {
|
|
59
|
-
const path = join(dataDir, "relay-key");
|
|
60
|
-
try {
|
|
61
|
-
const secretKey = fromBase64Url((await readFile(path, "utf8")).trim());
|
|
62
|
-
if (secretKey.length !== 32)
|
|
63
|
-
throw new Error(`bad relay key in ${path}`);
|
|
64
|
-
return { secretKey, publicKey: ed25519.getPublicKey(secretKey) };
|
|
65
|
-
} catch (error) {
|
|
66
|
-
if (error.code !== "ENOENT")
|
|
67
|
-
throw error;
|
|
68
|
-
const { secretKey, publicKey } = ed25519.keygen();
|
|
69
|
-
try {
|
|
70
|
-
await writeFile(path, `${toBase64Url(secretKey)}
|
|
71
|
-
`, { flag: "wx", mode: 384 });
|
|
72
|
-
} catch (race) {
|
|
73
|
-
if (race.code !== "EEXIST")
|
|
74
|
-
throw race;
|
|
75
|
-
return loadRelayKey(dataDir);
|
|
76
|
-
}
|
|
77
|
-
return { secretKey, publicKey };
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
function pairingOffers(ttlMs = 5 * 60 * 1000) {
|
|
81
|
-
const live = new Map;
|
|
82
|
-
const prune = () => {
|
|
83
|
-
const now = Date.now();
|
|
84
|
-
for (const [secret, expiresAt] of live)
|
|
85
|
-
if (expiresAt <= now)
|
|
86
|
-
live.delete(secret);
|
|
87
|
-
};
|
|
88
|
-
return {
|
|
89
|
-
create() {
|
|
90
|
-
prune();
|
|
91
|
-
const secret = toBase64Url(randomBytes(16));
|
|
92
|
-
const expiresAt = Date.now() + ttlMs;
|
|
93
|
-
live.set(secret, expiresAt);
|
|
94
|
-
return { secret, expiresAt };
|
|
95
|
-
},
|
|
96
|
-
consume(secret) {
|
|
97
|
-
prune();
|
|
98
|
-
const candidate = Buffer.from(secret);
|
|
99
|
-
for (const offer of live.keys()) {
|
|
100
|
-
const offerBytes = Buffer.from(offer);
|
|
101
|
-
if (offerBytes.length !== candidate.length)
|
|
102
|
-
continue;
|
|
103
|
-
if (timingSafeEqual(offerBytes, candidate)) {
|
|
104
|
-
live.delete(offer);
|
|
105
|
-
return true;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export { DevicesDoc, ensureDevices, isRegistered, registerDevice, revokeDevice, loadHostKey, loadRelayKey, pairingOffers };
|