feat: autopass-based cross-device sync for Holesail Browser
CI / Build & Test (push) Successful in 4m27s

- Native host: add autopass + corestore deps, sync-manager (createInvite, pairWithInvite, push/pull)
- Apply synced state via same flow as backup restore (state.json only, no certs)
- Holesail-manager: setOnStateSaved hook for sync push
- Extension: Sync dashboard page, getSyncStatus/createSyncInvite/pairWithInvite, syncApplied event
- Docs: NATIVE-HOST.md sync commands, SYNC.md
This commit is contained in:
Raven Scott
2026-03-15 01:31:51 -04:00
parent 410fd87357
commit 2595a01f65
16 changed files with 1322 additions and 4 deletions
+63
View File
@@ -511,6 +511,57 @@ Delete a backup.
--- ---
### `getSyncStatus`
Return whether this device is linked for sync and optional invite/last-synced info.
**Request payload:** `{}`
**Response payload:**
```json
{
"ok": true,
"linked": true,
"invite": "optional invite string if one was created",
"lastSyncedAt": 1710000000000
}
```
---
### `createSyncInvite`
Create a new sync group and return an invite string to share with another device. If this device is not yet linked, creates a new autopass instance and persists its identity.
**Request payload:** `{}`
**Response payload:**
```json
{ "ok": true, "invite": "z32-encoded invite string" }
```
On error: `{ "ok": false, "error": "..." }`
---
### `pairWithInvite`
Link this device to another by redeeming an invite. After pairing, state is pulled from the other device and applied (tunnels restarted). A `syncApplied` event is emitted so the dashboard can refresh.
**Request payload:**
```json
{ "invite": "z32-encoded invite from the other device" }
```
**Response payload:**
```json
{ "ok": true }
```
On error: `{ "ok": false, "error": "..." }`
---
### `getLogs` ### `getLogs`
Get recent log lines from the native host log file. Get recent log lines from the native host log file.
@@ -603,6 +654,18 @@ A connection ended.
} }
``` ```
### `syncApplied`
Emitted after state was applied from a linked device (e.g. after a remote update or after pairing). The dashboard should refresh state when it receives this event.
```json
{
"type": "event",
"event": "syncApplied",
"payload": {}
}
```
--- ---
## Tunnel states ## Tunnel states
+35
View File
@@ -0,0 +1,35 @@
# Device sync
Holesail Browser can sync its state across two devices using [autopass](https://www.npmjs.com/package/autopass). Once linked, changes on one device (virtual hosts, server tunnels, service tunnels, SSH/RDP connection lists, settings) are replicated to the other and applied automatically.
## What is synced
- **Included:** All persistent state stored in `state.json`: settings, virtual hosts, server tunnels, service tunnels, SSH connections (including saved passwords), RDP connections (including saved passwords), and ID counters.
- **Excluded:** CA certificates and wildcard TLS certificates. Each device keeps its own certs. After linking, if you use the same browser profile on the second device for virtual hosts, install the root CA there (Dashboard → Proxy & CA → Install Root CA).
## How to link two devices
1. **First device:** Open the dashboard → **Sync**. Click **Create invite**. Copy the invite string (or use **Copy invite**).
2. **Second device:** Open the dashboard → **Sync**. Paste the invite into the text field and click **Link device**. Wait for “Device linked. State has been synced.”
3. Both devices are now linked. The second devices state is replaced by the first devices state at link time; thereafter changes on either device sync to the other.
## Behaviour after linking
- **Push:** Any change on a device (add/remove virtual host, edit settings, etc.) is saved to disk and pushed to the sync group. Other linked devices receive the update and apply it (tunnels are restarted as needed).
- **Pull:** When an update is received from another device, the native host writes the new state to `state.json`, stops existing tunnels, and restarts them from the new state. The dashboard refreshes when it receives the `syncApplied` event.
- **Persistence:** The link is stored in `holesail-browser-storage/autopass-identity.json`. After closing the browser or restarting the native host, both devices remain linked and continue syncing when the host runs.
## Conflict semantics
Sync uses a **single state blob** per sync group. Updates are **last-write-wins**: the most recent write overwrites the whole state. If you edit different things on both devices at the same time, one devices changes can overwrite the others. For best results, avoid editing the same lists on both devices simultaneously.
## Storage location
- **Autopass data:** `~/.holesail-browser/holesail-browser-storage/autopass/`
- **Identity (key material):** `~/.holesail-browser/holesail-browser-storage/autopass-identity.json`
Do not share or back up `autopass-identity.json` to an untrusted location; it allows access to the synced state.
## Unlinking
To stop syncing, remove or rename the `autopass` directory and `autopass-identity.json` under `holesail-browser-storage`. The other device remains linked until it is restarted or its identity is removed; it will no longer receive updates from this device.
+5
View File
@@ -216,6 +216,11 @@ function connect() {
}).catch(() => {}); }).catch(() => {});
} }
} }
if (msg.event === 'syncApplied' && typeof dashboardTabs !== 'undefined') {
for (const tabId of dashboardTabs) {
browser.tabs.sendMessage(tabId, { type: 'holesail-event', payload: msg }).catch(() => {});
}
}
} }
}); });
+8
View File
@@ -34,8 +34,16 @@ async function init() {
setupSshEvents(); setupSshEvents();
setupRdpEvents(); setupRdpEvents();
setupBackupEvents(); setupBackupEvents();
setupSyncEvents();
await refresh(); await refresh();
// When native host emits syncApplied, refresh state immediately
chrome.runtime.onMessage.addListener((msg) => {
if (msg.type === 'holesail-event' && msg.payload && msg.payload.event === 'syncApplied') {
refresh();
}
});
// Main state-refresh interval: configurable (2s / 5s / 10s), or paused when tab is hidden or setting is 0. // Main state-refresh interval: configurable (2s / 5s / 10s), or paused when tab is hidden or setting is 0.
let _refreshIntervalId = null; let _refreshIntervalId = null;
function _getRefreshMs() { function _getRefreshMs() {
+1
View File
@@ -13,6 +13,7 @@ const PAGE_TITLES = {
ssh: 'SSH Connections', ssh: 'SSH Connections',
rdp: 'Remote Desktop', rdp: 'Remote Desktop',
backups: 'Backups', backups: 'Backups',
sync: 'Sync',
logs: 'Logs', logs: 'Logs',
settings: 'Settings' settings: 'Settings'
}; };
+38
View File
@@ -100,6 +100,15 @@
Backups Backups
<span class="nav-badge" id="backupCount">0</span> <span class="nav-badge" id="backupCount">0</span>
</div> </div>
<div class="nav-item" data-page="sync">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
<path d="M3 3v5h5"/>
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/>
<path d="M16 21h5v-5"/>
</svg>
Sync
</div>
<div class="nav-item" data-page="logs"> <div class="nav-item" data-page="logs">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="22,12 18,12 15,21 9,3 6,12 2,12"/> <polyline points="22,12 18,12 15,21 9,3 6,12 2,12"/>
@@ -574,6 +583,34 @@
</div> </div>
</div> </div>
<!-- ── Sync page ─────────────────────────────── -->
<div class="page" id="page-sync">
<div class="card" style="margin-bottom:16px;">
<div class="card-body">
<div class="section-heading">Device sync</div>
<p style="margin:0 0 14px 0;font-size:13px;color:var(--text3);">Sync your Holesail state (virtual hosts, servers, tunnels, SSH/RDP connections, settings) to another device. CA certificates are not synced; install the root CA on the other device if needed.</p>
<div id="syncStatus" style="display:flex;align-items:center;gap:8px;margin-bottom:14px;">
<div class="status-dot" id="syncStatusDot"></div>
<span id="syncStatusText">Checking…</span>
<span id="syncLastSynced" style="font-size:11px;color:var(--text4);"></span>
</div>
<div style="display:flex;flex-wrap:wrap;gap:10px;">
<button class="btn btn-secondary" id="btnCreateSyncInvite">
Create invite
</button>
<button class="btn btn-secondary" id="btnCopyInvite" style="display:none;">
Copy invite
</button>
<div style="display:flex;align-items:center;gap:8px;flex:1;min-width:200px;">
<input type="text" id="syncInviteInput" class="input" placeholder="Paste invite from other device" style="flex:1;" />
<button class="btn btn-primary" id="btnPairWithInvite">Link device</button>
</div>
</div>
<div id="syncInviteDisplay" style="display:none;margin-top:12px;padding:10px;background:var(--card);border:1px solid var(--border);border-radius:var(--radius);font-family:var(--font-mono);font-size:12px;word-break:break-all;"></div>
</div>
</div>
</div>
<!-- ── Settings page ──────────────────────────── --> <!-- ── Settings page ──────────────────────────── -->
<div class="page" id="page-settings"> <div class="page" id="page-settings">
<div class="card"> <div class="card">
@@ -1327,6 +1364,7 @@
<script src="pages/service-tunnels.js"></script> <script src="pages/service-tunnels.js"></script>
<script src="pages/proxy-ca.js"></script> <script src="pages/proxy-ca.js"></script>
<script src="pages/backups.js"></script> <script src="pages/backups.js"></script>
<script src="pages/sync.js"></script>
<script src="pages/logs.js"></script> <script src="pages/logs.js"></script>
<script src="pages/ssh.js"></script> <script src="pages/ssh.js"></script>
<script src="pages/rdp.js"></script> <script src="pages/rdp.js"></script>
+129
View File
@@ -0,0 +1,129 @@
/**
* Sync page — device linking via autopass; create invite, pair with invite, show status.
* Depends: core/utils.js ($), core/messaging.js (sendToNative), ui/toast.js (showToast)
*/
/**
* Update the sync status UI from the native host.
*/
function updateSyncStatus() {
chrome.runtime.sendMessage(
{ target: 'holesail-native', action: 'send', payload: { type: 'getSyncStatus' } },
(response) => {
if (chrome.runtime.lastError) return;
const dot = $('syncStatusDot');
const text = $('syncStatusText');
const lastSynced = $('syncLastSynced');
const btnCopy = $('btnCopyInvite');
const inviteDisplay = $('syncInviteDisplay');
if (!text) return;
if (!response || response.error) {
if (dot) dot.style.background = 'var(--text4)';
text.textContent = 'Not linked';
if (lastSynced) lastSynced.textContent = '';
if (btnCopy) btnCopy.style.display = 'none';
if (inviteDisplay) inviteDisplay.style.display = 'none';
return;
}
if (response.linked) {
if (dot) dot.style.background = 'var(--green)';
text.textContent = 'Linked';
if (response.lastSyncedAt) {
if (lastSynced) lastSynced.textContent = 'Last synced ' + timeAgo(response.lastSyncedAt);
} else if (lastSynced) lastSynced.textContent = '';
if (response.invite) {
if (btnCopy) btnCopy.style.display = '';
if (inviteDisplay) {
inviteDisplay.textContent = response.invite;
inviteDisplay.style.display = 'block';
}
} else {
if (btnCopy) btnCopy.style.display = 'none';
if (inviteDisplay) inviteDisplay.style.display = 'none';
}
} else {
if (dot) dot.style.background = 'var(--text4)';
text.textContent = 'Not linked';
if (lastSynced) lastSynced.textContent = '';
if (btnCopy) btnCopy.style.display = 'none';
if (inviteDisplay) inviteDisplay.style.display = 'none';
}
}
);
}
/**
* Attach event listeners for the Sync page.
*/
function setupSyncEvents() {
const btnCreate = $('btnCreateSyncInvite');
const btnCopy = $('btnCopyInvite');
const btnPair = $('btnPairWithInvite');
const inviteInput = $('syncInviteInput');
const inviteDisplay = $('syncInviteDisplay');
btnCreate?.addEventListener('click', () => {
if (btnCreate.disabled) return;
btnCreate.disabled = true;
btnCreate.textContent = 'Creating…';
chrome.runtime.sendMessage(
{ target: 'holesail-native', action: 'send', payload: { type: 'createSyncInvite' } },
(response) => {
btnCreate.disabled = false;
btnCreate.textContent = 'Create invite';
if (chrome.runtime.lastError) {
showToast('Failed: ' + (chrome.runtime.lastError.message || 'unknown'), 'error');
return;
}
if (response && response.ok && response.invite) {
inviteDisplay.style.display = 'block';
inviteDisplay.textContent = response.invite;
btnCopy.style.display = '';
showToast('Invite created. Share it with the other device.', 'success');
updateSyncStatus();
} else {
showToast(response && response.error ? response.error : 'Failed to create invite', 'error');
}
}
);
});
btnCopy?.addEventListener('click', () => {
if (!inviteDisplay || !inviteDisplay.textContent) return;
navigator.clipboard.writeText(inviteDisplay.textContent).then(() => {
showToast('Invite copied to clipboard', 'success');
}).catch(() => {
showToast('Copy failed', 'error');
});
});
btnPair?.addEventListener('click', () => {
const invite = inviteInput?.value?.trim();
if (!invite) {
showToast('Paste an invite first', 'error');
return;
}
if (btnPair.disabled) return;
btnPair.disabled = true;
btnPair.textContent = 'Linking…';
chrome.runtime.sendMessage(
{ target: 'holesail-native', action: 'send', payload: { type: 'pairWithInvite', payload: { invite } } },
(response) => {
btnPair.disabled = false;
btnPair.textContent = 'Link device';
if (chrome.runtime.lastError) {
showToast('Failed: ' + (chrome.runtime.lastError.message || 'unknown'), 'error');
return;
}
if (response && response.ok) {
showToast('Device linked. State has been synced.', 'success');
if (inviteInput) inviteInput.value = '';
updateSyncStatus();
if (typeof refresh === 'function') refresh();
} else {
showToast(response && response.error ? response.error : 'Linking failed', 'error');
}
}
);
});
}
+1
View File
@@ -106,5 +106,6 @@ async function refresh() {
const sshCountEl = $('sshCount'); const sshCountEl = $('sshCount');
if (sshCountEl) sshCountEl.textContent = sshConnections.length; if (sshCountEl) sshCountEl.textContent = sshConnections.length;
refreshBackups(); refreshBackups();
if (typeof updateSyncStatus === 'function') updateSyncStatus();
return state ?? null; return state ?? null;
} }
+16 -2
View File
@@ -25,6 +25,17 @@ function emit(event, payload) { if (eventEmit) eventEmit(event, payload); }
// ── Shared saveState snapshot ───────────────────────────────────────────────── // ── Shared saveState snapshot ─────────────────────────────────────────────────
// Collects current state from all sub-modules and persists it. // Collects current state from all sub-modules and persists it.
let onStateSaved = null;
/**
* Register a callback invoked after each saveState() with the snapshot object.
* Used by sync-manager to push state to autopass.
* @param {Function} fn - (snapshot: object) => void
*/
function setOnStateSaved(fn) {
onStateSaved = typeof fn === 'function' ? fn : null;
}
function saveState() { function saveState() {
const serversList = serversModule.getServers().map(s => ({ const serversList = serversModule.getServers().map(s => ({
id: s.id, port: s.port, host: s.host, secure: s.secure, udp: s.udp || false, label: s.label || '' id: s.id, port: s.port, host: s.host, secure: s.secure, udp: s.udp || false, label: s.label || ''
@@ -34,7 +45,7 @@ function saveState() {
const serviceTunnelsList = svcModule.getServiceTunnels().map(t => ({ const serviceTunnelsList = svcModule.getServiceTunnels().map(t => ({
id: t.id, label: t.label, hsUrl: t.hsUrl, localPort: t.localPort id: t.id, label: t.label, hsUrl: t.hsUrl, localPort: t.localPort
})); }));
stateModule.saveStateSync({ const snapshot = {
version: 2, version: 2,
settings: settingsModule.getSettings(), settings: settingsModule.getSettings(),
nextServerId: serversModule.getNextServerId(), nextServerId: serversModule.getNextServerId(),
@@ -44,7 +55,9 @@ function saveState() {
serviceTunnels: serviceTunnelsList, serviceTunnels: serviceTunnelsList,
sshConnections: connectionsModule.getSshConnections(), sshConnections: connectionsModule.getSshConnections(),
rdpConnections: connectionsModule.getRdpConnections() rdpConnections: connectionsModule.getRdpConnections()
}); };
stateModule.saveStateSync(snapshot);
if (onStateSaved) onStateSaved(snapshot);
} }
// Inject the shared saveState and emit callbacks into each sub-module // Inject the shared saveState and emit callbacks into each sub-module
@@ -113,6 +126,7 @@ module.exports = {
// Storage // Storage
setStoragePath, setStoragePath,
ensureStorageDir, ensureStorageDir,
setOnStateSaved,
// Settings // Settings
getSettings: settingsModule.getSettings, getSettings: settingsModule.getSettings,
updateSettings: settingsModule.updateSettings, updateSettings: settingsModule.updateSettings,
+3 -1
View File
@@ -11,6 +11,7 @@ const caHandlers = require('./ca.js');
const sshHandlers = require('./ssh.js'); const sshHandlers = require('./ssh.js');
const rdpHandlers = require('./rdp.js'); const rdpHandlers = require('./rdp.js');
const backupHandlers = require('./backup.js'); const backupHandlers = require('./backup.js');
const syncHandlers = require('./sync.js');
/** /**
* Build the handler registry. Pass the same deps that message-router has. * Build the handler registry. Pass the same deps that message-router has.
@@ -24,7 +25,8 @@ function buildHandlers(deps) {
...caHandlers.register(deps), ...caHandlers.register(deps),
...sshHandlers.register(deps), ...sshHandlers.register(deps),
...rdpHandlers.register(deps), ...rdpHandlers.register(deps),
...backupHandlers.register(deps) ...backupHandlers.register(deps),
...syncHandlers.register(deps)
]; ];
const map = new Map(); const map = new Map();
+46
View File
@@ -0,0 +1,46 @@
/**
* Handlers for sync: getSyncStatus, createSyncInvite, pairWithInvite.
* Context: syncManager
*/
function register(deps) {
const { syncManager } = deps;
return [
{
type: 'getSyncStatus',
handle: async (payload, reply) => {
try {
const status = await syncManager.getSyncStatus();
reply({ ok: true, ...status });
} catch (e) {
reply({ ok: false, error: e.message });
}
}
},
{
type: 'createSyncInvite',
handle: async (payload, reply) => {
try {
const result = await syncManager.createSyncInvite();
reply(result);
} catch (e) {
reply({ ok: false, error: e.message });
}
}
},
{
type: 'pairWithInvite',
handle: async (payload, reply) => {
const invite = payload && payload.invite;
try {
const result = await syncManager.pairWithInvite(invite);
reply(result);
} catch (e) {
reply({ ok: false, error: e.message });
}
}
}
];
}
module.exports = { register };
+14
View File
@@ -21,6 +21,18 @@ const sshManager = require('../managers/ssh-manager.js');
const backupManager = require('../managers/backup-manager.js'); const backupManager = require('../managers/backup-manager.js');
backupManager.setStoragePath(STORAGE_PATH); backupManager.setStoragePath(STORAGE_PATH);
backupManager.setCertsPath(certificateAuthority.getCertsDir()); backupManager.setCertsPath(certificateAuthority.getCertsDir());
const syncManager = require('../managers/sync-manager.js');
syncManager.setStoragePath(STORAGE_PATH);
syncManager.setDeps({
holesailManager,
setTunnelsRestoredPromise,
restorePersistedTunnels,
emitEvent: (event, payload) => {
if (_send) _send({ type: 'event', event, payload: payload || {} });
},
log
});
holesailManager.setOnStateSaved(syncManager.onStateSaved);
const rdpManager = require('../managers/rdp-manager.js'); const rdpManager = require('../managers/rdp-manager.js');
httpsProxy.setHostnameResolver((hostname) => holesailManager.getLocalBackend(hostname)); httpsProxy.setHostnameResolver((hostname) => holesailManager.getLocalBackend(hostname));
@@ -72,6 +84,7 @@ const _handlers = buildHandlers({
sshManager, sshManager,
backupManager, backupManager,
rdpManager, rdpManager,
syncManager,
getProxiesReadyPromise, getProxiesReadyPromise,
getTunnelsRestoredPromise, getTunnelsRestoredPromise,
setTunnelsRestoredPromise, setTunnelsRestoredPromise,
@@ -112,6 +125,7 @@ async function handleMessageAsync(send, msg) {
function cleanup() { function cleanup() {
if (_scheduledBackupTimer) { clearTimeout(_scheduledBackupTimer); _scheduledBackupTimer = null; } if (_scheduledBackupTimer) { clearTimeout(_scheduledBackupTimer); _scheduledBackupTimer = null; }
syncManager.cleanup().catch(() => {});
httpsProxy.stop(() => {}); httpsProxy.stop(() => {});
connectProxy.stop(() => {}); connectProxy.stop(() => {});
sshManager.cleanup(); sshManager.cleanup();
+303
View File
@@ -0,0 +1,303 @@
/**
* Sync manager for Holesail Browser.
* Uses autopass to sync state.json (no certs) across linked devices.
* Push on local state save; pull on autopass 'update' and apply (cleanup → restorePersistedState → restorePersistedTunnels).
*/
const path = require('bare-path');
const fs = require('bare-fs');
const b4a = require('b4a');
const STATE_KEY = 'holesail-state';
const IDENTITY_FILENAME = 'autopass-identity.json';
const STATE_FILENAME = 'state.json';
const AUTOPASS_DIRNAME = 'autopass';
let storageDir = null;
let holesailManager = null;
let setTunnelsRestoredPromise = null;
let restorePersistedTunnels = null;
let emitEvent = null;
let log = null;
let store = null;
let pass = null;
let currentInvite = null;
let initPromise = null;
let applyingSync = false;
let weJustPushed = false;
let lastSyncedAt = null;
function setStoragePath(dir) {
storageDir = dir;
}
function setDeps(deps) {
holesailManager = deps.holesailManager;
setTunnelsRestoredPromise = deps.setTunnelsRestoredPromise;
restorePersistedTunnels = deps.restorePersistedTunnels;
emitEvent = deps.emitEvent;
log = deps.log || (() => {});
}
function getStateFilePath() {
if (!storageDir) return null;
return path.join(storageDir, STATE_FILENAME);
}
function getAutopassPath() {
if (!storageDir) return null;
return path.join(storageDir, AUTOPASS_DIRNAME);
}
function getIdentityPath() {
if (!storageDir) return null;
return path.join(storageDir, IDENTITY_FILENAME);
}
function loadIdentity() {
const file = getIdentityPath();
if (!file || !fs.existsSync(file)) return null;
try {
const raw = fs.readFileSync(file, 'utf8');
const data = JSON.parse(raw);
if (!data || !data.key) return null;
return {
key: b4a.from(data.key, 'base64'),
encryptionKey: data.encryptionKey ? b4a.from(data.encryptionKey, 'base64') : undefined
};
} catch (e) {
if (process.stderr) process.stderr.write('[sync-manager] loadIdentity failed: ' + e.message + '\n');
return null;
}
}
function saveIdentity(key, encryptionKey) {
const file = getIdentityPath();
if (!file) return;
try {
const dir = path.dirname(file);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
const data = {
key: b4a.toString(key, 'base64'),
encryptionKey: encryptionKey ? b4a.toString(encryptionKey, 'base64') : null
};
fs.writeFileSync(file, JSON.stringify(data), 'utf8');
} catch (e) {
if (process.stderr) process.stderr.write('[sync-manager] saveIdentity failed: ' + e.message + '\n');
}
}
function clearIdentity() {
const file = getIdentityPath();
if (file && fs.existsSync(file)) {
try { fs.unlinkSync(file); } catch (_) {}
}
}
async function closePass() {
if (pass) {
try {
await pass.close();
} catch (e) {
if (process.stderr) process.stderr.write('[sync-manager] pass.close: ' + e.message + '\n');
}
pass = null;
}
if (store) {
try {
await store.close();
} catch (e) {
if (process.stderr) process.stderr.write('[sync-manager] store.close: ' + e.message + '\n');
}
store = null;
}
currentInvite = null;
}
async function ensureInitialized() {
if (initPromise) return initPromise;
initPromise = (async () => {
if (pass) return;
const identity = loadIdentity();
if (!identity) return;
const autopassPath = getAutopassPath();
if (!autopassPath) return;
try {
const Corestore = require('corestore');
const Autopass = require('autopass');
store = new Corestore(autopassPath);
await store.ready();
pass = new Autopass(store, { key: identity.key, encryptionKey: identity.encryptionKey });
await pass.ready();
pass.on('update', onRemoteUpdate);
log('Sync: reopened existing autopass instance');
} catch (e) {
if (process.stderr) process.stderr.write('[sync-manager] init failed: ' + e.message + '\n');
store = null;
pass = null;
}
})();
await initPromise;
}
async function onRemoteUpdate() {
if (weJustPushed) return;
if (!pass || !holesailManager) return;
try {
const entry = await pass.get(STATE_KEY);
if (!entry || entry.value == null) return;
const str = typeof entry.value === 'string' ? entry.value : (entry.value && entry.value.toString ? entry.value.toString() : '');
if (!str) return;
const snapshot = JSON.parse(str);
await applySyncedState(snapshot);
lastSyncedAt = Date.now();
if (emitEvent) emitEvent('syncApplied', {});
} catch (e) {
if (process.stderr) process.stderr.write('[sync-manager] onRemoteUpdate failed: ' + e.message + '\n');
}
}
/**
* Write state snapshot to state.json and run the same restore flow as backup restore.
* Does not touch certs.
*/
async function applySyncedState(snapshot) {
if (!holesailManager || !setTunnelsRestoredPromise || !restorePersistedTunnels) return;
const statePath = getStateFilePath();
if (!statePath) return;
applyingSync = true;
try {
const dir = path.dirname(statePath);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
const json = JSON.stringify(snapshot, null, 2);
fs.writeFileSync(statePath, json, 'utf8');
await holesailManager.cleanup().catch(() => {});
const restored = holesailManager.restorePersistedState();
setTunnelsRestoredPromise(
restorePersistedTunnels(holesailManager, restored).catch((e) => log('Post-sync tunnel restore failed:', e.message))
);
} finally {
applyingSync = false;
}
}
/**
* Called by holesail-manager after each saveState(). Push snapshot to autopass (if linked and not applying).
*/
function onStateSaved(snapshot) {
if (applyingSync || !pass) return;
(async () => {
try {
weJustPushed = true;
const value = JSON.stringify(snapshot);
await pass.add(STATE_KEY, value);
lastSyncedAt = Date.now();
if (process.nextTick) process.nextTick(() => { weJustPushed = false; });
else setTimeout(() => { weJustPushed = false; }, 0);
} catch (e) {
weJustPushed = false;
if (process.stderr) process.stderr.write('[sync-manager] push failed: ' + e.message + '\n');
}
})();
}
async function getSyncStatus() {
await ensureInitialized();
return {
linked: !!pass,
invite: currentInvite || null,
lastSyncedAt: lastSyncedAt || null
};
}
async function createSyncInvite() {
if (!storageDir) return { ok: false, error: 'Storage path not set' };
await closePass();
clearIdentity();
try {
const Corestore = require('corestore');
const Autopass = require('autopass');
const autopassPath = getAutopassPath();
store = new Corestore(autopassPath);
await store.ready();
pass = new Autopass(store);
await pass.ready();
pass.on('update', onRemoteUpdate);
saveIdentity(pass.key, pass.encryptionKey);
currentInvite = await pass.createInvite();
const snapshot = readCurrentStateFromDisk();
if (snapshot) await pass.add(STATE_KEY, JSON.stringify(snapshot));
log('Sync: created new instance and invite');
return { ok: true, invite: currentInvite };
} catch (e) {
pass = null;
store = null;
if (process.stderr) process.stderr.write('[sync-manager] createSyncInvite: ' + e.message + '\n');
return { ok: false, error: e.message };
}
}
async function pairWithInvite(invite) {
if (!storageDir || !invite || typeof invite !== 'string') return { ok: false, error: 'Storage path and invite required' };
await closePass();
clearIdentity();
try {
const Corestore = require('corestore');
const Autopass = require('autopass');
const autopassPath = getAutopassPath();
store = new Corestore(autopassPath);
const pair = Autopass.pair(store, invite.trim());
pass = await pair.finished();
pass.on('update', onRemoteUpdate);
saveIdentity(pass.key, pass.encryptionKey);
currentInvite = null;
log('Sync: paired with invite');
const entry = await pass.get(STATE_KEY);
if (entry && entry.value != null) {
const str = typeof entry.value === 'string' ? entry.value : (entry.value && entry.value.toString ? entry.value.toString() : '');
if (str) {
const snapshot = JSON.parse(str);
await applySyncedState(snapshot);
lastSyncedAt = Date.now();
if (emitEvent) emitEvent('syncApplied', {});
}
}
return { ok: true };
} catch (e) {
await closePass();
if (process.stderr) process.stderr.write('[sync-manager] pairWithInvite: ' + e.message + '\n');
return { ok: false, error: e.message };
}
}
function readCurrentStateFromDisk() {
const statePath = getStateFilePath();
if (!statePath || !fs.existsSync(statePath)) return null;
try {
const raw = fs.readFileSync(statePath, 'utf8');
const data = JSON.parse(raw);
return data && typeof data === 'object' ? data : null;
} catch (e) {
return null;
}
}
async function cleanup() {
initPromise = null;
weJustPushed = false;
applyingSync = false;
if (pass && pass.off) pass.off('update', onRemoteUpdate);
await closePass();
}
module.exports = {
setStoragePath,
setDeps,
getSyncStatus,
createSyncInvite,
pairWithInvite,
onStateSaved,
applySyncedState,
cleanup
};
+647
View File
@@ -10,6 +10,7 @@
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"assert": "npm:bare-node-assert@^1.0.0", "assert": "npm:bare-node-assert@^1.0.0",
"autopass": "^3.3.0",
"b4a": "^1.6.7", "b4a": "^1.6.7",
"bare-fs": "^4.5.5", "bare-fs": "^4.5.5",
"bare-http1": "^4.0.0", "bare-http1": "^4.0.0",
@@ -21,6 +22,7 @@
"bare-tcp": "^2.2.0", "bare-tcp": "^2.2.0",
"bare-ws": "^2.1.0", "bare-ws": "^2.1.0",
"child_process": "npm:bare-subprocess@^5.2.2", "child_process": "npm:bare-subprocess@^5.2.2",
"corestore": "^7.4.7",
"crypto": "npm:bare-node-crypto@^1.0.0", "crypto": "npm:bare-node-crypto@^1.0.0",
"events": "npm:bare-node-events@^1.0.1", "events": "npm:bare-node-events@^1.0.1",
"fs": "npm:bare-node-fs@^1.0.2", "fs": "npm:bare-node-fs@^1.0.2",
@@ -105,6 +107,58 @@
"bare-assert": "*" "bare-assert": "*"
} }
}, },
"node_modules/autobase": {
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/autobase/-/autobase-7.27.3.tgz",
"integrity": "sha512-eH0UUYYO2kvy9Vug0KLj/mjjSGEslA6YL7axBlPsArlmadBDJmkYj3olpbWVqnsA+VtTndiHGQZyC/004x/aVw==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.1",
"bare-events": "^2.2.0",
"compact-encoding": "^2.16.0",
"core-coupler": "^2.0.0",
"debounceify": "^1.0.0",
"encryption-encoding": "^1.0.3",
"hyperbee": "^2.22.0",
"hypercore": "^11.27.12",
"hypercore-crypto": "^3.4.0",
"hypercore-id-encoding": "^1.2.0",
"hyperschema": "^1.12.1",
"index-encoder": "^3.3.2",
"nanoassert": "^2.0.0",
"protomux-wakeup": "^2.0.0",
"ready-resource": "^1.0.0",
"resolve-reject-promise": "^1.1.0",
"safety-catch": "^1.0.2",
"scope-lock": "^1.2.4",
"signal-promise": "^1.0.3",
"sodium-universal": "^5.0.1",
"sub-encoder": "^2.1.1",
"tiny-buffer-map": "^1.1.1"
}
},
"node_modules/autopass": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/autopass/-/autopass-3.3.0.tgz",
"integrity": "sha512-v9AZqHccu7DegbYDxWPGAKJAO3W/0Vu1B85ZcKzHfa5MrpGL+ng1P4juSncUpVMvzrPQ3t72n3115gyEUP49pA==",
"license": "Apache-2.0",
"dependencies": {
"autobase": "^7.19.4",
"b4a": "^1.7.1",
"blind-encryption-sodium": "^1.0.2",
"blind-pairing": "^2.3.1",
"blind-peering": "^1.13.0",
"corestore": "^7.4.7",
"hyperbee": "^2.26.5",
"hypercore": "^11.16.2",
"hypercore-id-encoding": "^1.3.0",
"hyperdb": "^4.16.1",
"hyperdispatch": "^1.4.2",
"hyperschema": "^1.15.0",
"hyperswarm": "^4.14.0",
"ready-resource": "^1.2.0"
}
},
"node_modules/b4a": { "node_modules/b4a": {
"version": "1.8.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz",
@@ -656,6 +710,12 @@
"bare-ansi-escapes": "^2.2.3" "bare-ansi-escapes": "^2.2.3"
} }
}, },
"node_modules/big-sparse-array": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/big-sparse-array/-/big-sparse-array-1.0.3.tgz",
"integrity": "sha512-6RjV/3mSZORlMdpUaQ6rUSpG637cZm0//E54YYGtQg1c1O+AbZP8UTdJ/TchsDZcTVLmyWZcseBfp2HBeXUXOQ==",
"license": "MIT"
},
"node_modules/bits-to-bytes": { "node_modules/bits-to-bytes": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/bits-to-bytes/-/bits-to-bytes-1.3.0.tgz", "resolved": "https://registry.npmjs.org/bits-to-bytes/-/bits-to-bytes-1.3.0.tgz",
@@ -665,6 +725,93 @@
"b4a": "^1.5.0" "b4a": "^1.5.0"
} }
}, },
"node_modules/blind-encryption-sodium": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/blind-encryption-sodium/-/blind-encryption-sodium-1.0.2.tgz",
"integrity": "sha512-DSMrSNHi5pE12S7iOGDE/wG5ANLXxLpg2uxMNt74H49vqHvCAvOPyD/Z9MMpSYuX4xFGPkMkmLmaZDo0kAUnKQ==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.7.3",
"sodium-universal": "^5.0.1"
}
},
"node_modules/blind-pairing": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/blind-pairing/-/blind-pairing-2.3.1.tgz",
"integrity": "sha512-E678Mi6n07Yc8iRtgwzQLmk9EgHmYHBRzKDxpkdkS+Sh476RQ88iv75cvRkPp62rd7nsrP4lqku8uU9EeKGLiA==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.4",
"blind-pairing-core": "^2.0.0",
"debounceify": "^1.1.0",
"hypercore-crypto": "^3.4.0",
"is-options": "^1.0.2",
"ready-resource": "^1.0.0",
"safety-catch": "^1.0.2",
"xache": "^1.2.0"
}
},
"node_modules/blind-pairing-core": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/blind-pairing-core/-/blind-pairing-core-2.10.0.tgz",
"integrity": "sha512-XBfaQ7yP0eFPwTJEtml3GQ7bFrA43fMBSsLPxL7zXtsnLbempYK9O0yTaL1UxCARh2X1+OdzUqvwidVBpnePfg==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.4",
"bare-events": "^2.5.0",
"bogon": "^1.1.0",
"compact-encoding": "^2.17.0",
"hypercore-crypto": "^3.4.0",
"sodium-universal": "^5.0.1",
"tiny-buffer-map": "^1.1.1"
}
},
"node_modules/blind-peer-encodings": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/blind-peer-encodings/-/blind-peer-encodings-3.2.0.tgz",
"integrity": "sha512-t11kuB5oOX/ewiN89jv7I2us/UvT+o39Q82DR63qWyWlzgufLMlpRZ7A6x/w/+9BcUPF5ClAA4/5NwbTb3jv4w==",
"license": "Apache-2.0",
"dependencies": {
"compact-encoding": "^2.16.0",
"hyperdb": "^5.0.0"
}
},
"node_modules/blind-peer-encodings/node_modules/hyperdb": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/hyperdb/-/hyperdb-5.0.1.tgz",
"integrity": "sha512-aC9FN/HbMG5Fw4ZE3pYXR0hUdxl3WNY1j893n1i2c3uf1xfxqdlv/LIALyKE4YfEze/q9hXUcoA5LKbPxoMzTg==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.6",
"compact-encoding": "^2.15.0",
"generate-object-property": "^2.0.0",
"generate-string": "^1.0.1",
"hyperbee": "^2.24.2",
"hyperschema": "^1.9.2",
"index-encoder": "^3.4.0",
"refcounter": "^1.0.0",
"rocksdb-native": "^3.0.0",
"scope-lock": "^1.2.4",
"streamx": "^2.20.0"
}
},
"node_modules/blind-peering": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/blind-peering/-/blind-peering-1.15.0.tgz",
"integrity": "sha512-/Bx2EElemEnN9fKMJ8pUjGkb4q2qiV14rvGzd8/8xn6VjLq58y389vivKGQUKG5cJh/tyfn86m/GU0cyZltnqA==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.7",
"blind-peer-encodings": "^3.1.0",
"compact-encoding": "^2.16.0",
"hypercore-id-encoding": "^1.3.0",
"protomux-rpc": "^1.7.0",
"ready-resource": "^1.1.2",
"safety-catch": "^1.0.2",
"signal-promise": "^1.0.3",
"xor-distance": "^2.0.0"
}
},
"node_modules/blind-relay": { "node_modules/blind-relay": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/blind-relay/-/blind-relay-1.4.0.tgz", "resolved": "https://registry.npmjs.org/blind-relay/-/blind-relay-1.4.0.tgz",
@@ -758,6 +905,15 @@
"ul": "^5.2.1" "ul": "^5.2.1"
} }
}, },
"node_modules/codecs": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/codecs/-/codecs-3.1.0.tgz",
"integrity": "sha512-Dqx8NwvBvnMeuPQdVKy/XEF71igjR5apxBvCGeV0pP1tXadOiaLvDTXt7xh+/5wI1ASB195mXQGJbw3Ml4YDWQ==",
"license": "MIT",
"dependencies": {
"b4a": "^1.6.3"
}
},
"node_modules/colors": { "node_modules/colors": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
@@ -801,6 +957,31 @@
"license": "MIT", "license": "MIT",
"optional": true "optional": true
}, },
"node_modules/core-coupler": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/core-coupler/-/core-coupler-2.0.0.tgz",
"integrity": "sha512-FJuEvsdCMwx0Wu+gFQ49rGCi8LCXh8kizHsCQwkdgPZFEFiF0z2HDvyIs+fPt5wMIfU2UVFDuN+dtpfbIxJE6g==",
"license": "Apache-2.0",
"dependencies": {
"safety-catch": "^1.0.2"
}
},
"node_modules/corestore": {
"version": "7.9.1",
"resolved": "https://registry.npmjs.org/corestore/-/corestore-7.9.1.tgz",
"integrity": "sha512-iidOmmDozIV+WGSJkbEWEtDe80YQpVvh4p5sgnV40pq8tTeBb05i0grTcFbp2VUGeKl24o+qHD2AKlWeqL35eA==",
"license": "MIT",
"dependencies": {
"b4a": "^1.6.7",
"hypercore": "^11.19.0",
"hypercore-crypto": "^3.4.2",
"hypercore-errors": "^1.4.0",
"hypercore-id-encoding": "^1.3.0",
"ready-resource": "^1.1.1",
"sodium-universal": "^5.0.1",
"which-runtime": "^1.2.1"
}
},
"node_modules/crypto": { "node_modules/crypto": {
"name": "bare-node-crypto", "name": "bare-node-crypto",
"version": "1.0.0", "version": "1.0.0",
@@ -811,6 +992,12 @@
"bare-crypto": "*" "bare-crypto": "*"
} }
}, },
"node_modules/debounceify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/debounceify/-/debounceify-1.1.0.tgz",
"integrity": "sha512-eKuHDVfJVg+u/0nPy8P+fhnLgbyuTgVxuCRrS/R7EpDSMMkBDgSes41MJtSAY1F1hcqfHz3Zy/qpqHHIp/EhdA==",
"license": "MIT"
},
"node_modules/deffy": { "node_modules/deffy": {
"version": "2.2.5", "version": "2.2.5",
"resolved": "https://registry.npmjs.org/deffy/-/deffy-2.2.5.tgz", "resolved": "https://registry.npmjs.org/deffy/-/deffy-2.2.5.tgz",
@@ -820,6 +1007,29 @@
"typpy": "^2.0.0" "typpy": "^2.0.0"
} }
}, },
"node_modules/device-file": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/device-file/-/device-file-2.3.1.tgz",
"integrity": "sha512-bmON44lwxJPle9N2OcH4tqM44pMGZKT8G6OkzXkz0urvqQ9LKkoQdTS6w0ztYfmLdUE27R4UUmx0+y2gEz4Jug==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.7",
"bare-fs": "^4.0.1",
"bare-path": "^3.0.0",
"fd-lock": "^2.1.0",
"fs-native-extensions": "^1.4.0",
"ready-resource": "^1.2.0"
}
},
"node_modules/device-file/node_modules/bare-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
"integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
"license": "Apache-2.0",
"dependencies": {
"bare-os": "^3.0.1"
}
},
"node_modules/dht-rpc": { "node_modules/dht-rpc": {
"version": "6.26.3", "version": "6.26.3",
"resolved": "https://registry.npmjs.org/dht-rpc/-/dht-rpc-6.26.3.tgz", "resolved": "https://registry.npmjs.org/dht-rpc/-/dht-rpc-6.26.3.tgz",
@@ -854,6 +1064,15 @@
"node": ">=0.10" "node": ">=0.10"
} }
}, },
"node_modules/encryption-encoding": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/encryption-encoding/-/encryption-encoding-1.0.3.tgz",
"integrity": "sha512-+SlKCeULnNnwBF2rGrJlSLYjwITwfjO8PLSHzue4yt+2DsVJUU8GakRkH4+mTaa3FzpxVjpTy1kKR2gYGctNWg==",
"license": "Apache-2.0",
"dependencies": {
"hyperschema": "^1.19.0"
}
},
"node_modules/events": { "node_modules/events": {
"name": "bare-node-events", "name": "bare-node-events",
"version": "1.0.1", "version": "1.0.1",
@@ -879,6 +1098,24 @@
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/fd-lock": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/fd-lock/-/fd-lock-2.1.1.tgz",
"integrity": "sha512-H3VkcWFl39Rk0xBokDcUyBcVs6VrYTUo/DhDWMzJOF99wXWDAvmvq/GlLTS2NTehsznZhp3fXVyrtB2ldDXhwg==",
"license": "Apache-2.0",
"dependencies": {
"bare-fs": "^4.5.0",
"fs-native-extensions": "^1.4.4",
"ready-resource": "^1.2.0",
"resource-on-exit": "^1.0.0"
}
},
"node_modules/flat-tree": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/flat-tree/-/flat-tree-1.13.0.tgz",
"integrity": "sha512-fT3HIuCPwHhFgJ20QYzDHgUG0zMmFg5cHvFiFo5h+QMSJ28TihsEVY0f8HGliuO+pOzmvjMx1odToeaEWkTnyQ==",
"license": "MIT"
},
"node_modules/fs": { "node_modules/fs": {
"name": "bare-node-fs", "name": "bare-node-fs",
"version": "1.0.2", "version": "1.0.2",
@@ -889,6 +1126,16 @@
"bare-fs": "*" "bare-fs": "*"
} }
}, },
"node_modules/fs-native-extensions": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/fs-native-extensions/-/fs-native-extensions-1.4.5.tgz",
"integrity": "sha512-ekV0T//iDm4AvhOcuPaHpxub4DI7HvY5ucLJVDvi7T2J+NZkQ9S6MuvgP0yeQvoqNUaAGyLjVYb1905BF9bpmg==",
"license": "Apache-2.0",
"dependencies": {
"require-addon": "^1.1.0",
"which-runtime": "^1.2.0"
}
},
"node_modules/function.name": { "node_modules/function.name": {
"version": "1.0.14", "version": "1.0.14",
"resolved": "https://registry.npmjs.org/function.name/-/function.name-1.0.14.tgz", "resolved": "https://registry.npmjs.org/function.name/-/function.name-1.0.14.tgz",
@@ -898,6 +1145,21 @@
"noop6": "^1.0.1" "noop6": "^1.0.1"
} }
}, },
"node_modules/generate-object-property": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-2.0.0.tgz",
"integrity": "sha512-KwuURPyqn2Mz8DdV29pJwQu0Y7tcsbkULr82eeOcY/ZllFK6I9Wm8dsRByIu7CKWlFi9BdW1b3mcXMp/kQBQsw==",
"license": "MIT",
"dependencies": {
"is-property": "^1.0.0"
}
},
"node_modules/generate-string": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/generate-string/-/generate-string-1.0.1.tgz",
"integrity": "sha512-IfTY0dKZM43ACyGvXkbG7De7WY7MxTS5VO6Juhe8oJKpCmrYYXoqp/cJMskkpi0k9H8wuXq0H+eI898/BCqvXg==",
"license": "MIT"
},
"node_modules/glob": { "node_modules/glob": {
"version": "6.0.4", "version": "6.0.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
@@ -1077,6 +1339,55 @@
} }
} }
}, },
"node_modules/hyperbee": {
"version": "2.27.3",
"resolved": "https://registry.npmjs.org/hyperbee/-/hyperbee-2.27.3.tgz",
"integrity": "sha512-PXURH2U4juUZyJRKHTrY5z1zX851pmI1Q0jfv5F/hCIErDt/ND8jOZuxc3hfOLM9f0W3qJEDTMlV5AJBkVPy8w==",
"license": "MIT",
"dependencies": {
"b4a": "^1.6.0",
"codecs": "^3.0.0",
"debounceify": "^1.0.0",
"hypercore-errors": "^1.0.0",
"mutexify": "^1.4.0",
"protocol-buffers-encodings": "^1.2.0",
"rache": "^1.0.0",
"ready-resource": "^1.0.0",
"resolve-reject-promise": "^1.1.0",
"safety-catch": "^1.0.2",
"streamx": "^2.12.4",
"unslab": "^1.2.0"
}
},
"node_modules/hypercore": {
"version": "11.27.14",
"resolved": "https://registry.npmjs.org/hypercore/-/hypercore-11.27.14.tgz",
"integrity": "sha512-tyBNOwI5ZBtJBtWkcozAeq5ol5PH8hS1AVUU9gGAm+s5pUmzWC7HU5XhNmdP203eOEcwgTDX5AKyNwqFqI3iPg==",
"license": "MIT",
"dependencies": {
"@hyperswarm/secret-stream": "^6.0.0",
"b4a": "^1.1.0",
"bare-events": "^2.2.0",
"big-sparse-array": "^1.0.3",
"compact-encoding": "^2.11.0",
"fast-fifo": "^1.3.0",
"flat-tree": "^1.9.0",
"hypercore-crypto": "^3.2.1",
"hypercore-errors": "^1.5.0",
"hypercore-id-encoding": "^1.2.0",
"hypercore-storage": "^2.0.0",
"is-options": "^1.0.1",
"nanoassert": "^2.0.0",
"protomux": "^3.5.0",
"quickbit-universal": "^2.2.0",
"random-array-iterator": "^1.0.0",
"safety-catch": "^1.0.1",
"sodium-universal": "^5.0.1",
"streamx": "^2.12.4",
"unslab": "^1.3.0",
"z32": "^1.0.0"
}
},
"node_modules/hypercore-crypto": { "node_modules/hypercore-crypto": {
"version": "3.6.1", "version": "3.6.1",
"resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.6.1.tgz", "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.6.1.tgz",
@@ -1088,6 +1399,15 @@
"sodium-universal": "^5.0.0" "sodium-universal": "^5.0.0"
} }
}, },
"node_modules/hypercore-errors": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/hypercore-errors/-/hypercore-errors-1.5.0.tgz",
"integrity": "sha512-5KQ/SuDxsvet+7qWA35Ay6zdD9WyAHQoyWHGcPUTbmJBd300gvNIJoi3oma7kp4TTCSzii6qYumNZe/s0j/saQ==",
"license": "Apache-2.0",
"dependencies": {
"hypercore-id-encoding": "^1.3.0"
}
},
"node_modules/hypercore-id-encoding": { "node_modules/hypercore-id-encoding": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/hypercore-id-encoding/-/hypercore-id-encoding-1.3.0.tgz", "resolved": "https://registry.npmjs.org/hypercore-id-encoding/-/hypercore-id-encoding-1.3.0.tgz",
@@ -1098,6 +1418,55 @@
"z32": "^1.0.0" "z32": "^1.0.0"
} }
}, },
"node_modules/hypercore-storage": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/hypercore-storage/-/hypercore-storage-2.7.1.tgz",
"integrity": "sha512-eT3s227nS17c8+HWjl6ge0l1lPk9NDtBzQH///tOSZjCG4gxk0QnjhlJVS5q1dcnNB54Gs75JKceP798IymvTQ==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.7",
"bare-fs": "^4.0.1",
"bare-path": "^3.0.0",
"compact-encoding": "^2.16.0",
"device-file": "^2.1.2",
"flat-tree": "^1.12.1",
"hypercore-crypto": "^3.4.2",
"hyperschema": "^1.7.0",
"index-encoder": "^3.3.2",
"resolve-reject-promise": "^1.0.0",
"rocksdb-native": "^3.11.0",
"scope-lock": "^1.2.4",
"streamx": "^2.21.1"
}
},
"node_modules/hypercore-storage/node_modules/bare-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
"integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
"license": "Apache-2.0",
"dependencies": {
"bare-os": "^3.0.1"
}
},
"node_modules/hyperdb": {
"version": "4.22.3",
"resolved": "https://registry.npmjs.org/hyperdb/-/hyperdb-4.22.3.tgz",
"integrity": "sha512-CZifHGDiOKQbZLWQL7pH0Sct3AjmXG96MeZdE6Y8YH5vBqKif22YhfmaVk35ko/maXCl68jM/uFYUB62hxgA4w==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.6",
"compact-encoding": "^2.15.0",
"generate-object-property": "^2.0.0",
"generate-string": "^1.0.1",
"hyperbee": "^2.24.2",
"hyperschema": "^1.9.2",
"index-encoder": "^3.4.0",
"refcounter": "^1.0.0",
"rocksdb-native": "^3.0.0",
"scope-lock": "^1.2.4",
"streamx": "^2.20.0"
}
},
"node_modules/hyperdht": { "node_modules/hyperdht": {
"version": "6.29.0", "version": "6.29.0",
"resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.29.0.tgz", "resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.29.0.tgz",
@@ -1128,6 +1497,66 @@
"hyperdht": "bin.js" "hyperdht": "bin.js"
} }
}, },
"node_modules/hyperdispatch": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/hyperdispatch/-/hyperdispatch-1.5.1.tgz",
"integrity": "sha512-lQoN1dOH67fnnTzua5u05diaCRWU2X6OTk93r5LDpK5dquH8IBy/8lI67Dk0JW4TqPlFOnyF49dsCsJBdtNcEA==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.7",
"bare-fs": "^4.2.3",
"bare-path": "^3.0.0",
"compact-encoding": "^2.16.0",
"generate-string": "^1.0.1",
"hyperschema": "^1.3.2",
"nanoassert": "^2.0.0"
}
},
"node_modules/hyperdispatch/node_modules/bare-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
"integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
"license": "Apache-2.0",
"dependencies": {
"bare-os": "^3.0.1"
}
},
"node_modules/hyperschema": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/hyperschema/-/hyperschema-1.20.1.tgz",
"integrity": "sha512-7gnvaTNUs8FqdrU3NNE6nzhOneGyDlUlpROu9YFlHA61fE8ppKKf2gLZjZjsx/enNt+VFN4ITxy8ijypfyWBLw==",
"license": "Apache-2.0",
"dependencies": {
"bare-fs": "^4.0.1",
"compact-encoding": "^2.19.0",
"generate-object-property": "^2.0.0",
"generate-string": "^1.0.1"
}
},
"node_modules/hyperswarm": {
"version": "4.17.0",
"resolved": "https://registry.npmjs.org/hyperswarm/-/hyperswarm-4.17.0.tgz",
"integrity": "sha512-oe86sK961Ueg7rvDN/veFwG8xH+Iv6vObPhGDkPJcDVxk/NduW41ZhAcVDnHzRbm7S0eLU7WaDUvehOYoKSpRQ==",
"license": "MIT",
"dependencies": {
"b4a": "^1.3.1",
"bare-events": "^2.2.0",
"hyperdht": "^6.21.0",
"safety-catch": "^1.0.2",
"shuffled-priority-queue": "^2.1.0",
"streamx": "^2.22.1",
"unslab": "^1.3.0"
}
},
"node_modules/index-encoder": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/index-encoder/-/index-encoder-3.5.0.tgz",
"integrity": "sha512-idZ1cxtZz2dRV6rUiaP9Xo99UjXbSzjcMacoQmxUMu/A7fEQcNPngvwDJYeWelQUS5XFlY71/or70lKn6XnwbQ==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.4"
}
},
"node_modules/inflight": { "node_modules/inflight": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -1147,6 +1576,21 @@
"license": "ISC", "license": "ISC",
"optional": true "optional": true
}, },
"node_modules/is-options": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-options/-/is-options-1.0.2.tgz",
"integrity": "sha512-u+Ai74c8Q74aS8BuHwPdI1jptGOT1FQXgCq8/zv0xRuE+wRgSMEJLj8lVO8Zp9BeGb29BXY6AsNPinfqjkr7Fg==",
"license": "MIT",
"dependencies": {
"b4a": "^1.1.1"
}
},
"node_modules/is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
"license": "MIT"
},
"node_modules/is-undefined": { "node_modules/is-undefined": {
"version": "1.0.12", "version": "1.0.12",
"resolved": "https://registry.npmjs.org/is-undefined/-/is-undefined-1.0.12.tgz", "resolved": "https://registry.npmjs.org/is-undefined/-/is-undefined-1.0.12.tgz",
@@ -1285,6 +1729,15 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/mutexify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.4.0.tgz",
"integrity": "sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==",
"license": "MIT",
"dependencies": {
"queue-tick": "^1.0.0"
}
},
"node_modules/mv": { "node_modules/mv": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
@@ -1469,6 +1922,17 @@
"bare-tty": "^5.0.0" "bare-tty": "^5.0.0"
} }
}, },
"node_modules/protocol-buffers-encodings": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/protocol-buffers-encodings/-/protocol-buffers-encodings-1.2.0.tgz",
"integrity": "sha512-daeNPuKh1NlLD1uDfbLpD+xyUTc07nEtfHwmBZmt/vH0B7VOM+JOCOpDcx9ZRpqHjAiIkGqyTDi+wfGSl17R9w==",
"license": "MIT",
"dependencies": {
"b4a": "^1.6.0",
"signed-varint": "^2.0.1",
"varint": "5.0.0"
}
},
"node_modules/protomux": { "node_modules/protomux": {
"version": "3.10.1", "version": "3.10.1",
"resolved": "https://registry.npmjs.org/protomux/-/protomux-3.10.1.tgz", "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.10.1.tgz",
@@ -1482,6 +1946,34 @@
"unslab": "^1.3.0" "unslab": "^1.3.0"
} }
}, },
"node_modules/protomux-rpc": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/protomux-rpc/-/protomux-rpc-1.9.0.tgz",
"integrity": "sha512-+nOXXIDKZL849F6adj3R1SEi7PfPmvS6Y9HAArVC/RpONepRFq/Ot1LHVA+vyDKQMxBd8xTCHfy2fc3tchE1gA==",
"license": "Apache-2.0",
"dependencies": {
"bits-to-bytes": "^1.0.0",
"compact-encoding": "^2.6.1",
"compact-encoding-bitfield": "^1.0.0",
"protomux": "^3.7.0",
"safety-catch": "^1.0.2"
},
"optionalDependencies": {
"bare-events": "^2.2.0"
}
},
"node_modules/protomux-wakeup": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/protomux-wakeup/-/protomux-wakeup-2.9.0.tgz",
"integrity": "sha512-K93WhS9qIRL8WAQPU2V3PxdpQ1oRlQCFyCyIYInoq/iRkcuO1BegRERWo7SS1Tcme14VKGWOr/IivjjYnHan3Q==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.7",
"hypercore-crypto": "^3.5.0",
"hyperschema": "^1.10.4",
"protomux": "^3.10.1"
}
},
"node_modules/qrcode-terminal": { "node_modules/qrcode-terminal": {
"version": "0.12.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz",
@@ -1506,6 +1998,41 @@
"integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/quickbit-native": {
"version": "2.4.8",
"resolved": "https://registry.npmjs.org/quickbit-native/-/quickbit-native-2.4.8.tgz",
"integrity": "sha512-FcCcqI+nIAWGknqhtrYT5TSD7t/N+Xd8ctM+2PrIIBuwOi5hx0SxAvuPtzLIEMfT/2h9+fhBakUe2uALOHX6yw==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"require-addon": "^1.1.0"
}
},
"node_modules/quickbit-universal": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/quickbit-universal/-/quickbit-universal-2.2.0.tgz",
"integrity": "sha512-w02i1R8n7+6pEKTud8DfF8zbFY9o7RtPlUc3jWbtCkDKvhbx/AvV7oNnz4/TcmsPGpSJS+fq5Ud6RH6+YPvSGg==",
"license": "ISC",
"dependencies": {
"b4a": "^1.6.0",
"simdle-universal": "^1.1.0"
},
"optionalDependencies": {
"quickbit-native": "^2.2.0"
}
},
"node_modules/rache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/rache/-/rache-1.0.0.tgz",
"integrity": "sha512-e0k0g0w/8jOCB+7YqCIlOa+OJ38k0wrYS4x18pMSmqOvLKoyhmMhmQyCcvfY6VaP8D75cqkEnlakXs+RYYLqNg==",
"license": "Apache-2.0"
},
"node_modules/random-array-iterator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-array-iterator/-/random-array-iterator-1.0.0.tgz",
"integrity": "sha512-u7xCM93XqKEvPTP6xZp2ehttcAemKnh73oKNf1FvzuVCfpt6dILDt1Kxl1LeBjm2iNIeR49VGFhy4Iz3yOun+Q==",
"license": "MIT"
},
"node_modules/ready-resource": { "node_modules/ready-resource": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/ready-resource/-/ready-resource-1.2.0.tgz", "resolved": "https://registry.npmjs.org/ready-resource/-/ready-resource-1.2.0.tgz",
@@ -1524,6 +2051,12 @@
"b4a": "^1.3.1" "b4a": "^1.3.1"
} }
}, },
"node_modules/refcounter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/refcounter/-/refcounter-1.0.0.tgz",
"integrity": "sha512-1WosVzUy0kPUaPMEtlNDwm99UsteALIhXXR8rerELoa63WkYIXAl0hxgwPFrIYBRWZPGUyekQ04FRtPJ7dHk9w==",
"license": "Apache-2.0"
},
"node_modules/require-addon": { "node_modules/require-addon": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz",
@@ -1536,6 +2069,18 @@
"bare": ">=1.10.0" "bare": ">=1.10.0"
} }
}, },
"node_modules/resolve-reject-promise": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/resolve-reject-promise/-/resolve-reject-promise-1.1.0.tgz",
"integrity": "sha512-LWsTOA91AqzBTjSGgX79Tc130pwcBK6xjpJEO+qRT5IKZ6bGnHKcc8QL3upUBcWuU8OTIDzKK2VNSwmmlqvAVg==",
"license": "MIT"
},
"node_modules/resource-on-exit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resource-on-exit/-/resource-on-exit-1.0.0.tgz",
"integrity": "sha512-ViJwJAknCkLRJRPR+9SISQQ7R5eRgtdIHLJsM2hHx1MweAJbJxJ5XnMjjq0Lc7ZGv44ufzAqds1nKxiVkdy4ag==",
"license": "Apache-2.0"
},
"node_modules/rimraf": { "node_modules/rimraf": {
"version": "2.4.5", "version": "2.4.5",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
@@ -1550,6 +2095,24 @@
"rimraf": "bin.js" "rimraf": "bin.js"
} }
}, },
"node_modules/rocksdb-native": {
"version": "3.13.2",
"resolved": "https://registry.npmjs.org/rocksdb-native/-/rocksdb-native-3.13.2.tgz",
"integrity": "sha512-FB8gH5eBo+SjqA7uDVGWHe2zlYugF8H775tueWAl+jK26zZvxPP8nXCgs5rZTjMhdBY7wYC2nm3V20pyAKbkcQ==",
"license": "Apache-2.0",
"dependencies": {
"compact-encoding": "^2.15.0",
"ready-resource": "^1.0.0",
"refcounter": "^1.0.0",
"require-addon": "^1.0.2",
"resolve-reject-promise": "^1.1.0",
"signal-promise": "^1.0.3",
"streamx": "^2.16.1"
},
"engines": {
"bare": ">=1.16.0"
}
},
"node_modules/safe-json-stringify": { "node_modules/safe-json-stringify": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz",
@@ -1563,12 +2126,59 @@
"integrity": "sha512-C1UYVZ4dtbBxEtvOcpjBaaD27nP8MlvyAQEp2fOTOEe6pfUpk1cDUxij6BR1jZup6rSyUTaBBplK7LanskrULA==", "integrity": "sha512-C1UYVZ4dtbBxEtvOcpjBaaD27nP8MlvyAQEp2fOTOEe6pfUpk1cDUxij6BR1jZup6rSyUTaBBplK7LanskrULA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/scope-lock": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/scope-lock/-/scope-lock-1.2.4.tgz",
"integrity": "sha512-BpSd8VCuCxW9ZitcdIC/vjs3gMaP9bRBL5nkHcyfX2VrS52n13/rHuBA2xJ/S/4DPuRdAO/Bk8pWd8eD/gHCIA==",
"license": "Apache-2.0"
},
"node_modules/shuffled-priority-queue": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/shuffled-priority-queue/-/shuffled-priority-queue-2.1.0.tgz",
"integrity": "sha512-xhdh7fHyMsr0m/w2kDfRJuBFRS96b9l8ZPNWGaQ+PMvnUnZ/Eh+gJJ9NsHBd7P9k0399WYlCLzsy18EaMfyadA==",
"license": "MIT",
"dependencies": {
"unordered-set": "^2.0.1"
}
},
"node_modules/signal-promise": { "node_modules/signal-promise": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/signal-promise/-/signal-promise-1.0.3.tgz", "resolved": "https://registry.npmjs.org/signal-promise/-/signal-promise-1.0.3.tgz",
"integrity": "sha512-WBgv0UnIq2C+Aeh0/n+IRpP6967eIx9WpynTUoiW3isPpfe1zu2LJzyfXdo9Tgef8yR/sGjcMvoUXD7EYdiz+g==", "integrity": "sha512-WBgv0UnIq2C+Aeh0/n+IRpP6967eIx9WpynTUoiW3isPpfe1zu2LJzyfXdo9Tgef8yR/sGjcMvoUXD7EYdiz+g==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/signed-varint": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/signed-varint/-/signed-varint-2.0.1.tgz",
"integrity": "sha512-abgDPg1106vuZZOvw7cFwdCABddfJRz5akcCcchzTbhyhYnsG31y4AlZEgp315T7W3nQq5P4xeOm186ZiPVFzw==",
"license": "MIT",
"dependencies": {
"varint": "~5.0.0"
}
},
"node_modules/simdle-native": {
"version": "1.3.9",
"resolved": "https://registry.npmjs.org/simdle-native/-/simdle-native-1.3.9.tgz",
"integrity": "sha512-Isc8sP4OiiIU0mpslD4GHEnR0VQWvR/54WN7YtwEDkNdTJVWtpmvsSvsgRlw5BNGxdYXlVRegdnrSu10H/PhvA==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"b4a": "^1.6.0",
"require-addon": "^1.1.0"
}
},
"node_modules/simdle-universal": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/simdle-universal/-/simdle-universal-1.1.2.tgz",
"integrity": "sha512-3n3w1bs+uwgHKQjt6arez83EywNlhZzYvNOhvAASTl/8KqNIcqr6aHyGt3JRlfuUC7iB0tomJRPlJ2cRGIpBzA==",
"license": "ISC",
"dependencies": {
"b4a": "^1.6.0"
},
"optionalDependencies": {
"simdle-native": "^1.1.1"
}
},
"node_modules/sodium-native": { "node_modules/sodium-native": {
"version": "5.0.10", "version": "5.0.10",
"resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz", "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.0.10.tgz",
@@ -1630,6 +2240,16 @@
"text-decoder": "^1.1.0" "text-decoder": "^1.1.0"
} }
}, },
"node_modules/sub-encoder": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/sub-encoder/-/sub-encoder-2.1.3.tgz",
"integrity": "sha512-Xxx04ygZo/1J3yHvaSA6VhDmiSaBQkw/PmO3YnnYFXle+tfOGToC6FcDpIfMztWZXJzuKG14b/57HMkiL58C6A==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.0",
"codecs": "^3.1.0"
}
},
"node_modules/teex": { "node_modules/teex": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
@@ -1660,6 +2280,15 @@
"integrity": "sha512-SVqEcMZBsZF9mA78rjzCrYrUs37LMJk3ShZ851ygZYW1cMeIjs9mL57KO6Iv5mmjSQnOe/29/VAfGXo+oRCiVw==", "integrity": "sha512-SVqEcMZBsZF9mA78rjzCrYrUs37LMJk3ShZ851ygZYW1cMeIjs9mL57KO6Iv5mmjSQnOe/29/VAfGXo+oRCiVw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/tiny-buffer-map": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/tiny-buffer-map/-/tiny-buffer-map-1.1.1.tgz",
"integrity": "sha512-C1eDw6ks9CmkDbWVCPHobuixPTkxGa7IDERlaVk98dv4tOUdz42o3haHBr0uhNxbj0gczBTVIyS2uQsu+1vc2Q==",
"license": "MIT",
"dependencies": {
"b4a": "^1.6.0"
}
},
"node_modules/tls": { "node_modules/tls": {
"name": "bare-node-tls", "name": "bare-node-tls",
"version": "1.0.0", "version": "1.0.0",
@@ -1715,6 +2344,12 @@
"typpy": "^2.3.4" "typpy": "^2.3.4"
} }
}, },
"node_modules/unordered-set": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/unordered-set/-/unordered-set-2.0.1.tgz",
"integrity": "sha512-eUmNTPzdx+q/WvOHW0bgGYLWvWHNT3PTKEQLg0MAQhc0AHASHVHoP/9YytYd4RBVariqno/mEUhVZN98CmD7bg==",
"license": "MIT"
},
"node_modules/unslab": { "node_modules/unslab": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/unslab/-/unslab-1.3.0.tgz", "resolved": "https://registry.npmjs.org/unslab/-/unslab-1.3.0.tgz",
@@ -1753,6 +2388,12 @@
"bare-utils": "*" "bare-utils": "*"
} }
}, },
"node_modules/varint": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/varint/-/varint-5.0.0.tgz",
"integrity": "sha512-gC13b/bWrqQoKY2EmROCZ+AR0jitc6DnDGaQ6Ls9QpKmuSgJB1eQ7H3KETtQm7qSdMWMKCmsshyCmUwMLh3OAA==",
"license": "MIT"
},
"node_modules/which-runtime": { "node_modules/which-runtime": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz", "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz",
@@ -1772,6 +2413,12 @@
"integrity": "sha512-igRS6jPreJ54ABdzhh4mCDXcz+XMaWO2q1ABRV2yWYuk29jlp8VT7UBdCqNkX7rpYBbXsebVVKkwIuYZjyZNqA==", "integrity": "sha512-igRS6jPreJ54ABdzhh4mCDXcz+XMaWO2q1ABRV2yWYuk29jlp8VT7UBdCqNkX7rpYBbXsebVVKkwIuYZjyZNqA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/xor-distance": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xor-distance/-/xor-distance-2.0.0.tgz",
"integrity": "sha512-AsAqZfPAuWx7qB/0kyRDUEvoU3QKsHWzHU9smFlkaiprEpGfJ/NBbLze2Uq0rdkxCxkNM9uOLvz/KoNBCbZiLQ==",
"license": "MIT"
},
"node_modules/z32": { "node_modules/z32": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/z32/-/z32-1.1.0.tgz", "resolved": "https://registry.npmjs.org/z32/-/z32-1.1.0.tgz",
+6
View File
@@ -11,7 +11,9 @@
}, },
"dependencies": { "dependencies": {
"assert": "npm:bare-node-assert@^1.0.0", "assert": "npm:bare-node-assert@^1.0.0",
"autopass": "^3.3.0",
"b4a": "^1.6.7", "b4a": "^1.6.7",
"corestore": "^7.4.7",
"bare-fs": "^4.5.5", "bare-fs": "^4.5.5",
"bare-http1": "^4.0.0", "bare-http1": "^4.0.0",
"bare-https": "^2.1.2", "bare-https": "^2.1.2",
@@ -73,6 +75,10 @@
"bare": "path", "bare": "path",
"default": "path" "default": "path"
}, },
"process": {
"bare": "bare-process",
"default": "process"
},
"os": { "os": {
"bare": "os", "bare": "os",
"default": "os" "default": "os"
+7 -1
View File
@@ -67,6 +67,11 @@ function makeMockDeps(overrides = {}) {
pruneOldBackups: () => {}, pruneOldBackups: () => {},
DEFAULT_RETENTION: 5 DEFAULT_RETENTION: 5
}, },
syncManager: {
getSyncStatus: async () => ({}),
createSyncInvite: async () => ({}),
pairWithInvite: async () => ({})
},
...overrides ...overrides
}; };
} }
@@ -84,7 +89,8 @@ describe('handler registry', () => {
'installRootCA', 'installRootCA',
'startSshSession', 'stopSshSession', 'resizeSshSession', 'getSshSessions', 'startSshSession', 'stopSshSession', 'resizeSshSession', 'getSshSessions',
'startRdpSession', 'stopRdpSession', 'getRdpSessions', 'startRdpSession', 'stopRdpSession', 'getRdpSessions',
'createBackup', 'listBackups', 'restoreBackup', 'deleteBackup' 'createBackup', 'listBackups', 'restoreBackup', 'deleteBackup',
'getSyncStatus', 'createSyncInvite', 'pairWithInvite'
]; ];
for (const type of expectedTypes) { for (const type of expectedTypes) {
assert.ok(handlers.has(type), `missing handler: ${type}`); assert.ok(handlers.has(type), `missing handler: ${type}`);