tiddlywiki-plugin-dev
Version:
[](https://github.com/tiddly-gittly)
147 lines (135 loc) • 4.71 kB
JavaScript
const listenerTemplate = `(function () {
const moduleExports = exports;
moduleExports.name = 'devweb-listner';
moduleExports.platforms = ['browser'];
moduleExports.after = ['load-modules'];
moduleExports.synchronous = true;
moduleExports.startup = function () {
const WS =
(typeof globalThis !== 'undefined' && globalThis.WebSocket) ||
(typeof window !== 'undefined' ? window.WebSocket : undefined);
if (!WS) {
console.error(
'[Modern.TiddlyDev]',
'Unsupported browser, need WebSocket support',
);
return;
}
// Connect to the same host:port as the wiki page via /__dev_ws path.
// This means only one port needs to be forwarded through SSH / VS Code tunnels.
// The build generation is baked into the URL so the dev server can decide,
// at connection time, whether this page belongs to an older build and must
// reload. This is timing-independent and survives the WebSocket reconnect
// gap that happens whenever the server is restarted.
var generation = __DEV_BUILD_GENERATION__;
var protocol = document.location.protocol === 'https:' ? 'wss' : 'ws';
var url =
protocol +
'://' +
document.location.host +
'/__dev_ws?gen=' +
generation;
var reconnectDelay = 1000;
var maxReconnectDelay = 10000;
var reconnectTimer = null;
var disposed = false;
function connect() {
if (disposed) return;
var socket = new WS(url);
socket.onopen = function () {
reconnectDelay = 1000; // reset backoff
console.debug(
'[Modern.TiddlyDev]',
'Dev WebSocket connected - auto-refresh enabled.',
);
};
socket.onmessage = function (event) {
switch (event.data) {
case 'bye': {
socket.close();
break;
}
case 'refresh': {
disposed = true;
socket.close();
document.location.reload();
break;
}
default:
break;
}
};
socket.onclose = function () {
if (disposed) return;
console.warn(
'[Modern.TiddlyDev]',
'Dev WebSocket closed - reconnecting in ' +
Math.round(reconnectDelay / 1000) +
's...',
);
reconnectTimer = setTimeout(function () {
reconnectDelay = Math.min(
reconnectDelay * 1.5,
maxReconnectDelay,
);
connect();
}, reconnectDelay);
};
socket.onerror = function () {
// onclose will fire after this and handle reconnection.
};
return socket;
}
var socket = connect();
// ---------- watch for browser-initiated saves ----------
// When the browser saves tiddlers to disk (e.g. bulk-installing plugins,
// editing tiddlers in write-wiki mode) tell the dev server to pause
// watch-triggered restarts. Otherwise the first saved file would trigger
// a chokidar event → server restart → remaining saves fail with
// "Network Error" because the server is restarting.
var saveEndTimer = null;
var saving = false;
var notifySaveStart = function () {
if (saving) return;
saving = true;
try { socket.send('save-start'); } catch (_) {}
};
var notifySaveEnd = function () {
if (!saving) return;
saving = false;
try { socket.send('save-end'); } catch (_) {}
};
var debouncedSaveEnd = function () {
if (saveEndTimer) clearTimeout(saveEndTimer);
// Wait 1500ms after the last hook call — that is comfortably longer
// than chokidar's awaitWriteFinish stabilityThreshold (1000ms).
saveEndTimer = setTimeout(notifySaveEnd, 1500);
};
// TW5 fires these hooks synchronously, once per tiddler. We keep the
// save-start notification alive by resetting the debounce timer on each
// hook call and only send save-end after the trailing edge.
if (typeof $tw !== 'undefined' && $tw && $tw.hooks) {
$tw.hooks.addHook('th-saving-tiddler', function () {
notifySaveStart();
debouncedSaveEnd();
return arguments[0]; // pass through unchanged
});
$tw.hooks.addHook('th-deleting-tiddler', function () {
notifySaveStart();
debouncedSaveEnd();
return arguments[0];
});
}
};
})();
`;
const renderDevWebListenerScript = (generation = 0) => {
const safeGeneration = Number.isFinite(generation) ? Math.trunc(generation) : 0;
return listenerTemplate.replace(
"__DEV_BUILD_GENERATION__",
String(safeGeneration)
);
};
export {
renderDevWebListenerScript
};