@web-auto/camo 0.1.26 → 0.2.1
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/LICENSE +21 -21
- package/README.md +586 -586
- package/bin/browser-service.mjs +11 -11
- package/bin/camo.mjs +22 -22
- package/package.json +48 -48
- package/scripts/build.mjs +19 -19
- package/scripts/bump-version.mjs +34 -34
- package/scripts/check-file-size.mjs +80 -80
- package/scripts/file-size-policy.json +12 -2
- package/scripts/install.mjs +76 -76
- package/scripts/release.sh +54 -54
- package/src/autoscript/action-providers/index.mjs +6 -6
- package/src/autoscript/impact-engine.mjs +78 -78
- package/src/autoscript/runtime.mjs +1017 -1017
- package/src/autoscript/schema.mjs +376 -376
- package/src/cli.mjs +405 -405
- package/src/commands/attach.mjs +141 -141
- package/src/commands/autoscript.mjs +1011 -1011
- package/src/commands/browser.mjs +1255 -1257
- package/src/commands/container.mjs +401 -401
- package/src/commands/cookies.mjs +69 -69
- package/src/commands/create.mjs +98 -98
- package/src/commands/devtools.mjs +349 -349
- package/src/commands/events.mjs +152 -152
- package/src/commands/highlight-mode.mjs +24 -24
- package/src/commands/init.mjs +68 -68
- package/src/commands/lifecycle.mjs +275 -275
- package/src/commands/mouse.mjs +45 -45
- package/src/commands/profile.mjs +46 -46
- package/src/commands/record.mjs +115 -115
- package/src/commands/system.mjs +14 -14
- package/src/commands/window.mjs +123 -123
- package/src/container/change-notifier.mjs +362 -362
- package/src/container/element-filter.mjs +143 -143
- package/src/container/index.mjs +3 -3
- package/src/container/runtime-core/checkpoint.mjs +209 -209
- package/src/container/runtime-core/index.mjs +21 -21
- package/src/container/runtime-core/operations/index.mjs +774 -774
- package/src/container/runtime-core/operations/selector-scripts.mjs +277 -277
- package/src/container/runtime-core/operations/tab-pool.mjs +746 -746
- package/src/container/runtime-core/operations/viewport.mjs +189 -189
- package/src/container/runtime-core/search.mjs +190 -190
- package/src/container/runtime-core/subscription.mjs +224 -224
- package/src/container/runtime-core/utils.mjs +94 -94
- package/src/container/runtime-core/validation.mjs +127 -184
- package/src/container/runtime-core.mjs +1 -1
- package/src/container/subscription-registry.mjs +459 -459
- package/src/core/actions.mjs +561 -561
- package/src/core/browser.mjs +266 -266
- package/src/core/index.mjs +52 -52
- package/src/core/utils.mjs +91 -91
- package/src/events/daemon-entry.mjs +33 -33
- package/src/events/daemon.mjs +80 -80
- package/src/events/progress-log.mjs +109 -109
- package/src/events/ws-server.mjs +239 -239
- package/src/lib/client.mjs +200 -200
- package/src/lifecycle/cleanup.mjs +83 -83
- package/src/lifecycle/lock.mjs +126 -126
- package/src/lifecycle/session-registry.mjs +279 -279
- package/src/lifecycle/session-view.mjs +76 -76
- package/src/lifecycle/session-watchdog.mjs +281 -281
- package/src/services/browser-service/index.js +671 -674
- package/src/services/browser-service/internal/BrowserSession.input.test.js +389 -389
- package/src/services/browser-service/internal/BrowserSession.js +325 -336
- package/src/services/browser-service/internal/ElementRegistry.js +60 -60
- package/src/services/browser-service/internal/ProfileLock.js +84 -84
- package/src/services/browser-service/internal/SessionManager.js +184 -184
- package/src/services/browser-service/internal/SessionManager.test.js +39 -39
- package/src/services/browser-service/internal/browser-session/cookies.js +144 -144
- package/src/services/browser-service/internal/browser-session/input-ops.js +222 -219
- package/src/services/browser-service/internal/browser-session/input-pipeline.js +144 -144
- package/src/services/browser-service/internal/browser-session/logging.js +46 -46
- package/src/services/browser-service/internal/browser-session/navigation.js +38 -38
- package/src/services/browser-service/internal/browser-session/page-hooks.js +442 -442
- package/src/services/browser-service/internal/browser-session/page-management.js +302 -336
- package/src/services/browser-service/internal/browser-session/page-management.test.js +148 -148
- package/src/services/browser-service/internal/browser-session/recording.js +198 -198
- package/src/services/browser-service/internal/browser-session/runtime-events.js +61 -61
- package/src/services/browser-service/internal/browser-session/session-core.js +84 -84
- package/src/services/browser-service/internal/browser-session/session-state.js +38 -38
- package/src/services/browser-service/internal/browser-session/types.js +14 -14
- package/src/services/browser-service/internal/browser-session/utils.js +95 -95
- package/src/services/browser-service/internal/browser-session/viewport-manager.js +46 -46
- package/src/services/browser-service/internal/browser-session/viewport.js +215 -215
- package/src/services/browser-service/internal/container-matcher.js +851 -851
- package/src/services/browser-service/internal/container-registry.js +182 -182
- package/src/services/browser-service/internal/engine-manager.js +259 -259
- package/src/services/browser-service/internal/fingerprint.js +203 -203
- package/src/services/browser-service/internal/heartbeat.js +137 -137
- package/src/services/browser-service/internal/logging.js +46 -46
- package/src/services/browser-service/internal/page-runtime/runtime.js +1317 -1317
- package/src/services/browser-service/internal/pageRuntime.js +28 -28
- package/src/services/browser-service/internal/runtimeInjector.js +31 -31
- package/src/services/browser-service/internal/service-process-logger.js +140 -140
- package/src/services/browser-service/internal/state-bus.js +45 -45
- package/src/services/browser-service/internal/storage-paths.js +42 -42
- package/src/services/browser-service/internal/ws-server.js +1194 -1194
- package/src/services/browser-service/internal/ws-server.test.js +58 -58
- package/src/services/browser-service/server.mjs +6 -6
- package/src/services/controller/cli-bridge.js +93 -93
- package/src/services/controller/container-index.js +50 -50
- package/src/services/controller/container-storage.js +36 -36
- package/src/services/controller/controller-actions.js +207 -207
- package/src/services/controller/controller.js +1138 -1138
- package/src/services/controller/selectors.js +54 -54
- package/src/services/controller/transport.js +125 -125
- package/src/utils/args.mjs +26 -26
- package/src/utils/browser-service.mjs +544 -544
- package/src/utils/command-log.mjs +64 -64
- package/src/utils/config.mjs +214 -214
- package/src/utils/fingerprint.mjs +181 -181
- package/src/utils/help.mjs +216 -216
- package/src/utils/js-policy.mjs +13 -13
- package/src/utils/ws-client.mjs +30 -30
- package/src/container/runtime-core/operations/tab-pool.mjs.bak +0 -762
- package/src/container/runtime-core/operations/tab-pool.mjs.syntax-error +0 -762
- package/src/services/browser-service/index.js.bak +0 -671
|
@@ -1,182 +1,182 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
|
|
5
|
-
const DEFAULT_DATA_ROOT = String(process.env.CAMO_DATA_ROOT || '').trim()
|
|
6
|
-
|| path.join(os.homedir(), '.camo');
|
|
7
|
-
const DEFAULT_CONTAINER_ROOT = String(process.env.CAMO_CONTAINER_ROOT || '').trim()
|
|
8
|
-
|| path.join(DEFAULT_DATA_ROOT, 'containers');
|
|
9
|
-
|
|
10
|
-
function resolveIndexPath() {
|
|
11
|
-
const envPath = String(process.env.CAMO_CONTAINER_INDEX || '').trim();
|
|
12
|
-
if (envPath) return path.resolve(envPath);
|
|
13
|
-
return path.join(DEFAULT_CONTAINER_ROOT, 'container-library.index.json');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function resolveBuiltinRoot() {
|
|
17
|
-
const envRoot = String(process.env.CAMO_CONTAINER_BUILTIN_ROOT || '').trim();
|
|
18
|
-
if (envRoot) return path.resolve(envRoot);
|
|
19
|
-
return DEFAULT_CONTAINER_ROOT;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function resolveUserRoot() {
|
|
23
|
-
const envRoot = String(process.env.CAMO_CONTAINER_USER_ROOT || '').trim();
|
|
24
|
-
if (envRoot) return path.resolve(envRoot);
|
|
25
|
-
return path.join(DEFAULT_CONTAINER_ROOT, 'user');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function isLegacyContainer(definition) {
|
|
29
|
-
try {
|
|
30
|
-
return Boolean(definition?.metadata?.legacy_data);
|
|
31
|
-
} catch {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export class ContainerRegistry {
|
|
37
|
-
constructor() {
|
|
38
|
-
this.indexCache = null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
listSites() {
|
|
42
|
-
const registry = this.ensureIndex();
|
|
43
|
-
return Object.entries(registry).map(([key, meta]) => ({
|
|
44
|
-
key,
|
|
45
|
-
website: meta.website || '',
|
|
46
|
-
path: meta.path || '',
|
|
47
|
-
}));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
getContainersForSite(siteKey) {
|
|
51
|
-
if (!siteKey) return {};
|
|
52
|
-
const registry = this.ensureIndex();
|
|
53
|
-
const site = registry[siteKey] || { path: path.join('containers', siteKey) };
|
|
54
|
-
return this.fetchContainersForSite(siteKey, site);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
resolveSiteKey(url) {
|
|
58
|
-
const registry = this.ensureIndex();
|
|
59
|
-
return this.findSiteKey(url, registry);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async load() {
|
|
63
|
-
this.ensureIndex();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
getContainersForUrl(url) {
|
|
67
|
-
const registry = this.ensureIndex();
|
|
68
|
-
const siteKey = this.findSiteKey(url, registry);
|
|
69
|
-
if (!siteKey) return {};
|
|
70
|
-
const site = registry[siteKey] || { path: path.join('containers', siteKey) };
|
|
71
|
-
return this.fetchContainersForSite(siteKey, site);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
fetchContainersForSite(siteKey, site) {
|
|
75
|
-
return this.loadSiteContainers(siteKey, site?.path);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
ensureIndex() {
|
|
79
|
-
if (this.indexCache) return this.indexCache;
|
|
80
|
-
const indexPath = resolveIndexPath();
|
|
81
|
-
if (fs.existsSync(indexPath)) {
|
|
82
|
-
try {
|
|
83
|
-
this.indexCache = JSON.parse(fs.readFileSync(indexPath, 'utf-8')) || {};
|
|
84
|
-
return this.indexCache;
|
|
85
|
-
} catch {
|
|
86
|
-
// fall through
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
this.indexCache = {};
|
|
90
|
-
return this.indexCache;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
loadSiteContainers(siteKey, relativePath) {
|
|
94
|
-
const containers = {};
|
|
95
|
-
const builtinRoot = resolveBuiltinRoot();
|
|
96
|
-
const userRoot = resolveUserRoot();
|
|
97
|
-
const builtinPath = path.join(builtinRoot, relativePath || path.join('containers', siteKey));
|
|
98
|
-
if (fs.existsSync(builtinPath)) {
|
|
99
|
-
this.walkSite(builtinPath, containers);
|
|
100
|
-
this.loadLegacyFile(builtinPath, containers);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const userPath = path.join(userRoot, siteKey);
|
|
104
|
-
if (fs.existsSync(userPath)) {
|
|
105
|
-
this.walkSite(userPath, containers);
|
|
106
|
-
this.loadLegacyFile(userPath, containers);
|
|
107
|
-
}
|
|
108
|
-
return containers;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
walkSite(sitePath, output) {
|
|
112
|
-
const stack = [{ dir: sitePath, parts: [] }];
|
|
113
|
-
while (stack.length) {
|
|
114
|
-
const { dir, parts } = stack.pop();
|
|
115
|
-
let hasContainerFile = false;
|
|
116
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
117
|
-
if (entry.isFile() && entry.name === 'container.json') {
|
|
118
|
-
const relParts = parts.length ? parts : [path.basename(dir)];
|
|
119
|
-
const containerId = relParts.join('.');
|
|
120
|
-
try {
|
|
121
|
-
const raw = JSON.parse(fs.readFileSync(path.join(dir, entry.name), 'utf-8'));
|
|
122
|
-
if (raw && typeof raw === 'object') {
|
|
123
|
-
if (isLegacyContainer(raw)) continue;
|
|
124
|
-
const id = raw.id || containerId;
|
|
125
|
-
output[id] = { id, ...raw };
|
|
126
|
-
}
|
|
127
|
-
} catch {
|
|
128
|
-
// ignore malformed container
|
|
129
|
-
}
|
|
130
|
-
hasContainerFile = true;
|
|
131
|
-
} else if (entry.isDirectory()) {
|
|
132
|
-
stack.push({ dir: path.join(dir, entry.name), parts: [...parts, entry.name] });
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
if (!hasContainerFile && parts.length === 0) {
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
loadLegacyFile(sitePath, output) {
|
|
142
|
-
const legacyFile = path.join(sitePath, 'containers.json');
|
|
143
|
-
if (!fs.existsSync(legacyFile)) return;
|
|
144
|
-
try {
|
|
145
|
-
const raw = JSON.parse(fs.readFileSync(legacyFile, 'utf-8'));
|
|
146
|
-
const containers = raw?.containers;
|
|
147
|
-
if (containers && typeof containers === 'object') {
|
|
148
|
-
for (const [key, value] of Object.entries(containers)) {
|
|
149
|
-
if (!output[key] && value && typeof value === 'object') {
|
|
150
|
-
if (isLegacyContainer(value)) continue;
|
|
151
|
-
output[key] = { id: key, ...(value) };
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
} catch {
|
|
156
|
-
// ignore legacy parse error
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
findSiteKey(url, registry) {
|
|
161
|
-
let host = '';
|
|
162
|
-
try {
|
|
163
|
-
const parsed = new URL(url);
|
|
164
|
-
host = (parsed.hostname || '').toLowerCase();
|
|
165
|
-
} catch {
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
let bestKey = null;
|
|
169
|
-
let bestLen = -1;
|
|
170
|
-
for (const [key, value] of Object.entries(registry)) {
|
|
171
|
-
const domain = String(value.website || '').toLowerCase();
|
|
172
|
-
if (!domain) continue;
|
|
173
|
-
if (host === domain || host.endsWith(`.${domain}`)) {
|
|
174
|
-
if (domain.length > bestLen) {
|
|
175
|
-
bestKey = key;
|
|
176
|
-
bestLen = domain.length;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return bestKey;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_DATA_ROOT = String(process.env.CAMO_DATA_ROOT || '').trim()
|
|
6
|
+
|| path.join(os.homedir(), '.camo');
|
|
7
|
+
const DEFAULT_CONTAINER_ROOT = String(process.env.CAMO_CONTAINER_ROOT || '').trim()
|
|
8
|
+
|| path.join(DEFAULT_DATA_ROOT, 'containers');
|
|
9
|
+
|
|
10
|
+
function resolveIndexPath() {
|
|
11
|
+
const envPath = String(process.env.CAMO_CONTAINER_INDEX || '').trim();
|
|
12
|
+
if (envPath) return path.resolve(envPath);
|
|
13
|
+
return path.join(DEFAULT_CONTAINER_ROOT, 'container-library.index.json');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function resolveBuiltinRoot() {
|
|
17
|
+
const envRoot = String(process.env.CAMO_CONTAINER_BUILTIN_ROOT || '').trim();
|
|
18
|
+
if (envRoot) return path.resolve(envRoot);
|
|
19
|
+
return DEFAULT_CONTAINER_ROOT;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveUserRoot() {
|
|
23
|
+
const envRoot = String(process.env.CAMO_CONTAINER_USER_ROOT || '').trim();
|
|
24
|
+
if (envRoot) return path.resolve(envRoot);
|
|
25
|
+
return path.join(DEFAULT_CONTAINER_ROOT, 'user');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isLegacyContainer(definition) {
|
|
29
|
+
try {
|
|
30
|
+
return Boolean(definition?.metadata?.legacy_data);
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class ContainerRegistry {
|
|
37
|
+
constructor() {
|
|
38
|
+
this.indexCache = null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
listSites() {
|
|
42
|
+
const registry = this.ensureIndex();
|
|
43
|
+
return Object.entries(registry).map(([key, meta]) => ({
|
|
44
|
+
key,
|
|
45
|
+
website: meta.website || '',
|
|
46
|
+
path: meta.path || '',
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getContainersForSite(siteKey) {
|
|
51
|
+
if (!siteKey) return {};
|
|
52
|
+
const registry = this.ensureIndex();
|
|
53
|
+
const site = registry[siteKey] || { path: path.join('containers', siteKey) };
|
|
54
|
+
return this.fetchContainersForSite(siteKey, site);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
resolveSiteKey(url) {
|
|
58
|
+
const registry = this.ensureIndex();
|
|
59
|
+
return this.findSiteKey(url, registry);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async load() {
|
|
63
|
+
this.ensureIndex();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getContainersForUrl(url) {
|
|
67
|
+
const registry = this.ensureIndex();
|
|
68
|
+
const siteKey = this.findSiteKey(url, registry);
|
|
69
|
+
if (!siteKey) return {};
|
|
70
|
+
const site = registry[siteKey] || { path: path.join('containers', siteKey) };
|
|
71
|
+
return this.fetchContainersForSite(siteKey, site);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fetchContainersForSite(siteKey, site) {
|
|
75
|
+
return this.loadSiteContainers(siteKey, site?.path);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
ensureIndex() {
|
|
79
|
+
if (this.indexCache) return this.indexCache;
|
|
80
|
+
const indexPath = resolveIndexPath();
|
|
81
|
+
if (fs.existsSync(indexPath)) {
|
|
82
|
+
try {
|
|
83
|
+
this.indexCache = JSON.parse(fs.readFileSync(indexPath, 'utf-8')) || {};
|
|
84
|
+
return this.indexCache;
|
|
85
|
+
} catch {
|
|
86
|
+
// fall through
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
this.indexCache = {};
|
|
90
|
+
return this.indexCache;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
loadSiteContainers(siteKey, relativePath) {
|
|
94
|
+
const containers = {};
|
|
95
|
+
const builtinRoot = resolveBuiltinRoot();
|
|
96
|
+
const userRoot = resolveUserRoot();
|
|
97
|
+
const builtinPath = path.join(builtinRoot, relativePath || path.join('containers', siteKey));
|
|
98
|
+
if (fs.existsSync(builtinPath)) {
|
|
99
|
+
this.walkSite(builtinPath, containers);
|
|
100
|
+
this.loadLegacyFile(builtinPath, containers);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const userPath = path.join(userRoot, siteKey);
|
|
104
|
+
if (fs.existsSync(userPath)) {
|
|
105
|
+
this.walkSite(userPath, containers);
|
|
106
|
+
this.loadLegacyFile(userPath, containers);
|
|
107
|
+
}
|
|
108
|
+
return containers;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
walkSite(sitePath, output) {
|
|
112
|
+
const stack = [{ dir: sitePath, parts: [] }];
|
|
113
|
+
while (stack.length) {
|
|
114
|
+
const { dir, parts } = stack.pop();
|
|
115
|
+
let hasContainerFile = false;
|
|
116
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
117
|
+
if (entry.isFile() && entry.name === 'container.json') {
|
|
118
|
+
const relParts = parts.length ? parts : [path.basename(dir)];
|
|
119
|
+
const containerId = relParts.join('.');
|
|
120
|
+
try {
|
|
121
|
+
const raw = JSON.parse(fs.readFileSync(path.join(dir, entry.name), 'utf-8'));
|
|
122
|
+
if (raw && typeof raw === 'object') {
|
|
123
|
+
if (isLegacyContainer(raw)) continue;
|
|
124
|
+
const id = raw.id || containerId;
|
|
125
|
+
output[id] = { id, ...raw };
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
// ignore malformed container
|
|
129
|
+
}
|
|
130
|
+
hasContainerFile = true;
|
|
131
|
+
} else if (entry.isDirectory()) {
|
|
132
|
+
stack.push({ dir: path.join(dir, entry.name), parts: [...parts, entry.name] });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!hasContainerFile && parts.length === 0) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
loadLegacyFile(sitePath, output) {
|
|
142
|
+
const legacyFile = path.join(sitePath, 'containers.json');
|
|
143
|
+
if (!fs.existsSync(legacyFile)) return;
|
|
144
|
+
try {
|
|
145
|
+
const raw = JSON.parse(fs.readFileSync(legacyFile, 'utf-8'));
|
|
146
|
+
const containers = raw?.containers;
|
|
147
|
+
if (containers && typeof containers === 'object') {
|
|
148
|
+
for (const [key, value] of Object.entries(containers)) {
|
|
149
|
+
if (!output[key] && value && typeof value === 'object') {
|
|
150
|
+
if (isLegacyContainer(value)) continue;
|
|
151
|
+
output[key] = { id: key, ...(value) };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
// ignore legacy parse error
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
findSiteKey(url, registry) {
|
|
161
|
+
let host = '';
|
|
162
|
+
try {
|
|
163
|
+
const parsed = new URL(url);
|
|
164
|
+
host = (parsed.hostname || '').toLowerCase();
|
|
165
|
+
} catch {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
let bestKey = null;
|
|
169
|
+
let bestLen = -1;
|
|
170
|
+
for (const [key, value] of Object.entries(registry)) {
|
|
171
|
+
const domain = String(value.website || '').toLowerCase();
|
|
172
|
+
if (!domain) continue;
|
|
173
|
+
if (host === domain || host.endsWith(`.${domain}`)) {
|
|
174
|
+
if (domain.length > bestLen) {
|
|
175
|
+
bestKey = key;
|
|
176
|
+
bestLen = domain.length;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return bestKey;
|
|
181
|
+
}
|
|
182
|
+
}
|