Skip to content

Commit 9ce11d7

Browse files
committed
chore: support running multiple clients with same client id
1 parent 0b32cb0 commit 9ce11d7

19 files changed

Lines changed: 206 additions & 397 deletions

packages/sdk/electron/__tests__/ElectronClient.ipcMain.test.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ import type {
66
LDEvaluationDetail,
77
LDEvaluationDetailTyped,
88
LDIdentifyOptions,
9-
LDLogger,
109
} from '@launchdarkly/js-client-sdk-common';
1110

11+
import { deriveNamespace } from '../src/deriveNamespace';
1212
import { ElectronClient } from '../src/ElectronClient';
1313
import { getIPCChannelName } from '../src/ElectronIPC';
1414
import ElectronCrypto from '../src/platform/ElectronCrypto';
1515
import ElectronEncoding from '../src/platform/ElectronEncoding';
1616
import ElectronInfo from '../src/platform/ElectronInfo';
17+
import { createMockLogger } from './testHelpers';
1718

1819
type MockIpcMain = IpcMain & {
1920
getHandler: (eventName: string) => Function | undefined;
@@ -63,7 +64,7 @@ const mockPort: MockPort = {
6364
};
6465

6566
const getEventName = (baseName: Parameters<typeof getIPCChannelName>[1]) =>
66-
getIPCChannelName(clientSideId, baseName);
67+
getIPCChannelName(deriveNamespace(clientSideId), baseName);
6768

6869
const DEFAULT_INITIAL_CONTEXT = { kind: 'user' as const, key: 'test-user' };
6970

@@ -72,12 +73,7 @@ beforeEach(() => {
7273
});
7374

7475
describe('given an initialized ElectronClient', () => {
75-
const logger: LDLogger = {
76-
debug: jest.fn(),
77-
info: jest.fn(),
78-
warn: jest.fn(),
79-
error: jest.fn(),
80-
};
76+
const logger = createMockLogger();
8177

8278
const client = new ElectronClient(clientSideId, DEFAULT_INITIAL_CONTEXT, {
8379
initialConnectionMode: 'offline',
@@ -492,12 +488,7 @@ describe('given an initialized ElectronClient', () => {
492488
});
493489

494490
describe('close()', () => {
495-
const logger: LDLogger = {
496-
debug: jest.fn(),
497-
info: jest.fn(),
498-
warn: jest.fn(),
499-
error: jest.fn(),
500-
};
491+
const logger = createMockLogger();
501492

502493
it('removes all ipcMain listeners and handlers for the client so channels are no longer registered', async () => {
503494
const client = new ElectronClient(clientSideId, DEFAULT_INITIAL_CONTEXT, {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { deriveNamespace } from '../src/deriveNamespace';
2+
import { getIPCChannelName } from '../src/ElectronIPC';
3+
4+
it('derives namespace from credential alone', () => {
5+
expect(deriveNamespace('mob-abc-123')).toBe('mob-abc-123');
6+
});
7+
8+
it('derives namespace from credential with custom namespace', () => {
9+
expect(deriveNamespace('mob-abc-123', 'my-namespace')).toBe('my-namespace:mob-abc-123');
10+
});
11+
12+
it('produces different namespaces with and without custom namespace', () => {
13+
const credential = 'mob-abc-123';
14+
expect(deriveNamespace(credential)).not.toBe(deriveNamespace(credential, 'ns'));
15+
});
16+
17+
it('produces different namespaces for different custom namespaces', () => {
18+
const credential = 'mob-abc-123';
19+
expect(deriveNamespace(credential, 'ns-a')).not.toBe(deriveNamespace(credential, 'ns-b'));
20+
});
21+
22+
it('undefined namespace equals no namespace', () => {
23+
const credential = 'mob-abc-123';
24+
expect(deriveNamespace(credential, undefined)).toBe(deriveNamespace(credential));
25+
});
26+
27+
it('builds IPC channel names', () => {
28+
expect(getIPCChannelName('ns', 'allFlags')).toBe('ld:ns:allFlags');
29+
});

packages/sdk/electron/__tests__/bridge/LDClientBridge.test.ts

Lines changed: 20 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import { ipcRenderer } from 'electron';
22

33
import '../../src/bridge';
44
import type { LDClientBridge } from '../../src/bridge/LDClientBridge';
5-
import type { LDContext, LDEvaluationDetail, LDEvaluationDetailTyped } from '../../src/index';
5+
import { deriveNamespace } from '../../src/deriveNamespace';
6+
import type { LDContext } from '../../src/index';
67

78
const clientSideId = 'client-side-id';
8-
let ldClientBridge: (clientSideId: string) => LDClientBridge;
9+
let ldClientBridge: (namespace: string) => LDClientBridge;
910

1011
jest.mock('electron', () => ({
1112
contextBridge: {
@@ -42,7 +43,7 @@ globalThis.MessageChannel = jest.fn().mockImplementation(() => ({
4243
port2: port2Mock,
4344
}));
4445

45-
const getEventName = (baseName: string) => `ld:${clientSideId}:${baseName}`;
46+
const getEventName = (baseName: string) => `ld:${deriveNamespace(clientSideId)}:${baseName}`;
4647

4748
beforeEach(() => {
4849
jest.clearAllMocks();
@@ -59,7 +60,7 @@ describe('given a registered LDClientBridge', () => {
5960
let bridge: LDClientBridge;
6061

6162
beforeEach(() => {
62-
bridge = ldClientBridge(clientSideId);
63+
bridge = ldClientBridge(deriveNamespace(clientSideId));
6364
});
6465

6566
it('passes allFlags() call through to ipcRenderer', () => {
@@ -72,37 +73,28 @@ describe('given a registered LDClientBridge', () => {
7273
expect(result).toEqual({ flag1: true });
7374
});
7475

75-
it('passes boolVariation() call through to ipcRenderer', () => {
76-
(ipcRenderer.sendSync as jest.Mock).mockReturnValueOnce(true);
77-
78-
const result = bridge.boolVariation('flag1', false);
79-
80-
expect(ipcRenderer.sendSync).toHaveBeenCalledTimes(1);
81-
expect(ipcRenderer.sendSync).toHaveBeenNthCalledWith(
82-
1,
83-
getEventName('boolVariation'),
84-
'flag1',
85-
false,
86-
);
87-
expect(result).toEqual(true);
88-
});
89-
90-
it('passes boolVariationDetail() call through to ipcRenderer', () => {
91-
const expected: LDEvaluationDetailTyped<boolean> = {
92-
value: true,
93-
reason: { kind: 'RULE_MATCH' },
94-
};
95-
76+
it.each([
77+
['boolVariation', true, false],
78+
['boolVariationDetail', { value: true, reason: { kind: 'RULE_MATCH' } }, false],
79+
['numberVariation', 1234.5, 0],
80+
['numberVariationDetail', { value: 1234.5, reason: { kind: 'RULE_MATCH' } }, 0],
81+
['stringVariation', 'value', ''],
82+
['stringVariationDetail', { value: 'value', reason: { kind: 'RULE_MATCH' } }, ''],
83+
['jsonVariation', { key1: 'value1' }, {}],
84+
['jsonVariationDetail', { value: { key1: 'value1' }, reason: { kind: 'RULE_MATCH' } }, {}],
85+
['variation', true, false],
86+
['variationDetail', { value: true, reason: { kind: 'RULE_MATCH' } }, false],
87+
])('passes %s() call through to ipcRenderer', (method, expected, defaultValue) => {
9688
(ipcRenderer.sendSync as jest.Mock).mockReturnValueOnce(expected);
9789

98-
const result = bridge.boolVariationDetail('flag1', false);
90+
const result = (bridge as any)[method]('flag1', defaultValue);
9991

10092
expect(ipcRenderer.sendSync).toHaveBeenCalledTimes(1);
10193
expect(ipcRenderer.sendSync).toHaveBeenNthCalledWith(
10294
1,
103-
getEventName('boolVariationDetail'),
95+
getEventName(method),
10496
'flag1',
105-
false,
97+
defaultValue,
10698
);
10799
expect(result).toEqual(expected);
108100
});
@@ -140,113 +132,6 @@ describe('given a registered LDClientBridge', () => {
140132
});
141133
});
142134

143-
it('passes jsonVariation() call through to ipcRenderer', () => {
144-
const expected = { key1: 'value1', key2: true };
145-
146-
(ipcRenderer.sendSync as jest.Mock).mockReturnValueOnce(expected);
147-
148-
const result = bridge.jsonVariation('flag1', {});
149-
150-
expect(ipcRenderer.sendSync).toHaveBeenCalledTimes(1);
151-
expect(ipcRenderer.sendSync).toHaveBeenNthCalledWith(
152-
1,
153-
getEventName('jsonVariation'),
154-
'flag1',
155-
{},
156-
);
157-
expect(result).toEqual(expected);
158-
});
159-
160-
it('passes jsonVariationDetail() call through to ipcRenderer', () => {
161-
const expected: LDEvaluationDetailTyped<unknown> = {
162-
value: { key1: 'value1', key2: true },
163-
reason: { kind: 'RULE_MATCH' },
164-
};
165-
166-
(ipcRenderer.sendSync as jest.Mock).mockReturnValueOnce(expected);
167-
168-
const result = bridge.jsonVariationDetail('flag1', {});
169-
170-
expect(ipcRenderer.sendSync).toHaveBeenCalledTimes(1);
171-
expect(ipcRenderer.sendSync).toHaveBeenNthCalledWith(
172-
1,
173-
getEventName('jsonVariationDetail'),
174-
'flag1',
175-
{},
176-
);
177-
expect(result).toEqual(expected);
178-
});
179-
180-
it('passes numberVariation() call through to ipcRenderer', () => {
181-
(ipcRenderer.sendSync as jest.Mock).mockReturnValueOnce(1234.5);
182-
183-
const result = bridge.numberVariation('flag1', 0);
184-
185-
expect(ipcRenderer.sendSync).toHaveBeenCalledTimes(1);
186-
expect(ipcRenderer.sendSync).toHaveBeenNthCalledWith(
187-
1,
188-
getEventName('numberVariation'),
189-
'flag1',
190-
0,
191-
);
192-
expect(result).toEqual(1234.5);
193-
});
194-
195-
it('passes numberVariationDetail() call through to ipcRenderer', () => {
196-
const expected: LDEvaluationDetailTyped<number> = {
197-
value: 1234.5,
198-
reason: { kind: 'RULE_MATCH' },
199-
};
200-
201-
(ipcRenderer.sendSync as jest.Mock).mockReturnValueOnce(expected);
202-
203-
const result = bridge.numberVariationDetail('flag1', 0);
204-
205-
expect(ipcRenderer.sendSync).toHaveBeenCalledTimes(1);
206-
expect(ipcRenderer.sendSync).toHaveBeenNthCalledWith(
207-
1,
208-
getEventName('numberVariationDetail'),
209-
'flag1',
210-
0,
211-
);
212-
expect(result).toEqual(expected);
213-
});
214-
215-
it('passes stringVariation() call through to ipcRenderer', () => {
216-
(ipcRenderer.sendSync as jest.Mock).mockReturnValueOnce('value');
217-
218-
const result = bridge.stringVariation('flag1', '');
219-
220-
expect(ipcRenderer.sendSync).toHaveBeenCalledTimes(1);
221-
expect(ipcRenderer.sendSync).toHaveBeenNthCalledWith(
222-
1,
223-
getEventName('stringVariation'),
224-
'flag1',
225-
'',
226-
);
227-
expect(result).toEqual('value');
228-
});
229-
230-
it('passes stringVariationDetail() call through to ipcRenderer', () => {
231-
const expected: LDEvaluationDetailTyped<string> = {
232-
value: 'value',
233-
reason: { kind: 'RULE_MATCH' },
234-
};
235-
236-
(ipcRenderer.sendSync as jest.Mock).mockReturnValueOnce(expected);
237-
238-
const result = bridge.stringVariationDetail('flag1', '');
239-
240-
expect(ipcRenderer.sendSync).toHaveBeenCalledTimes(1);
241-
expect(ipcRenderer.sendSync).toHaveBeenNthCalledWith(
242-
1,
243-
getEventName('stringVariationDetail'),
244-
'flag1',
245-
'',
246-
);
247-
expect(result).toEqual(expected);
248-
});
249-
250135
it('passes track() call through to ipcRenderer', () => {
251136
bridge.track('event1', { key1: 'value1' }, 1234.5);
252137

@@ -260,41 +145,6 @@ describe('given a registered LDClientBridge', () => {
260145
);
261146
});
262147

263-
it('passes variation() call through to ipcRenderer', () => {
264-
(ipcRenderer.sendSync as jest.Mock).mockReturnValueOnce(true);
265-
266-
const result = bridge.variation('flag1', false);
267-
268-
expect(ipcRenderer.sendSync).toHaveBeenCalledTimes(1);
269-
expect(ipcRenderer.sendSync).toHaveBeenNthCalledWith(
270-
1,
271-
getEventName('variation'),
272-
'flag1',
273-
false,
274-
);
275-
expect(result).toEqual(true);
276-
});
277-
278-
it('passes variationDetail() call through to ipcRenderer', () => {
279-
const expected: LDEvaluationDetail = {
280-
value: true,
281-
reason: { kind: 'RULE_MATCH' },
282-
};
283-
284-
(ipcRenderer.sendSync as jest.Mock).mockReturnValueOnce(expected);
285-
286-
const result = bridge.variationDetail('flag1', false);
287-
288-
expect(ipcRenderer.sendSync).toHaveBeenCalledTimes(1);
289-
expect(ipcRenderer.sendSync).toHaveBeenNthCalledWith(
290-
1,
291-
getEventName('variationDetail'),
292-
'flag1',
293-
false,
294-
);
295-
expect(result).toEqual(expected);
296-
});
297-
298148
it('passes setConnectionMode() call through to ipcRenderer', async () => {
299149
await bridge.setConnectionMode('streaming');
300150

0 commit comments

Comments
 (0)