@lvce-editor/extension-management-worker 4.72.1 → 4.74.0
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/extensionManagementWorkerMain.js +375 -176
- package/package.json +1 -1
|
@@ -34,12 +34,12 @@ const state$4 = {
|
|
|
34
34
|
extensionsState: createInitialState(),
|
|
35
35
|
nextApplicationGeneration: 0
|
|
36
36
|
};
|
|
37
|
-
const applications$
|
|
37
|
+
const applications$2 = new Map();
|
|
38
38
|
const createApplication = (applicationId, platform, webExtensions) => {
|
|
39
|
-
if (!applicationId || applications$
|
|
39
|
+
if (!applicationId || applications$2.has(applicationId)) {
|
|
40
40
|
throw new Error(`Invalid or duplicate extension application: ${applicationId}`);
|
|
41
41
|
}
|
|
42
|
-
applications$
|
|
42
|
+
applications$2.set(applicationId, {
|
|
43
43
|
...createInitialState(),
|
|
44
44
|
applicationGeneration: ++state$4.nextApplicationGeneration,
|
|
45
45
|
applicationId,
|
|
@@ -48,13 +48,13 @@ const createApplication = (applicationId, platform, webExtensions) => {
|
|
|
48
48
|
});
|
|
49
49
|
};
|
|
50
50
|
const removeApplication = applicationId => {
|
|
51
|
-
applications$
|
|
51
|
+
applications$2.delete(applicationId);
|
|
52
52
|
};
|
|
53
53
|
const isCurrentApplication = application => {
|
|
54
54
|
if (application.applicationId === undefined) {
|
|
55
55
|
return true;
|
|
56
56
|
}
|
|
57
|
-
const current = applications$
|
|
57
|
+
const current = applications$2.get(application.applicationId);
|
|
58
58
|
return current !== undefined && current.applicationGeneration === application.applicationGeneration;
|
|
59
59
|
};
|
|
60
60
|
const assertCurrentApplication = application => {
|
|
@@ -64,7 +64,7 @@ const assertCurrentApplication = application => {
|
|
|
64
64
|
};
|
|
65
65
|
const get$7 = applicationId => {
|
|
66
66
|
if (applicationId !== undefined) {
|
|
67
|
-
const application = applications$
|
|
67
|
+
const application = applications$2.get(applicationId);
|
|
68
68
|
if (!application) {
|
|
69
69
|
throw new Error(`Extension application not found: ${applicationId}`);
|
|
70
70
|
}
|
|
@@ -78,7 +78,7 @@ const set$d = newState => {
|
|
|
78
78
|
if (newState.applicationGeneration !== currentState.applicationGeneration) {
|
|
79
79
|
throw new Error(`Stale extension application state: ${newState.applicationId}`);
|
|
80
80
|
}
|
|
81
|
-
applications$
|
|
81
|
+
applications$2.set(newState.applicationId, newState);
|
|
82
82
|
return;
|
|
83
83
|
}
|
|
84
84
|
state$4.extensionsState = newState;
|
|
@@ -770,7 +770,7 @@ let CommandNotFoundError$1 = class CommandNotFoundError extends Error {
|
|
|
770
770
|
}
|
|
771
771
|
};
|
|
772
772
|
const commands = Object.create(null);
|
|
773
|
-
const register$
|
|
773
|
+
const register$2 = commandMap => {
|
|
774
774
|
Object.assign(commands, commandMap);
|
|
775
775
|
};
|
|
776
776
|
const getCommand = key => {
|
|
@@ -1293,7 +1293,7 @@ const create$7 = async ({
|
|
|
1293
1293
|
messagePort
|
|
1294
1294
|
}) => {
|
|
1295
1295
|
// TODO create a commandMap per rpc instance
|
|
1296
|
-
register$
|
|
1296
|
+
register$2(commandMap);
|
|
1297
1297
|
const rawIpc = await IpcParentWithMessagePort$1.create({
|
|
1298
1298
|
isMessagePortOpen,
|
|
1299
1299
|
messagePort
|
|
@@ -1361,7 +1361,7 @@ const create$4 = async ({
|
|
|
1361
1361
|
webSocket
|
|
1362
1362
|
}) => {
|
|
1363
1363
|
// TODO create a commandMap per rpc instance
|
|
1364
|
-
register$
|
|
1364
|
+
register$2(commandMap);
|
|
1365
1365
|
let rawIpc;
|
|
1366
1366
|
try {
|
|
1367
1367
|
rawIpc = await IpcParentWithWebSocket$1.create({
|
|
@@ -1436,7 +1436,7 @@ const create$2 = async ({
|
|
|
1436
1436
|
messagePort
|
|
1437
1437
|
}) => {
|
|
1438
1438
|
// TODO create a commandMap per rpc instance
|
|
1439
|
-
register$
|
|
1439
|
+
register$2(commandMap);
|
|
1440
1440
|
const rawIpc = await IpcParentWithMessagePort$1.create({
|
|
1441
1441
|
isMessagePortOpen,
|
|
1442
1442
|
messagePort
|
|
@@ -1451,7 +1451,7 @@ const create$1 = async ({
|
|
|
1451
1451
|
commandMap
|
|
1452
1452
|
}) => {
|
|
1453
1453
|
// TODO create a commandMap per rpc instance
|
|
1454
|
-
register$
|
|
1454
|
+
register$2(commandMap);
|
|
1455
1455
|
const ipc = await listen$1(IpcChildWithModuleWorkerAndMessagePort$1);
|
|
1456
1456
|
handleIpc(ipc);
|
|
1457
1457
|
const rpc = createRpc(ipc);
|
|
@@ -1687,35 +1687,35 @@ const createNodeRpcMessagePort = async (extensionId, rpcId, port) => {
|
|
|
1687
1687
|
};
|
|
1688
1688
|
|
|
1689
1689
|
const extensionIds = new Set();
|
|
1690
|
-
const applications = new Map();
|
|
1691
|
-
const register = (extensionId, applicationId) => {
|
|
1690
|
+
const applications$1 = new Map();
|
|
1691
|
+
const register$1 = (extensionId, applicationId) => {
|
|
1692
1692
|
if (applicationId === undefined) {
|
|
1693
1693
|
extensionIds.add(extensionId);
|
|
1694
1694
|
return;
|
|
1695
1695
|
}
|
|
1696
|
-
const ids = applications.get(applicationId) || new Set();
|
|
1696
|
+
const ids = applications$1.get(applicationId) || new Set();
|
|
1697
1697
|
ids.add(extensionId);
|
|
1698
|
-
applications.set(applicationId, ids);
|
|
1698
|
+
applications$1.set(applicationId, ids);
|
|
1699
1699
|
};
|
|
1700
1700
|
const unregister = (extensionId, applicationId) => {
|
|
1701
1701
|
if (applicationId === undefined) {
|
|
1702
1702
|
extensionIds.delete(extensionId);
|
|
1703
1703
|
return;
|
|
1704
1704
|
}
|
|
1705
|
-
const ids = applications.get(applicationId);
|
|
1705
|
+
const ids = applications$1.get(applicationId);
|
|
1706
1706
|
ids?.delete(extensionId);
|
|
1707
1707
|
if (ids?.size === 0) {
|
|
1708
|
-
applications.delete(applicationId);
|
|
1708
|
+
applications$1.delete(applicationId);
|
|
1709
1709
|
}
|
|
1710
1710
|
};
|
|
1711
1711
|
const getRegisteredExtensionIds = applicationId => {
|
|
1712
|
-
return [...(applicationId === undefined ? extensionIds : applications.get(applicationId) || [])];
|
|
1712
|
+
return [...(applicationId === undefined ? extensionIds : applications$1.get(applicationId) || [])];
|
|
1713
1713
|
};
|
|
1714
1714
|
const reset = applicationId => {
|
|
1715
1715
|
if (applicationId === undefined) {
|
|
1716
1716
|
extensionIds.clear();
|
|
1717
1717
|
} else {
|
|
1718
|
-
applications.delete(applicationId);
|
|
1718
|
+
applications$1.delete(applicationId);
|
|
1719
1719
|
}
|
|
1720
1720
|
};
|
|
1721
1721
|
|
|
@@ -1957,7 +1957,7 @@ const createExtensionCommandMap = (extensionId, invokeCommand = invokeGlobalComm
|
|
|
1957
1957
|
return getSecret(extensionId, key);
|
|
1958
1958
|
},
|
|
1959
1959
|
'Extensions.registerFileChangeHandler'() {
|
|
1960
|
-
register(extensionId);
|
|
1960
|
+
register$1(extensionId);
|
|
1961
1961
|
},
|
|
1962
1962
|
'Extensions.sendMessagePortToElectron'(port, initialCommand) {
|
|
1963
1963
|
if (initialCommand !== allowedElectronPortCommand) {
|
|
@@ -1986,6 +1986,269 @@ const createExtensionCommandExecutor = commandMap => {
|
|
|
1986
1986
|
};
|
|
1987
1987
|
};
|
|
1988
1988
|
|
|
1989
|
+
const sourcePathMarker = '/extension-management-worker/src/';
|
|
1990
|
+
const getExtensionHostSubWorkerUrl = (currentUrl = import.meta.url) => {
|
|
1991
|
+
const relativePath = currentUrl.includes(sourcePathMarker) ? '../../../../extension-host-sub-worker/src/extensionHostSubWorkerMain.js' : '../../extension-host-sub-worker/dist/extensionHostSubWorkerMain.js';
|
|
1992
|
+
return new URL(relativePath, currentUrl).href;
|
|
1993
|
+
};
|
|
1994
|
+
const extensionHostSubWorkerUrl = getExtensionHostSubWorkerUrl();
|
|
1995
|
+
|
|
1996
|
+
// TODO
|
|
1997
|
+
|
|
1998
|
+
const getOrigin = () => {
|
|
1999
|
+
return globalThis.location?.origin || 'http://localhost';
|
|
2000
|
+
};
|
|
2001
|
+
|
|
2002
|
+
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
|
|
2003
|
+
|
|
2004
|
+
const allowedScriptSources = new Set([`'self'`, `'unsafe-eval'`]);
|
|
2005
|
+
const allowedConnectSchemeSources = new Set(['https:', 'wss:']);
|
|
2006
|
+
const reservedDirectives = new Set(['child-src', 'connect-src', 'default-src', 'script-src', 'worker-src']);
|
|
2007
|
+
const nodeProcessType = 'node-process';
|
|
2008
|
+
const loopbackWildcardPortSourceRegex = /^(?:http|ws):\/\/(?:127\.0\.0\.1|localhost):\*$/;
|
|
2009
|
+
const parseDirective = directive => {
|
|
2010
|
+
if (directive.includes('\n') || directive.includes('\r')) {
|
|
2011
|
+
throw new Error('content security policy directives cannot contain newlines');
|
|
2012
|
+
}
|
|
2013
|
+
return directive.replace(/;$/, '').trim().split(/\s+/);
|
|
2014
|
+
};
|
|
2015
|
+
const getWorkerUrls = absolutePath => {
|
|
2016
|
+
const applicationUrl = new URL(getOrigin());
|
|
2017
|
+
const workerUrl = new URL(absolutePath, applicationUrl);
|
|
2018
|
+
if (workerUrl.protocol !== 'http:' && workerUrl.protocol !== 'https:') {
|
|
2019
|
+
return {
|
|
2020
|
+
origin: applicationUrl.origin
|
|
2021
|
+
};
|
|
2022
|
+
}
|
|
2023
|
+
const webSocketProtocol = applicationUrl.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
2024
|
+
return {
|
|
2025
|
+
assetSource: new URL('./', workerUrl).href,
|
|
2026
|
+
capabilitySource: `${webSocketProtocol}//${applicationUrl.host}/websocket/capability`,
|
|
2027
|
+
nodeProcessSource: `${webSocketProtocol}//${applicationUrl.host}/websocket/extension-node-process`,
|
|
2028
|
+
origin: applicationUrl.origin
|
|
2029
|
+
};
|
|
2030
|
+
};
|
|
2031
|
+
const hasNodeProcess = rpcInfos => {
|
|
2032
|
+
return rpcInfos?.some(rpcInfo => typeof rpcInfo === 'object' && rpcInfo !== null && rpcInfo.type === nodeProcessType) === true;
|
|
2033
|
+
};
|
|
2034
|
+
const validateExternalConnectSource = (source, applicationOrigin) => {
|
|
2035
|
+
if (allowedConnectSchemeSources.has(source) || loopbackWildcardPortSourceRegex.test(source)) {
|
|
2036
|
+
return source;
|
|
2037
|
+
}
|
|
2038
|
+
if (source.includes('*') || source === `'none'` || source === 'http:' || source === 'ws:') {
|
|
2039
|
+
throw new Error(`invalid isolated extension connect source ${source}`);
|
|
2040
|
+
}
|
|
2041
|
+
let url;
|
|
2042
|
+
try {
|
|
2043
|
+
url = new URL(source);
|
|
2044
|
+
} catch {
|
|
2045
|
+
throw new Error(`isolated extension connect source must be an absolute URL. Invalid value: ${JSON.stringify(source)}`);
|
|
2046
|
+
}
|
|
2047
|
+
if (!['http:', 'https:', 'ws:', 'wss:'].includes(url.protocol) || !url.hostname || url.username || url.password) {
|
|
2048
|
+
throw new Error(`invalid isolated extension connect source ${source}`);
|
|
2049
|
+
}
|
|
2050
|
+
const applicationUrl = new URL(applicationOrigin);
|
|
2051
|
+
if (url.host === applicationUrl.host) {
|
|
2052
|
+
throw new Error(`isolated extensions cannot allow the application origin in connect-src`);
|
|
2053
|
+
}
|
|
2054
|
+
return source;
|
|
2055
|
+
};
|
|
2056
|
+
const addScriptSources = (scriptSources, sources) => {
|
|
2057
|
+
for (const source of sources) {
|
|
2058
|
+
if (!allowedScriptSources.has(source)) {
|
|
2059
|
+
throw new Error(`invalid isolated extension script source ${source}`);
|
|
2060
|
+
}
|
|
2061
|
+
scriptSources.add(source);
|
|
2062
|
+
}
|
|
2063
|
+
};
|
|
2064
|
+
const addConnectSources = (connectSources, sources, assetSource, origin) => {
|
|
2065
|
+
for (const source of sources) {
|
|
2066
|
+
if (source === `'self'`) {
|
|
2067
|
+
if (assetSource) {
|
|
2068
|
+
connectSources.add(assetSource);
|
|
2069
|
+
}
|
|
2070
|
+
} else {
|
|
2071
|
+
connectSources.add(validateExternalConnectSource(source, origin));
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
};
|
|
2075
|
+
const validateDefaultSources = sources => {
|
|
2076
|
+
if (sources.length !== 1 || sources[0] !== `'none'`) {
|
|
2077
|
+
throw new Error(`isolated extension default-src must be 'none'`);
|
|
2078
|
+
}
|
|
2079
|
+
};
|
|
2080
|
+
const applyDirective = (directive, scriptSources, connectSources, assetSource, origin) => {
|
|
2081
|
+
const [name, ...sources] = parseDirective(directive);
|
|
2082
|
+
if (!name) {
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
if (name === 'default-src') {
|
|
2086
|
+
validateDefaultSources(sources);
|
|
2087
|
+
return;
|
|
2088
|
+
}
|
|
2089
|
+
if (name === 'script-src') {
|
|
2090
|
+
addScriptSources(scriptSources, sources);
|
|
2091
|
+
return;
|
|
2092
|
+
}
|
|
2093
|
+
if (name === 'connect-src') {
|
|
2094
|
+
addConnectSources(connectSources, sources, assetSource, origin);
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
if (reservedDirectives.has(name)) {
|
|
2098
|
+
throw new Error(`isolated extensions cannot override ${name}`);
|
|
2099
|
+
}
|
|
2100
|
+
};
|
|
2101
|
+
const getContentSecurityPolicy = (directives, absolutePath, rpcInfos) => {
|
|
2102
|
+
const {
|
|
2103
|
+
assetSource,
|
|
2104
|
+
capabilitySource,
|
|
2105
|
+
nodeProcessSource,
|
|
2106
|
+
origin
|
|
2107
|
+
} = getWorkerUrls(absolutePath);
|
|
2108
|
+
const scriptSources = new Set([`'self'`]);
|
|
2109
|
+
const connectSources = new Set();
|
|
2110
|
+
if (capabilitySource) {
|
|
2111
|
+
connectSources.add(capabilitySource);
|
|
2112
|
+
}
|
|
2113
|
+
if (assetSource) {
|
|
2114
|
+
connectSources.add(assetSource);
|
|
2115
|
+
}
|
|
2116
|
+
if (nodeProcessSource && hasNodeProcess(rpcInfos)) {
|
|
2117
|
+
connectSources.add(nodeProcessSource);
|
|
2118
|
+
}
|
|
2119
|
+
const manifestDirectives = directives || [];
|
|
2120
|
+
for (const directive of manifestDirectives) {
|
|
2121
|
+
applyDirective(directive, scriptSources, connectSources, assetSource, origin);
|
|
2122
|
+
}
|
|
2123
|
+
const connectDirective = connectSources.size > 0 ? [...connectSources].join(' ') : `'none'`;
|
|
2124
|
+
return `default-src 'none'; script-src ${[...scriptSources].join(' ')}; worker-src 'none'; child-src 'none'; connect-src ${connectDirective};`;
|
|
2125
|
+
};
|
|
2126
|
+
|
|
2127
|
+
const getUrlPrefix = (platform, extensionPath) => {
|
|
2128
|
+
if (extensionPath.startsWith('http://') || extensionPath.startsWith('https://')) {
|
|
2129
|
+
return extensionPath;
|
|
2130
|
+
}
|
|
2131
|
+
if (platform === Web) {
|
|
2132
|
+
return extensionPath;
|
|
2133
|
+
}
|
|
2134
|
+
if (extensionPath.startsWith('/')) {
|
|
2135
|
+
return `/remote${extensionPath}`;
|
|
2136
|
+
}
|
|
2137
|
+
return `/remote/${extensionPath}`;
|
|
2138
|
+
};
|
|
2139
|
+
|
|
2140
|
+
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
|
|
2141
|
+
|
|
2142
|
+
const applications = new Map();
|
|
2143
|
+
const getServices = application => {
|
|
2144
|
+
assertCurrentApplication(application);
|
|
2145
|
+
const generation = application.applicationGeneration;
|
|
2146
|
+
let services = applications.get(generation);
|
|
2147
|
+
if (!services) {
|
|
2148
|
+
services = {
|
|
2149
|
+
children: new Map(),
|
|
2150
|
+
declarations: new Map(),
|
|
2151
|
+
extensionVersions: new Map(),
|
|
2152
|
+
nextChild: 0,
|
|
2153
|
+
ports: new Map()
|
|
2154
|
+
};
|
|
2155
|
+
applications.set(generation, services);
|
|
2156
|
+
}
|
|
2157
|
+
return services;
|
|
2158
|
+
};
|
|
2159
|
+
const register = (extension, platform) => {
|
|
2160
|
+
const application = get$7(extension.applicationId);
|
|
2161
|
+
assertCurrentApplication(extension);
|
|
2162
|
+
const services = getServices(application);
|
|
2163
|
+
const prefix = getUrlPrefix(platform, extension.path);
|
|
2164
|
+
const declarations = extension.rpc || [];
|
|
2165
|
+
for (const info of declarations) {
|
|
2166
|
+
if (info.type !== 'web-worker') {
|
|
2167
|
+
throw new Error('Application extensions only support web-worker RPCs');
|
|
2168
|
+
}
|
|
2169
|
+
services.declarations.set(info.id, {
|
|
2170
|
+
...info,
|
|
2171
|
+
extensionId: extension.id,
|
|
2172
|
+
url: `${prefix}/${info.url}`
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
};
|
|
2176
|
+
const getRpcInfo$1 = (application, id) => {
|
|
2177
|
+
const info = getServices(application).declarations.get(id);
|
|
2178
|
+
if (!info) throw new Error(`Rpc not found ${id}`);
|
|
2179
|
+
return info;
|
|
2180
|
+
};
|
|
2181
|
+
const createWorker$1 = async (application, info, port, legacy = false, extensionId) => {
|
|
2182
|
+
const services = getServices(application);
|
|
2183
|
+
const id = JSON.stringify([application.applicationId, application.applicationGeneration, 'child', ++services.nextChild]);
|
|
2184
|
+
services.children.set(id, extensionId);
|
|
2185
|
+
const url = legacy ? extensionHostSubWorkerUrl : info.url;
|
|
2186
|
+
try {
|
|
2187
|
+
const policy = getContentSecurityPolicy(info.contentSecurityPolicy || [], url);
|
|
2188
|
+
await invokeAndTransfer('LaunchIsolatedExtensionHostWorker.launchIsolatedExtensionHostWorker', port, id, url, info.name || '', policy);
|
|
2189
|
+
assertCurrentApplication(application);
|
|
2190
|
+
if (!services.children.has(id)) throw new Error('Extension worker was disposed during launch');
|
|
2191
|
+
} catch (error) {
|
|
2192
|
+
services.children.delete(id);
|
|
2193
|
+
await invoke('LaunchIsolatedExtensionHostWorker.disposeIsolatedExtensionHostWorker', id);
|
|
2194
|
+
throw error;
|
|
2195
|
+
}
|
|
2196
|
+
};
|
|
2197
|
+
const fileSystemMethods = new Set(['readFile', 'readDirWithFileTypes', 'stat', 'exists', 'writeFile', 'mkdir', 'remove', 'rename', 'copy', 'getBlob']);
|
|
2198
|
+
const createFileSystemPort = async (application, port, extensionId) => {
|
|
2199
|
+
const services = getServices(application);
|
|
2200
|
+
const version = extensionId === undefined ? 0 : services.extensionVersions.get(extensionId) || 0;
|
|
2201
|
+
const rpc = await create$7({
|
|
2202
|
+
commandMap: {},
|
|
2203
|
+
messagePort: port
|
|
2204
|
+
});
|
|
2205
|
+
// Bind only this connection: registering callbacks globally would let the
|
|
2206
|
+
// second IDE replace the first IDE's filesystem routing.
|
|
2207
|
+
rpc.ipc.execute = async (command, ...args) => {
|
|
2208
|
+
assertCurrentApplication(application);
|
|
2209
|
+
const method = command.startsWith('FileSystem.') ? command.slice('FileSystem.'.length) : '';
|
|
2210
|
+
if (!fileSystemMethods.has(method)) throw new Error(`Unsupported application filesystem command: ${command}`);
|
|
2211
|
+
const value = await invoke('Application.execute', application.applicationId, command, ...args);
|
|
2212
|
+
return method === 'stat' ? value.type : value;
|
|
2213
|
+
};
|
|
2214
|
+
try {
|
|
2215
|
+
assertCurrentApplication(application);
|
|
2216
|
+
if (extensionId !== undefined && (services.extensionVersions.get(extensionId) || 0) !== version) {
|
|
2217
|
+
throw new Error('Extension was reloaded while opening its filesystem port');
|
|
2218
|
+
}
|
|
2219
|
+
services.ports.set(rpc, extensionId);
|
|
2220
|
+
} catch (error) {
|
|
2221
|
+
await rpc.dispose();
|
|
2222
|
+
throw error;
|
|
2223
|
+
}
|
|
2224
|
+
};
|
|
2225
|
+
const dispose = async application => {
|
|
2226
|
+
const services = applications.get(application.applicationGeneration);
|
|
2227
|
+
if (!services) return;
|
|
2228
|
+
applications.delete(application.applicationGeneration);
|
|
2229
|
+
const results = await Promise.allSettled([...Array.from(services.ports.keys(), rpc => Promise.try(() => rpc.dispose())), ...Array.from(services.children.keys(), id => invoke('LaunchIsolatedExtensionHostWorker.disposeIsolatedExtensionHostWorker', id))]);
|
|
2230
|
+
services.ports.clear();
|
|
2231
|
+
services.children.clear();
|
|
2232
|
+
services.declarations.clear();
|
|
2233
|
+
const errors = results.filter(result => result.status === 'rejected').map(result => result.reason);
|
|
2234
|
+
if (errors.length > 0) throw new AggregateError(errors, 'Failed to dispose application services');
|
|
2235
|
+
};
|
|
2236
|
+
const disposeExtension = async (application, extensionId) => {
|
|
2237
|
+
const services = applications.get(application.applicationGeneration);
|
|
2238
|
+
if (!services) return;
|
|
2239
|
+
services.extensionVersions.set(extensionId, (services.extensionVersions.get(extensionId) || 0) + 1);
|
|
2240
|
+
const ports = [...services.ports].filter(([, owner]) => owner === extensionId).map(([rpc]) => rpc);
|
|
2241
|
+
const children = [...services.children].filter(([, owner]) => owner === extensionId).map(([id]) => id);
|
|
2242
|
+
for (const rpc of ports) services.ports.delete(rpc);
|
|
2243
|
+
for (const id of children) services.children.delete(id);
|
|
2244
|
+
for (const [id, info] of services.declarations) {
|
|
2245
|
+
if (info.extensionId === extensionId) services.declarations.delete(id);
|
|
2246
|
+
}
|
|
2247
|
+
const results = await Promise.allSettled([...ports.map(rpc => Promise.try(() => rpc.dispose())), ...children.map(id => invoke('LaunchIsolatedExtensionHostWorker.disposeIsolatedExtensionHostWorker', id))]);
|
|
2248
|
+
const errors = results.filter(result => result.status === 'rejected').map(result => result.reason);
|
|
2249
|
+
if (errors.length > 0) throw new AggregateError(errors, `Failed to dispose extension services: ${extensionId}`);
|
|
2250
|
+
};
|
|
2251
|
+
|
|
1989
2252
|
const rpcs$1 = Object.create(null);
|
|
1990
2253
|
const applicationRpcs = new Map();
|
|
1991
2254
|
const get$3 = (extensionId, applicationId) => {
|
|
@@ -2016,6 +2279,15 @@ const set$2 = (extensionId, rpc, applicationId) => {
|
|
|
2016
2279
|
rpcs$1[extensionId] = rpc;
|
|
2017
2280
|
};
|
|
2018
2281
|
const remove$1 = (extensionId, applicationId) => {
|
|
2282
|
+
if (applicationId !== undefined) {
|
|
2283
|
+
const entries = applicationRpcs.get(applicationId);
|
|
2284
|
+
const rpc = entries?.get(extensionId);
|
|
2285
|
+
entries?.delete(extensionId);
|
|
2286
|
+
if (entries?.size === 0) {
|
|
2287
|
+
applicationRpcs.delete(applicationId);
|
|
2288
|
+
}
|
|
2289
|
+
return rpc;
|
|
2290
|
+
}
|
|
2019
2291
|
const rpc = rpcs$1[extensionId];
|
|
2020
2292
|
delete rpcs$1[extensionId];
|
|
2021
2293
|
return rpc;
|
|
@@ -2053,11 +2325,17 @@ const createIsolatedExtensionHostWorker = async (extensionId, absolutePath, work
|
|
|
2053
2325
|
const invokeCommand = application === undefined ? undefined : (method, ...args) => {
|
|
2054
2326
|
assertCurrentApplication(application);
|
|
2055
2327
|
if (method === 'Extensions.registerFileChangeHandler') {
|
|
2056
|
-
return register(extensionId, application.applicationId);
|
|
2328
|
+
return register$1(extensionId, application.applicationId);
|
|
2057
2329
|
}
|
|
2058
2330
|
if (method === 'Extensions.unregisterFileChangeHandler') {
|
|
2059
2331
|
return unregister(extensionId, application.applicationId);
|
|
2060
2332
|
}
|
|
2333
|
+
if (method === 'Extensions.sendMessagePortToFileSystemWorker') {
|
|
2334
|
+
return createFileSystemPort(application, args[0], extensionId);
|
|
2335
|
+
}
|
|
2336
|
+
if (method === 'Extensions.createWebViewWorkerRpc' || method === 'Extensions.createWebViewWorkerRpc2') {
|
|
2337
|
+
return createWorker$1(application, args[0], args[1], method === 'Extensions.createWebViewWorkerRpc', extensionId);
|
|
2338
|
+
}
|
|
2061
2339
|
const invokeApplication = commandMapRef['Extensions.invokeForApplication'];
|
|
2062
2340
|
if (!invokeApplication) {
|
|
2063
2341
|
throw new Error('Application extension command routing is not initialized');
|
|
@@ -2158,135 +2436,6 @@ const activateIsolatedExtension = async (extensionId, absolutePath, workerName,
|
|
|
2158
2436
|
}
|
|
2159
2437
|
};
|
|
2160
2438
|
|
|
2161
|
-
const getOrigin = () => {
|
|
2162
|
-
return globalThis.location?.origin || 'http://localhost';
|
|
2163
|
-
};
|
|
2164
|
-
|
|
2165
|
-
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
|
|
2166
|
-
|
|
2167
|
-
const allowedScriptSources = new Set([`'self'`, `'unsafe-eval'`]);
|
|
2168
|
-
const allowedConnectSchemeSources = new Set(['https:', 'wss:']);
|
|
2169
|
-
const reservedDirectives = new Set(['child-src', 'connect-src', 'default-src', 'script-src', 'worker-src']);
|
|
2170
|
-
const nodeProcessType = 'node-process';
|
|
2171
|
-
const loopbackWildcardPortSourceRegex = /^(?:http|ws):\/\/(?:127\.0\.0\.1|localhost):\*$/;
|
|
2172
|
-
const parseDirective = directive => {
|
|
2173
|
-
if (directive.includes('\n') || directive.includes('\r')) {
|
|
2174
|
-
throw new Error('content security policy directives cannot contain newlines');
|
|
2175
|
-
}
|
|
2176
|
-
return directive.replace(/;$/, '').trim().split(/\s+/);
|
|
2177
|
-
};
|
|
2178
|
-
const getWorkerUrls = absolutePath => {
|
|
2179
|
-
const applicationUrl = new URL(getOrigin());
|
|
2180
|
-
const workerUrl = new URL(absolutePath, applicationUrl);
|
|
2181
|
-
if (workerUrl.protocol !== 'http:' && workerUrl.protocol !== 'https:') {
|
|
2182
|
-
return {
|
|
2183
|
-
origin: applicationUrl.origin
|
|
2184
|
-
};
|
|
2185
|
-
}
|
|
2186
|
-
const webSocketProtocol = applicationUrl.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
2187
|
-
return {
|
|
2188
|
-
assetSource: new URL('./', workerUrl).href,
|
|
2189
|
-
capabilitySource: `${webSocketProtocol}//${applicationUrl.host}/websocket/capability`,
|
|
2190
|
-
nodeProcessSource: `${webSocketProtocol}//${applicationUrl.host}/websocket/extension-node-process`,
|
|
2191
|
-
origin: applicationUrl.origin
|
|
2192
|
-
};
|
|
2193
|
-
};
|
|
2194
|
-
const hasNodeProcess = rpcInfos => {
|
|
2195
|
-
return rpcInfos?.some(rpcInfo => typeof rpcInfo === 'object' && rpcInfo !== null && rpcInfo.type === nodeProcessType) === true;
|
|
2196
|
-
};
|
|
2197
|
-
const validateExternalConnectSource = (source, applicationOrigin) => {
|
|
2198
|
-
if (allowedConnectSchemeSources.has(source) || loopbackWildcardPortSourceRegex.test(source)) {
|
|
2199
|
-
return source;
|
|
2200
|
-
}
|
|
2201
|
-
if (source.includes('*') || source === `'none'` || source === 'http:' || source === 'ws:') {
|
|
2202
|
-
throw new Error(`invalid isolated extension connect source ${source}`);
|
|
2203
|
-
}
|
|
2204
|
-
let url;
|
|
2205
|
-
try {
|
|
2206
|
-
url = new URL(source);
|
|
2207
|
-
} catch {
|
|
2208
|
-
throw new Error(`isolated extension connect source must be an absolute URL. Invalid value: ${JSON.stringify(source)}`);
|
|
2209
|
-
}
|
|
2210
|
-
if (!['http:', 'https:', 'ws:', 'wss:'].includes(url.protocol) || !url.hostname || url.username || url.password) {
|
|
2211
|
-
throw new Error(`invalid isolated extension connect source ${source}`);
|
|
2212
|
-
}
|
|
2213
|
-
const applicationUrl = new URL(applicationOrigin);
|
|
2214
|
-
if (url.host === applicationUrl.host) {
|
|
2215
|
-
throw new Error(`isolated extensions cannot allow the application origin in connect-src`);
|
|
2216
|
-
}
|
|
2217
|
-
return source;
|
|
2218
|
-
};
|
|
2219
|
-
const addScriptSources = (scriptSources, sources) => {
|
|
2220
|
-
for (const source of sources) {
|
|
2221
|
-
if (!allowedScriptSources.has(source)) {
|
|
2222
|
-
throw new Error(`invalid isolated extension script source ${source}`);
|
|
2223
|
-
}
|
|
2224
|
-
scriptSources.add(source);
|
|
2225
|
-
}
|
|
2226
|
-
};
|
|
2227
|
-
const addConnectSources = (connectSources, sources, assetSource, origin) => {
|
|
2228
|
-
for (const source of sources) {
|
|
2229
|
-
if (source === `'self'`) {
|
|
2230
|
-
if (assetSource) {
|
|
2231
|
-
connectSources.add(assetSource);
|
|
2232
|
-
}
|
|
2233
|
-
} else {
|
|
2234
|
-
connectSources.add(validateExternalConnectSource(source, origin));
|
|
2235
|
-
}
|
|
2236
|
-
}
|
|
2237
|
-
};
|
|
2238
|
-
const validateDefaultSources = sources => {
|
|
2239
|
-
if (sources.length !== 1 || sources[0] !== `'none'`) {
|
|
2240
|
-
throw new Error(`isolated extension default-src must be 'none'`);
|
|
2241
|
-
}
|
|
2242
|
-
};
|
|
2243
|
-
const applyDirective = (directive, scriptSources, connectSources, assetSource, origin) => {
|
|
2244
|
-
const [name, ...sources] = parseDirective(directive);
|
|
2245
|
-
if (!name) {
|
|
2246
|
-
return;
|
|
2247
|
-
}
|
|
2248
|
-
if (name === 'default-src') {
|
|
2249
|
-
validateDefaultSources(sources);
|
|
2250
|
-
return;
|
|
2251
|
-
}
|
|
2252
|
-
if (name === 'script-src') {
|
|
2253
|
-
addScriptSources(scriptSources, sources);
|
|
2254
|
-
return;
|
|
2255
|
-
}
|
|
2256
|
-
if (name === 'connect-src') {
|
|
2257
|
-
addConnectSources(connectSources, sources, assetSource, origin);
|
|
2258
|
-
return;
|
|
2259
|
-
}
|
|
2260
|
-
if (reservedDirectives.has(name)) {
|
|
2261
|
-
throw new Error(`isolated extensions cannot override ${name}`);
|
|
2262
|
-
}
|
|
2263
|
-
};
|
|
2264
|
-
const getContentSecurityPolicy = (directives, absolutePath, rpcInfos) => {
|
|
2265
|
-
const {
|
|
2266
|
-
assetSource,
|
|
2267
|
-
capabilitySource,
|
|
2268
|
-
nodeProcessSource,
|
|
2269
|
-
origin
|
|
2270
|
-
} = getWorkerUrls(absolutePath);
|
|
2271
|
-
const scriptSources = new Set([`'self'`]);
|
|
2272
|
-
const connectSources = new Set();
|
|
2273
|
-
if (capabilitySource) {
|
|
2274
|
-
connectSources.add(capabilitySource);
|
|
2275
|
-
}
|
|
2276
|
-
if (assetSource) {
|
|
2277
|
-
connectSources.add(assetSource);
|
|
2278
|
-
}
|
|
2279
|
-
if (nodeProcessSource && hasNodeProcess(rpcInfos)) {
|
|
2280
|
-
connectSources.add(nodeProcessSource);
|
|
2281
|
-
}
|
|
2282
|
-
const manifestDirectives = directives || [];
|
|
2283
|
-
for (const directive of manifestDirectives) {
|
|
2284
|
-
applyDirective(directive, scriptSources, connectSources, assetSource, origin);
|
|
2285
|
-
}
|
|
2286
|
-
const connectDirective = connectSources.size > 0 ? [...connectSources].join(' ') : `'none'`;
|
|
2287
|
-
return `default-src 'none'; script-src ${[...scriptSources].join(' ')}; worker-src 'none'; child-src 'none'; connect-src ${connectDirective};`;
|
|
2288
|
-
};
|
|
2289
|
-
|
|
2290
2439
|
const rpcs = Object.create(null);
|
|
2291
2440
|
const add = (id, rpc) => {
|
|
2292
2441
|
rpcs[id] = rpc;
|
|
@@ -2295,23 +2444,10 @@ const get$2 = id => {
|
|
|
2295
2444
|
return rpcs[id];
|
|
2296
2445
|
};
|
|
2297
2446
|
|
|
2298
|
-
const getUrlPrefix = (platform, extensionPath) => {
|
|
2299
|
-
if (extensionPath.startsWith('http://') || extensionPath.startsWith('https://')) {
|
|
2300
|
-
return extensionPath;
|
|
2301
|
-
}
|
|
2302
|
-
if (platform === Web) {
|
|
2303
|
-
return extensionPath;
|
|
2304
|
-
}
|
|
2305
|
-
if (extensionPath.startsWith('/')) {
|
|
2306
|
-
return `/remote${extensionPath}`;
|
|
2307
|
-
}
|
|
2308
|
-
return `/remote/${extensionPath}`;
|
|
2309
|
-
};
|
|
2310
|
-
|
|
2311
2447
|
const handleRpcInfos = (extension, platform) => {
|
|
2312
2448
|
if (extension?.applicationId !== undefined) {
|
|
2313
2449
|
if (Array.isArray(extension.rpc) && extension.rpc.length > 0) {
|
|
2314
|
-
|
|
2450
|
+
register(extension, platform);
|
|
2315
2451
|
}
|
|
2316
2452
|
return;
|
|
2317
2453
|
}
|
|
@@ -3088,15 +3224,6 @@ const createWebViewWorkerRpc2 = async (rpcInfo, port) => {
|
|
|
3088
3224
|
});
|
|
3089
3225
|
};
|
|
3090
3226
|
|
|
3091
|
-
const sourcePathMarker = '/extension-management-worker/src/';
|
|
3092
|
-
const getExtensionHostSubWorkerUrl = (currentUrl = import.meta.url) => {
|
|
3093
|
-
const relativePath = currentUrl.includes(sourcePathMarker) ? '../../../../extension-host-sub-worker/src/extensionHostSubWorkerMain.js' : '../../extension-host-sub-worker/dist/extensionHostSubWorkerMain.js';
|
|
3094
|
-
return new URL(relativePath, currentUrl).href;
|
|
3095
|
-
};
|
|
3096
|
-
const extensionHostSubWorkerUrl = getExtensionHostSubWorkerUrl();
|
|
3097
|
-
|
|
3098
|
-
// TODO
|
|
3099
|
-
|
|
3100
3227
|
/**
|
|
3101
3228
|
* @deprecated use createWebViewWorkerRpc2 which passes the worker url as a parameter
|
|
3102
3229
|
*/
|
|
@@ -3296,7 +3423,7 @@ const disposeExtensionApplication = async applicationId => {
|
|
|
3296
3423
|
clear(applicationId);
|
|
3297
3424
|
resetAllExtensionActivations(application);
|
|
3298
3425
|
reset(applicationId);
|
|
3299
|
-
const results = await Promise.allSettled(runtimes.map(async ({
|
|
3426
|
+
const results = await Promise.allSettled([dispose(application), ...runtimes.map(async ({
|
|
3300
3427
|
id,
|
|
3301
3428
|
rpc
|
|
3302
3429
|
}) => {
|
|
@@ -3306,7 +3433,7 @@ const disposeExtensionApplication = async applicationId => {
|
|
|
3306
3433
|
if (errors.length > 0) {
|
|
3307
3434
|
throw new AggregateError(errors, `Failed to dispose extension ${id}`);
|
|
3308
3435
|
}
|
|
3309
|
-
}));
|
|
3436
|
+
})]);
|
|
3310
3437
|
const errors = results.filter(result => result.status === 'rejected').map(result => result.reason);
|
|
3311
3438
|
if (errors.length > 0) {
|
|
3312
3439
|
throw new AggregateError(errors, `Failed to dispose extension application ${applicationId}`);
|
|
@@ -5716,6 +5843,69 @@ const getLanguages = async (platform, assetDir) => {
|
|
|
5716
5843
|
}
|
|
5717
5844
|
};
|
|
5718
5845
|
|
|
5846
|
+
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
|
|
5847
|
+
|
|
5848
|
+
const queues = new Map();
|
|
5849
|
+
const replace = async (application, extensionId, extension) => {
|
|
5850
|
+
assertCurrentApplication(application);
|
|
5851
|
+
if (!extension || typeof extension.id !== 'string' || !extension.id || typeof extension.browser !== 'string' || extension.isolated !== true) {
|
|
5852
|
+
throw new Error('Expected an isolated web extension');
|
|
5853
|
+
}
|
|
5854
|
+
const applicationId = application.applicationId;
|
|
5855
|
+
const current = get$7(applicationId);
|
|
5856
|
+
if (current.webExtensions.every(({
|
|
5857
|
+
id
|
|
5858
|
+
}) => id !== extensionId)) {
|
|
5859
|
+
throw new Error(`Application extension not found: ${extensionId}`);
|
|
5860
|
+
}
|
|
5861
|
+
if (extension.id !== extensionId && current.webExtensions.some(({
|
|
5862
|
+
id
|
|
5863
|
+
}) => id === extension.id)) {
|
|
5864
|
+
throw new Error(`Duplicate application extension: ${extension.id}`);
|
|
5865
|
+
}
|
|
5866
|
+
// Finish pending startup before removing its RPC, including failed activations.
|
|
5867
|
+
try {
|
|
5868
|
+
await waitForExtensionActivations(current);
|
|
5869
|
+
} catch {
|
|
5870
|
+
// Failed startup must still allow replacing the broken extension.
|
|
5871
|
+
}
|
|
5872
|
+
assertCurrentApplication(application);
|
|
5873
|
+
const rpc = remove$1(extensionId, applicationId);
|
|
5874
|
+
unregister(extensionId, applicationId);
|
|
5875
|
+
const results = await Promise.allSettled([disposeExtension(application, extensionId), Promise.try(() => rpc?.dispose()), invoke('LaunchIsolatedExtensionHostWorker.disposeIsolatedExtensionHostWorker', getRuntimeId(extensionId, application))]);
|
|
5876
|
+
assertCurrentApplication(application);
|
|
5877
|
+
resetExtensionActivation(extensionId, application);
|
|
5878
|
+
resetExtensionRuntimeState(extensionId, applicationId);
|
|
5879
|
+
const errors = results.filter(result => result.status === 'rejected').map(result => result.reason);
|
|
5880
|
+
if (errors.length > 0) throw new AggregateError(errors, `Failed to reload extension ${extensionId}`);
|
|
5881
|
+
update({
|
|
5882
|
+
cachedExtensions: undefined,
|
|
5883
|
+
webExtensions: get$7(applicationId).webExtensions.map(previous => previous.id === extensionId ? {
|
|
5884
|
+
...extension
|
|
5885
|
+
} : previous)
|
|
5886
|
+
}, applicationId);
|
|
5887
|
+
};
|
|
5888
|
+
const run = async (previous, application, extensionId, extension) => {
|
|
5889
|
+
try {
|
|
5890
|
+
await previous;
|
|
5891
|
+
} catch {
|
|
5892
|
+
// A later save can recover from an earlier reload failure.
|
|
5893
|
+
}
|
|
5894
|
+
await replace(application, extensionId, extension);
|
|
5895
|
+
};
|
|
5896
|
+
const reloadApplicationExtension = async (applicationId, extensionId, extension) => {
|
|
5897
|
+
const application = get$7(applicationId);
|
|
5898
|
+
const key = application.applicationGeneration;
|
|
5899
|
+
const previous = queues.get(key) || Promise.resolve();
|
|
5900
|
+
const operation = run(previous, application, extensionId, extension);
|
|
5901
|
+
queues.set(key, operation);
|
|
5902
|
+
try {
|
|
5903
|
+
await operation;
|
|
5904
|
+
} finally {
|
|
5905
|
+
if (queues.get(key) === operation) queues.delete(key);
|
|
5906
|
+
}
|
|
5907
|
+
};
|
|
5908
|
+
|
|
5719
5909
|
const sendMessagePortToElectron = async (port, initialCommand) => {
|
|
5720
5910
|
await invokeAndTransfer$1('SendMessagePortToElectron.sendMessagePortToElectron', port, initialCommand, EmbedsWorker);
|
|
5721
5911
|
};
|
|
@@ -5784,10 +5974,16 @@ const invokeForApplication = async (applicationId, method, ...args) => {
|
|
|
5784
5974
|
return invoke('Application.execute', applicationId, 'FileSystem.readFile', ...args);
|
|
5785
5975
|
case 'Extensions.activateByEvent':
|
|
5786
5976
|
return activateByEvent(args[0], args[1], args[2] ?? application.platform, application);
|
|
5977
|
+
case 'Extensions.createWebViewWorkerRpc':
|
|
5978
|
+
return createWorker$1(application, args[0], args[1], true);
|
|
5979
|
+
case 'Extensions.createWebViewWorkerRpc2':
|
|
5980
|
+
return createWorker$1(application, args[0], args[1]);
|
|
5787
5981
|
case 'Extensions.getAllExtensions':
|
|
5788
5982
|
return getAllExtensionsWithState(application, args[0] || '', args[1] ?? application.platform);
|
|
5789
5983
|
case 'Extensions.getDynamicWebExtensions':
|
|
5790
5984
|
return application.webExtensions;
|
|
5985
|
+
case 'Extensions.getRpcInfo':
|
|
5986
|
+
return getRpcInfo$1(application, args[0]);
|
|
5791
5987
|
case 'Extensions.getRuntimeStatus':
|
|
5792
5988
|
return getRuntimeStatus$1(args[0], applicationId);
|
|
5793
5989
|
case 'Extensions.getStatusBarItems':
|
|
@@ -5799,6 +5995,8 @@ const invokeForApplication = async (applicationId, method, ...args) => {
|
|
|
5799
5995
|
}
|
|
5800
5996
|
case 'Extensions.handleFileChanges':
|
|
5801
5997
|
return handleFileChanges(args[0], applicationId);
|
|
5998
|
+
case 'Extensions.sendMessagePortToFileSystemWorker':
|
|
5999
|
+
return createFileSystemPort(application, args[0]);
|
|
5802
6000
|
default:
|
|
5803
6001
|
throw new Error(`Extension command does not support application context: ${method}`);
|
|
5804
6002
|
}
|
|
@@ -5897,6 +6095,7 @@ const commandMap = {
|
|
|
5897
6095
|
'Extensions.invalidateExtensionsCache': invalidateExtensionsCache,
|
|
5898
6096
|
'Extensions.invokeForApplication': invokeForApplication,
|
|
5899
6097
|
'Extensions.readOutputChannel': wrapCommand(readOutputChannel),
|
|
6098
|
+
'Extensions.reloadApplicationExtension': reloadApplicationExtension,
|
|
5900
6099
|
'Extensions.renderViewInstance': renderViewInstance,
|
|
5901
6100
|
'Extensions.requestViewRerender': requestViewRerender,
|
|
5902
6101
|
'Extensions.saveViewInstanceState': saveViewInstanceState,
|