@theia/core 1.75.0-next.3 → 1.75.0-next.35
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/README.md +15 -15
- package/lib/browser/authentication-service.js +1 -1
- package/lib/browser/authentication-service.js.map +1 -1
- package/lib/browser/browser-clipboard-service.js +1 -0
- package/lib/browser/browser-clipboard-service.js.map +1 -1
- package/lib/browser/catalog.json +130 -38
- package/lib/browser/common-frontend-contribution.d.ts +3 -0
- package/lib/browser/common-frontend-contribution.d.ts.map +1 -1
- package/lib/browser/common-frontend-contribution.js +15 -3
- package/lib/browser/common-frontend-contribution.js.map +1 -1
- package/lib/browser/common-frontend-contribution.spec.d.ts +2 -0
- package/lib/browser/common-frontend-contribution.spec.d.ts.map +1 -0
- package/lib/browser/common-frontend-contribution.spec.js +71 -0
- package/lib/browser/common-frontend-contribution.spec.js.map +1 -0
- package/lib/browser/connection-status-service.d.ts +1 -0
- package/lib/browser/connection-status-service.d.ts.map +1 -1
- package/lib/browser/connection-status-service.js +7 -0
- package/lib/browser/connection-status-service.js.map +1 -1
- package/lib/browser/frontend-application-contribution.d.ts +1 -1
- package/lib/browser/frontend-application-state.js +1 -0
- package/lib/browser/frontend-application-state.js.map +1 -1
- package/lib/browser/icon-theme-service.d.ts +5 -3
- package/lib/browser/icon-theme-service.d.ts.map +1 -1
- package/lib/browser/icon-theme-service.js +7 -1
- package/lib/browser/icon-theme-service.js.map +1 -1
- package/lib/browser/keyboard/browser-keyboard-layout-provider.js +1 -0
- package/lib/browser/keyboard/browser-keyboard-layout-provider.js.map +1 -1
- package/lib/browser/markdown-rendering/markdown-link-handler.d.ts +11 -0
- package/lib/browser/markdown-rendering/markdown-link-handler.d.ts.map +1 -1
- package/lib/browser/markdown-rendering/markdown-link-handler.js +23 -1
- package/lib/browser/markdown-rendering/markdown-link-handler.js.map +1 -1
- package/lib/browser/markdown-rendering/markdown-renderer.d.ts +11 -0
- package/lib/browser/markdown-rendering/markdown-renderer.d.ts.map +1 -1
- package/lib/browser/markdown-rendering/markdown-renderer.js +41 -0
- package/lib/browser/markdown-rendering/markdown-renderer.js.map +1 -1
- package/lib/browser/markdown-rendering/markdown-renderer.spec.d.ts +2 -0
- package/lib/browser/markdown-rendering/markdown-renderer.spec.d.ts.map +1 -0
- package/lib/browser/markdown-rendering/markdown-renderer.spec.js +79 -0
- package/lib/browser/markdown-rendering/markdown-renderer.spec.js.map +1 -0
- package/lib/browser/messaging/ws-connection-source.d.ts.map +1 -1
- package/lib/browser/messaging/ws-connection-source.js +3 -0
- package/lib/browser/messaging/ws-connection-source.js.map +1 -1
- package/lib/browser/open-with-service.d.ts +2 -1
- package/lib/browser/open-with-service.d.ts.map +1 -1
- package/lib/browser/open-with-service.js +6 -1
- package/lib/browser/open-with-service.js.map +1 -1
- package/lib/browser/performance/frontend-stopwatch.d.ts +2 -1
- package/lib/browser/performance/frontend-stopwatch.d.ts.map +1 -1
- package/lib/browser/performance/frontend-stopwatch.js +6 -1
- package/lib/browser/performance/frontend-stopwatch.js.map +1 -1
- package/lib/browser/perspective-service.js +1 -1
- package/lib/browser/perspective-service.js.map +1 -1
- package/lib/browser/preferences/preference-proxy.spec.js +3 -0
- package/lib/browser/preferences/preference-proxy.spec.js.map +1 -1
- package/lib/browser/preferences/preference-service.spec.js +3 -0
- package/lib/browser/preferences/preference-service.spec.js.map +1 -1
- package/lib/browser/preferences/preference-validation-service.js +1 -1
- package/lib/browser/preferences/preference-validation-service.js.map +1 -1
- package/lib/browser/saveable-service.d.ts +2 -1
- package/lib/browser/saveable-service.d.ts.map +1 -1
- package/lib/browser/saveable-service.js +6 -1
- package/lib/browser/saveable-service.js.map +1 -1
- package/lib/browser/shell/application-shell.js +3 -3
- package/lib/browser/shell/application-shell.js.map +1 -1
- package/lib/browser/shell/application-shell.spec.d.ts +2 -0
- package/lib/browser/shell/application-shell.spec.d.ts.map +1 -0
- package/lib/browser/shell/application-shell.spec.js +57 -0
- package/lib/browser/shell/application-shell.spec.js.map +1 -0
- package/lib/browser/shell/shell-layout-restorer.d.ts +1 -1
- package/lib/browser/shell/shell-layout-restorer.d.ts.map +1 -1
- package/lib/browser/shell/shell-layout-restorer.js +5 -4
- package/lib/browser/shell/shell-layout-restorer.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.d.ts.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js +3 -2
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts +2 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.d.ts.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js +6 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js.map +1 -1
- package/lib/browser/status-bar/status-bar-view-model.d.ts +2 -1
- package/lib/browser/status-bar/status-bar-view-model.d.ts.map +1 -1
- package/lib/browser/status-bar/status-bar-view-model.js +6 -1
- package/lib/browser/status-bar/status-bar-view-model.js.map +1 -1
- package/lib/browser/storage-service.d.ts +1 -1
- package/lib/browser/storage-service.d.ts.map +1 -1
- package/lib/browser/storage-service.js +1 -0
- package/lib/browser/storage-service.js.map +1 -1
- package/lib/browser/tree/tree-consistency.spec.js +103 -0
- package/lib/browser/tree/tree-consistency.spec.js.map +1 -1
- package/lib/browser/tree/tree-model.d.ts.map +1 -1
- package/lib/browser/tree/tree-model.js +1 -0
- package/lib/browser/tree/tree-model.js.map +1 -1
- package/lib/browser/tree/tree.d.ts.map +1 -1
- package/lib/browser/tree/tree.js +6 -0
- package/lib/browser/tree/tree.js.map +1 -1
- package/lib/browser/widget-manager.js +1 -0
- package/lib/browser/widget-manager.js.map +1 -1
- package/lib/browser/widgets/select-component.d.ts +13 -0
- package/lib/browser/widgets/select-component.d.ts.map +1 -1
- package/lib/browser/widgets/select-component.js +44 -12
- package/lib/browser/widgets/select-component.js.map +1 -1
- package/lib/browser/window/default-secondary-window-service.d.ts +4 -1
- package/lib/browser/window/default-secondary-window-service.d.ts.map +1 -1
- package/lib/browser/window/default-secondary-window-service.js +17 -6
- package/lib/browser/window/default-secondary-window-service.js.map +1 -1
- package/lib/browser/window/default-window-service.d.ts +19 -0
- package/lib/browser/window/default-window-service.d.ts.map +1 -1
- package/lib/browser/window/default-window-service.js +34 -5
- package/lib/browser/window/default-window-service.js.map +1 -1
- package/lib/browser/window/default-window-service.spec.js +22 -0
- package/lib/browser/window/default-window-service.spec.js.map +1 -1
- package/lib/browser/window/secondary-window-service.d.ts +7 -0
- package/lib/browser/window/secondary-window-service.d.ts.map +1 -1
- package/lib/browser/window/window-service.d.ts +1 -1
- package/lib/browser-only/frontend-only-application-module.d.ts.map +1 -1
- package/lib/browser-only/frontend-only-application-module.js +25 -1
- package/lib/browser-only/frontend-only-application-module.js.map +1 -1
- package/lib/common/command.d.ts +37 -0
- package/lib/common/command.d.ts.map +1 -1
- package/lib/common/command.js +74 -4
- package/lib/common/command.js.map +1 -1
- package/lib/common/command.spec.js +109 -0
- package/lib/common/command.spec.js.map +1 -1
- package/lib/common/message-service-protocol.d.ts +2 -0
- package/lib/common/message-service-protocol.d.ts.map +1 -1
- package/lib/common/message-service-protocol.js +8 -2
- package/lib/common/message-service-protocol.js.map +1 -1
- package/lib/common/performance/stopwatch.js +1 -0
- package/lib/common/performance/stopwatch.js.map +1 -1
- package/lib/common/preferences/preference-schema-service.d.ts +2 -0
- package/lib/common/preferences/preference-schema-service.d.ts.map +1 -1
- package/lib/common/preferences/preference-schema-service.js +11 -5
- package/lib/common/preferences/preference-schema-service.js.map +1 -1
- package/lib/common/preferences/preference-service.d.ts +2 -0
- package/lib/common/preferences/preference-service.d.ts.map +1 -1
- package/lib/common/preferences/preference-service.js +7 -1
- package/lib/common/preferences/preference-service.js.map +1 -1
- package/lib/electron-browser/menu/electron-menu-contribution.js +1 -1
- package/lib/electron-browser/menu/electron-menu-contribution.js.map +1 -1
- package/lib/electron-browser/window/electron-secondary-window-service.js +1 -1
- package/lib/electron-browser/window/electron-secondary-window-service.js.map +1 -1
- package/lib/electron-browser/window/electron-window-service.js +3 -3
- package/lib/electron-browser/window/electron-window-service.js.map +1 -1
- package/lib/node/backend-application.js +5 -5
- package/lib/node/backend-application.js.map +1 -1
- package/lib/node/backend-application.spec.js +6 -3
- package/lib/node/backend-application.spec.js.map +1 -1
- package/lib/node/console-logger-server.d.ts.map +1 -1
- package/lib/node/console-logger-server.js +2 -0
- package/lib/node/console-logger-server.js.map +1 -1
- package/lib/node/env-variables/env-variables-server.d.ts +2 -0
- package/lib/node/env-variables/env-variables-server.d.ts.map +1 -1
- package/lib/node/env-variables/env-variables-server.js +7 -1
- package/lib/node/env-variables/env-variables-server.js.map +1 -1
- package/lib/node/hosting/browser-connection-token.d.ts +2 -0
- package/lib/node/hosting/browser-connection-token.d.ts.map +1 -1
- package/lib/node/hosting/browser-connection-token.js +14 -1
- package/lib/node/hosting/browser-connection-token.js.map +1 -1
- package/lib/node/hosting/browser-connection-token.spec.js +25 -2
- package/lib/node/hosting/browser-connection-token.spec.js.map +1 -1
- package/lib/node/key-store-server.d.ts +2 -0
- package/lib/node/key-store-server.d.ts.map +1 -1
- package/lib/node/key-store-server.js +6 -1
- package/lib/node/key-store-server.js.map +1 -1
- package/lib/node/logger-cli-contribution.d.ts.map +1 -1
- package/lib/node/logger-cli-contribution.js.map +1 -1
- package/lib/node/messaging/default-messaging-service.js +1 -1
- package/lib/node/messaging/default-messaging-service.js.map +1 -1
- package/lib/node/messaging/ipc-connection-provider.js +1 -0
- package/lib/node/messaging/ipc-connection-provider.js.map +1 -1
- package/lib/node/messaging/websocket-endpoint.js +1 -1
- package/lib/node/messaging/websocket-endpoint.js.map +1 -1
- package/lib/node/messaging/websocket-frontend-connection-service.d.ts +2 -1
- package/lib/node/messaging/websocket-frontend-connection-service.d.ts.map +1 -1
- package/lib/node/messaging/websocket-frontend-connection-service.js +11 -6
- package/lib/node/messaging/websocket-frontend-connection-service.js.map +1 -1
- package/lib/node/setting-service.js +1 -0
- package/lib/node/setting-service.js.map +1 -1
- package/package.json +19 -19
- package/src/browser/authentication-service.ts +1 -1
- package/src/browser/browser-clipboard-service.ts +2 -2
- package/src/browser/common-frontend-contribution.spec.ts +84 -0
- package/src/browser/common-frontend-contribution.ts +15 -3
- package/src/browser/connection-status-service.ts +7 -3
- package/src/browser/frontend-application-contribution.ts +1 -1
- package/src/browser/frontend-application-state.ts +2 -2
- package/src/browser/icon-theme-service.ts +6 -2
- package/src/browser/keyboard/browser-keyboard-layout-provider.ts +2 -2
- package/src/browser/markdown-rendering/markdown-link-handler.ts +23 -1
- package/src/browser/markdown-rendering/markdown-renderer.spec.ts +88 -0
- package/src/browser/markdown-rendering/markdown-renderer.ts +43 -0
- package/src/browser/messaging/ws-connection-source.ts +4 -0
- package/src/browser/open-with-service.ts +6 -3
- package/src/browser/performance/frontend-stopwatch.ts +6 -3
- package/src/browser/perspective-service.ts +1 -1
- package/src/browser/preferences/preference-proxy.spec.ts +3 -0
- package/src/browser/preferences/preference-service.spec.ts +3 -0
- package/src/browser/preferences/preference-validation-service.ts +1 -1
- package/src/browser/saveable-service.ts +5 -2
- package/src/browser/shell/application-shell.spec.ts +62 -0
- package/src/browser/shell/application-shell.ts +4 -4
- package/src/browser/shell/shell-layout-restorer.ts +6 -5
- package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.tsx +3 -2
- package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.ts +5 -2
- package/src/browser/status-bar/status-bar-view-model.ts +7 -3
- package/src/browser/storage-service.ts +4 -2
- package/src/browser/tree/tree-consistency.spec.ts +119 -0
- package/src/browser/tree/tree-model.ts +4 -2
- package/src/browser/tree/tree.ts +6 -0
- package/src/browser/widget-manager.ts +1 -1
- package/src/browser/widgets/select-component.tsx +49 -12
- package/src/browser/window/default-secondary-window-service.ts +17 -8
- package/src/browser/window/default-window-service.spec.ts +22 -0
- package/src/browser/window/default-window-service.ts +37 -5
- package/src/browser/window/secondary-window-service.ts +7 -0
- package/src/browser/window/window-service.ts +1 -1
- package/src/browser-only/frontend-only-application-module.ts +25 -1
- package/src/common/command.spec.ts +125 -0
- package/src/common/command.ts +77 -4
- package/src/common/message-service-protocol.ts +7 -3
- package/src/common/performance/stopwatch.ts +2 -2
- package/src/common/preferences/preference-schema-service.ts +9 -5
- package/src/common/preferences/preference-service.ts +6 -2
- package/src/electron-browser/menu/electron-menu-contribution.ts +1 -1
- package/src/electron-browser/window/electron-secondary-window-service.ts +1 -1
- package/src/electron-browser/window/electron-window-service.ts +3 -3
- package/src/node/backend-application.spec.ts +6 -3
- package/src/node/backend-application.ts +5 -5
- package/src/node/console-logger-server.ts +3 -0
- package/src/node/env-variables/env-variables-server.ts +6 -2
- package/src/node/hosting/browser-connection-token.spec.ts +30 -3
- package/src/node/hosting/browser-connection-token.ts +13 -2
- package/src/node/key-store-server.ts +6 -3
- package/src/node/logger-cli-contribution.ts +3 -0
- package/src/node/messaging/default-messaging-service.ts +1 -1
- package/src/node/messaging/ipc-connection-provider.ts +2 -2
- package/src/node/messaging/websocket-endpoint.ts +1 -1
- package/src/node/messaging/websocket-frontend-connection-service.ts +11 -8
- package/src/node/setting-service.ts +2 -2
|
@@ -139,6 +139,131 @@ describe('Commands', () => {
|
|
|
139
139
|
expect(commandRegistry.getAllHandlers('id').length).to.be.equal(2);
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
+
describe('registerAlias', () => {
|
|
143
|
+
|
|
144
|
+
it('onDidExecuteCommand fires for alias when target is executed', async () => {
|
|
145
|
+
commandRegistry.registerCommand({ id: 'target' }, new StubCommandHandler());
|
|
146
|
+
commandRegistry.registerAlias('alias', 'target');
|
|
147
|
+
const events: string[] = [];
|
|
148
|
+
commandRegistry.onDidExecuteCommand(e => events.push(e.commandId));
|
|
149
|
+
await commandRegistry.executeCommand('target');
|
|
150
|
+
expect(events).to.deep.equal(['target', 'alias']);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('onDidExecuteCommand fires only for alias when alias is executed (no reverse)', async () => {
|
|
154
|
+
commandRegistry.registerCommand({ id: 'alias' }, new StubCommandHandler());
|
|
155
|
+
commandRegistry.registerAlias('alias', 'target');
|
|
156
|
+
const events: string[] = [];
|
|
157
|
+
commandRegistry.onDidExecuteCommand(e => events.push(e.commandId));
|
|
158
|
+
await commandRegistry.executeCommand('alias');
|
|
159
|
+
// Unidirectional: an alias fires for its target, but not the other way round
|
|
160
|
+
expect(events).to.deep.equal(['alias']);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('onWillExecuteCommand fires for alias when target is executed', async () => {
|
|
164
|
+
commandRegistry.registerCommand({ id: 'target' }, new StubCommandHandler());
|
|
165
|
+
commandRegistry.registerAlias('alias', 'target');
|
|
166
|
+
const events: string[] = [];
|
|
167
|
+
commandRegistry.onWillExecuteCommand(e => events.push(e.commandId));
|
|
168
|
+
await commandRegistry.executeCommand('target');
|
|
169
|
+
expect(events).to.deep.equal(['target', 'alias']);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('disposing the alias removes the event link', async () => {
|
|
173
|
+
commandRegistry.registerCommand({ id: 'target' }, new StubCommandHandler());
|
|
174
|
+
const disposable = commandRegistry.registerAlias('alias', 'target');
|
|
175
|
+
disposable.dispose();
|
|
176
|
+
const events: string[] = [];
|
|
177
|
+
commandRegistry.onDidExecuteCommand(e => events.push(e.commandId));
|
|
178
|
+
await commandRegistry.executeCommand('target');
|
|
179
|
+
expect(events).to.deep.equal(['target']);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('getAliases returns the aliases registered for a target', () => {
|
|
183
|
+
commandRegistry.registerAlias('a', 'b');
|
|
184
|
+
expect(commandRegistry.getAliases('b')).to.deep.equal(['a']);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('getAliases returns nothing for the alias direction (unidirectional)', () => {
|
|
188
|
+
commandRegistry.registerAlias('a', 'b');
|
|
189
|
+
expect(commandRegistry.getAliases('a')).to.be.empty;
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('getAliases returns nothing for unaliased commands', () => {
|
|
193
|
+
expect(commandRegistry.getAliases('nonexistent')).to.be.empty;
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('a target can carry several aliases', async () => {
|
|
197
|
+
commandRegistry.registerCommand({ id: 'target' }, new StubCommandHandler());
|
|
198
|
+
commandRegistry.registerAlias('alias1', 'target');
|
|
199
|
+
commandRegistry.registerAlias('alias2', 'target');
|
|
200
|
+
const events: string[] = [];
|
|
201
|
+
commandRegistry.onDidExecuteCommand(e => events.push(e.commandId));
|
|
202
|
+
await commandRegistry.executeCommand('target');
|
|
203
|
+
expect(events).to.deep.equal(['target', 'alias1', 'alias2']);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('disposing one alias keeps the others', async () => {
|
|
207
|
+
commandRegistry.registerCommand({ id: 'target' }, new StubCommandHandler());
|
|
208
|
+
const first = commandRegistry.registerAlias('alias1', 'target');
|
|
209
|
+
commandRegistry.registerAlias('alias2', 'target');
|
|
210
|
+
first.dispose();
|
|
211
|
+
expect(commandRegistry.getAliases('target')).to.deep.equal(['alias2']);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('alias events include correct args', async () => {
|
|
215
|
+
commandRegistry.registerCommand({ id: 'target' }, new ConcatCommandHandler());
|
|
216
|
+
commandRegistry.registerAlias('alias', 'target');
|
|
217
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
218
|
+
const capturedArgs: any[][] = [];
|
|
219
|
+
commandRegistry.onDidExecuteCommand(e => capturedArgs.push(e.args));
|
|
220
|
+
await commandRegistry.executeCommand('target', 'x', 'y');
|
|
221
|
+
expect(capturedArgs.length).to.equal(2);
|
|
222
|
+
expect(capturedArgs[0]).to.deep.equal(['x', 'y']);
|
|
223
|
+
expect(capturedArgs[1]).to.deep.equal(['x', 'y']);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('nested execution: alias handler delegating to target fires target and alias events', async () => {
|
|
227
|
+
commandRegistry.registerCommand({ id: 'target' }, new StubCommandHandler());
|
|
228
|
+
commandRegistry.registerCommand({ id: 'alias' }, {
|
|
229
|
+
execute: () => commandRegistry.executeCommand('target')
|
|
230
|
+
});
|
|
231
|
+
commandRegistry.registerAlias('alias', 'target');
|
|
232
|
+
const events: string[] = [];
|
|
233
|
+
commandRegistry.onDidExecuteCommand(e => events.push(e.commandId));
|
|
234
|
+
await commandRegistry.executeCommand('alias');
|
|
235
|
+
// The alias is on the stack while the target runs, so its event is only reported once.
|
|
236
|
+
expect(events).to.deep.equal(['target', 'alias']);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('reports the alias once for the will execute event as well', async () => {
|
|
240
|
+
commandRegistry.registerCommand({ id: 'target' }, new StubCommandHandler());
|
|
241
|
+
commandRegistry.registerCommand({ id: 'alias' }, {
|
|
242
|
+
execute: () => commandRegistry.executeCommand('target')
|
|
243
|
+
});
|
|
244
|
+
commandRegistry.registerAlias('alias', 'target');
|
|
245
|
+
const events: string[] = [];
|
|
246
|
+
commandRegistry.onWillExecuteCommand(e => events.push(e.commandId));
|
|
247
|
+
await commandRegistry.executeCommand('alias');
|
|
248
|
+
expect(events).to.deep.equal(['alias', 'target']);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('reports the alias again once the delegating execution finished', async () => {
|
|
252
|
+
commandRegistry.registerCommand({ id: 'target' }, new StubCommandHandler());
|
|
253
|
+
commandRegistry.registerCommand({ id: 'alias' }, {
|
|
254
|
+
execute: () => commandRegistry.executeCommand('target')
|
|
255
|
+
});
|
|
256
|
+
commandRegistry.registerAlias('alias', 'target');
|
|
257
|
+
await commandRegistry.executeCommand('alias');
|
|
258
|
+
|
|
259
|
+
const events: string[] = [];
|
|
260
|
+
commandRegistry.onDidExecuteCommand(e => events.push(e.commandId));
|
|
261
|
+
await commandRegistry.executeCommand('target');
|
|
262
|
+
expect(events).to.deep.equal(['target', 'alias']);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
});
|
|
266
|
+
|
|
142
267
|
describe('compareCommands', () => {
|
|
143
268
|
|
|
144
269
|
it('should sort command \'a\' before command \'b\' with categories', () => {
|
package/src/common/command.ts
CHANGED
|
@@ -202,6 +202,19 @@ export class CommandRegistry implements CommandService {
|
|
|
202
202
|
// List of recently used commands.
|
|
203
203
|
protected _recent: string[] = [];
|
|
204
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Unidirectional alias map from a target command id to the alias command ids registered for it.
|
|
207
|
+
* When the target (for instance a native Theia command) is executed, events also fire for its
|
|
208
|
+
* aliases (for instance the VS Code command ids).
|
|
209
|
+
*/
|
|
210
|
+
protected readonly _aliases = new Map<string, Set<string>>();
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* How often each command is currently executing, so that an alias event can be skipped while the
|
|
214
|
+
* alias command itself is on the stack.
|
|
215
|
+
*/
|
|
216
|
+
protected readonly executionDepth = new Map<string, number>();
|
|
217
|
+
|
|
205
218
|
protected readonly onWillExecuteCommandEmitter = new Emitter<WillExecuteCommandEvent>();
|
|
206
219
|
readonly onWillExecuteCommand = this.onWillExecuteCommandEmitter.event;
|
|
207
220
|
|
|
@@ -277,6 +290,42 @@ export class CommandRegistry implements CommandService {
|
|
|
277
290
|
}
|
|
278
291
|
}
|
|
279
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Register a unidirectional alias from a target command to an alias command.
|
|
295
|
+
* When the target command is executed, {@link onDidExecuteCommand} and {@link onWillExecuteCommand}
|
|
296
|
+
* fire for the alias id as well.
|
|
297
|
+
*
|
|
298
|
+
* This does not affect command handlers, enablement or visibility, only event emission. A target may
|
|
299
|
+
* carry several aliases.
|
|
300
|
+
*
|
|
301
|
+
* No event is fired for an alias while that alias is executing, which keeps an alias command that
|
|
302
|
+
* delegates to its target from reporting itself twice. The check is by command id rather than by call
|
|
303
|
+
* stack, so an unrelated execution of the alias that happens to overlap suppresses the event as well.
|
|
304
|
+
*
|
|
305
|
+
* @param aliasId the alias command id, for instance a VS Code command id
|
|
306
|
+
* @param targetId the target command id, for instance the native Theia command id
|
|
307
|
+
* @returns a `Disposable` that removes this alias again
|
|
308
|
+
*/
|
|
309
|
+
registerAlias(aliasId: string, targetId: string): Disposable {
|
|
310
|
+
const aliases = this._aliases.get(targetId) ?? new Set<string>();
|
|
311
|
+
aliases.add(aliasId);
|
|
312
|
+
this._aliases.set(targetId, aliases);
|
|
313
|
+
return Disposable.create(() => {
|
|
314
|
+
const registered = this._aliases.get(targetId);
|
|
315
|
+
if (registered?.delete(aliasId) && registered.size === 0) {
|
|
316
|
+
this._aliases.delete(targetId);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Returns the alias ids registered for the given command id.
|
|
323
|
+
*/
|
|
324
|
+
getAliases(commandId: string): readonly string[] {
|
|
325
|
+
const aliases = this._aliases.get(commandId);
|
|
326
|
+
return aliases ? Array.from(aliases) : [];
|
|
327
|
+
}
|
|
328
|
+
|
|
280
329
|
/**
|
|
281
330
|
* Register the given handler for the given command identifier.
|
|
282
331
|
*
|
|
@@ -341,10 +390,23 @@ export class CommandRegistry implements CommandService {
|
|
|
341
390
|
async executeCommand<T>(commandId: string, ...args: any[]): Promise<T | undefined> {
|
|
342
391
|
const handler = this.getActiveHandler(commandId, ...args);
|
|
343
392
|
if (handler) {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
393
|
+
this.executionDepth.set(commandId, (this.executionDepth.get(commandId) ?? 0) + 1);
|
|
394
|
+
try {
|
|
395
|
+
await this.fireWillExecuteCommand(commandId, args);
|
|
396
|
+
const result = await handler.execute(...args);
|
|
397
|
+
this.onDidExecuteCommandEmitter.fire({ commandId, args });
|
|
398
|
+
for (const alias of this.getPendingAliases(commandId)) {
|
|
399
|
+
this.onDidExecuteCommandEmitter.fire({ commandId: alias, args });
|
|
400
|
+
}
|
|
401
|
+
return result;
|
|
402
|
+
} finally {
|
|
403
|
+
const depth = this.executionDepth.get(commandId) ?? 1;
|
|
404
|
+
if (depth > 1) {
|
|
405
|
+
this.executionDepth.set(commandId, depth - 1);
|
|
406
|
+
} else {
|
|
407
|
+
this.executionDepth.delete(commandId);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
348
410
|
}
|
|
349
411
|
throw Object.assign(new Error(`The command '${commandId}' cannot be executed. There are no active handlers available for the command.`), { code: 'NO_ACTIVE_HANDLER' });
|
|
350
412
|
}
|
|
@@ -352,6 +414,17 @@ export class CommandRegistry implements CommandService {
|
|
|
352
414
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
353
415
|
protected async fireWillExecuteCommand(commandId: string, args: any[] = []): Promise<void> {
|
|
354
416
|
await WaitUntilEvent.fire(this.onWillExecuteCommandEmitter, { commandId, args }, 30000);
|
|
417
|
+
for (const alias of this.getPendingAliases(commandId)) {
|
|
418
|
+
await WaitUntilEvent.fire(this.onWillExecuteCommandEmitter, { commandId: alias, args }, 30000);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* The aliases of the given command that are not executing themselves, and for which events are
|
|
424
|
+
* therefore still owed.
|
|
425
|
+
*/
|
|
426
|
+
protected getPendingAliases(commandId: string): readonly string[] {
|
|
427
|
+
return this.getAliases(commandId).filter(alias => !this.executionDepth.has(alias));
|
|
355
428
|
}
|
|
356
429
|
|
|
357
430
|
/**
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
|
-
import { injectable } from 'inversify';
|
|
17
|
+
import { injectable, inject, named } from 'inversify';
|
|
18
18
|
import { CancellationToken } from './cancellation';
|
|
19
19
|
import { nls } from './nls';
|
|
20
|
+
import { ILogger } from './logger';
|
|
20
21
|
|
|
21
22
|
export const messageServicePath = '/services/messageService';
|
|
22
23
|
|
|
@@ -115,6 +116,9 @@ export interface ProgressUpdate {
|
|
|
115
116
|
@injectable()
|
|
116
117
|
export class MessageClient {
|
|
117
118
|
|
|
119
|
+
@inject(ILogger) @named('core:MessageClient')
|
|
120
|
+
protected readonly logger: ILogger;
|
|
121
|
+
|
|
118
122
|
/**
|
|
119
123
|
* Show a message of the given type and possible actions to the user.
|
|
120
124
|
* Resolve to a chosen action.
|
|
@@ -123,7 +127,7 @@ export class MessageClient {
|
|
|
123
127
|
* To be implemented by an extension, e.g. by the messages extension.
|
|
124
128
|
*/
|
|
125
129
|
showMessage(message: Message): Promise<string | undefined> {
|
|
126
|
-
|
|
130
|
+
this.logger.info(message.text);
|
|
127
131
|
return Promise.resolve(undefined);
|
|
128
132
|
}
|
|
129
133
|
|
|
@@ -133,7 +137,7 @@ export class MessageClient {
|
|
|
133
137
|
* To be implemented by an extension, e.g. by the messages extension.
|
|
134
138
|
*/
|
|
135
139
|
showProgress(progressId: string, message: ProgressMessage, cancellationToken: CancellationToken): Promise<string | undefined> {
|
|
136
|
-
|
|
140
|
+
this.logger.info(message.text);
|
|
137
141
|
return Promise.resolve(undefined);
|
|
138
142
|
}
|
|
139
143
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
18
18
|
|
|
19
|
-
import { inject, injectable, unmanaged } from 'inversify';
|
|
19
|
+
import { inject, injectable, unmanaged, named } from 'inversify';
|
|
20
20
|
import { ILogger, LogLevel } from '../logger';
|
|
21
21
|
import { MaybePromise } from '../types';
|
|
22
22
|
import { Measurement, MeasurementOptions, MeasurementResult } from './measurement';
|
|
@@ -48,7 +48,7 @@ interface LogOptions extends MeasurementOptions {
|
|
|
48
48
|
@injectable()
|
|
49
49
|
export abstract class Stopwatch {
|
|
50
50
|
|
|
51
|
-
@inject(ILogger)
|
|
51
|
+
@inject(ILogger) @named('core:Stopwatch')
|
|
52
52
|
protected readonly logger: ILogger;
|
|
53
53
|
|
|
54
54
|
protected _storedMeasurements: MeasurementResult[] = [];
|
|
@@ -24,6 +24,7 @@ import { PreferenceScope, ValidPreferenceScopes } from './preference-scope';
|
|
|
24
24
|
import { PreferenceUtils } from './preference-provider';
|
|
25
25
|
import { ContributionProvider } from '../contribution-provider';
|
|
26
26
|
import { Deferred } from '../promise-util';
|
|
27
|
+
import { ILogger } from '../logger';
|
|
27
28
|
|
|
28
29
|
export const NO_OVERRIDE = {};
|
|
29
30
|
export const OVERRIDE_PROPERTY = '\\[(.*)\\]$';
|
|
@@ -78,6 +79,9 @@ export class PreferenceSchemaServiceImpl implements PreferenceSchemaService {
|
|
|
78
79
|
@inject(ContributionProvider) @named(PreferenceContribution)
|
|
79
80
|
protected readonly preferenceContributions: ContributionProvider<PreferenceContribution>;
|
|
80
81
|
|
|
82
|
+
@inject(ILogger) @named('core:PreferenceSchemaServiceImpl')
|
|
83
|
+
protected readonly logger: ILogger;
|
|
84
|
+
|
|
81
85
|
@postConstruct()
|
|
82
86
|
protected init(): void {
|
|
83
87
|
for (const scope of this.validScopes) {
|
|
@@ -128,7 +132,7 @@ export class PreferenceSchemaServiceImpl implements PreferenceSchemaService {
|
|
|
128
132
|
|
|
129
133
|
for (const [key, property] of Object.entries(schema.properties)) {
|
|
130
134
|
if (this.properties.has(key)) {
|
|
131
|
-
|
|
135
|
+
this.logger.warn(`Property with id '${key}' already exists`);
|
|
132
136
|
continue;
|
|
133
137
|
}
|
|
134
138
|
this.pendingDefaultRegistrationWarnings?.delete(key);
|
|
@@ -210,14 +214,14 @@ export class PreferenceSchemaServiceImpl implements PreferenceSchemaService {
|
|
|
210
214
|
this.setJSONSchemasProperty(key, updatedProperty);
|
|
211
215
|
this.schemaChangedEmitter.fire();
|
|
212
216
|
} else {
|
|
213
|
-
|
|
217
|
+
this.logger.warn(`Trying to update non-existent property ${key}`);
|
|
214
218
|
}
|
|
215
219
|
}
|
|
216
220
|
|
|
217
221
|
protected pendingDefaultRegistrationWarnings: Set<string> | undefined = new Set();
|
|
218
222
|
protected logDefaultRegistrationWarnings(): void {
|
|
219
223
|
if (this.pendingDefaultRegistrationWarnings?.size) {
|
|
220
|
-
|
|
224
|
+
this.logger.warn('Default overrides registered for these keys, but preferences not registered', Array.from(this.pendingDefaultRegistrationWarnings));
|
|
221
225
|
}
|
|
222
226
|
this.pendingDefaultRegistrationWarnings = undefined;
|
|
223
227
|
}
|
|
@@ -226,7 +230,7 @@ export class PreferenceSchemaServiceImpl implements PreferenceSchemaService {
|
|
|
226
230
|
if (this.pendingDefaultRegistrationWarnings) {
|
|
227
231
|
this.pendingDefaultRegistrationWarnings.add(key);
|
|
228
232
|
} else {
|
|
229
|
-
|
|
233
|
+
this.logger.warn(`Trying to register default override for non-existent preference: ${key}`);
|
|
230
234
|
}
|
|
231
235
|
}
|
|
232
236
|
|
|
@@ -236,7 +240,7 @@ export class PreferenceSchemaServiceImpl implements PreferenceSchemaService {
|
|
|
236
240
|
if (!property) {
|
|
237
241
|
this.logDefaultRegistrationWarning(key);
|
|
238
242
|
} else if (!property.overridable && overrideIdentifier) {
|
|
239
|
-
|
|
243
|
+
this.logger.warn(`Trying to register default override for identifier ${overrideIdentifier} for non-overridable preference: ${key}`);
|
|
240
244
|
}
|
|
241
245
|
|
|
242
246
|
let overrides = this.defaultOverrides.get(key);
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
18
18
|
|
|
19
19
|
import { JSONValue } from '@lumino/coreutils';
|
|
20
|
-
import { inject, injectable, postConstruct } from 'inversify';
|
|
20
|
+
import { inject, injectable, postConstruct, named } from 'inversify';
|
|
21
21
|
import { Disposable, DisposableCollection } from '../disposable';
|
|
22
22
|
import { Emitter, Event } from '../event';
|
|
23
23
|
import { Deferred } from '../promise-util';
|
|
@@ -29,6 +29,7 @@ import { PreferenceScope } from './preference-scope';
|
|
|
29
29
|
import { PreferenceConfigurations } from './preference-configurations';
|
|
30
30
|
import { deepFreeze } from '../objects';
|
|
31
31
|
import { unreachable } from '../types';
|
|
32
|
+
import { ILogger } from '../logger';
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Representation of a preference change. A preference value can be set to `undefined` for a specific scope.
|
|
@@ -296,6 +297,9 @@ export class PreferenceServiceImpl implements PreferenceService {
|
|
|
296
297
|
@inject(PreferenceLanguageOverrideService)
|
|
297
298
|
protected readonly preferenceOverrideService: PreferenceLanguageOverrideService;
|
|
298
299
|
|
|
300
|
+
@inject(ILogger) @named('core:PreferenceServiceImpl')
|
|
301
|
+
protected readonly logger: ILogger;
|
|
302
|
+
|
|
299
303
|
protected readonly preferenceProviders = new Map<PreferenceScope, PreferenceProvider>();
|
|
300
304
|
|
|
301
305
|
protected async initializeProviders(): Promise<void> {
|
|
@@ -309,7 +313,7 @@ export class PreferenceServiceImpl implements PreferenceService {
|
|
|
309
313
|
));
|
|
310
314
|
await provider.ready;
|
|
311
315
|
} else {
|
|
312
|
-
|
|
316
|
+
this.logger.warn(`No preference provider bound for ${PreferenceScope[scope]}`);
|
|
313
317
|
}
|
|
314
318
|
}
|
|
315
319
|
this._ready.resolve();
|
|
@@ -139,7 +139,7 @@ export class ElectronMenuContribution extends BrowserMenuBarContribution impleme
|
|
|
139
139
|
const disposeHandler = window.electronTheiaCore.onWindowEvent('focus', () => {
|
|
140
140
|
this.setMenu(app);
|
|
141
141
|
});
|
|
142
|
-
window.addEventListener('
|
|
142
|
+
window.addEventListener('pagehide', () => disposeHandler.dispose());
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
protected attachMenuBarVisibilityListener(): void {
|
|
@@ -61,7 +61,7 @@ export class ElectronSecondaryWindowService extends DefaultSecondaryWindowServic
|
|
|
61
61
|
for (let i = this.secondaryWindows.length - 1; i >= 0; i--) {
|
|
62
62
|
const windowClosed = new Deferred<void>();
|
|
63
63
|
const win = this.secondaryWindows[i];
|
|
64
|
-
win.addEventListener('
|
|
64
|
+
win.addEventListener('pagehide', () => {
|
|
65
65
|
windowClosed.resolve();
|
|
66
66
|
});
|
|
67
67
|
promises.push(windowClosed.promise);
|
|
@@ -89,9 +89,9 @@ export class ElectronWindowService extends DefaultWindowService {
|
|
|
89
89
|
}
|
|
90
90
|
return willShutDown;
|
|
91
91
|
});
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
// Intentionally not calling `super`: in Electron the close request handler above takes the role of
|
|
93
|
+
// `beforeunload`, and there is no back/forward cache, so only the `pagehide` listener is shared.
|
|
94
|
+
this.registerPageHideListener();
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
/**
|
|
@@ -142,7 +142,8 @@ describe('BackendApplication', () => {
|
|
|
142
142
|
const container = createTestContainer();
|
|
143
143
|
const cleanupError = new Error('cleanup boom');
|
|
144
144
|
sandbox.stub(container, 'unbindAllAsync').rejects(cleanupError);
|
|
145
|
-
const
|
|
145
|
+
const mockLogger = container.get(ILogger) as ILogger;
|
|
146
|
+
const warnStub = sandbox.stub(mockLogger, 'warn');
|
|
146
147
|
|
|
147
148
|
const app = container.get(TestBackendApplication);
|
|
148
149
|
|
|
@@ -157,7 +158,8 @@ describe('BackendApplication', () => {
|
|
|
157
158
|
const clock = sandbox.useFakeTimers();
|
|
158
159
|
const container = createTestContainer();
|
|
159
160
|
sandbox.stub(container, 'unbindAllAsync').returns(new Promise<void>(() => { /* never */ }));
|
|
160
|
-
const
|
|
161
|
+
const mockLogger = container.get(ILogger) as ILogger;
|
|
162
|
+
const warnStub = sandbox.stub(mockLogger, 'warn');
|
|
161
163
|
|
|
162
164
|
const app = container.get(TestBackendApplication);
|
|
163
165
|
|
|
@@ -220,7 +222,8 @@ describe('BackendApplication', () => {
|
|
|
220
222
|
onStop: () => new Promise<void>(() => { /* never */ })
|
|
221
223
|
});
|
|
222
224
|
const unbindSpy = sandbox.spy(container, 'unbindAllAsync');
|
|
223
|
-
const
|
|
225
|
+
const mockLogger = container.get(ILogger) as ILogger;
|
|
226
|
+
const warnStub = sandbox.stub(mockLogger, 'warn');
|
|
224
227
|
|
|
225
228
|
const app = container.get(TestBackendApplication);
|
|
226
229
|
const shutdownPromise = app.invokeGracefulShutdown();
|
|
@@ -226,7 +226,7 @@ export class BackendApplication {
|
|
|
226
226
|
|
|
227
227
|
// Handles `Ctrl+C` and `kill pid`. Delegates to gracefulShutdown so that
|
|
228
228
|
// root-scoped singletons get their @preDestroy hooks invoked before exit.
|
|
229
|
-
const onSignal = () => { this.gracefulShutdown().catch(err =>
|
|
229
|
+
const onSignal = () => { this.gracefulShutdown().catch(err => this.logger.error(err)); };
|
|
230
230
|
process.on('SIGINT', onSignal);
|
|
231
231
|
process.on('SIGTERM', onSignal);
|
|
232
232
|
}
|
|
@@ -407,7 +407,7 @@ export class BackendApplication {
|
|
|
407
407
|
]);
|
|
408
408
|
} catch (err) {
|
|
409
409
|
const message = err instanceof Error ? err.message : String(err);
|
|
410
|
-
|
|
410
|
+
this.logger.warn(`Backend contributions cleanup failed: ${message}`);
|
|
411
411
|
}
|
|
412
412
|
|
|
413
413
|
try {
|
|
@@ -417,7 +417,7 @@ export class BackendApplication {
|
|
|
417
417
|
]);
|
|
418
418
|
} catch (err) {
|
|
419
419
|
const message = err instanceof Error ? err.message : String(err);
|
|
420
|
-
|
|
420
|
+
this.logger.warn(`Backend root container cleanup failed: ${message}`);
|
|
421
421
|
}
|
|
422
422
|
|
|
423
423
|
process.exit(1);
|
|
@@ -428,7 +428,7 @@ export class BackendApplication {
|
|
|
428
428
|
return;
|
|
429
429
|
}
|
|
430
430
|
this.stoppedContributions = true;
|
|
431
|
-
|
|
431
|
+
this.logger.info('>>> Stopping backend contributions...');
|
|
432
432
|
// The `async` wrapper converts a synchronous throw inside a non-async
|
|
433
433
|
// contribution's `onStop` into a rejected promise so the per-contribution
|
|
434
434
|
// try/catch can handle it; otherwise `Promise.all` would abort.
|
|
@@ -441,7 +441,7 @@ export class BackendApplication {
|
|
|
441
441
|
}
|
|
442
442
|
}
|
|
443
443
|
}));
|
|
444
|
-
|
|
444
|
+
this.logger.info('<<< All backend contributions have been stopped.');
|
|
445
445
|
}
|
|
446
446
|
|
|
447
447
|
protected onStop(): void {
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
|
+
// This is the console logger itself, so it cannot log through an ILogger.
|
|
18
|
+
/* eslint-disable @theia/named-logger-check */
|
|
19
|
+
|
|
17
20
|
import { inject, injectable, postConstruct } from 'inversify';
|
|
18
21
|
import { LoggerWatcher } from '../common/logger-watcher';
|
|
19
22
|
import { LogLevelCliContribution } from './logger-cli-contribution';
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import { join } from 'path';
|
|
18
18
|
import { homedir } from 'os';
|
|
19
|
-
import { injectable } from 'inversify';
|
|
19
|
+
import { injectable, inject, named } from 'inversify';
|
|
20
20
|
import * as drivelist from 'drivelist';
|
|
21
21
|
import { pathExists, mkdir } from 'fs-extra';
|
|
22
22
|
import { EnvVariable, EnvVariablesServer } from '../../common/env-variables';
|
|
@@ -24,6 +24,7 @@ import { isWindows } from '../../common/os';
|
|
|
24
24
|
import { FileUri } from '../../common/file-uri';
|
|
25
25
|
import { BackendApplicationPath } from '../backend-application';
|
|
26
26
|
import { BackendApplicationConfigProvider } from '../backend-application-config-provider';
|
|
27
|
+
import { ILogger } from '../../common/logger';
|
|
27
28
|
|
|
28
29
|
@injectable()
|
|
29
30
|
export class EnvVariablesServerImpl implements EnvVariablesServer {
|
|
@@ -33,9 +34,12 @@ export class EnvVariablesServerImpl implements EnvVariablesServer {
|
|
|
33
34
|
protected readonly configDirUri: Promise<string>;
|
|
34
35
|
protected readonly pathExistenceCache: { [key: string]: boolean } = {};
|
|
35
36
|
|
|
37
|
+
@inject(ILogger) @named('core:EnvVariablesServerImpl')
|
|
38
|
+
protected readonly logger: ILogger;
|
|
39
|
+
|
|
36
40
|
constructor() {
|
|
37
41
|
this.configDirUri = this.createConfigDirUri();
|
|
38
|
-
this.configDirUri.then(configDirUri =>
|
|
42
|
+
this.configDirUri.then(configDirUri => this.logger.info(`Configuration directory URI: '${configDirUri}'`));
|
|
39
43
|
const prEnv = process.env;
|
|
40
44
|
Object.keys(prEnv).forEach((key: string) => {
|
|
41
45
|
let keyName = key;
|
|
@@ -109,19 +109,24 @@ describe('BrowserConnectionTokenBackendContribution', () => {
|
|
|
109
109
|
interface MockResponse {
|
|
110
110
|
cookieCalls: CookieCall[];
|
|
111
111
|
nextCalled: boolean;
|
|
112
|
+
requestHeaders: Record<string, string | undefined>;
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
function callMiddleware(cookieValue?: string): MockResponse {
|
|
115
|
+
function callMiddleware(cookieValue?: string, conditionalHeaders = false): MockResponse {
|
|
115
116
|
const contribution = createContribution();
|
|
116
|
-
const result: MockResponse = { cookieCalls: [], nextCalled: false };
|
|
117
|
-
|
|
118
117
|
const req = {
|
|
119
118
|
headers: {} as Record<string, string | undefined>,
|
|
120
119
|
socket: { remoteAddress: '127.0.0.1' }
|
|
121
120
|
};
|
|
121
|
+
const result: MockResponse = { cookieCalls: [], nextCalled: false, requestHeaders: req.headers };
|
|
122
|
+
|
|
122
123
|
if (cookieValue !== undefined) {
|
|
123
124
|
req.headers.cookie = `${BROWSER_TOKEN_COOKIE_NAME}=${cookieValue}`;
|
|
124
125
|
}
|
|
126
|
+
if (conditionalHeaders) {
|
|
127
|
+
req.headers['if-none-match'] = 'W/"etag"';
|
|
128
|
+
req.headers['if-modified-since'] = 'Thu, 01 Jan 2026 00:00:00 GMT';
|
|
129
|
+
}
|
|
125
130
|
|
|
126
131
|
const res = {
|
|
127
132
|
cookie: (name: string, value: string, options: Record<string, unknown>) => {
|
|
@@ -164,6 +169,28 @@ describe('BrowserConnectionTokenBackendContribution', () => {
|
|
|
164
169
|
expect(result.cookieCalls).to.have.length(1);
|
|
165
170
|
expect(result.cookieCalls[0].value).to.equal(token.value);
|
|
166
171
|
});
|
|
172
|
+
|
|
173
|
+
it('should drop conditional request headers when re-issuing the cookie (stale token)', () => {
|
|
174
|
+
const result = callMiddleware('stale-token-from-old-server', true);
|
|
175
|
+
expect(result.nextCalled).to.be.true;
|
|
176
|
+
expect(result.cookieCalls).to.have.length(1);
|
|
177
|
+
expect(result.requestHeaders['if-none-match']).to.be.undefined;
|
|
178
|
+
expect(result.requestHeaders['if-modified-since']).to.be.undefined;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should drop conditional request headers when issuing the cookie (no cookie)', () => {
|
|
182
|
+
const result = callMiddleware(undefined, true);
|
|
183
|
+
expect(result.cookieCalls).to.have.length(1);
|
|
184
|
+
expect(result.requestHeaders['if-none-match']).to.be.undefined;
|
|
185
|
+
expect(result.requestHeaders['if-modified-since']).to.be.undefined;
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should keep conditional request headers when the cookie is valid', () => {
|
|
189
|
+
const result = callMiddleware(token.value, true);
|
|
190
|
+
expect(result.cookieCalls).to.have.length(0);
|
|
191
|
+
expect(result.requestHeaders['if-none-match']).to.equal('W/"etag"');
|
|
192
|
+
expect(result.requestHeaders['if-modified-since']).to.equal('Thu, 01 Jan 2026 00:00:00 GMT');
|
|
193
|
+
});
|
|
167
194
|
});
|
|
168
195
|
|
|
169
196
|
describe('validateRequest (HTTP token enforcement)', () => {
|
|
@@ -18,12 +18,13 @@ import * as cookie from 'cookie';
|
|
|
18
18
|
import * as crypto from 'crypto';
|
|
19
19
|
import * as http from 'http';
|
|
20
20
|
import express = require('express');
|
|
21
|
-
import { inject, injectable } from 'inversify';
|
|
21
|
+
import { inject, injectable, named } from 'inversify';
|
|
22
22
|
import { environment } from '../../common/index';
|
|
23
23
|
import { MaybePromise } from '../../common';
|
|
24
24
|
import { BackendApplicationContribution, EarlyExpressMiddleware } from '../backend-application';
|
|
25
25
|
import { WsRequestValidatorContribution } from '../ws-request-validators';
|
|
26
26
|
import { generateUuid } from '../../common/uuid';
|
|
27
|
+
import { ILogger } from '../../common/logger';
|
|
27
28
|
|
|
28
29
|
export const BrowserConnectionToken = Symbol('BrowserConnectionToken');
|
|
29
30
|
|
|
@@ -79,6 +80,9 @@ export class BrowserConnectionTokenBackendContribution implements BackendApplica
|
|
|
79
80
|
@inject(EarlyExpressMiddleware)
|
|
80
81
|
protected readonly earlyMiddleware: EarlyExpressMiddleware;
|
|
81
82
|
|
|
83
|
+
@inject(ILogger) @named('core:BrowserConnectionTokenBackendContribution')
|
|
84
|
+
protected readonly logger: ILogger;
|
|
85
|
+
|
|
82
86
|
/**
|
|
83
87
|
* Register the cookie middleware during `initialize()` via `EarlyExpressMiddleware`
|
|
84
88
|
* so it runs before `express.static()` (which is registered later during `configure()`).
|
|
@@ -138,6 +142,13 @@ export class BrowserConnectionTokenBackendContribution implements BackendApplica
|
|
|
138
142
|
sameSite: 'strict',
|
|
139
143
|
path: '/'
|
|
140
144
|
});
|
|
145
|
+
// The static handlers would answer a revalidation of an unchanged page with a 304,
|
|
146
|
+
// and reverse proxies commonly drop Set-Cookie from 304 replies or answer the
|
|
147
|
+
// revalidation from their own cache. The browser would then keep the stale token
|
|
148
|
+
// and every WebSocket handshake would be rejected, with no way to recover short of
|
|
149
|
+
// a cache-bypassing hard reload. Force a full 200 so the cookie reaches the browser.
|
|
150
|
+
delete req.headers['if-none-match'];
|
|
151
|
+
delete req.headers['if-modified-since'];
|
|
141
152
|
}
|
|
142
153
|
next();
|
|
143
154
|
}
|
|
@@ -156,7 +167,7 @@ export class BrowserConnectionTokenBackendContribution implements BackendApplica
|
|
|
156
167
|
const expected = Buffer.from(this.browserConnectionToken.value, 'utf8');
|
|
157
168
|
return received.byteLength === expected.byteLength && crypto.timingSafeEqual(received, expected);
|
|
158
169
|
} catch (error) {
|
|
159
|
-
|
|
170
|
+
this.logger.error(error);
|
|
160
171
|
}
|
|
161
172
|
return false;
|
|
162
173
|
}
|