tina4-nodejs 3.13.111 → 3.13.112
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/CLAUDE.md +2 -2
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +164 -150
- package/packages/core/dist/index.js +164 -150
- package/packages/core/src/devAdmin.ts +181 -145
- package/packages/core/src/server.ts +6 -0
- package/packages/orm/dist/index.js +164 -150
- package/types/core/src/devAdmin.d.ts +1 -0
|
@@ -706,6 +706,11 @@ export class DevAdmin {
|
|
|
706
706
|
{ method: "GET", pattern: "/__dev/api/docs/.well-known.json", handler: handleDocsWellKnown },
|
|
707
707
|
// JS asset
|
|
708
708
|
{ method: "GET", pattern: "/__dev/js/tina4-dev-admin.min.js", handler: handleDevAdminJs },
|
|
709
|
+
// Dev-toolbar assets — served as external CSS/JS so the injected toolbar is
|
|
710
|
+
// CSP-clean (no inline style=, onclick=, or <script>) under a strict
|
|
711
|
+
// `default-src 'self'`. Parity with PHP's /__dev/toolbar.css + toolbar.js.
|
|
712
|
+
{ method: "GET", pattern: "/__dev/toolbar.css", handler: handleToolbarCss },
|
|
713
|
+
{ method: "GET", pattern: "/__dev/toolbar.js", handler: handleToolbarJs },
|
|
709
714
|
];
|
|
710
715
|
|
|
711
716
|
for (const route of routes) {
|
|
@@ -767,6 +772,7 @@ export class DevAdmin {
|
|
|
767
772
|
matchedPattern: string;
|
|
768
773
|
requestId: string;
|
|
769
774
|
routeCount: number;
|
|
775
|
+
reload?: boolean;
|
|
770
776
|
}): string {
|
|
771
777
|
return renderToolbarHtml(ctx);
|
|
772
778
|
}
|
|
@@ -2978,6 +2984,7 @@ function renderToolbarHtml(ctx: {
|
|
|
2978
2984
|
matchedPattern: string;
|
|
2979
2985
|
requestId: string;
|
|
2980
2986
|
routeCount: number;
|
|
2987
|
+
reload?: boolean;
|
|
2981
2988
|
}): string {
|
|
2982
2989
|
const nodeVersion = process.version;
|
|
2983
2990
|
// DEVADMIN-DEC-04: the toolbar is injected into every text/html response
|
|
@@ -2987,47 +2994,140 @@ function renderToolbarHtml(ctx: {
|
|
|
2987
2994
|
const method = escapeHtml(ctx.method);
|
|
2988
2995
|
const path = escapeHtml(ctx.path);
|
|
2989
2996
|
const matchedPattern = escapeHtml(ctx.matchedPattern);
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2997
|
+
// CSP-clean toolbar: the framework serves a strict `default-src 'self'`, so the
|
|
2998
|
+
// toolbar carries NO inline style=, NO onclick=, and NO inline <script>. The CSS
|
|
2999
|
+
// and JS load as external assets from /__dev/toolbar.css and /__dev/toolbar.js.
|
|
3000
|
+
// The reloader is suppressed on the AI/stable port by data-reload="0" (the JS
|
|
3001
|
+
// early-returns when it is not "1"). Mirrors PHP DevAdmin::renderToolbar().
|
|
3002
|
+
const reload = ctx.reload === false ? "0" : "1";
|
|
3003
|
+
return `<link rel="stylesheet" href="/__dev/toolbar.css">
|
|
3004
|
+
<div id="tina4-dev-toolbar" data-reload="${reload}">
|
|
3005
|
+
<span id="tina4-ver-btn" title="Click to check for updates">Tina4 v${ctx.version}</span>
|
|
3006
|
+
<div id="tina4-ver-modal">
|
|
3007
|
+
<div class="t4-modal-head">
|
|
3008
|
+
<strong class="t4-modal-title">Version Info</strong>
|
|
3009
|
+
<span id="tina4-ver-close" class="t4-x">×</span>
|
|
2996
3010
|
</div>
|
|
2997
|
-
<div id="tina4-ver-body"
|
|
2998
|
-
<div>Current: <strong
|
|
2999
|
-
<div id="tina4-ver-latest"
|
|
3011
|
+
<div id="tina4-ver-body">
|
|
3012
|
+
<div>Current: <strong class="t4-ok">v${ctx.version}</strong></div>
|
|
3013
|
+
<div id="tina4-ver-latest" class="t4-dim">Checking for updates...</div>
|
|
3000
3014
|
</div>
|
|
3001
3015
|
</div>
|
|
3002
|
-
<span
|
|
3016
|
+
<span class="t4-green">${method}</span>
|
|
3003
3017
|
<span>${path}</span>
|
|
3004
|
-
<span
|
|
3005
|
-
<span
|
|
3006
|
-
<span
|
|
3007
|
-
<span
|
|
3008
|
-
<a href="#"
|
|
3009
|
-
<span
|
|
3018
|
+
<span class="t4-arrow">→ ${matchedPattern}</span>
|
|
3019
|
+
<span class="t4-yellow">req:${ctx.requestId}</span>
|
|
3020
|
+
<span class="t4-blue">${ctx.routeCount} routes</span>
|
|
3021
|
+
<span class="t4-dim">Node.js ${nodeVersion}</span>
|
|
3022
|
+
<a href="#" id="tina4-dash-link" class="t4-dash">Dashboard ↗</a>
|
|
3023
|
+
<span id="tina4-bar-close" class="t4-x t4-bar-close">✕</span>
|
|
3010
3024
|
</div>
|
|
3011
|
-
<script
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
(
|
|
3025
|
+
<script src="/__dev/toolbar.js"></script>`;
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
/**
|
|
3029
|
+
* CSS for the injected dev toolbar. Served as an external stylesheet (see
|
|
3030
|
+
* register()) so the toolbar carries no inline `style=` and stays CSP-clean
|
|
3031
|
+
* under a strict `default-src 'self'`. Mirrors PHP DevAdmin::toolbarCss().
|
|
3032
|
+
*/
|
|
3033
|
+
function toolbarCss(): string {
|
|
3034
|
+
return `#tina4-dev-toolbar{position:fixed;bottom:0;left:0;right:0;background:#333;color:#fff;font-family:monospace;font-size:12px;padding:6px 16px;z-index:99999;display:flex;align-items:center;gap:16px}
|
|
3035
|
+
#tina4-dev-toolbar a{text-decoration:none}
|
|
3036
|
+
#tina4-ver-btn{color:#2e7d32;font-weight:bold;cursor:pointer;text-decoration:underline dotted}
|
|
3037
|
+
#tina4-ver-modal{display:none;position:fixed;bottom:3rem;left:1rem;background:#1e1e2e;border:1px solid #2e7d32;border-radius:8px;padding:16px 20px;z-index:100000;min-width:320px;box-shadow:0 8px 32px rgba(0,0,0,.5);font-family:monospace;font-size:13px;color:#cdd6f4}
|
|
3038
|
+
.t4-modal-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
|
|
3039
|
+
.t4-modal-title{color:#89b4fa}
|
|
3040
|
+
#tina4-ver-body{line-height:1.8}
|
|
3041
|
+
.t4-x{cursor:pointer;color:#888}
|
|
3042
|
+
.t4-bar-close{margin-left:8px}
|
|
3043
|
+
.t4-green{color:#4caf50}
|
|
3044
|
+
.t4-dim{color:#888}
|
|
3045
|
+
.t4-arrow{color:#666}
|
|
3046
|
+
.t4-yellow{color:#ffeb3b}
|
|
3047
|
+
.t4-blue{color:#90caf9}
|
|
3048
|
+
.t4-ok{color:#a6e3a1}
|
|
3049
|
+
.t4-warn{color:#f9e2af}
|
|
3050
|
+
.t4-err{color:#f38ba8}
|
|
3051
|
+
.t4-purple{color:#cba6f7}
|
|
3052
|
+
.t4-link{color:#89b4fa}
|
|
3053
|
+
.t4-code{background:#313244;padding:2px 6px;border-radius:3px}
|
|
3054
|
+
.t4-note{margin-top:6px}
|
|
3055
|
+
.t4-dash{color:#ef9a9a;margin-left:auto;cursor:pointer}
|
|
3056
|
+
#tina4-dev-panel{position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all .2s}
|
|
3057
|
+
#tina4-dev-panel iframe{width:100%;height:100%;border:1px solid #2e7d32;border-radius:.5rem;box-shadow:0 8px 32px rgba(0,0,0,.5);background:#0f172a}`;
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
/**
|
|
3061
|
+
* JS for the injected dev toolbar — the version-check modal, the dashboard
|
|
3062
|
+
* overlay, and the WebSocket-primary live reloader. Served as an external
|
|
3063
|
+
* script so the toolbar carries no inline handlers or `<script>` and stays
|
|
3064
|
+
* CSP-clean. Every interaction is wired via addEventListener. The reloader only
|
|
3065
|
+
* starts when the toolbar's `data-reload` is "1" (reload not suppressed for this
|
|
3066
|
+
* request/port). Mirrors PHP DevAdmin::toolbarJs().
|
|
3067
|
+
*/
|
|
3068
|
+
function toolbarJs(): string {
|
|
3069
|
+
return `(function () {
|
|
3070
|
+
var bar = document.getElementById('tina4-dev-toolbar');
|
|
3071
|
+
if (!bar) { return; }
|
|
3072
|
+
|
|
3073
|
+
var modal = document.getElementById('tina4-ver-modal');
|
|
3074
|
+
function upToDate(el, latest) {
|
|
3075
|
+
el.className = 't4-ok';
|
|
3076
|
+
el.innerHTML = 'Latest: <strong class="t4-ok">v' + latest + '</strong> — You are up to date!';
|
|
3077
|
+
}
|
|
3078
|
+
function checkVersion() {
|
|
3079
|
+
if (modal.style.display === 'block') { modal.style.display = 'none'; return; }
|
|
3080
|
+
modal.style.display = 'block';
|
|
3081
|
+
var el = document.getElementById('tina4-ver-latest');
|
|
3082
|
+
el.className = 't4-dim';
|
|
3083
|
+
el.textContent = 'Checking for updates...';
|
|
3084
|
+
fetch('/__dev/api/version-check').then(function (r) { return r.json(); }).then(function (d) {
|
|
3085
|
+
var latest = d.latest, current = d.current;
|
|
3086
|
+
if (latest === current) { upToDate(el, latest); return; }
|
|
3087
|
+
var cP = current.split('.').map(Number), lP = latest.split('.').map(Number);
|
|
3088
|
+
var isNewer = false, i, c, l;
|
|
3089
|
+
for (i = 0; i < Math.max(cP.length, lP.length); i++) { c = cP[i] || 0; l = lP[i] || 0; if (l > c) { isNewer = true; break; } if (l < c) { break; } }
|
|
3090
|
+
var isAhead = false;
|
|
3091
|
+
if (!isNewer) { for (i = 0; i < Math.max(cP.length, lP.length); i++) { var c2 = cP[i] || 0, l2 = lP[i] || 0; if (c2 > l2) { isAhead = true; break; } if (c2 < l2) { break; } } }
|
|
3092
|
+
if (isNewer) {
|
|
3093
|
+
var breaking = (lP[0] !== cP[0] || lP[1] !== cP[1]);
|
|
3094
|
+
el.className = '';
|
|
3095
|
+
el.innerHTML = 'Latest: <strong class="t4-warn">v' + latest + '</strong>';
|
|
3096
|
+
if (breaking) {
|
|
3097
|
+
el.innerHTML += '<div class="t4-err t4-note">⚠ Major/minor version change — check the <a href="https://github.com/tina4stack/tina4-nodejs/releases" target="_blank" class="t4-link">changelog</a> for breaking changes before upgrading.</div>';
|
|
3098
|
+
} else {
|
|
3099
|
+
el.innerHTML += '<div class="t4-warn t4-note">Patch update available. Run: <code class="t4-code">npm install tina4-nodejs@latest</code></div>';
|
|
3100
|
+
}
|
|
3101
|
+
} else if (isAhead) {
|
|
3102
|
+
el.className = 't4-purple';
|
|
3103
|
+
el.innerHTML = 'You are running <strong class="t4-purple">v' + current + '</strong> (ahead of npm <strong>v' + latest + '</strong> — not yet published).';
|
|
3104
|
+
} else {
|
|
3105
|
+
upToDate(el, latest);
|
|
3106
|
+
}
|
|
3107
|
+
}).catch(function () {
|
|
3108
|
+
el.className = 't4-err';
|
|
3109
|
+
el.textContent = 'Could not check for updates (offline?)';
|
|
3110
|
+
});
|
|
3111
|
+
}
|
|
3112
|
+
var verBtn = document.getElementById('tina4-ver-btn');
|
|
3113
|
+
if (verBtn) { verBtn.addEventListener('click', checkVersion); }
|
|
3114
|
+
var verClose = document.getElementById('tina4-ver-close');
|
|
3115
|
+
if (verClose) { verClose.addEventListener('click', function () { modal.style.display = 'none'; }); }
|
|
3116
|
+
var barClose = document.getElementById('tina4-bar-close');
|
|
3117
|
+
if (barClose) { barClose.addEventListener('click', function () { bar.style.display = 'none'; }); }
|
|
3118
|
+
|
|
3017
3119
|
var STATE_KEY = 'tina4_dev_overlay_open';
|
|
3018
3120
|
function buildOverlay() {
|
|
3019
3121
|
var c = document.createElement('div');
|
|
3020
3122
|
c.id = 'tina4-dev-panel';
|
|
3021
|
-
c.style.cssText = 'position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all 0.2s';
|
|
3022
3123
|
var f = document.createElement('iframe');
|
|
3023
3124
|
f.src = '/__dev';
|
|
3024
|
-
f.style.cssText = 'width:100%;height:100%;border:1px solid #2e7d32;border-radius:0.5rem;box-shadow:0 8px 32px rgba(0,0,0,0.5);background:#0f172a';
|
|
3025
3125
|
c.appendChild(f);
|
|
3026
3126
|
document.body.appendChild(c);
|
|
3027
3127
|
return c;
|
|
3028
3128
|
}
|
|
3029
|
-
|
|
3030
|
-
if (e) e.preventDefault();
|
|
3129
|
+
function toggleOverlay(e) {
|
|
3130
|
+
if (e) { e.preventDefault(); }
|
|
3031
3131
|
var p = document.getElementById('tina4-dev-panel');
|
|
3032
3132
|
if (p) {
|
|
3033
3133
|
var hide = p.style.display !== 'none';
|
|
@@ -3037,133 +3137,69 @@ function renderToolbarHtml(ctx: {
|
|
|
3037
3137
|
}
|
|
3038
3138
|
buildOverlay();
|
|
3039
3139
|
try { localStorage.setItem(STATE_KEY, '1'); } catch (_) {}
|
|
3040
|
-
};
|
|
3041
|
-
function restoreIfOpen() {
|
|
3042
|
-
try {
|
|
3043
|
-
if (location.pathname.indexOf('/__dev') === 0) return;
|
|
3044
|
-
if (localStorage.getItem(STATE_KEY) === '1' && !document.getElementById('tina4-dev-panel')) {
|
|
3045
|
-
buildOverlay();
|
|
3046
|
-
}
|
|
3047
|
-
} catch (_) {}
|
|
3048
|
-
}
|
|
3049
|
-
if (document.readyState === 'loading') {
|
|
3050
|
-
document.addEventListener('DOMContentLoaded', restoreIfOpen);
|
|
3051
|
-
} else {
|
|
3052
|
-
restoreIfOpen();
|
|
3053
3140
|
}
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
var el=document.getElementById('tina4-ver-latest');
|
|
3062
|
-
el.innerHTML='Checking for updates...';
|
|
3063
|
-
el.style.color='#888';
|
|
3064
|
-
fetch('/__dev/api/version-check')
|
|
3065
|
-
.then(function(r){return r.json()})
|
|
3066
|
-
.then(function(d){
|
|
3067
|
-
var latest=d.latest;
|
|
3068
|
-
var current=d.current;
|
|
3069
|
-
if(latest===current){
|
|
3070
|
-
el.innerHTML='Latest: <strong style="color:#a6e3a1;">v'+latest+'</strong> — You are up to date!';
|
|
3071
|
-
el.style.color='#a6e3a1';
|
|
3072
|
-
}else{
|
|
3073
|
-
var cParts=current.split('.').map(Number);
|
|
3074
|
-
var lParts=latest.split('.').map(Number);
|
|
3075
|
-
var isNewer=false;
|
|
3076
|
-
for(var i=0;i<Math.max(cParts.length,lParts.length);i++){
|
|
3077
|
-
var c=cParts[i]||0,l=lParts[i]||0;
|
|
3078
|
-
if(l>c){isNewer=true;break;}
|
|
3079
|
-
if(l<c)break;
|
|
3080
|
-
}
|
|
3081
|
-
var isAhead=false;
|
|
3082
|
-
if(!isNewer){for(var i=0;i<Math.max(cParts.length,lParts.length);i++){var c2=cParts[i]||0,l2=lParts[i]||0;if(c2>l2){isAhead=true;break;}if(c2<l2)break;}}
|
|
3083
|
-
if(isNewer){
|
|
3084
|
-
var breaking=(lParts[0]!==cParts[0]||lParts[1]!==cParts[1]);
|
|
3085
|
-
el.innerHTML='Latest: <strong style="color:#f9e2af;">v'+latest+'</strong>';
|
|
3086
|
-
if(breaking){
|
|
3087
|
-
el.innerHTML+='<div style="color:#f38ba8;margin-top:6px;">⚠ Major/minor version change — check the <a href="https://github.com/tina4stack/tina4-nodejs/releases" target="_blank" style="color:#89b4fa;">changelog</a> for breaking changes before upgrading.</div>';
|
|
3088
|
-
}else{
|
|
3089
|
-
el.innerHTML+='<div style="color:#f9e2af;margin-top:6px;">Patch update available. Run: <code style="background:#313244;padding:2px 6px;border-radius:3px;">npm install tina4-nodejs@latest</code></div>';
|
|
3090
|
-
}
|
|
3091
|
-
}else if(isAhead){
|
|
3092
|
-
el.innerHTML='You are running <strong style="color:#cba6f7;">v'+current+'</strong> (ahead of npm <strong>v'+latest+'</strong> — not yet published).';
|
|
3093
|
-
el.style.color='#cba6f7';
|
|
3094
|
-
}else{
|
|
3095
|
-
el.innerHTML='Latest: <strong style="color:#a6e3a1;">v'+latest+'</strong> — You are up to date!';
|
|
3096
|
-
el.style.color='#a6e3a1';
|
|
3097
|
-
}
|
|
3141
|
+
var dash = document.getElementById('tina4-dash-link');
|
|
3142
|
+
if (dash) { dash.addEventListener('click', toggleOverlay); }
|
|
3143
|
+
try {
|
|
3144
|
+
if (location.pathname.indexOf('/__dev') !== 0
|
|
3145
|
+
&& localStorage.getItem(STATE_KEY) === '1'
|
|
3146
|
+
&& !document.getElementById('tina4-dev-panel')) {
|
|
3147
|
+
buildOverlay();
|
|
3098
3148
|
}
|
|
3099
|
-
})
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
(function(){
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
var _t4_css_exts=['.css','.scss'],_t4_debounce=null;
|
|
3113
|
-
var _t4_interval=3000;
|
|
3114
|
-
var _t4_ws=null,_t4_poll_timer=null,_t4_mtime=null;
|
|
3115
|
-
function _t4_apply(d){
|
|
3116
|
-
d=d||{};
|
|
3117
|
-
var f=d.file||'',t=d.type||'';
|
|
3118
|
-
var isCss=t==='css'||_t4_css_exts.some(function(e){return f.endsWith(e)});
|
|
3119
|
-
if(isCss){
|
|
3120
|
-
var links=document.querySelectorAll('link[rel="stylesheet"]');
|
|
3121
|
-
links.forEach(function(l){
|
|
3122
|
-
var href=l.getAttribute('href');
|
|
3123
|
-
if(href){l.setAttribute('href',href.split('?')[0]+'?_t4='+(d.mtime||Date.now()))}
|
|
3149
|
+
} catch (_) {}
|
|
3150
|
+
|
|
3151
|
+
if (bar.getAttribute('data-reload') !== '1') { return; }
|
|
3152
|
+
var cssExts = ['.css', '.scss'], debounce = null, interval = 3000;
|
|
3153
|
+
var ws = null, pollTimer = null, mtime = null;
|
|
3154
|
+
function apply(d) {
|
|
3155
|
+
d = d || {};
|
|
3156
|
+
var f = d.file || '', t = d.type || '';
|
|
3157
|
+
var isCss = t === 'css' || cssExts.some(function (e) { return f.endsWith(e); });
|
|
3158
|
+
if (isCss) {
|
|
3159
|
+
document.querySelectorAll('link[rel="stylesheet"]').forEach(function (l) {
|
|
3160
|
+
var href = l.getAttribute('href');
|
|
3161
|
+
if (href) { l.setAttribute('href', href.split('?')[0] + '?_t4=' + (d.mtime || Date.now())); }
|
|
3124
3162
|
});
|
|
3125
|
-
}else{
|
|
3163
|
+
} else {
|
|
3126
3164
|
location.reload();
|
|
3127
3165
|
}
|
|
3128
3166
|
}
|
|
3129
|
-
function
|
|
3130
|
-
fetch('/__dev/api/mtime').then(function(r){return r.json()}).then(function(d){
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
if(_t4_mtime===null){_t4_mtime=d.mtime;return;}
|
|
3135
|
-
if(d.mtime!==_t4_mtime){
|
|
3136
|
-
_t4_mtime=d.mtime;
|
|
3137
|
-
if(_t4_debounce)clearTimeout(_t4_debounce);
|
|
3138
|
-
_t4_debounce=setTimeout(function(){_t4_apply(d);},500);
|
|
3139
|
-
}
|
|
3140
|
-
}).catch(function(){});
|
|
3141
|
-
}
|
|
3142
|
-
function _t4_startPoll(){
|
|
3143
|
-
if(_t4_poll_timer)return;
|
|
3144
|
-
_t4_mtime=null;
|
|
3145
|
-
_t4_poll_timer=setInterval(_t4_poll,_t4_interval);
|
|
3146
|
-
}
|
|
3147
|
-
function _t4_stopPoll(){
|
|
3148
|
-
if(_t4_poll_timer){clearInterval(_t4_poll_timer);_t4_poll_timer=null;}
|
|
3167
|
+
function poll() {
|
|
3168
|
+
fetch('/__dev/api/mtime').then(function (r) { return r.json(); }).then(function (d) {
|
|
3169
|
+
if (mtime === null) { mtime = d.mtime; return; }
|
|
3170
|
+
if (d.mtime !== mtime) { mtime = d.mtime; if (debounce) { clearTimeout(debounce); } debounce = setTimeout(function () { apply(d); }, 500); }
|
|
3171
|
+
}).catch(function () {});
|
|
3149
3172
|
}
|
|
3150
|
-
function
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3173
|
+
function startPoll() { if (pollTimer) { return; } mtime = null; pollTimer = setInterval(poll, interval); }
|
|
3174
|
+
function stopPoll() { if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } }
|
|
3175
|
+
function connect() {
|
|
3176
|
+
var url = (location.protocol === 'https:' ? 'wss' : 'ws') + '://' + location.host + '/__dev_reload';
|
|
3177
|
+
try { ws = new WebSocket(url); } catch (_) { startPoll(); return; }
|
|
3178
|
+
ws.addEventListener('open', function () { stopPoll(); });
|
|
3179
|
+
ws.addEventListener('message', function (ev) {
|
|
3180
|
+
var d = null;
|
|
3181
|
+
try { d = typeof ev.data === 'string' ? JSON.parse(ev.data) : null; } catch (_) {}
|
|
3182
|
+
if (!d) { return; }
|
|
3183
|
+
if (d.type === 'reload' || d.type === 'change' || d.type === 'css') {
|
|
3184
|
+
if (debounce) { clearTimeout(debounce); }
|
|
3185
|
+
debounce = setTimeout(function () { apply(d); }, 150);
|
|
3161
3186
|
}
|
|
3162
3187
|
});
|
|
3163
|
-
|
|
3164
|
-
|
|
3188
|
+
ws.addEventListener('close', function () { ws = null; startPoll(); setTimeout(connect, 2000); });
|
|
3189
|
+
ws.addEventListener('error', function () { try { ws && ws.close(); } catch (_) {} });
|
|
3165
3190
|
}
|
|
3166
|
-
|
|
3167
|
-
})()
|
|
3168
|
-
</script>`;
|
|
3191
|
+
connect();
|
|
3192
|
+
})();`;
|
|
3169
3193
|
}
|
|
3194
|
+
|
|
3195
|
+
const handleToolbarCss: RouteHandler = (_req, res) => {
|
|
3196
|
+
// text()+header rather than html() so the toolbar is never injected into its
|
|
3197
|
+
// own asset (parity with PHP's DevAdmin route registration).
|
|
3198
|
+
res.raw.writeHead(200, { "Content-Type": "text/css; charset=utf-8", "Cache-Control": "no-cache" });
|
|
3199
|
+
res.raw.end(toolbarCss());
|
|
3200
|
+
};
|
|
3201
|
+
|
|
3202
|
+
const handleToolbarJs: RouteHandler = (_req, res) => {
|
|
3203
|
+
res.raw.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" });
|
|
3204
|
+
res.raw.end(toolbarJs());
|
|
3205
|
+
};
|
|
@@ -495,6 +495,8 @@ interface DevToolbarContext {
|
|
|
495
495
|
matchedPattern: string;
|
|
496
496
|
requestId: string;
|
|
497
497
|
routeCount: number;
|
|
498
|
+
/** When false, the toolbar sets data-reload="0" and its JS suppresses the reloader (AI/stable port). */
|
|
499
|
+
reload?: boolean;
|
|
498
500
|
}
|
|
499
501
|
|
|
500
502
|
function injectDevToolbar(html: string, ctx: DevToolbarContext): string {
|
|
@@ -1250,6 +1252,10 @@ function injectIntoHtml(ctx: ResponseWrapContext, devToolbar: boolean, html: str
|
|
|
1250
1252
|
matchedPattern: ctx.matchedPattern.value || ctx.pathname,
|
|
1251
1253
|
requestId: ctx.requestId,
|
|
1252
1254
|
routeCount: ctx.router.getRoutes().length,
|
|
1255
|
+
// Suppress the live reloader on the AI/stable port (data-reload="0"); the
|
|
1256
|
+
// toolbar JS early-returns when data-reload !== "1". Mirrors PHP's
|
|
1257
|
+
// suppressReload flag.
|
|
1258
|
+
reload: !ctx.isAiPortRequest,
|
|
1253
1259
|
};
|
|
1254
1260
|
return injectFeedbackWidget(ctx.req, injectDevToolbar(html, toolbarCtx));
|
|
1255
1261
|
}
|