veryfront 0.1.18 → 0.1.19
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/esm/deno.js +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/cache/memory.d.ts.map +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/cache/memory.js +2 -1
- package/esm/src/modules/react-loader/ssr-module-loader/loader.d.ts.map +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/loader.js +2 -1
- package/esm/src/modules/react-loader/ssr-module-loader/tmp-paths.d.ts.map +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/tmp-paths.js +3 -2
- package/esm/src/modules/react-loader/ssr-module-loader/vf-module-resolver.d.ts.map +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/vf-module-resolver.js +2 -1
- package/esm/src/platform/adapters/fs/veryfront/file-list-index.d.ts +1 -0
- package/esm/src/platform/adapters/fs/veryfront/file-list-index.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/file-list-index.js +28 -1
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts +2 -0
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.js +44 -11
- package/esm/src/routing/api/route-executor.d.ts.map +1 -1
- package/esm/src/routing/api/route-executor.js +28 -6
- package/esm/src/server/context/request-context.d.ts.map +1 -1
- package/esm/src/server/context/request-context.js +2 -1
- package/esm/src/server/dev-server/server.d.ts +1 -0
- package/esm/src/server/dev-server/server.d.ts.map +1 -1
- package/esm/src/server/dev-server/server.js +23 -1
- package/esm/src/server/dev-server/types.d.ts +1 -0
- package/esm/src/server/dev-server/types.d.ts.map +1 -1
- package/esm/src/server/handlers/preview/hmr.handler.d.ts +2 -0
- package/esm/src/server/handlers/preview/hmr.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/preview/hmr.handler.js +21 -2
- package/esm/src/server/index.d.ts +3 -1
- package/esm/src/server/index.d.ts.map +1 -1
- package/esm/src/server/index.js +9 -3
- package/esm/src/server/utils/domain-parser.d.ts +8 -0
- package/esm/src/server/utils/domain-parser.d.ts.map +1 -1
- package/esm/src/server/utils/domain-parser.js +45 -0
- package/esm/src/utils/constants/security.d.ts +2 -2
- package/esm/src/utils/constants/security.d.ts.map +1 -1
- package/esm/src/utils/constants/security.js +3 -3
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/src/modules/react-loader/ssr-module-loader/cache/memory.ts +2 -1
- package/src/src/modules/react-loader/ssr-module-loader/loader.ts +2 -1
- package/src/src/modules/react-loader/ssr-module-loader/tmp-paths.ts +3 -2
- package/src/src/modules/react-loader/ssr-module-loader/vf-module-resolver.ts +2 -1
- package/src/src/platform/adapters/fs/veryfront/file-list-index.ts +30 -1
- package/src/src/platform/adapters/fs/veryfront/websocket-manager.ts +45 -10
- package/src/src/routing/api/route-executor.ts +34 -7
- package/src/src/server/context/request-context.ts +2 -1
- package/src/src/server/dev-server/server.ts +28 -1
- package/src/src/server/dev-server/types.ts +1 -0
- package/src/src/server/handlers/preview/hmr.handler.ts +28 -2
- package/src/src/server/index.ts +16 -2
- package/src/src/server/utils/domain-parser.ts +55 -0
- package/src/src/utils/constants/security.ts +3 -3
package/esm/deno.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../../../../src/src/modules/react-loader/ssr-module-loader/cache/memory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../../../../src/src/modules/react-loader/ssr-module-loader/cache/memory.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAO5D,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAExD,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAOnE,eAAO,MAAM,iBAAiB,oCAE5B,CAAC;AAEH,eAAO,MAAM,uBAAuB,oCAElC,CAAC;AAEH,eAAO,MAAM,gBAAgB,4BAAmC,CAAC;AAEjE,eAAO,MAAM,aAAa,0BAExB,CAAC;AAEH,eAAO,MAAM,gBAAgB,4BAAmC,CAAC;AAGjE,wBAAgB,qBAAqB,IAAI,SAAS,CAKjD;AAsBD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAW/D;AAKD;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAU5D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI;IACnC,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC,CAOA;AAiDD,wBAAgB,mBAAmB,IAAI,IAAI,CAqB1C;AAED,wBAAgB,6BAA6B,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAiDrE"}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import * as dntShim from "../../../../../_dnt.shims.js";
|
|
16
16
|
import { registerCache } from "../../../../utils/memory/index.js";
|
|
17
17
|
import { isKeyForProject, registerMapCache } from "../../../../cache/keys.js";
|
|
18
|
+
import { hashCodeHex } from "../../../../utils/hash-utils.js";
|
|
18
19
|
import { rendererLogger } from "../../../../utils/index.js";
|
|
19
20
|
import { LRUCache } from "../../../../utils/lru-wrapper.js";
|
|
20
21
|
import { getMaxConcurrentTransforms, getTransformPerProjectLimit, resetCachedTransformLimits, SSR_TMP_DIRS_MAX_ENTRIES, } from "../constants.js";
|
|
@@ -179,7 +180,7 @@ export function clearSSRModuleCache() {
|
|
|
179
180
|
}
|
|
180
181
|
export function clearSSRModuleCacheForProject(projectId) {
|
|
181
182
|
let cleared = 0;
|
|
182
|
-
const encodedProjectId =
|
|
183
|
+
const encodedProjectId = hashCodeHex(projectId);
|
|
183
184
|
for (const key of globalModuleCache.keys()) {
|
|
184
185
|
if (!isKeyForProject(key, projectId))
|
|
185
186
|
continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/loader.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/loader.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAiCpC,OAAO,KAAK,EAAoB,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAwB3E;;;;;GAKG;AACH,qBAAa,eAAe;IAKd,OAAO,CAAC,OAAO;IAJ3B,OAAO,CAAC,KAAK,CAAkB;IAC/B,OAAO,CAAC,cAAc,CAA2B;IACjD,OAAO,CAAC,YAAY,CAAyB;gBAEzB,OAAO,EAAE,sBAAsB;IAWnD,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,4BAA4B;YAetB,qBAAqB;YAuCrB,0BAA0B;IA2FxC,UAAU,CACR,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;YA+C1C,2BAA2B;IAYzC,OAAO,CAAC,yBAAyB;YAiBnB,2BAA2B;CAqU1C"}
|
|
@@ -11,6 +11,7 @@ import { parseLocalImports, } from "../../../transforms/esm/import-parser.js";
|
|
|
11
11
|
import { createFileSystem } from "../../../platform/compat/fs.js";
|
|
12
12
|
import { verifyCacheFileExists, writeCacheFile } from "../../../utils/cache-file-ops.js";
|
|
13
13
|
import { createError, toError } from "../../../errors/veryfront-error.js";
|
|
14
|
+
import { hashCodeHex } from "../../../utils/hash-utils.js";
|
|
14
15
|
import { rendererLogger } from "../../../utils/index.js";
|
|
15
16
|
import { withSpan } from "../../../observability/tracing/otlp-setup.js";
|
|
16
17
|
import { SpanNames } from "../../../observability/tracing/span-names.js";
|
|
@@ -253,7 +254,7 @@ export class SSRModuleLoader {
|
|
|
253
254
|
}
|
|
254
255
|
if (this.options.projectId && this.options.contentSourceId) {
|
|
255
256
|
const baseCacheDir = getMdxEsmCacheDir();
|
|
256
|
-
const projectKey =
|
|
257
|
+
const projectKey = hashCodeHex(this.options.projectId);
|
|
257
258
|
const sourceKey = this.options.contentSourceId;
|
|
258
259
|
const mdxCacheDir = join(baseCacheDir, projectKey, sourceKey);
|
|
259
260
|
const mdxCacheResult = await lookupMdxEsmCache(filePath, mdxCacheDir, this.options.projectDir, contentHash);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tmp-paths.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/tmp-paths.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"tmp-paths.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/tmp-paths.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM,GACtB,MAAM,CAGR;AAED,wBAAgB,eAAe,CAC7B,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM,GACtB,MAAM,CAGR;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAYR"}
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* Temp directory and temp file path helpers for SSR module loader cache.
|
|
3
3
|
*/
|
|
4
4
|
import { join } from "../../../platform/compat/path/index.js";
|
|
5
|
+
import { hashCodeHex } from "../../../utils/hash-utils.js";
|
|
5
6
|
export function getTmpDirCacheKey(baseCacheDir, projectId, contentSourceId) {
|
|
6
|
-
const projectKey =
|
|
7
|
+
const projectKey = hashCodeHex(projectId);
|
|
7
8
|
return `${baseCacheDir}|${projectKey}|${contentSourceId}`;
|
|
8
9
|
}
|
|
9
10
|
export function buildTmpDirPath(baseCacheDir, projectId, contentSourceId) {
|
|
10
|
-
const projectKey =
|
|
11
|
+
const projectKey = hashCodeHex(projectId);
|
|
11
12
|
return join(baseCacheDir, projectKey, contentSourceId);
|
|
12
13
|
}
|
|
13
14
|
export function buildTempModulePath(tmpDir, filePath, projectDir, version, contentHash) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vf-module-resolver.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/vf-module-resolver.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"vf-module-resolver.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/vf-module-resolver.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAWzE,UAAU,cAAc;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,6BAA6B;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,EAAE,CAelE;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,MAAM,CAAC,CA8CjB"}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Extracts and resolves /_vf_modules/* imports into file:// cache paths.
|
|
5
5
|
*/
|
|
6
6
|
import { join } from "../../../platform/compat/path/index.js";
|
|
7
|
+
import { hashCodeHex } from "../../../utils/hash-utils.js";
|
|
7
8
|
import { rendererLogger } from "../../../utils/index.js";
|
|
8
9
|
import { getMdxEsmCacheDir } from "../../../utils/cache-dir.js";
|
|
9
10
|
import { createModuleFetcherContext, fetchAndCacheModule, } from "../../../transforms/mdx/esm-module-loader/module-fetcher/index.js";
|
|
@@ -40,7 +41,7 @@ export async function resolveVfModuleImports(code, options) {
|
|
|
40
41
|
paths: imports.map((i) => i.path).slice(0, 5),
|
|
41
42
|
});
|
|
42
43
|
const baseCacheDir = getMdxEsmCacheDir();
|
|
43
|
-
const projectKey =
|
|
44
|
+
const projectKey = hashCodeHex(options.projectId);
|
|
44
45
|
const esmCacheDir = join(baseCacheDir, projectKey, options.contentSourceId);
|
|
45
46
|
const fetcherContext = createModuleFetcherContext(esmCacheDir, options.adapter, options.projectDir, options.projectId, {
|
|
46
47
|
reactVersion: options.reactVersion,
|
|
@@ -6,6 +6,7 @@ export declare class FileListIndex {
|
|
|
6
6
|
private readonly getFileListCache?;
|
|
7
7
|
private index;
|
|
8
8
|
private indexKey;
|
|
9
|
+
private indexBuiltAt;
|
|
9
10
|
private readyPromise;
|
|
10
11
|
constructor(getFileListCache?: (() => Promise<Array<FileListCacheEntry> | undefined>) | undefined);
|
|
11
12
|
setReadyPromise(promise: Promise<void>): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-list-index.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/fs/veryfront/file-list-index.ts"],"names":[],"mappings":"AAIA,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;
|
|
1
|
+
{"version":3,"file":"file-list-index.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/fs/veryfront/file-list-index.ts"],"names":[],"mappings":"AAIA,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAeD,qBAAa,aAAa;IAOtB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IANpC,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,YAAY,CAA8B;gBAG/B,gBAAgB,CAAC,GAAE,MAAM,OAAO,CAAC,KAAK,CAAC,kBAAkB,CAAC,GAAG,SAAS,CAAC,aAAA;IAG1F,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAI7C,KAAK,IAAI,IAAI;IAUP,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;YAkCnD,UAAU;CA0EzB"}
|
|
@@ -9,10 +9,12 @@ function hashPreview(content) {
|
|
|
9
9
|
function previewText(content, max = 80) {
|
|
10
10
|
return content.length > max ? `${content.slice(0, max)}...` : content;
|
|
11
11
|
}
|
|
12
|
+
const INDEX_STALENESS_LIMIT_MS = 5 * 60 * 1000; // 5 minutes
|
|
12
13
|
export class FileListIndex {
|
|
13
14
|
getFileListCache;
|
|
14
15
|
index = null;
|
|
15
16
|
indexKey = null;
|
|
17
|
+
indexBuiltAt = 0;
|
|
16
18
|
readyPromise = null;
|
|
17
19
|
constructor(getFileListCache) {
|
|
18
20
|
this.getFileListCache = getFileListCache;
|
|
@@ -26,6 +28,7 @@ export class FileListIndex {
|
|
|
26
28
|
const size = this.index.size;
|
|
27
29
|
this.index = null;
|
|
28
30
|
this.indexKey = null;
|
|
31
|
+
this.indexBuiltAt = 0;
|
|
29
32
|
logger.debug("Cleared file list index", { entriesCleared: size });
|
|
30
33
|
}
|
|
31
34
|
async lookup(normalizedPath) {
|
|
@@ -65,6 +68,27 @@ export class FileListIndex {
|
|
|
65
68
|
}
|
|
66
69
|
const fileList = await this.getFileListCache();
|
|
67
70
|
if (!fileList) {
|
|
71
|
+
// Cache entry expired or unavailable. If we already have a built index from a
|
|
72
|
+
// previous successful cache read, keep using it rather than forcing network fetches.
|
|
73
|
+
// The index stays valid until explicitly cleared via clear() (triggered by WebSocket pokes)
|
|
74
|
+
// or until INDEX_STALENESS_LIMIT_MS elapses (safety net for missed pokes).
|
|
75
|
+
if (this.index) {
|
|
76
|
+
const age = Date.now() - this.indexBuiltAt;
|
|
77
|
+
if (age < INDEX_STALENESS_LIMIT_MS) {
|
|
78
|
+
logger.debug("getOrBuildFileListIndex: cache expired, using existing in-memory index", {
|
|
79
|
+
indexSize: this.index.size,
|
|
80
|
+
indexAgeMs: age,
|
|
81
|
+
});
|
|
82
|
+
return this.index;
|
|
83
|
+
}
|
|
84
|
+
logger.debug("getOrBuildFileListIndex: in-memory index too stale, discarding", {
|
|
85
|
+
indexSize: this.index.size,
|
|
86
|
+
indexAgeMs: age,
|
|
87
|
+
staleLimitMs: INDEX_STALENESS_LIMIT_MS,
|
|
88
|
+
});
|
|
89
|
+
this.index = null;
|
|
90
|
+
this.indexKey = null;
|
|
91
|
+
}
|
|
68
92
|
logger.debug("[ReadOperations] getOrBuildFileListIndex: getFileListCache returned null/undefined");
|
|
69
93
|
return null;
|
|
70
94
|
}
|
|
@@ -77,8 +101,10 @@ export class FileListIndex {
|
|
|
77
101
|
sampleContentPreview: cacheCheckSample?.content?.slice(0, 200)?.replace(/\n/g, "\\n"),
|
|
78
102
|
});
|
|
79
103
|
const indexKey = `${fileList.length}:${fileList[0]?.path ?? ""}:${fileList[fileList.length - 1]?.path ?? ""}`;
|
|
80
|
-
if (this.index && this.indexKey === indexKey)
|
|
104
|
+
if (this.index && this.indexKey === indexKey) {
|
|
105
|
+
this.indexBuiltAt = Date.now();
|
|
81
106
|
return this.index;
|
|
107
|
+
}
|
|
82
108
|
const index = new Map();
|
|
83
109
|
for (const file of fileList) {
|
|
84
110
|
if (file.content)
|
|
@@ -86,6 +112,7 @@ export class FileListIndex {
|
|
|
86
112
|
}
|
|
87
113
|
this.index = index;
|
|
88
114
|
this.indexKey = indexKey;
|
|
115
|
+
this.indexBuiltAt = Date.now();
|
|
89
116
|
const sampleFile = fileList.find((f) => /welcome/i.test(f.path));
|
|
90
117
|
const sampleContent = sampleFile?.content;
|
|
91
118
|
logger.debug("Built file list index", {
|
|
@@ -28,6 +28,7 @@ export declare class WebSocketManager {
|
|
|
28
28
|
private selectiveInvalidationTimer;
|
|
29
29
|
private pendingChangedPaths;
|
|
30
30
|
private wsConnectionId;
|
|
31
|
+
private wsConsecutiveFailures;
|
|
31
32
|
private pokeMetrics;
|
|
32
33
|
constructor(deps: WebSocketDeps);
|
|
33
34
|
getPokeMetrics(): {
|
|
@@ -37,6 +38,7 @@ export declare class WebSocketManager {
|
|
|
37
38
|
connectionId: string | null;
|
|
38
39
|
};
|
|
39
40
|
connect(projectId: string): void;
|
|
41
|
+
private getReconnectDelay;
|
|
40
42
|
dispose(): void;
|
|
41
43
|
private handlePokeMessage;
|
|
42
44
|
private clearPersistentCacheForPublish;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-manager.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/fs/veryfront/websocket-manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"websocket-manager.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/fs/veryfront/websocket-manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAsB/F,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,qBAAqB,EAAE,qBAAqB,CAAC;IAE7C,iBAAiB,EAAE,MAAM,sBAAsB,GAAG,IAAI,CAAC;IACvD,gBAAgB,EAAE,MAAM,aAAa,CAAC;IACtC,aAAa,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACxC,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAC9B,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,gBAAgB,EAAE,CAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,KAC7C,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB;AAED,qBAAa,gBAAgB;IAiBf,OAAO,CAAC,QAAQ,CAAC,IAAI;IAhBjC,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,gBAAgB,CAAsD;IAC9E,OAAO,CAAC,gBAAgB,CAAuD;IAC/E,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,iBAAiB,CAAsD;IAC/E,OAAO,CAAC,0BAA0B,CAAsD;IACxF,OAAO,CAAC,mBAAmB,CAAqB;IAEhD,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,qBAAqB,CAAK;IAClC,OAAO,CAAC,WAAW,CAIjB;gBAE2B,IAAI,EAAE,aAAa;IAEhD,cAAc,IAAI;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;KAC7B;IAID,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IA0EhC,OAAO,CAAC,iBAAiB;IAMzB,OAAO,IAAI,IAAI;IAwBf,OAAO,CAAC,iBAAiB;IAkJzB,OAAO,CAAC,8BAA8B;IA0GtC,OAAO,CAAC,oBAAoB;IAa5B,OAAO,CAAC,6BAA6B;YAiBvB,4BAA4B;YA2H5B,mBAAmB;IAmIjC,OAAO,CAAC,cAAc;IA4BtB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,WAAW;CAyBpB"}
|
|
@@ -5,6 +5,8 @@ import { addPendingInvalidation, getPendingInvalidationsCount, removePendingInva
|
|
|
5
5
|
const logger = baseLogger.component("web-socket-manager");
|
|
6
6
|
const INVALIDATION_DEBOUNCE_MS = 100;
|
|
7
7
|
const WS_RECONNECT_DELAY_MS = 5000;
|
|
8
|
+
const WS_RECONNECT_MAX_DELAY_MS = 120000;
|
|
9
|
+
const WS_RECONNECT_MAX_FAILURES = 10;
|
|
8
10
|
const WS_HEARTBEAT_INTERVAL_MS = 60000;
|
|
9
11
|
const WS_HEARTBEAT_TIMEOUT_MS = 300000;
|
|
10
12
|
export class WebSocketManager {
|
|
@@ -17,6 +19,7 @@ export class WebSocketManager {
|
|
|
17
19
|
selectiveInvalidationTimer = null;
|
|
18
20
|
pendingChangedPaths = new Set();
|
|
19
21
|
wsConnectionId = null;
|
|
22
|
+
wsConsecutiveFailures = 0;
|
|
20
23
|
pokeMetrics = {
|
|
21
24
|
received: 0,
|
|
22
25
|
invalidationsTriggered: 0,
|
|
@@ -30,6 +33,15 @@ export class WebSocketManager {
|
|
|
30
33
|
}
|
|
31
34
|
connect(projectId) {
|
|
32
35
|
this.cleanupTimers();
|
|
36
|
+
if (this.wsConsecutiveFailures >= WS_RECONNECT_MAX_FAILURES) {
|
|
37
|
+
logger.warn("WebSocket reconnect failure cap reached, resetting failure counter", {
|
|
38
|
+
consecutiveFailures: this.wsConsecutiveFailures,
|
|
39
|
+
maxFailures: WS_RECONNECT_MAX_FAILURES,
|
|
40
|
+
cappedDelayMs: WS_RECONNECT_MAX_DELAY_MS,
|
|
41
|
+
projectId,
|
|
42
|
+
});
|
|
43
|
+
this.wsConsecutiveFailures = 0;
|
|
44
|
+
}
|
|
33
45
|
const wsUrl = this.deps.apiBaseUrl
|
|
34
46
|
.replace(/^http:/, "ws:")
|
|
35
47
|
.replace(/^https:/, "wss:")
|
|
@@ -37,11 +49,13 @@ export class WebSocketManager {
|
|
|
37
49
|
const url = `${wsUrl}/ws/${projectId}/events?token=${this.deps.apiToken}`;
|
|
38
50
|
logger.debug("Connecting to WebSocket", {
|
|
39
51
|
url: url.replace(this.deps.apiToken, "***"),
|
|
52
|
+
consecutiveFailures: this.wsConsecutiveFailures,
|
|
40
53
|
});
|
|
41
54
|
try {
|
|
42
55
|
this.ws = new WebSocket(url);
|
|
43
56
|
this.wsConnectionId = dntShim.crypto.randomUUID().slice(0, 8);
|
|
44
57
|
this.ws.onopen = () => {
|
|
58
|
+
this.wsConsecutiveFailures = 0;
|
|
45
59
|
logger.debug("WebSocket connected to events channel", {
|
|
46
60
|
projectId,
|
|
47
61
|
connectionId: this.wsConnectionId,
|
|
@@ -57,24 +71,37 @@ export class WebSocketManager {
|
|
|
57
71
|
this.handlePokeMessage(event);
|
|
58
72
|
};
|
|
59
73
|
this.ws.onclose = () => {
|
|
74
|
+
this.wsConsecutiveFailures++;
|
|
75
|
+
const delay = this.getReconnectDelay();
|
|
60
76
|
logger.debug("WebSocket closed, reconnecting", {
|
|
61
|
-
delayMs:
|
|
77
|
+
delayMs: delay,
|
|
62
78
|
connectionId: this.wsConnectionId,
|
|
63
79
|
totalPokesReceived: this.pokeMetrics.received,
|
|
80
|
+
consecutiveFailures: this.wsConsecutiveFailures,
|
|
64
81
|
});
|
|
65
82
|
this.wsConnectionId = null;
|
|
66
83
|
this.cleanupTimers();
|
|
67
|
-
this.wsReconnectTimer = dntShim.setTimeout(() => this.connect(projectId),
|
|
84
|
+
this.wsReconnectTimer = dntShim.setTimeout(() => this.connect(projectId), delay);
|
|
68
85
|
};
|
|
69
86
|
this.ws.onerror = (error) => {
|
|
70
87
|
logger.warn("WebSocket error", { error });
|
|
71
88
|
};
|
|
72
89
|
}
|
|
73
90
|
catch (error) {
|
|
74
|
-
|
|
75
|
-
|
|
91
|
+
this.wsConsecutiveFailures++;
|
|
92
|
+
const delay = this.getReconnectDelay();
|
|
93
|
+
logger.warn("Failed to connect WebSocket", {
|
|
94
|
+
error,
|
|
95
|
+
consecutiveFailures: this.wsConsecutiveFailures,
|
|
96
|
+
});
|
|
97
|
+
this.wsReconnectTimer = dntShim.setTimeout(() => this.connect(projectId), delay);
|
|
76
98
|
}
|
|
77
99
|
}
|
|
100
|
+
getReconnectDelay() {
|
|
101
|
+
// Exponential backoff: 5s, 10s, 20s, 40s, 80s, capped at 120s
|
|
102
|
+
const delay = WS_RECONNECT_DELAY_MS * Math.pow(2, this.wsConsecutiveFailures - 1);
|
|
103
|
+
return Math.min(delay, WS_RECONNECT_MAX_DELAY_MS);
|
|
104
|
+
}
|
|
78
105
|
dispose() {
|
|
79
106
|
this.cleanupTimers();
|
|
80
107
|
if (this.invalidationTimer) {
|
|
@@ -529,13 +556,19 @@ export class WebSocketManager {
|
|
|
529
556
|
logger.warn("WebSocket heartbeat timeout, reconnecting", {
|
|
530
557
|
timeSinceLastPong,
|
|
531
558
|
});
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
559
|
+
// Detach onclose before closing to prevent double-reconnect:
|
|
560
|
+
// ws.close() triggers onclose asynchronously, which would increment
|
|
561
|
+
// the failure counter and schedule a separate reconnect timer.
|
|
562
|
+
if (this.ws) {
|
|
563
|
+
this.ws.onclose = null;
|
|
564
|
+
try {
|
|
565
|
+
this.ws.close();
|
|
566
|
+
}
|
|
567
|
+
catch (error) {
|
|
568
|
+
logger.error("WebSocket close failed during heartbeat timeout", {
|
|
569
|
+
error: error instanceof Error ? error.message : String(error),
|
|
570
|
+
});
|
|
571
|
+
}
|
|
539
572
|
}
|
|
540
573
|
this.cleanupTimers();
|
|
541
574
|
this.connect(projectId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-executor.d.ts","sourceRoot":"","sources":["../../../../src/src/routing/api/route-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAC;AAClD,OAAO,KAAK,EAAqB,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEzF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,KAAK,EACV,QAAQ,EAKT,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"route-executor.d.ts","sourceRoot":"","sources":["../../../../src/src/routing/api/route-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAC;AAClD,OAAO,KAAK,EAAqB,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEzF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,KAAK,EACV,QAAQ,EAKT,MAAM,0BAA0B,CAAC;AA0GlC,wBAAgB,eAAe,CAC7B,OAAO,EAAE,QAAQ,EACjB,OAAO,EAAE,OAAO,CAAC,OAAO,EACxB,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CA4B3B;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,QAAQ,EACjB,OAAO,EAAE,OAAO,CAAC,OAAO,EACxB,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,cAAc,EACvB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAsB3B"}
|
|
@@ -44,9 +44,34 @@ function createProjectScopedFs(fs, projectDir) {
|
|
|
44
44
|
resolveFile: fs.resolveFile ? (path) => fs.resolveFile(resolvePath(path)) : undefined,
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if an object is a cross-context Response (e.g. Deno native Response
|
|
49
|
+
* when this code runs in the npm package context with a different constructor).
|
|
50
|
+
*/
|
|
51
|
+
function isCrossContextResponse(value) {
|
|
52
|
+
if (value == null || typeof value !== "object")
|
|
53
|
+
return false;
|
|
54
|
+
const r = value;
|
|
55
|
+
return (typeof r.status === "number" &&
|
|
56
|
+
typeof r.headers === "object" &&
|
|
57
|
+
r.headers !== null &&
|
|
58
|
+
typeof r.headers.get === "function" &&
|
|
59
|
+
typeof r.text === "function" &&
|
|
60
|
+
typeof r.arrayBuffer === "function");
|
|
61
|
+
}
|
|
47
62
|
function validateResponse(response) {
|
|
48
63
|
if (response instanceof dntShim.Response)
|
|
49
|
-
return;
|
|
64
|
+
return response;
|
|
65
|
+
// Normalize cross-context Response objects into a real Response so downstream
|
|
66
|
+
// code (toHeadResponse, applyCORSHeaders, withHeaders) always receives a
|
|
67
|
+
// genuine instance with correct body, headers, and status.
|
|
68
|
+
if (isCrossContextResponse(response)) {
|
|
69
|
+
return new dntShim.Response(response.body, {
|
|
70
|
+
status: response.status,
|
|
71
|
+
statusText: response.statusText,
|
|
72
|
+
headers: response.headers,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
50
75
|
throw toError(createError({
|
|
51
76
|
type: "api",
|
|
52
77
|
message: "API handler must return a Response",
|
|
@@ -69,8 +94,7 @@ export function executeAppRoute(handler, request, match, pathname, adapter) {
|
|
|
69
94
|
return createAppRouteMethodNotAllowed(handlerModule);
|
|
70
95
|
try {
|
|
71
96
|
const appContext = { params: normalizeParams(match.params) };
|
|
72
|
-
const response = await resolvedFn(request, appContext);
|
|
73
|
-
validateResponse(response);
|
|
97
|
+
const response = validateResponse(await resolvedFn(request, appContext));
|
|
74
98
|
return method === "HEAD" ? toHeadResponse(response) : response;
|
|
75
99
|
}
|
|
76
100
|
catch (error) {
|
|
@@ -88,9 +112,7 @@ export function executePagesRoute(handler, request, match, pathname, adapter, pr
|
|
|
88
112
|
try {
|
|
89
113
|
const fs = projectDir ? createProjectScopedFs(adapter.fs, projectDir) : adapter.fs;
|
|
90
114
|
const ctx = createContext(request, match, fs);
|
|
91
|
-
|
|
92
|
-
validateResponse(response);
|
|
93
|
-
return response;
|
|
115
|
+
return validateResponse(await methodHandler(ctx));
|
|
94
116
|
}
|
|
95
117
|
catch (error) {
|
|
96
118
|
return handleAPIError(error, pathname, adapter);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../../../../src/src/server/context/request-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAC;AAIlD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,SAAS,GAAG,YAAY,CAAC;CAChC;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,GAAG,cAAc,
|
|
1
|
+
{"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../../../../src/src/server/context/request-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAC;AAIlD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,SAAS,GAAG,YAAY,CAAC;CAChC;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,GAAG,cAAc,CAwBzE;AAED,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,cAAc,EACnB,cAAc,CAAC,EAAE,OAAO,GACvB,MAAM,GAAG,YAAY,GAAG,WAAW,CAIrC;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,cAAc,EAAE,cAAc,CAAC,EAAE,OAAO,GAAG,OAAO,CAExF;AAED,wBAAgB,uBAAuB,CAAC,GAAG,CAAC,EAAE,cAAc,EAAE,cAAc,CAAC,EAAE,OAAO,GAAG,OAAO,CAG/F"}
|
|
@@ -9,7 +9,8 @@ export function createRequestContext(req) {
|
|
|
9
9
|
const effectiveHost = forwardedHost ?? hostHeader ?? hostname;
|
|
10
10
|
const parsed = parseProjectDomain(effectiveHost);
|
|
11
11
|
const xEnvironment = req.headers.get("x-environment");
|
|
12
|
-
const mode =
|
|
12
|
+
const mode = parsed.environment === "preview" ||
|
|
13
|
+
effectiveHost.includes(".preview.") ||
|
|
13
14
|
xEnvironment === "preview"
|
|
14
15
|
? "preview"
|
|
15
16
|
: "production";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../../src/src/server/dev-server/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../../src/src/server/dev-server/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAC;AAclD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAInD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAkCvD,qBAAa,SAAS;IAiBR,OAAO,CAAC,OAAO;IAhB3B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,iBAAiB,CAAqB;IAC9C,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,SAAS,CAA8B;IAC/C,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,CAAsD;IACvE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,iBAAiB,CAAC,CAAa;IACvC,OAAO,CAAC,qBAAqB,CAAC,CAAa;IAC3C,OAAO,CAAC,8BAA8B,CAAC,CAAa;gBAEhC,OAAO,EAAE,gBAAgB;IAQ7C,OAAO,CAAC,OAAO;YAID,YAAY;IAWpB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAqL5B,sEAAsE;IACtE,IAAI,OAAO,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAGjE;IAED,OAAO,CAAC,oBAAoB;YAcd,cAAc;YAiBd,yBAAyB;YAoBzB,uBAAuB;YA4BvB,wBAAwB;IAkBtC,OAAO,CAAC,kBAAkB;IAuBpB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;YAarB,iBAAiB;IAoC/B,qBAAqB,IAAI,UAAU,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,GAAG,IAAI;IAIlE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CA6B5B"}
|
|
@@ -7,6 +7,8 @@ import { ComponentRegistry } from "../../modules/component-registry/index.js";
|
|
|
7
7
|
import { MiddlewarePipeline } from "../../middleware/core/pipeline/index.js";
|
|
8
8
|
import { bootstrapDev } from "../bootstrap.js";
|
|
9
9
|
import { ReloadNotifier } from "../reload-notifier.js";
|
|
10
|
+
import { broadcastUpdate } from "../handlers/preview/hmr-message-router.js";
|
|
11
|
+
import { HMRHandler } from "../handlers/preview/hmr.handler.js";
|
|
10
12
|
import { RequestHandler } from "./request-handler.js";
|
|
11
13
|
import { setupMiddleware } from "./middleware.js";
|
|
12
14
|
import { RouteDiscovery } from "./route-discovery.js";
|
|
@@ -50,6 +52,7 @@ export class DevServer {
|
|
|
50
52
|
_isReady = false;
|
|
51
53
|
reloadUnsubscribe;
|
|
52
54
|
invalidateUnsubscribe;
|
|
55
|
+
releaseExternalBroadcastSource;
|
|
53
56
|
constructor(options) {
|
|
54
57
|
this.options = options;
|
|
55
58
|
this.ready = new Promise((resolve) => {
|
|
@@ -98,6 +101,9 @@ export class DevServer {
|
|
|
98
101
|
hmr: this.options.enableHMR,
|
|
99
102
|
fastRefresh: this.options.enableFastRefresh,
|
|
100
103
|
});
|
|
104
|
+
if (this.options.hmrPort !== undefined) {
|
|
105
|
+
devServerLog.warn("`hmrPort` is deprecated and ignored. HMR now uses /_ws on the main dev server port.", { hmrPort: this.options.hmrPort, serverPort: this.options.port });
|
|
106
|
+
}
|
|
101
107
|
// Set VERYFRONT_DEV_PORT for ESM module loader HTTP fallback
|
|
102
108
|
// This ensures the correct port is used when fetching modules via localhost
|
|
103
109
|
setEnv("VERYFRONT_DEV_PORT", String(this.options.port));
|
|
@@ -125,7 +131,22 @@ export class DevServer {
|
|
|
125
131
|
devServerLog.debug("INVALIDATE callback triggered - clearing runtime handler");
|
|
126
132
|
this.requestHandler?.invalidateRuntimeHandler();
|
|
127
133
|
});
|
|
128
|
-
|
|
134
|
+
// Subscribe to debounced reload for broadcasting updates to connected HMR clients.
|
|
135
|
+
// This subscription must be eagerly registered here rather than lazily inside
|
|
136
|
+
// HMRHandler.initialize(), because HMRHandler.initialize() only runs when the
|
|
137
|
+
// first /_ws WebSocket request arrives. If that connection fails or hasn't
|
|
138
|
+
// happened yet, file changes are silently lost.
|
|
139
|
+
this.releaseExternalBroadcastSource = HMRHandler.registerExternalBroadcastSource();
|
|
140
|
+
this.reloadUnsubscribe = ReloadNotifier.subscribe((changedPaths, project) => {
|
|
141
|
+
hmrLog.debug("RELOAD callback triggered - broadcasting to HMR clients", {
|
|
142
|
+
changedPaths,
|
|
143
|
+
projectSlug: project?.projectSlug,
|
|
144
|
+
});
|
|
145
|
+
// Broadcast without projectSlug filter so that connectHMR() clients
|
|
146
|
+
// (which are registered without a projectSlug) also receive updates.
|
|
147
|
+
broadcastUpdate(changedPaths);
|
|
148
|
+
});
|
|
149
|
+
hmrLog.debug("ReloadNotifier subscriptions registered (invalidate + reload broadcast)");
|
|
129
150
|
}
|
|
130
151
|
const moduleServerUrl = buildLocalhostUrl(this.options.port);
|
|
131
152
|
const vendorBundleHash = "dev-vendor-bundle";
|
|
@@ -334,6 +355,7 @@ export class DevServer {
|
|
|
334
355
|
logger.info("Shutting down dev server...");
|
|
335
356
|
this.reloadUnsubscribe?.();
|
|
336
357
|
this.invalidateUnsubscribe?.();
|
|
358
|
+
this.releaseExternalBroadcastSource?.();
|
|
337
359
|
if (this.fileWatchSetup) {
|
|
338
360
|
const metrics = this.fileWatchSetup.getMetrics();
|
|
339
361
|
if (metrics) {
|
|
@@ -6,6 +6,7 @@ export interface DevServerOptions {
|
|
|
6
6
|
handlerOnly?: boolean;
|
|
7
7
|
/** 0.0.0.0 = all interfaces, 127.0.0.1 = localhost only */
|
|
8
8
|
bindAddress?: string;
|
|
9
|
+
/** @deprecated Ignored: HMR now uses /_ws on the main dev server port. */
|
|
9
10
|
hmrPort?: number;
|
|
10
11
|
moduleServerPort?: number;
|
|
11
12
|
enableHMR?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/src/server/dev-server/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAC;AAClD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,mGAAmG;IACnG,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1F,sFAAsF;IACtF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,oBAAoB,EAAE,MAAM,CAAC;CAC9B"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/src/server/dev-server/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,wBAAwB,CAAC;AAClD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,mGAAmG;IACnG,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1F,sFAAsF;IACtF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,oBAAoB,EAAE,MAAM,CAAC;CAC9B"}
|
|
@@ -5,6 +5,7 @@ export type { HMRClientInfo } from "./hmr-client-manager.js";
|
|
|
5
5
|
export declare class HMRHandler extends BaseHandler {
|
|
6
6
|
private static rateLimiter;
|
|
7
7
|
private static reloadUnsubscribe;
|
|
8
|
+
private static externalBroadcastSourceCount;
|
|
8
9
|
private static initialized;
|
|
9
10
|
metadata: HandlerMetadata;
|
|
10
11
|
private static initialize;
|
|
@@ -23,6 +24,7 @@ export declare class HMRHandler extends BaseHandler {
|
|
|
23
24
|
messagesForwarded: number;
|
|
24
25
|
lastBroadcastTime: number;
|
|
25
26
|
};
|
|
27
|
+
static registerExternalBroadcastSource(): () => void;
|
|
26
28
|
static shutdown(): void;
|
|
27
29
|
private static getMessageSize;
|
|
28
30
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hmr.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/preview/hmr.handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AASrD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,eAAe,EAEpB,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"hmr.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/preview/hmr.handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AASrD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,eAAe,EAEpB,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAmBrB,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAK7D,qBAAa,UAAW,SAAQ,WAAW;IACzC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAgD;IAC1E,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAA6B;IAC7D,OAAO,CAAC,MAAM,CAAC,4BAA4B,CAAK;IAChD,OAAO,CAAC,MAAM,CAAC,WAAW,CAAS;IAEnC,QAAQ,EAAE,eAAe,CAKvB;IAEF,OAAO,CAAC,MAAM,CAAC,UAAU;IAsCzB,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IA0JzE;;;;;OAKG;YACW,wBAAwB;IAuCtC,MAAM,CAAC,cAAc,IAAI,MAAM;IAI/B,MAAM,CAAC,UAAU,IAAI;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,cAAc,EAAE,MAAM,CAAC;QACvB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,iBAAiB,EAAE,MAAM,CAAC;KAC3B;IAID,MAAM,CAAC,+BAA+B,IAAI,MAAM,IAAI;IAWpD,MAAM,CAAC,QAAQ,IAAI,IAAI;IAcvB,OAAO,CAAC,MAAM,CAAC,cAAc;CAO9B"}
|
|
@@ -6,6 +6,7 @@ import { HandlerPriority, } from "../types.js";
|
|
|
6
6
|
import { ReloadNotifier } from "../../reload-notifier.js";
|
|
7
7
|
import { invalidateProjectCaches } from "../../context/cache-invalidation.js";
|
|
8
8
|
import { isExtendedFSAdapter } from "../../../platform/adapters/fs/wrapper.js";
|
|
9
|
+
import { isLocalDevHost } from "../../utils/domain-parser.js";
|
|
9
10
|
import { addClient, clearAll, getClient, getClientCount, getClientDetails, removeClient, } from "./hmr-client-manager.js";
|
|
10
11
|
import { getPingIntervalMs, startPingInterval, stopPingInterval } from "./hmr-ping-keepalive.js";
|
|
11
12
|
import { broadcastUpdate, getMetrics } from "./hmr-message-router.js";
|
|
@@ -15,6 +16,7 @@ const PRIORITY_HMR = HandlerPriority.EARLY;
|
|
|
15
16
|
export class HMRHandler extends BaseHandler {
|
|
16
17
|
static rateLimiter = new RateLimiter(HMR_MAX_MESSAGES_PER_MINUTE);
|
|
17
18
|
static reloadUnsubscribe = null;
|
|
19
|
+
static externalBroadcastSourceCount = 0;
|
|
18
20
|
static initialized = false;
|
|
19
21
|
metadata = {
|
|
20
22
|
name: "HMRHandler",
|
|
@@ -39,6 +41,13 @@ export class HMRHandler extends BaseHandler {
|
|
|
39
41
|
environment: project?.environment,
|
|
40
42
|
branchId: project?.branch ?? undefined,
|
|
41
43
|
});
|
|
44
|
+
if (HMRHandler.externalBroadcastSourceCount > 0) {
|
|
45
|
+
logger.debug("Skipping handler broadcast - external source active", {
|
|
46
|
+
projectSlug: project?.projectSlug,
|
|
47
|
+
externalBroadcastSourceCount: HMRHandler.externalBroadcastSourceCount,
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
42
51
|
broadcastUpdate(changedPaths, project?.projectSlug);
|
|
43
52
|
});
|
|
44
53
|
startPingInterval();
|
|
@@ -54,13 +63,16 @@ export class HMRHandler extends BaseHandler {
|
|
|
54
63
|
const isPreviewMode = ctx.requestContext?.mode === "preview" || queryEnv === "preview";
|
|
55
64
|
const isLocal = !!ctx.isLocalProject;
|
|
56
65
|
const host = req.headers.get("host") ?? "";
|
|
57
|
-
const isLocalhost =
|
|
66
|
+
const isLocalhost = isLocalDevHost(host);
|
|
58
67
|
if (!isPreviewMode && !isLocal && !isLocalhost) {
|
|
59
|
-
logger.
|
|
68
|
+
logger.warn("Skipping /_ws - not preview, local dev, or localhost", {
|
|
60
69
|
mode: ctx.requestContext?.mode,
|
|
61
70
|
queryEnv,
|
|
62
71
|
isLocalProject: ctx.isLocalProject,
|
|
63
72
|
host,
|
|
73
|
+
isPreviewMode,
|
|
74
|
+
isLocal,
|
|
75
|
+
isLocalhost,
|
|
64
76
|
});
|
|
65
77
|
return Promise.resolve(this.continue());
|
|
66
78
|
}
|
|
@@ -218,12 +230,19 @@ export class HMRHandler extends BaseHandler {
|
|
|
218
230
|
static getMetrics() {
|
|
219
231
|
return getMetrics();
|
|
220
232
|
}
|
|
233
|
+
static registerExternalBroadcastSource() {
|
|
234
|
+
HMRHandler.externalBroadcastSourceCount++;
|
|
235
|
+
return () => {
|
|
236
|
+
HMRHandler.externalBroadcastSourceCount = Math.max(0, HMRHandler.externalBroadcastSourceCount - 1);
|
|
237
|
+
};
|
|
238
|
+
}
|
|
221
239
|
static shutdown() {
|
|
222
240
|
HMRHandler.reloadUnsubscribe?.();
|
|
223
241
|
HMRHandler.reloadUnsubscribe = null;
|
|
224
242
|
stopPingInterval();
|
|
225
243
|
clearAll();
|
|
226
244
|
HMRHandler.rateLimiter = new RateLimiter(HMR_MAX_MESSAGES_PER_MINUTE);
|
|
245
|
+
HMRHandler.externalBroadcastSourceCount = 0;
|
|
227
246
|
HMRHandler.initialized = false;
|
|
228
247
|
logger.debug("Shutdown complete");
|
|
229
248
|
}
|
|
@@ -14,7 +14,8 @@ import { DevServer, type DevServerOptions, type FileWatcherMetrics, type RouteDi
|
|
|
14
14
|
import { type DiscoveryOptions, type ServerHandle, startProductionServer, type StartProductionServerOptions } from "./production-server.js";
|
|
15
15
|
export { DevServer, startDevServer, startProductionServer };
|
|
16
16
|
export type { DevServerOptions, DiscoveryOptions, FileWatcherMetrics, RouteDirectory, ServerHandle, StartProductionServerOptions, };
|
|
17
|
-
|
|
17
|
+
import { ReloadNotifier } from "./reload-notifier.js";
|
|
18
|
+
export { ReloadNotifier };
|
|
18
19
|
export type { BuildOptions, BuildStats } from "./build-types.js";
|
|
19
20
|
/** Shared options for both development and production server modes. */
|
|
20
21
|
interface BaseServerOptions {
|
|
@@ -36,6 +37,7 @@ interface BaseServerOptions {
|
|
|
36
37
|
}
|
|
37
38
|
export interface StartDevModeOptions extends BaseServerOptions {
|
|
38
39
|
mode?: "development";
|
|
40
|
+
/** @deprecated Ignored: HMR now uses /_ws on the main server port. */
|
|
39
41
|
hmrPort?: number;
|
|
40
42
|
moduleServerPort?: number;
|
|
41
43
|
enableHMR?: boolean;
|