@ikas/component-cli 1.4.0-beta.231 → 1.4.0-beta.233
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +57 -28
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +28 -13
- package/dist/commands/dev.js.map +1 -1
- package/dist/utils/css-import-resolver.d.ts +16 -1
- package/dist/utils/css-import-resolver.d.ts.map +1 -1
- package/dist/utils/css-import-resolver.js +64 -9
- package/dist/utils/css-import-resolver.js.map +1 -1
- package/dist/utils/design-asset-room-envelope.d.ts +70 -0
- package/dist/utils/design-asset-room-envelope.d.ts.map +1 -0
- package/dist/utils/design-asset-room-envelope.js +110 -0
- package/dist/utils/design-asset-room-envelope.js.map +1 -0
- package/dist/utils/design-asset-room.d.ts +47 -0
- package/dist/utils/design-asset-room.d.ts.map +1 -0
- package/dist/utils/design-asset-room.js +251 -0
- package/dist/utils/design-asset-room.js.map +1 -0
- package/dist/utils/websocket-server.d.ts +33 -0
- package/dist/utils/websocket-server.d.ts.map +1 -1
- package/dist/utils/websocket-server.js +83 -56
- package/dist/utils/websocket-server.js.map +1 -1
- package/package.json +5 -2
|
@@ -1,8 +1,60 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
|
+
import postcss from "postcss";
|
|
4
|
+
// `@import "x.css"`, `@import 'x.css'`, `@import url(x.css)`, `@import url("x.css")`
|
|
5
|
+
const IMPORT_PATH_RE = /(?:url\(\s*(["']?)([^"')]+)\1\s*\)|(["'])([^"']+)\3)/;
|
|
6
|
+
const importRegex = new RegExp(`@import\\s+${IMPORT_PATH_RE.source}\\s*;?`, "g");
|
|
7
|
+
// http:, https:, data:, protocol-relative — left for the browser to fetch.
|
|
8
|
+
const isExternalImport = (importPath) => /^([a-z][a-z0-9+.-]*:|\/\/)/i.test(importPath);
|
|
9
|
+
/**
|
|
10
|
+
* Parses with postcss, the parser the generated theme's Vite build runs on these files, and checks
|
|
11
|
+
* every local @import resolves. Either failure there fails the whole theme publish with a bare
|
|
12
|
+
* "an error occurred", so catch it here.
|
|
13
|
+
*/
|
|
14
|
+
export function findCssError(css, filePath) {
|
|
15
|
+
const file = path.relative(process.cwd(), filePath);
|
|
16
|
+
let root;
|
|
17
|
+
try {
|
|
18
|
+
root = postcss.parse(css);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (error?.name !== "CssSyntaxError")
|
|
22
|
+
throw error;
|
|
23
|
+
return {
|
|
24
|
+
file,
|
|
25
|
+
line: error.line ?? 0,
|
|
26
|
+
column: error.column ?? 0,
|
|
27
|
+
message: `CSS syntax error: ${error.reason}`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
let issue = null;
|
|
31
|
+
root.walkAtRules("import", (rule) => {
|
|
32
|
+
const match = rule.params.match(IMPORT_PATH_RE);
|
|
33
|
+
const importPath = match?.[2] ?? match?.[4];
|
|
34
|
+
if (!importPath || isExternalImport(importPath))
|
|
35
|
+
return;
|
|
36
|
+
if (fs.existsSync(path.resolve(path.dirname(filePath), importPath)))
|
|
37
|
+
return;
|
|
38
|
+
issue = {
|
|
39
|
+
file,
|
|
40
|
+
line: rule.source?.start?.line ?? 0,
|
|
41
|
+
column: rule.source?.start?.column ?? 0,
|
|
42
|
+
message: `CSS import not found: ${importPath}`,
|
|
43
|
+
};
|
|
44
|
+
return false;
|
|
45
|
+
});
|
|
46
|
+
return issue;
|
|
47
|
+
}
|
|
48
|
+
function assertValidCss(css, filePath) {
|
|
49
|
+
const issue = findCssError(css, filePath);
|
|
50
|
+
if (issue) {
|
|
51
|
+
throw new Error(`${issue.file}:${issue.line}:${issue.column}: ${issue.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
3
54
|
/**
|
|
4
55
|
* Resolves CSS @import statements by inlining the imported file contents.
|
|
5
|
-
* Handles relative paths and nested imports recursively.
|
|
56
|
+
* Handles relative paths and nested imports recursively. External imports (http:, data:, //)
|
|
57
|
+
* are left in place.
|
|
6
58
|
*
|
|
7
59
|
* @param css - The CSS content to process
|
|
8
60
|
* @param cssFilePath - The absolute path to the CSS file (used to resolve relative imports)
|
|
@@ -13,14 +65,20 @@ import * as path from "path";
|
|
|
13
65
|
* nesting depth) because another CSS artifact already carries it — e.g. a shared sub-component's
|
|
14
66
|
* styles.css that the entry's chunk CSS covers. Skipped imports are replaced with a comment.
|
|
15
67
|
* @returns The CSS with all @import statements replaced by the imported content
|
|
68
|
+
* @throws When the file or any file it imports has a CSS syntax error or a local @import that
|
|
69
|
+
* does not exist (`file:line:column: ...`)
|
|
16
70
|
*/
|
|
17
71
|
export function resolveCssImports(css, cssFilePath, resolvedPaths, skipPaths) {
|
|
18
|
-
const importRegex = /@import\s+["']([^"']+)["']\s*;?/g;
|
|
19
72
|
const processedPaths = resolvedPaths ?? new Set();
|
|
20
73
|
function resolveImports(content, currentDir) {
|
|
21
|
-
return content.replace(importRegex, (match,
|
|
22
|
-
|
|
74
|
+
return content.replace(importRegex, (match, _q1, urlPath, _q2, quotedPath) => {
|
|
75
|
+
const importPath = urlPath ?? quotedPath;
|
|
76
|
+
if (isExternalImport(importPath))
|
|
77
|
+
return match;
|
|
23
78
|
const resolvedPath = path.resolve(currentDir, importPath);
|
|
79
|
+
// assertValidCss already rejected a real missing import; this one sits inside a comment.
|
|
80
|
+
if (!fs.existsSync(resolvedPath))
|
|
81
|
+
return match;
|
|
24
82
|
// Already covered by another artifact (shared chunk CSS) — inlining it here would
|
|
25
83
|
// duplicate every rule for this component.
|
|
26
84
|
if (skipPaths?.has(resolvedPath)) {
|
|
@@ -31,19 +89,16 @@ export function resolveCssImports(css, cssFilePath, resolvedPaths, skipPaths) {
|
|
|
31
89
|
console.warn(`Circular CSS import detected: ${resolvedPath}`);
|
|
32
90
|
return `/* Circular import skipped: ${importPath} */`;
|
|
33
91
|
}
|
|
34
|
-
// Check if file exists
|
|
35
|
-
if (!fs.existsSync(resolvedPath)) {
|
|
36
|
-
console.warn(`CSS import not found: ${importPath} (resolved to ${resolvedPath})`);
|
|
37
|
-
return `/* Import not found: ${importPath} */`;
|
|
38
|
-
}
|
|
39
92
|
processedPaths.add(resolvedPath);
|
|
40
93
|
// Read and recursively resolve imports in the imported file
|
|
41
94
|
const importedCss = fs.readFileSync(resolvedPath, "utf-8");
|
|
95
|
+
assertValidCss(importedCss, resolvedPath);
|
|
42
96
|
const importedDir = path.dirname(resolvedPath);
|
|
43
97
|
const resolvedCss = resolveImports(importedCss, importedDir);
|
|
44
98
|
return `/* Imported from: ${importPath} */\n${resolvedCss}\n/* End import: ${importPath} */`;
|
|
45
99
|
});
|
|
46
100
|
}
|
|
101
|
+
assertValidCss(css, cssFilePath);
|
|
47
102
|
// Add the main file to processed paths to prevent self-import
|
|
48
103
|
processedPaths.add(path.resolve(cssFilePath));
|
|
49
104
|
return resolveImports(css, path.dirname(cssFilePath));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"css-import-resolver.js","sourceRoot":"","sources":["../../src/utils/css-import-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"css-import-resolver.js","sourceRoot":"","sources":["../../src/utils/css-import-resolver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire format of the dev-server protocol carried over a design asset room.
|
|
3
|
+
*
|
|
4
|
+
* KEEP IN SYNC with `apps/editor/src/models/editor-design/dev-server/room-envelope.ts`
|
|
5
|
+
* (the editor and the CLI ship separately, so the module is copied, not shared).
|
|
6
|
+
*
|
|
7
|
+
* The room's gateway relays every Hocuspocus stateless message to all OTHER
|
|
8
|
+
* participants: the dev server and every editor tab of the same user and
|
|
9
|
+
* theme. Each payload is one JSON envelope:
|
|
10
|
+
*
|
|
11
|
+
* { v: 1, from, to?, msg } — one whole protocol message
|
|
12
|
+
* { v: 1, from, to?, chunk: {id,i,n}, data } — slice `i` of `n` of a large one
|
|
13
|
+
* { v: 1, from, to?, ctl } — room bookkeeping (join/leave/hello/ping)
|
|
14
|
+
*
|
|
15
|
+
* `from` is the dev server (`DEV_SERVER_PEER`) or an editor tab's `tabId`;
|
|
16
|
+
* `to` addresses one peer, absent means everyone. The protocol messages
|
|
17
|
+
* themselves are the same JSON the dev server's localhost socket carries.
|
|
18
|
+
*/
|
|
19
|
+
export declare const ROOM_ENVELOPE_VERSION = 1;
|
|
20
|
+
/** `from` / `to` of the dev server. Editor tabs use their `tabId`. */
|
|
21
|
+
export declare const DEV_SERVER_PEER = "dev-server";
|
|
22
|
+
/**
|
|
23
|
+
* Largest serialized message sent whole, in UTF-16 code units. JSON escaping
|
|
24
|
+
* and UTF-8 can grow a slice up to ~6x, which stays under the gateway's 5 MiB
|
|
25
|
+
* per-message limit (a larger message closes the connection).
|
|
26
|
+
*/
|
|
27
|
+
export declare const ROOM_CHUNK_CHARS: number;
|
|
28
|
+
/**
|
|
29
|
+
* - `join`: an editor tab is here (sent after it authenticates and on `hello`).
|
|
30
|
+
* - `leave`: an editor tab is going away.
|
|
31
|
+
* - `hello`: the dev server (re)joined; tabs answer with `join`.
|
|
32
|
+
* - `ping`: the dev server is alive (editors treat silence as a lost dev server).
|
|
33
|
+
*/
|
|
34
|
+
export type RoomControl = "join" | "leave" | "hello" | "ping";
|
|
35
|
+
export interface RoomEnvelope {
|
|
36
|
+
v: number;
|
|
37
|
+
from: string;
|
|
38
|
+
to?: string;
|
|
39
|
+
msg?: unknown;
|
|
40
|
+
chunk?: {
|
|
41
|
+
id: string;
|
|
42
|
+
i: number;
|
|
43
|
+
n: number;
|
|
44
|
+
};
|
|
45
|
+
data?: string;
|
|
46
|
+
ctl?: RoomControl;
|
|
47
|
+
}
|
|
48
|
+
/** Envelope for a control message. */
|
|
49
|
+
export declare function encodeRoomControl(from: string, ctl: RoomControl, to?: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Envelopes for one protocol message, already serialized as `json`. A message
|
|
52
|
+
* up to `ROOM_CHUNK_CHARS` is embedded as is (no second serialization); a
|
|
53
|
+
* larger one is split into chunks the receiver reassembles.
|
|
54
|
+
*/
|
|
55
|
+
export declare function encodeRoomMessage(from: string, json: string, to?: string, newId?: () => string): string[];
|
|
56
|
+
/** Parses a stateless payload; anything that is not a v1 envelope is null. */
|
|
57
|
+
export declare function parseRoomEnvelope(payload: string): RoomEnvelope | null;
|
|
58
|
+
/**
|
|
59
|
+
* Reassembles chunked messages. Chunks of one message come from one sender
|
|
60
|
+
* over one socket, so they arrive in order; a partial message whose sender
|
|
61
|
+
* went quiet is dropped after `CHUNK_TTL_MS`.
|
|
62
|
+
*/
|
|
63
|
+
export declare class RoomChunkAssembler {
|
|
64
|
+
private partial;
|
|
65
|
+
/** Returns the serialized message when `envelope` completes one, else null. */
|
|
66
|
+
accept(envelope: RoomEnvelope, now?: number): string | null;
|
|
67
|
+
/** Drops partial messages of one sender (it left or reconnected). */
|
|
68
|
+
forget(from: string): void;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=design-asset-room-envelope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"design-asset-room-envelope.d.ts","sourceRoot":"","sources":["../../src/utils/design-asset-room-envelope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC,sEAAsE;AACtE,eAAO,MAAM,eAAe,eAAe,CAAC;AAE5C;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAAa,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAE9D,MAAM,WAAW,YAAY;IAC3B,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,WAAW,CAAC;CACnB;AAMD,sCAAsC;AACtC,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAErF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,MAAiB,GAAG,MAAM,EAAE,CAYnH;AAED,8EAA8E;AAC9E,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAQtE;AAID;;;;GAIG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAwE;IAEvF,+EAA+E;IAC/E,MAAM,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,SAAa,GAAG,MAAM,GAAG,IAAI;IAwB/D,qEAAqE;IACrE,MAAM,CAAC,IAAI,EAAE,MAAM;CAKpB"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire format of the dev-server protocol carried over a design asset room.
|
|
3
|
+
*
|
|
4
|
+
* KEEP IN SYNC with `apps/editor/src/models/editor-design/dev-server/room-envelope.ts`
|
|
5
|
+
* (the editor and the CLI ship separately, so the module is copied, not shared).
|
|
6
|
+
*
|
|
7
|
+
* The room's gateway relays every Hocuspocus stateless message to all OTHER
|
|
8
|
+
* participants: the dev server and every editor tab of the same user and
|
|
9
|
+
* theme. Each payload is one JSON envelope:
|
|
10
|
+
*
|
|
11
|
+
* { v: 1, from, to?, msg } — one whole protocol message
|
|
12
|
+
* { v: 1, from, to?, chunk: {id,i,n}, data } — slice `i` of `n` of a large one
|
|
13
|
+
* { v: 1, from, to?, ctl } — room bookkeeping (join/leave/hello/ping)
|
|
14
|
+
*
|
|
15
|
+
* `from` is the dev server (`DEV_SERVER_PEER`) or an editor tab's `tabId`;
|
|
16
|
+
* `to` addresses one peer, absent means everyone. The protocol messages
|
|
17
|
+
* themselves are the same JSON the dev server's localhost socket carries.
|
|
18
|
+
*/
|
|
19
|
+
export const ROOM_ENVELOPE_VERSION = 1;
|
|
20
|
+
/** `from` / `to` of the dev server. Editor tabs use their `tabId`. */
|
|
21
|
+
export const DEV_SERVER_PEER = "dev-server";
|
|
22
|
+
/**
|
|
23
|
+
* Largest serialized message sent whole, in UTF-16 code units. JSON escaping
|
|
24
|
+
* and UTF-8 can grow a slice up to ~6x, which stays under the gateway's 5 MiB
|
|
25
|
+
* per-message limit (a larger message closes the connection).
|
|
26
|
+
*/
|
|
27
|
+
export const ROOM_CHUNK_CHARS = 512 * 1024;
|
|
28
|
+
function header(from, to) {
|
|
29
|
+
return `{"v":${ROOM_ENVELOPE_VERSION},"from":${JSON.stringify(from)}${to ? `,"to":${JSON.stringify(to)}` : ""}`;
|
|
30
|
+
}
|
|
31
|
+
/** Envelope for a control message. */
|
|
32
|
+
export function encodeRoomControl(from, ctl, to) {
|
|
33
|
+
return `${header(from, to)},"ctl":"${ctl}"}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Envelopes for one protocol message, already serialized as `json`. A message
|
|
37
|
+
* up to `ROOM_CHUNK_CHARS` is embedded as is (no second serialization); a
|
|
38
|
+
* larger one is split into chunks the receiver reassembles.
|
|
39
|
+
*/
|
|
40
|
+
export function encodeRoomMessage(from, json, to, newId = randomId) {
|
|
41
|
+
const head = header(from, to);
|
|
42
|
+
if (json.length <= ROOM_CHUNK_CHARS)
|
|
43
|
+
return [`${head},"msg":${json}}`];
|
|
44
|
+
const id = newId();
|
|
45
|
+
const n = Math.ceil(json.length / ROOM_CHUNK_CHARS);
|
|
46
|
+
const out = [];
|
|
47
|
+
for (let i = 0; i < n; i++) {
|
|
48
|
+
const data = json.slice(i * ROOM_CHUNK_CHARS, (i + 1) * ROOM_CHUNK_CHARS);
|
|
49
|
+
out.push(`${head},"chunk":{"id":"${id}","i":${i},"n":${n}},"data":${JSON.stringify(data)}}`);
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
/** Parses a stateless payload; anything that is not a v1 envelope is null. */
|
|
54
|
+
export function parseRoomEnvelope(payload) {
|
|
55
|
+
try {
|
|
56
|
+
const envelope = JSON.parse(payload);
|
|
57
|
+
if (!envelope || envelope.v !== ROOM_ENVELOPE_VERSION || typeof envelope.from !== "string")
|
|
58
|
+
return null;
|
|
59
|
+
return envelope;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const CHUNK_TTL_MS = 60_000;
|
|
66
|
+
/**
|
|
67
|
+
* Reassembles chunked messages. Chunks of one message come from one sender
|
|
68
|
+
* over one socket, so they arrive in order; a partial message whose sender
|
|
69
|
+
* went quiet is dropped after `CHUNK_TTL_MS`.
|
|
70
|
+
*/
|
|
71
|
+
export class RoomChunkAssembler {
|
|
72
|
+
partial = new Map();
|
|
73
|
+
/** Returns the serialized message when `envelope` completes one, else null. */
|
|
74
|
+
accept(envelope, now = Date.now()) {
|
|
75
|
+
const chunk = envelope.chunk;
|
|
76
|
+
if (!chunk || typeof envelope.data !== "string")
|
|
77
|
+
return null;
|
|
78
|
+
if (!(chunk.n > 0) || !(chunk.i >= 0) || chunk.i >= chunk.n)
|
|
79
|
+
return null;
|
|
80
|
+
for (const [key, entry] of this.partial) {
|
|
81
|
+
if (now - entry.at > CHUNK_TTL_MS)
|
|
82
|
+
this.partial.delete(key);
|
|
83
|
+
}
|
|
84
|
+
const key = `${envelope.from}:${chunk.id}`;
|
|
85
|
+
let entry = this.partial.get(key);
|
|
86
|
+
if (!entry) {
|
|
87
|
+
entry = { parts: new Array(chunk.n), received: 0, at: now };
|
|
88
|
+
this.partial.set(key, entry);
|
|
89
|
+
}
|
|
90
|
+
if (entry.parts[chunk.i] === undefined)
|
|
91
|
+
entry.received += 1;
|
|
92
|
+
entry.parts[chunk.i] = envelope.data;
|
|
93
|
+
entry.at = now;
|
|
94
|
+
if (entry.received < chunk.n)
|
|
95
|
+
return null;
|
|
96
|
+
this.partial.delete(key);
|
|
97
|
+
return entry.parts.join("");
|
|
98
|
+
}
|
|
99
|
+
/** Drops partial messages of one sender (it left or reconnected). */
|
|
100
|
+
forget(from) {
|
|
101
|
+
for (const key of this.partial.keys()) {
|
|
102
|
+
if (key.startsWith(`${from}:`))
|
|
103
|
+
this.partial.delete(key);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function randomId() {
|
|
108
|
+
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=design-asset-room-envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"design-asset-room-envelope.js","sourceRoot":"","sources":["../../src/utils/design-asset-room-envelope.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { DevServerWebSocket } from "./websocket-server.js";
|
|
2
|
+
/**
|
|
3
|
+
* The design asset room `ikas theme dev` / `ikas theme customize` opened for
|
|
4
|
+
* this dev server, from the environment the CLI starts it with.
|
|
5
|
+
*/
|
|
6
|
+
export interface DesignAssetRoomEnv {
|
|
7
|
+
/** `wss://<gateway>/design-asset-room/<userId>/<roomId>` */
|
|
8
|
+
wsUrl: string;
|
|
9
|
+
/** Hocuspocus document name: the URL's last two segments, `<userId>/<roomId>`. */
|
|
10
|
+
documentName: string;
|
|
11
|
+
/** Private file holding the CLI user's access token; the CLI rewrites it every few minutes. */
|
|
12
|
+
tokenFile: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function readDesignAssetRoomEnv(env?: NodeJS.ProcessEnv): DesignAssetRoomEnv | null;
|
|
15
|
+
/**
|
|
16
|
+
* Joins the design asset room as the dev server and feeds every editor tab of
|
|
17
|
+
* the room into `DevServerWebSocket` as a client of its own, so tab routing,
|
|
18
|
+
* request ids and initial state work exactly as on the localhost socket.
|
|
19
|
+
* Messages travel as Hocuspocus stateless messages (see the envelope module).
|
|
20
|
+
*/
|
|
21
|
+
export declare class DesignAssetRoomBridge {
|
|
22
|
+
private readonly env;
|
|
23
|
+
private readonly server;
|
|
24
|
+
private provider;
|
|
25
|
+
private peers;
|
|
26
|
+
private assembler;
|
|
27
|
+
private pingTimer;
|
|
28
|
+
private authRetryTimer;
|
|
29
|
+
private hasJoined;
|
|
30
|
+
private hasWarnedAuth;
|
|
31
|
+
constructor(env: DesignAssetRoomEnv, server: DevServerWebSocket);
|
|
32
|
+
start(): void;
|
|
33
|
+
stop(): void;
|
|
34
|
+
/** Sends one serialized protocol message to one tab, or to every tab when `tabId` is undefined. */
|
|
35
|
+
sendTo(tabId: string | undefined, data: string): void;
|
|
36
|
+
private onJoined;
|
|
37
|
+
private onRefused;
|
|
38
|
+
/** Lost the gateway: tabs re-join after our `hello` once we are back. */
|
|
39
|
+
private onLeft;
|
|
40
|
+
private onStateless;
|
|
41
|
+
private messageOf;
|
|
42
|
+
private addPeer;
|
|
43
|
+
private dropPeer;
|
|
44
|
+
private dropAllPeers;
|
|
45
|
+
private tick;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=design-asset-room.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"design-asset-room.d.ts","sourceRoot":"","sources":["../../src/utils/design-asset-room.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAyC,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEvG;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,YAAY,EAAE,MAAM,CAAC;IACrB,+FAA+F;IAC/F,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,sBAAsB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,kBAAkB,GAAG,IAAI,CAoBtG;AA2CD;;;;;GAKG;AACH,qBAAa,qBAAqB;IAU9B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAVzB,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,aAAa,CAAS;gBAGX,GAAG,EAAE,kBAAkB,EACvB,MAAM,EAAE,kBAAkB;IAG7C,KAAK;IA0CL,IAAI;IAaJ,mGAAmG;IACnG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM;IAO9C,OAAO,CAAC,QAAQ;IAUhB,OAAO,CAAC,SAAS;IAmBjB,yEAAyE;IACzE,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,WAAW;IAgCnB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,IAAI;CASb"}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { HocuspocusProvider, HocuspocusProviderWebsocket } from "@hocuspocus/provider";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { WebSocket } from "ws";
|
|
5
|
+
import * as Y from "yjs";
|
|
6
|
+
import { DEV_SERVER_PEER, encodeRoomControl, encodeRoomMessage, parseRoomEnvelope, RoomChunkAssembler, } from "./design-asset-room-envelope.js";
|
|
7
|
+
export function readDesignAssetRoomEnv(env = process.env) {
|
|
8
|
+
const wsUrl = env.IKAS_DESIGN_ASSET_ROOM_WS_URL;
|
|
9
|
+
const roomId = env.IKAS_DESIGN_ASSET_ROOM_ID;
|
|
10
|
+
const tokenFile = env.IKAS_DESIGN_ASSET_ROOM_TOKEN_FILE;
|
|
11
|
+
if (!wsUrl || !roomId || !tokenFile)
|
|
12
|
+
return null;
|
|
13
|
+
let url;
|
|
14
|
+
try {
|
|
15
|
+
url = new URL(wsUrl);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
// The token goes to this host: only over TLS (plain ws only to a local gateway).
|
|
21
|
+
const isLocal = url.hostname === "localhost" || url.hostname === "127.0.0.1";
|
|
22
|
+
if (url.protocol !== "wss:" && !(url.protocol === "ws:" && isLocal))
|
|
23
|
+
return null;
|
|
24
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
25
|
+
if (segments.length < 2 || segments[segments.length - 1] !== roomId)
|
|
26
|
+
return null;
|
|
27
|
+
const documentName = segments.slice(-2).join("/");
|
|
28
|
+
return { wsUrl, documentName, tokenFile };
|
|
29
|
+
}
|
|
30
|
+
/** The dev server says it is alive this often; editors treat a longer silence as a lost dev server. */
|
|
31
|
+
const PING_INTERVAL_MS = 15_000;
|
|
32
|
+
/** An editor tab silent this long is gone (tabs ping every 30s). */
|
|
33
|
+
const PEER_TIMEOUT_MS = 75_000;
|
|
34
|
+
/** Retry after the room refused us: the CLI may still be refreshing the token file. */
|
|
35
|
+
const AUTH_RETRY_MS = 30_000;
|
|
36
|
+
/**
|
|
37
|
+
* A tab joins once for itself and once more when our `hello` crosses its
|
|
38
|
+
* first join; a second join this soon after its state went out is that echo,
|
|
39
|
+
* not a reconnect (which waits out the editor's 3s reconnect delay).
|
|
40
|
+
*/
|
|
41
|
+
const REJOIN_ECHO_MS = 2_000;
|
|
42
|
+
/** Room diagnostics with an ISO timestamp, to match against the gateway's logs. Never logs the token. */
|
|
43
|
+
function logRoom(message) {
|
|
44
|
+
console.log(chalk.gray(` [design-asset-room ${new Date().toISOString()}] ${message}`));
|
|
45
|
+
}
|
|
46
|
+
/** An editor tab of the room, as the dev server's `DevServerWebSocket` sees it. */
|
|
47
|
+
class RoomPeer {
|
|
48
|
+
tabId;
|
|
49
|
+
room;
|
|
50
|
+
viaRoom = true;
|
|
51
|
+
readyState = WebSocket.OPEN;
|
|
52
|
+
lastSeenAt = Date.now();
|
|
53
|
+
stateSentAt = Date.now();
|
|
54
|
+
hooks = null;
|
|
55
|
+
constructor(tabId, room) {
|
|
56
|
+
this.tabId = tabId;
|
|
57
|
+
this.room = room;
|
|
58
|
+
}
|
|
59
|
+
send(data) {
|
|
60
|
+
if (this.readyState !== WebSocket.OPEN)
|
|
61
|
+
return;
|
|
62
|
+
this.room.sendTo(this.tabId, data);
|
|
63
|
+
}
|
|
64
|
+
close() {
|
|
65
|
+
this.readyState = WebSocket.CLOSED;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Joins the design asset room as the dev server and feeds every editor tab of
|
|
70
|
+
* the room into `DevServerWebSocket` as a client of its own, so tab routing,
|
|
71
|
+
* request ids and initial state work exactly as on the localhost socket.
|
|
72
|
+
* Messages travel as Hocuspocus stateless messages (see the envelope module).
|
|
73
|
+
*/
|
|
74
|
+
export class DesignAssetRoomBridge {
|
|
75
|
+
env;
|
|
76
|
+
server;
|
|
77
|
+
provider = null;
|
|
78
|
+
peers = new Map();
|
|
79
|
+
assembler = new RoomChunkAssembler();
|
|
80
|
+
pingTimer = null;
|
|
81
|
+
authRetryTimer = null;
|
|
82
|
+
hasJoined = false;
|
|
83
|
+
hasWarnedAuth = false;
|
|
84
|
+
constructor(env, server) {
|
|
85
|
+
this.env = env;
|
|
86
|
+
this.server = server;
|
|
87
|
+
}
|
|
88
|
+
start() {
|
|
89
|
+
logRoom(`connecting to ${this.env.wsUrl} as document ${this.env.documentName} (token file ${this.env.tokenFile})`);
|
|
90
|
+
const websocketProvider = new HocuspocusProviderWebsocket({
|
|
91
|
+
url: this.env.wsUrl,
|
|
92
|
+
WebSocketPolyfill: WebSocket,
|
|
93
|
+
});
|
|
94
|
+
this.provider = new HocuspocusProvider({
|
|
95
|
+
websocketProvider,
|
|
96
|
+
name: this.env.documentName,
|
|
97
|
+
document: new Y.Doc(),
|
|
98
|
+
// Read on every (re)connect: the CLI keeps the file fresh. Never logged.
|
|
99
|
+
token: () => {
|
|
100
|
+
try {
|
|
101
|
+
return readFileSync(this.env.tokenFile, "utf8").trim();
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
logRoom(`cannot read the token file: ${error.message}`);
|
|
105
|
+
return "";
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
websocketProvider.on("connect", () => logRoom("socket connected"));
|
|
110
|
+
websocketProvider.on("close", ({ event }) => logRoom(`socket closed (code ${event?.code ?? "?"}${event?.reason ? `, reason ${event.reason}` : ""})`));
|
|
111
|
+
this.provider.on("authenticated", () => {
|
|
112
|
+
logRoom("authenticated");
|
|
113
|
+
this.onJoined();
|
|
114
|
+
});
|
|
115
|
+
this.provider.on("authenticationFailed", ({ reason }) => {
|
|
116
|
+
logRoom(`authentication failed: ${reason}`);
|
|
117
|
+
this.onRefused();
|
|
118
|
+
});
|
|
119
|
+
this.provider.on("disconnect", () => this.onLeft());
|
|
120
|
+
this.provider.on("stateless", ({ payload }) => this.onStateless(payload));
|
|
121
|
+
// A provider given its own socket does not attach to it by itself.
|
|
122
|
+
this.provider.attach();
|
|
123
|
+
this.server.roomBroadcast = (data) => this.sendTo(undefined, data);
|
|
124
|
+
this.pingTimer = setInterval(() => this.tick(), PING_INTERVAL_MS);
|
|
125
|
+
this.pingTimer.unref();
|
|
126
|
+
}
|
|
127
|
+
stop() {
|
|
128
|
+
if (this.pingTimer)
|
|
129
|
+
clearInterval(this.pingTimer);
|
|
130
|
+
if (this.authRetryTimer)
|
|
131
|
+
clearTimeout(this.authRetryTimer);
|
|
132
|
+
this.pingTimer = null;
|
|
133
|
+
this.authRetryTimer = null;
|
|
134
|
+
this.server.roomBroadcast = null;
|
|
135
|
+
this.dropAllPeers();
|
|
136
|
+
const websocketProvider = this.provider?.configuration.websocketProvider;
|
|
137
|
+
this.provider?.destroy();
|
|
138
|
+
websocketProvider?.destroy();
|
|
139
|
+
this.provider = null;
|
|
140
|
+
}
|
|
141
|
+
/** Sends one serialized protocol message to one tab, or to every tab when `tabId` is undefined. */
|
|
142
|
+
sendTo(tabId, data) {
|
|
143
|
+
if (!this.provider?.isAuthenticated)
|
|
144
|
+
return;
|
|
145
|
+
for (const payload of encodeRoomMessage(DEV_SERVER_PEER, data, tabId)) {
|
|
146
|
+
this.provider.sendStateless(payload);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
onJoined() {
|
|
150
|
+
if (this.authRetryTimer)
|
|
151
|
+
clearTimeout(this.authRetryTimer);
|
|
152
|
+
this.authRetryTimer = null;
|
|
153
|
+
this.hasWarnedAuth = false;
|
|
154
|
+
if (!this.hasJoined)
|
|
155
|
+
console.log(chalk.green(" Joined the design asset room — waiting for the editor"));
|
|
156
|
+
this.hasJoined = true;
|
|
157
|
+
// Tabs that were already here answer with `join` and get their initial state.
|
|
158
|
+
this.provider?.sendStateless(encodeRoomControl(DEV_SERVER_PEER, "hello"));
|
|
159
|
+
}
|
|
160
|
+
onRefused() {
|
|
161
|
+
if (!this.hasWarnedAuth) {
|
|
162
|
+
this.hasWarnedAuth = true;
|
|
163
|
+
console.log(chalk.yellow(" The design asset room refused this dev server (room expired or session ended). Retrying — restart `ikas theme dev` if this persists."));
|
|
164
|
+
}
|
|
165
|
+
if (this.authRetryTimer)
|
|
166
|
+
return;
|
|
167
|
+
this.authRetryTimer = setTimeout(() => {
|
|
168
|
+
this.authRetryTimer = null;
|
|
169
|
+
const websocketProvider = this.provider?.configuration.websocketProvider;
|
|
170
|
+
websocketProvider?.disconnect();
|
|
171
|
+
void websocketProvider?.connect();
|
|
172
|
+
}, AUTH_RETRY_MS);
|
|
173
|
+
this.authRetryTimer.unref();
|
|
174
|
+
}
|
|
175
|
+
/** Lost the gateway: tabs re-join after our `hello` once we are back. */
|
|
176
|
+
onLeft() {
|
|
177
|
+
this.dropAllPeers();
|
|
178
|
+
}
|
|
179
|
+
onStateless(payload) {
|
|
180
|
+
const envelope = parseRoomEnvelope(payload);
|
|
181
|
+
// Only editor tabs talk to us; another dev server in the room is ignored.
|
|
182
|
+
if (!envelope || envelope.from === DEV_SERVER_PEER)
|
|
183
|
+
return;
|
|
184
|
+
if (envelope.to && envelope.to !== DEV_SERVER_PEER)
|
|
185
|
+
return;
|
|
186
|
+
const tabId = envelope.from;
|
|
187
|
+
if (envelope.ctl === "leave") {
|
|
188
|
+
this.dropPeer(tabId);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (envelope.ctl === "join") {
|
|
192
|
+
const existing = this.peers.get(tabId);
|
|
193
|
+
if (existing) {
|
|
194
|
+
existing.lastSeenAt = Date.now();
|
|
195
|
+
if (existing.lastSeenAt - existing.stateSentAt < REJOIN_ECHO_MS)
|
|
196
|
+
return;
|
|
197
|
+
// The tab reconnected (or we said hello again): resend its state.
|
|
198
|
+
existing.stateSentAt = existing.lastSeenAt;
|
|
199
|
+
existing.hooks?.receive(JSON.stringify({ type: "request-state" }));
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
this.addPeer(tabId);
|
|
203
|
+
}
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const data = this.messageOf(envelope);
|
|
207
|
+
if (data === null)
|
|
208
|
+
return;
|
|
209
|
+
const peer = this.peers.get(tabId) ?? this.addPeer(tabId);
|
|
210
|
+
peer.lastSeenAt = Date.now();
|
|
211
|
+
peer.hooks?.receive(data);
|
|
212
|
+
}
|
|
213
|
+
messageOf(envelope) {
|
|
214
|
+
if (envelope.chunk)
|
|
215
|
+
return this.assembler.accept(envelope);
|
|
216
|
+
if (envelope.msg === undefined)
|
|
217
|
+
return null;
|
|
218
|
+
return JSON.stringify(envelope.msg);
|
|
219
|
+
}
|
|
220
|
+
addPeer(tabId) {
|
|
221
|
+
const peer = new RoomPeer(tabId, this);
|
|
222
|
+
this.peers.set(tabId, peer);
|
|
223
|
+
// Registering sends the tab its initial state.
|
|
224
|
+
peer.hooks = this.server.attachRoomClient(peer);
|
|
225
|
+
return peer;
|
|
226
|
+
}
|
|
227
|
+
dropPeer(tabId) {
|
|
228
|
+
const peer = this.peers.get(tabId);
|
|
229
|
+
if (!peer)
|
|
230
|
+
return;
|
|
231
|
+
this.peers.delete(tabId);
|
|
232
|
+
this.assembler.forget(tabId);
|
|
233
|
+
peer.close();
|
|
234
|
+
peer.hooks?.close();
|
|
235
|
+
}
|
|
236
|
+
dropAllPeers() {
|
|
237
|
+
for (const tabId of [...this.peers.keys()])
|
|
238
|
+
this.dropPeer(tabId);
|
|
239
|
+
}
|
|
240
|
+
tick() {
|
|
241
|
+
if (this.provider?.isAuthenticated) {
|
|
242
|
+
this.provider.sendStateless(encodeRoomControl(DEV_SERVER_PEER, "ping"));
|
|
243
|
+
}
|
|
244
|
+
const staleBefore = Date.now() - PEER_TIMEOUT_MS;
|
|
245
|
+
for (const peer of [...this.peers.values()]) {
|
|
246
|
+
if (peer.lastSeenAt < staleBefore)
|
|
247
|
+
this.dropPeer(peer.tabId);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=design-asset-room.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"design-asset-room.js","sourceRoot":"","sources":["../../src/utils/design-asset-room.ts"],"names":[],"mappings":"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"}
|