Skip to content

Commit 44ac854

Browse files
authored
chore(connection-storage): split connection storage between processes COMPASS-7078 (#4704)
1 parent b4a1b5a commit 44ac854

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+938
-754
lines changed

package-lock.json

Lines changed: 5 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/atlas-service/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"dependencies": {
7070
"@mongodb-js/compass-components": "^1.11.0",
7171
"@mongodb-js/compass-logging": "^1.1.7",
72+
"@mongodb-js/compass-utils": "^0.3.1",
7273
"@mongodb-js/devtools-connect": "^2.4.0",
7374
"@mongodb-js/oidc-plugin": "^0.3.0",
7475
"electron": "^23.3.12",

packages/atlas-service/src/main.ts

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ import fetch from 'node-fetch';
1212
import type { SimplifiedSchema } from 'mongodb-schema';
1313
import type { Document } from 'mongodb';
1414
import type { AIQuery, IntrospectInfo, Token, UserInfo } from './util';
15-
import { broadcast, ipcExpose } from './util';
15+
import {
16+
broadcast,
17+
ipcExpose,
18+
throwIfAborted,
19+
} from '@mongodb-js/compass-utils';
1620
import {
1721
createLoggerAndTelemetry,
1822
mongoLogId,
@@ -335,10 +339,7 @@ export class AtlasService {
335339
static async isAuthenticated({
336340
signal,
337341
}: { signal?: AbortSignal } = {}): Promise<boolean> {
338-
if (signal?.aborted) {
339-
const err = signal.reason ?? new Error('This operation was aborted.');
340-
throw err;
341-
}
342+
throwIfAborted(signal);
342343
if (!this.token) {
343344
return false;
344345
}
@@ -356,10 +357,7 @@ export class AtlasService {
356357
return this.signInPromise;
357358
}
358359
try {
359-
if (signal?.aborted) {
360-
const err = signal.reason ?? new Error('This operation was aborted.');
361-
throw err;
362-
}
360+
throwIfAborted(signal);
363361

364362
this.signInPromise = (async () => {
365363
log.info(mongoLogId(1_001_000_218), 'AtlasService', 'Starting sign in');
@@ -402,10 +400,7 @@ export class AtlasService {
402400
static async getUserInfo({
403401
signal,
404402
}: { signal?: AbortSignal } = {}): Promise<UserInfo> {
405-
if (signal?.aborted) {
406-
const err = signal.reason ?? new Error('This operation was aborted.');
407-
throw err;
408-
}
403+
throwIfAborted(signal);
409404
await this.maybeWaitForToken({ signal });
410405
const res = await this.fetch(`${this.issuer}/v1/userinfo`, {
411406
headers: {
@@ -421,10 +416,7 @@ export class AtlasService {
421416
}
422417

423418
static async introspect({ signal }: { signal?: AbortSignal } = {}) {
424-
if (signal?.aborted) {
425-
const err = signal.reason ?? new Error('This operation was aborted.');
426-
throw err;
427-
}
419+
throwIfAborted(signal);
428420

429421
const url = new URL(`${this.issuer}/v1/introspect`);
430422
url.searchParams.set('client_id', this.clientId);
@@ -463,10 +455,7 @@ export class AtlasService {
463455
sampleDocuments?: Document[];
464456
signal?: AbortSignal;
465457
}) {
466-
if (signal?.aborted) {
467-
const err = signal.reason ?? new Error('This operation was aborted.');
468-
throw err;
469-
}
458+
throwIfAborted(signal);
470459

471460
let msgBody = JSON.stringify({
472461
userInput,

packages/atlas-service/src/renderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { AtlasService as AtlasServiceMain } from './main';
2-
import { ipcInvoke } from './util';
2+
import { ipcInvoke } from '@mongodb-js/compass-utils';
33
import {
44
signInWithModalPrompt,
55
tokenRefreshFailed,

packages/atlas-service/src/util.ts

Lines changed: 0 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { BrowserWindow, ipcMain, ipcRenderer } from 'electron';
21
import type * as plugin from '@mongodb-js/oidc-plugin';
32

43
export type UserInfo = {
@@ -17,159 +16,3 @@ export type AIQuery = {
1716
query?: unknown;
1817
};
1918
};
20-
21-
type SerializedError = { $$error: Error & { statusCode?: number } };
22-
23-
// We are serializing errors to get a better error shape on the other end, ipc
24-
// will only preserve message from the original error. See https://github.com/electron/electron/issues/24427
25-
function serializeErrorForIpc(err: any): SerializedError {
26-
return {
27-
$$error: {
28-
name: err.name,
29-
message: err.message,
30-
statusCode: err.statusCode,
31-
stack: err.stack,
32-
},
33-
};
34-
}
35-
36-
function deserializeErrorFromIpc({ $$error: err }: SerializedError) {
37-
const e = new Error(err.message);
38-
e.name = err.name;
39-
e.stack = err.stack;
40-
(e as any).statusCode = err.statusCode;
41-
return e;
42-
}
43-
44-
function isSerializedError(err: any): err is { $$error: Error } {
45-
return err !== null && typeof err === 'object' && !!err.$$error;
46-
}
47-
48-
// Exported for testing purposes
49-
export const ControllerMap = new Map<string, AbortController>();
50-
51-
let cId = 0;
52-
53-
let setup = false;
54-
55-
export function setupSignalHandler(
56-
_ipcMain: Pick<typeof ipcMain, 'handle'> = ipcMain,
57-
forceSetup = false
58-
) {
59-
if (!forceSetup && setup) {
60-
return;
61-
}
62-
63-
setup = true;
64-
65-
_ipcMain.handle('ipcHandlerInvoke', (_evt, id: string) => {
66-
ControllerMap.set(id, new AbortController());
67-
});
68-
69-
_ipcMain.handle('ipcHandlerAborted', (_evt, id: string) => {
70-
ControllerMap.get(id)?.abort();
71-
});
72-
}
73-
74-
type PickByValue<T, K> = Pick<
75-
T,
76-
{ [k in keyof T]: T[k] extends K ? k : never }[keyof T]
77-
>;
78-
79-
export function ipcExpose<T>(
80-
serviceName: string,
81-
obj: T,
82-
methodNames: Extract<
83-
keyof PickByValue<T, (options: any) => Promise<any>>,
84-
string
85-
>[],
86-
_ipcMain: Pick<typeof ipcMain, 'handle'> = ipcMain,
87-
_forceSetup = false
88-
) {
89-
setupSignalHandler(_ipcMain, _forceSetup);
90-
91-
for (const name of methodNames) {
92-
const channel = `${serviceName}.${name}`;
93-
_ipcMain.handle(
94-
channel,
95-
async (
96-
_evt,
97-
{ signal, ...rest }: { signal: string } & Record<string, unknown>
98-
) => {
99-
try {
100-
const controller = ControllerMap.get(signal);
101-
return await (obj[name] as (...args: any[]) => any).call(obj, {
102-
signal: controller?.signal,
103-
...rest,
104-
});
105-
} catch (err) {
106-
return serializeErrorForIpc(err);
107-
} finally {
108-
ControllerMap.delete(signal);
109-
}
110-
}
111-
);
112-
}
113-
}
114-
115-
export function ipcInvoke<
116-
T,
117-
K extends Extract<
118-
keyof PickByValue<T, (options: any) => Promise<any>>,
119-
string
120-
>
121-
>(
122-
serviceName: string,
123-
methodNames: K[],
124-
_ipcRenderer: Pick<typeof ipcRenderer, 'invoke'> = ipcRenderer
125-
) {
126-
return Object.fromEntries(
127-
methodNames.map((name) => {
128-
const channel = `${serviceName}.${name}`;
129-
const signalId = `${channel}:${++cId}`;
130-
return [
131-
name,
132-
async ({
133-
signal,
134-
...rest
135-
}: { signal?: AbortSignal } & Record<string, unknown> = {}) => {
136-
await _ipcRenderer.invoke('ipcHandlerInvoke', signalId);
137-
const onAbort = () => {
138-
return _ipcRenderer.invoke('ipcHandlerAborted', signalId);
139-
};
140-
// If signal is already aborted, make sure that handler will see it
141-
// when it runs, otherwise just set up abort listener to communicate
142-
// this to main process
143-
if (signal?.aborted) {
144-
await onAbort();
145-
} else {
146-
signal?.addEventListener(
147-
'abort',
148-
() => {
149-
void onAbort();
150-
},
151-
{ once: true }
152-
);
153-
}
154-
const res = await _ipcRenderer.invoke(`${serviceName}.${name}`, {
155-
// We replace this with a matched signal on the other side, this
156-
// is mostly for testing / debugging purposes
157-
signal: signalId,
158-
...rest,
159-
});
160-
if (isSerializedError(res)) {
161-
throw deserializeErrorFromIpc(res);
162-
}
163-
return res;
164-
},
165-
];
166-
})
167-
) as Pick<T, K>;
168-
}
169-
170-
export function broadcast(channel: string, ...args: any[]) {
171-
// We might not be in electron environment
172-
BrowserWindow?.getAllWindows().forEach((browserWindow) => {
173-
browserWindow.webContents.send(channel, ...args);
174-
});
175-
}

packages/compass-connection-import-export/src/components/export-modal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { SelectTable } from './select-table';
1313
import type { ImportExportResult } from '../hooks/common';
1414
import { useOpenModalThroughIpc } from '../hooks/common';
1515
import { useExportConnections } from '../hooks/use-export';
16-
import type { ConnectionInfo } from '@mongodb-js/connection-storage';
16+
import type { ConnectionInfo } from '@mongodb-js/connection-storage/renderer';
1717
import { usePreference } from 'compass-preferences-model';
1818

1919
const TOAST_TIMEOUT_MS = 5000;

packages/compass-connection-import-export/src/components/import-modal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { SelectTable } from './select-table';
1414
import type { ImportExportResult } from '../hooks/common';
1515
import { useOpenModalThroughIpc } from '../hooks/common';
1616
import { useImportConnections } from '../hooks/use-import';
17-
import type { ConnectionInfo } from '@mongodb-js/connection-storage';
17+
import type { ConnectionInfo } from '@mongodb-js/connection-storage/renderer';
1818

1919
const TOAST_TIMEOUT_MS = 5000;
2020

packages/compass-connection-import-export/src/hooks/common.spec.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import sinon from 'sinon';
33
import {
44
useImportExportConnectionsCommon,
55
COMMON_INITIAL_STATE,
6-
makeConnectionInfoFilter,
76
useOpenModalThroughIpc,
87
} from './common';
98
import { renderHook, act } from '@testing-library/react-hooks';
@@ -56,18 +55,6 @@ describe('common utilities', function () {
5655
});
5756
});
5857

59-
describe('makeConnectionInfoFilter', function () {
60-
it('creates a filter function from a list of selected connections', function () {
61-
const filter = makeConnectionInfoFilter([
62-
{ id: 'id1', name: 'name1', selected: true },
63-
{ id: 'id2', name: 'name2', selected: false },
64-
]);
65-
expect(filter({ id: 'id1' })).to.equal(true);
66-
expect(filter({ id: 'id2' })).to.equal(false);
67-
expect(filter({ id: 'none' })).to.equal(false);
68-
});
69-
});
70-
7158
describe('useOpenModalThroughIpc', function () {
7259
it("allows modifying a modal's state through ipc events", function () {
7360
const fakeIpc = new EventEmitter();

0 commit comments

Comments
 (0)