Skip to content

Commit a843a33

Browse files
authored
feat: Add client-side SDK plugin support. (#834)
Adds plugin support for react native and browser. Types are common, as well as most functionality, but SDK specific implementation is required. This is because the leaf-node SDKs have different types for the LDClient. Also the SDK needs control over the ordering of operations that happen during construction in order to register plugins at the correct point. There is a bit more complexity in this implementation than would be required for most SDKs. Some refactoring of the base client implementation, potentially to use composition instead of inheritance, may allow for some better sharing here in the long-term. Initially I tried using generics in the base implementation, to account for the different client implementations, but this introduced a lot of changes to all the layers and didn't play well with configuration, which also then needs to be generic. The approach I took was to have the configuration in the individual SDKs and then use generics for utility methods to collect hooks and register plugins. In the long-term this would also benefit from the base implementation using generics for the configuration, which would allow a shared configuration and validation.
1 parent 1e0cf6a commit a843a33

34 files changed

+1530
-178
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import {
2+
EventSourceCapabilities,
3+
EventSourceInitDict,
4+
Platform,
5+
PlatformData,
6+
Requests,
7+
SdkData,
8+
} from '@launchdarkly/js-client-sdk-common';
9+
10+
import { BrowserOptions } from '../src/options';
11+
import { MockHasher } from './MockHasher';
12+
13+
function mockResponse(value: string, statusCode: number) {
14+
const response: Response = {
15+
// @ts-ignore
16+
headers: {
17+
// @ts-ignore
18+
get: jest.fn(),
19+
// @ts-ignore
20+
keys: jest.fn(),
21+
// @ts-ignore
22+
values: jest.fn(),
23+
// @ts-ignore
24+
entries: jest.fn(),
25+
// @ts-ignore
26+
has: jest.fn(),
27+
},
28+
status: statusCode,
29+
text: () => Promise.resolve(value),
30+
json: () => Promise.resolve(JSON.parse(value)),
31+
};
32+
return Promise.resolve(response);
33+
}
34+
35+
function mockFetch(value: string, statusCode: number = 200) {
36+
const f = jest.fn();
37+
// @ts-ignore
38+
f.mockResolvedValue(mockResponse(value, statusCode));
39+
return f;
40+
}
41+
42+
export function makeRequests(): Requests {
43+
return {
44+
// @ts-ignore
45+
fetch: jest.fn((url: string, _options: any) => {
46+
if (url.includes('/sdk/goals/')) {
47+
return mockFetch(
48+
JSON.stringify([
49+
{
50+
key: 'pageview',
51+
kind: 'pageview',
52+
urls: [{ kind: 'exact', url: 'http://browserclientintegration.com' }],
53+
},
54+
{
55+
key: 'click',
56+
kind: 'click',
57+
selector: '.button',
58+
urls: [{ kind: 'exact', url: 'http://browserclientintegration.com' }],
59+
},
60+
]),
61+
200,
62+
)();
63+
}
64+
return mockFetch('{ "flagA": true }', 200)();
65+
}),
66+
// @ts-ignore
67+
createEventSource(_url: string, _eventSourceInitDict: EventSourceInitDict): EventSource {
68+
throw new Error('Function not implemented.');
69+
},
70+
getEventSourceCapabilities(): EventSourceCapabilities {
71+
return {
72+
readTimeout: false,
73+
headers: false,
74+
customMethod: false,
75+
};
76+
},
77+
};
78+
}
79+
80+
export function makeBasicPlatform(options?: BrowserOptions): Platform {
81+
return {
82+
requests: makeRequests(),
83+
info: {
84+
platformData(): PlatformData {
85+
return {
86+
name: 'browser',
87+
};
88+
},
89+
sdkData(): SdkData {
90+
const sdkData: SdkData = {
91+
name: 'browser-sdk',
92+
version: '1.0.0',
93+
userAgentBase: 'MockBrowserSDK',
94+
};
95+
if (options?.wrapperName) {
96+
sdkData.wrapperName = options.wrapperName;
97+
}
98+
if (options?.wrapperVersion) {
99+
sdkData.wrapperVersion = options.wrapperVersion;
100+
}
101+
return sdkData;
102+
},
103+
},
104+
crypto: {
105+
createHash: () => new MockHasher(),
106+
randomUUID: () => '123',
107+
},
108+
storage: {
109+
get: async (_key: string) => null,
110+
set: async (_key: string, _value: string) => {},
111+
clear: async (_key: string) => {},
112+
},
113+
encoding: {
114+
btoa: (str: string) => str,
115+
},
116+
} as Platform;
117+
}

0 commit comments

Comments
 (0)