Skip to content

Commit dee53af

Browse files
authored
feat: adds ping stream support (#624)
**Requirements** - [x] I have added test coverage for new or changed functionality - [x] I have followed the repository's [pull request submission guidelines](../blob/main/CONTRIBUTING.md#submitting-pull-requests) - [ ] I have validated my changes against all supported platform versions -I have tested this against browser and mobile. **Related issues** SDK-198 **Describe the solution you've provided** Added ping pathing Passing Requestor into StreamingProcessor that now uses it when a ping event is received. Commonized requestor creation logic and using that helper function in various data managers.
1 parent 8c84e01 commit dee53af

File tree

17 files changed

+627
-242
lines changed

17 files changed

+627
-242
lines changed

packages/sdk/browser/__tests__/BrowserDataManager.test.ts

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -144,18 +144,24 @@ describe('given a BrowserDataManager with mocked dependencies', () => {
144144
browserConfig,
145145
() => ({
146146
pathGet(encoding: Encoding, _plainContextString: string): string {
147-
return `/msdk/evalx/contexts/${base64UrlEncode(_plainContextString, encoding)}`;
147+
return `/path/get/${base64UrlEncode(_plainContextString, encoding)}`;
148148
},
149-
pathReport(_encoding: Encoding, _plainContextString: string): string {
150-
return `/msdk/evalx/context`;
149+
pathReport(encoding: Encoding, _plainContextString: string): string {
150+
return `/path/report/${base64UrlEncode(_plainContextString, encoding)}`;
151+
},
152+
pathPing(encoding: Encoding, _plainContextString: string): string {
153+
return `/path/ping/${base64UrlEncode(_plainContextString, encoding)}`;
151154
},
152155
}),
153156
() => ({
154157
pathGet(encoding: Encoding, _plainContextString: string): string {
155-
return `/meval/${base64UrlEncode(_plainContextString, encoding)}`;
158+
return `/path/get/${base64UrlEncode(_plainContextString, encoding)}`;
159+
},
160+
pathReport(encoding: Encoding, _plainContextString: string): string {
161+
return `/path/report/${base64UrlEncode(_plainContextString, encoding)}`;
156162
},
157-
pathReport(_encoding: Encoding, _plainContextString: string): string {
158-
return `/meval`;
163+
pathPing(encoding: Encoding, _plainContextString: string): string {
164+
return `/path/ping/${base64UrlEncode(_plainContextString, encoding)}`;
159165
},
160166
}),
161167
baseHeaders,
@@ -177,18 +183,24 @@ describe('given a BrowserDataManager with mocked dependencies', () => {
177183
validateOptions({ streaming: true }, logger),
178184
() => ({
179185
pathGet(encoding: Encoding, _plainContextString: string): string {
180-
return `/msdk/evalx/contexts/${base64UrlEncode(_plainContextString, encoding)}`;
186+
return `/path/get/${base64UrlEncode(_plainContextString, encoding)}`;
181187
},
182-
pathReport(_encoding: Encoding, _plainContextString: string): string {
183-
return `/msdk/evalx/context`;
188+
pathReport(encoding: Encoding, _plainContextString: string): string {
189+
return `/path/report/${base64UrlEncode(_plainContextString, encoding)}`;
190+
},
191+
pathPing(encoding: Encoding, _plainContextString: string): string {
192+
return `/path/ping/${base64UrlEncode(_plainContextString, encoding)}`;
184193
},
185194
}),
186195
() => ({
187196
pathGet(encoding: Encoding, _plainContextString: string): string {
188-
return `/meval/${base64UrlEncode(_plainContextString, encoding)}`;
197+
return `/path/get/${base64UrlEncode(_plainContextString, encoding)}`;
198+
},
199+
pathReport(encoding: Encoding, _plainContextString: string): string {
200+
return `/path/report/${base64UrlEncode(_plainContextString, encoding)}`;
189201
},
190-
pathReport(_encoding: Encoding, _plainContextString: string): string {
191-
return `/meval`;
202+
pathPing(encoding: Encoding, _plainContextString: string): string {
203+
return `/path/ping/${base64UrlEncode(_plainContextString, encoding)}`;
192204
},
193205
}),
194206
baseHeaders,
@@ -215,18 +227,24 @@ describe('given a BrowserDataManager with mocked dependencies', () => {
215227
validateOptions({ streaming: true }, logger),
216228
() => ({
217229
pathGet(encoding: Encoding, _plainContextString: string): string {
218-
return `/msdk/evalx/contexts/${base64UrlEncode(_plainContextString, encoding)}`;
230+
return `/path/get/${base64UrlEncode(_plainContextString, encoding)}`;
219231
},
220-
pathReport(_encoding: Encoding, _plainContextString: string): string {
221-
return `/msdk/evalx/context`;
232+
pathReport(encoding: Encoding, _plainContextString: string): string {
233+
return `/path/report/${base64UrlEncode(_plainContextString, encoding)}`;
234+
},
235+
pathPing(encoding: Encoding, _plainContextString: string): string {
236+
return `/path/ping/${base64UrlEncode(_plainContextString, encoding)}`;
222237
},
223238
}),
224239
() => ({
225240
pathGet(encoding: Encoding, _plainContextString: string): string {
226-
return `/meval/${base64UrlEncode(_plainContextString, encoding)}`;
241+
return `/path/get/${base64UrlEncode(_plainContextString, encoding)}`;
242+
},
243+
pathReport(encoding: Encoding, _plainContextString: string): string {
244+
return `/path/report/${base64UrlEncode(_plainContextString, encoding)}`;
227245
},
228-
pathReport(_encoding: Encoding, _plainContextString: string): string {
229-
return `/meval`;
246+
pathPing(encoding: Encoding, _plainContextString: string): string {
247+
return `/path/ping/${base64UrlEncode(_plainContextString, encoding)}`;
230248
},
231249
}),
232250
baseHeaders,
@@ -242,7 +260,7 @@ describe('given a BrowserDataManager with mocked dependencies', () => {
242260
await dataManager.identify(identifyResolve, identifyReject, context, identifyOptions);
243261

244262
expect(platform.requests.createEventSource).toHaveBeenCalledWith(
245-
'/meval/eyJraW5kIjoidXNlciIsImtleSI6InRlc3QtdXNlciJ9?h=potato&withReasons=true',
263+
'/path/get/eyJraW5kIjoidXNlciIsImtleSI6InRlc3QtdXNlciJ9?h=potato&withReasons=true',
246264
expect.anything(),
247265
);
248266
});
@@ -256,18 +274,24 @@ describe('given a BrowserDataManager with mocked dependencies', () => {
256274
validateOptions({ streaming: false }, logger),
257275
() => ({
258276
pathGet(encoding: Encoding, _plainContextString: string): string {
259-
return `/msdk/evalx/contexts/${base64UrlEncode(_plainContextString, encoding)}`;
277+
return `/path/get/${base64UrlEncode(_plainContextString, encoding)}`;
260278
},
261-
pathReport(_encoding: Encoding, _plainContextString: string): string {
262-
return `/msdk/evalx/context`;
279+
pathReport(encoding: Encoding, _plainContextString: string): string {
280+
return `/path/report/${base64UrlEncode(_plainContextString, encoding)}`;
281+
},
282+
pathPing(encoding: Encoding, _plainContextString: string): string {
283+
return `/path/ping/${base64UrlEncode(_plainContextString, encoding)}`;
263284
},
264285
}),
265286
() => ({
266287
pathGet(encoding: Encoding, _plainContextString: string): string {
267-
return `/meval/${base64UrlEncode(_plainContextString, encoding)}`;
288+
return `/path/get/${base64UrlEncode(_plainContextString, encoding)}`;
289+
},
290+
pathReport(encoding: Encoding, _plainContextString: string): string {
291+
return `/path/report/${base64UrlEncode(_plainContextString, encoding)}`;
268292
},
269-
pathReport(_encoding: Encoding, _plainContextString: string): string {
270-
return `/meval`;
293+
pathPing(encoding: Encoding, _plainContextString: string): string {
294+
return `/path/ping/${base64UrlEncode(_plainContextString, encoding)}`;
271295
},
272296
}),
273297
baseHeaders,
@@ -283,7 +307,7 @@ describe('given a BrowserDataManager with mocked dependencies', () => {
283307
await dataManager.identify(identifyResolve, identifyReject, context, identifyOptions);
284308

285309
expect(platform.requests.fetch).toHaveBeenCalledWith(
286-
'/msdk/evalx/contexts/eyJraW5kIjoidXNlciIsImtleSI6InRlc3QtdXNlciJ9?withReasons=true&h=potato',
310+
'/path/get/eyJraW5kIjoidXNlciIsImtleSI6InRlc3QtdXNlciJ9?withReasons=true&h=potato',
287311
expect.anything(),
288312
);
289313
});

packages/sdk/browser/src/BrowserClient.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,11 @@ export class BrowserClient extends LDClientImpl implements LDClient {
140140
pathReport(_encoding: Encoding, _plainContextString: string): string {
141141
return `/sdk/evalx/${clientSideId}/context`;
142142
},
143+
pathPing(_encoding: Encoding, _plainContextString: string): string {
144+
// Note: if you are seeing this error, it is a coding error. This DataSourcePaths implementation is for polling endpoints. /ping is not currently
145+
// used in a polling situation. It is probably the case that this was called by streaming logic erroneously.
146+
throw new Error('Ping for polling unsupported.');
147+
},
143148
}),
144149
() => ({
145150
pathGet(encoding: Encoding, _plainContextString: string): string {
@@ -148,6 +153,9 @@ export class BrowserClient extends LDClientImpl implements LDClient {
148153
pathReport(_encoding: Encoding, _plainContextString: string): string {
149154
return `/eval/${clientSideId}`;
150155
},
156+
pathPing(_encoding: Encoding, _plainContextString: string): string {
157+
return `/ping/${clientSideId}`;
158+
},
151159
}),
152160
baseHeaders,
153161
emitter,

packages/sdk/browser/src/BrowserDataManager.ts

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@ import {
66
DataSourcePaths,
77
DataSourceState,
88
FlagManager,
9-
getPollingUri,
109
internal,
1110
LDEmitter,
1211
LDHeaders,
1312
LDIdentifyOptions,
13+
makeRequestor,
1414
Platform,
15-
Requestor,
1615
} from '@launchdarkly/js-client-sdk-common';
1716

1817
import { readFlagsFromBootstrap } from './bootstrap';
@@ -92,23 +91,35 @@ export default class BrowserDataManager extends BaseDataManager {
9291
if (await this.flagManager.loadCached(context)) {
9392
this._debugLog('Identify - Flags loaded from cache. Continuing to initialize via a poll.');
9493
}
95-
const plainContextString = JSON.stringify(Context.toLDContext(context));
96-
const requestor = this._getRequestor(plainContextString);
97-
await this._finishIdentifyFromPoll(requestor, context, identifyResolve, identifyReject);
98-
}
9994

95+
await this._finishIdentifyFromPoll(context, identifyResolve, identifyReject);
96+
}
10097
this._updateStreamingState();
10198
}
10299

103100
private async _finishIdentifyFromPoll(
104-
requestor: Requestor,
105101
context: Context,
106102
identifyResolve: () => void,
107103
identifyReject: (err: Error) => void,
108104
) {
109105
try {
110106
this.dataSourceStatusManager.requestStateUpdate(DataSourceState.Initializing);
111-
const payload = await requestor.requestPayload();
107+
108+
const plainContextString = JSON.stringify(Context.toLDContext(context));
109+
const pollingRequestor = makeRequestor(
110+
plainContextString,
111+
this.config.serviceEndpoints,
112+
this.getPollingPaths(),
113+
this.platform.requests,
114+
this.platform.encoding!,
115+
this.baseHeaders,
116+
[],
117+
this.config.withReasons,
118+
this.config.useReport,
119+
this._secureModeHash,
120+
);
121+
122+
const payload = await pollingRequestor.requestPayload();
112123
try {
113124
const listeners = this.createStreamListeners(context, identifyResolve);
114125
const putListener = listeners.get('put');
@@ -196,35 +207,29 @@ export default class BrowserDataManager extends BaseDataManager {
196207
const rawContext = Context.toLDContext(context)!;
197208

198209
this.updateProcessor?.close();
199-
this.createStreamingProcessor(rawContext, context, identifyResolve, identifyReject);
200210

201-
this.updateProcessor!.start();
202-
}
203-
204-
private _getRequestor(plainContextString: string): Requestor {
205-
const paths = this.getPollingPaths();
206-
const path = this.config.useReport
207-
? paths.pathReport(this.platform.encoding!, plainContextString)
208-
: paths.pathGet(this.platform.encoding!, plainContextString);
209-
210-
const parameters: { key: string; value: string }[] = [];
211-
if (this.config.withReasons) {
212-
parameters.push({ key: 'withReasons', value: 'true' });
213-
}
214-
if (this._secureModeHash) {
215-
parameters.push({ key: 'h', value: this._secureModeHash });
216-
}
211+
const plainContextString = JSON.stringify(Context.toLDContext(context));
212+
const pollingRequestor = makeRequestor(
213+
plainContextString,
214+
this.config.serviceEndpoints,
215+
this.getPollingPaths(),
216+
this.platform.requests,
217+
this.platform.encoding!,
218+
this.baseHeaders,
219+
[],
220+
this.config.withReasons,
221+
this.config.useReport,
222+
this._secureModeHash,
223+
);
217224

218-
const headers: { [key: string]: string } = { ...this.baseHeaders };
219-
let body;
220-
let method = 'GET';
221-
if (this.config.useReport) {
222-
method = 'REPORT';
223-
headers['content-type'] = 'application/json';
224-
body = plainContextString; // context is in body for REPORT
225-
}
225+
this.createStreamingProcessor(
226+
rawContext,
227+
context,
228+
pollingRequestor,
229+
identifyResolve,
230+
identifyReject,
231+
);
226232

227-
const uri = getPollingUri(this.config.serviceEndpoints, path, parameters);
228-
return new Requestor(this.platform.requests, uri, headers, method, body);
233+
this.updateProcessor!.start();
229234
}
230235
}

packages/sdk/react-native/__tests__/MobileDataManager.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ describe('given a MobileDataManager with mocked dependencies', () => {
133133
pathReport(_encoding: Encoding, _plainContextString: string): string {
134134
return `/msdk/evalx/context`;
135135
},
136+
pathPing(_encoding: Encoding, _plainContextString: string): string {
137+
// Note: if you are seeing this error, it is a coding error. This DataSourcePaths implementation is for polling endpoints. /ping is not currently
138+
// used in a polling situation. It is probably the case that this was called by streaming logic erroneously.
139+
throw new Error('Ping for polling unsupported.');
140+
},
136141
}),
137142
() => ({
138143
pathGet(encoding: Encoding, _plainContextString: string): string {
@@ -141,6 +146,9 @@ describe('given a MobileDataManager with mocked dependencies', () => {
141146
pathReport(_encoding: Encoding, _plainContextString: string): string {
142147
return `/meval`;
143148
},
149+
pathPing(_encoding: Encoding, _plainContextString: string): string {
150+
return `/mping`;
151+
},
144152
}),
145153
baseHeaders,
146154
emitter,

packages/sdk/react-native/src/MobileDataManager.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
LDEmitter,
1010
LDHeaders,
1111
LDIdentifyOptions,
12+
makeRequestor,
1213
Platform,
1314
} from '@launchdarkly/js-client-sdk-common';
1415

@@ -95,13 +96,38 @@ export default class MobileDataManager extends BaseDataManager {
9596
) {
9697
const rawContext = Context.toLDContext(context)!;
9798

99+
const plainContextString = JSON.stringify(Context.toLDContext(context));
100+
const requestor = makeRequestor(
101+
plainContextString,
102+
this.config.serviceEndpoints,
103+
this.getPollingPaths(),
104+
this.platform.requests,
105+
this.platform.encoding!,
106+
this.baseHeaders,
107+
[],
108+
this.config.useReport,
109+
this.config.withReasons,
110+
);
111+
98112
this.updateProcessor?.close();
99113
switch (this.connectionMode) {
100114
case 'streaming':
101-
this.createStreamingProcessor(rawContext, context, identifyResolve, identifyReject);
115+
this.createStreamingProcessor(
116+
rawContext,
117+
context,
118+
requestor,
119+
identifyResolve,
120+
identifyReject,
121+
);
102122
break;
103123
case 'polling':
104-
this.createPollingProcessor(rawContext, context, identifyResolve, identifyReject);
124+
this.createPollingProcessor(
125+
rawContext,
126+
context,
127+
requestor,
128+
identifyResolve,
129+
identifyReject,
130+
);
105131
break;
106132
default:
107133
break;

packages/sdk/react-native/src/ReactNativeLDClient.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ export default class ReactNativeLDClient extends LDClientImpl {
8989
pathReport(_encoding: Encoding, _plainContextString: string): string {
9090
return `/msdk/evalx/context`;
9191
},
92+
pathPing(_encoding: Encoding, _plainContextString: string): string {
93+
// Note: if you are seeing this error, it is a coding error. This DataSourcePaths implementation is for polling endpoints. /ping is not currently
94+
// used in a polling situation. It is probably the case that this was called by streaming logic erroneously.
95+
throw new Error('Ping for polling unsupported.');
96+
},
9297
}),
9398
() => ({
9499
pathGet(encoding: Encoding, _plainContextString: string): string {
@@ -97,6 +102,9 @@ export default class ReactNativeLDClient extends LDClientImpl {
97102
pathReport(_encoding: Encoding, _plainContextString: string): string {
98103
return `/meval`;
99104
},
105+
pathPing(_encoding: Encoding, _plainContextString: string): string {
106+
return `/mping`;
107+
},
100108
}),
101109
baseHeaders,
102110
emitter,

packages/shared/sdk-client/__tests__/LDClientImpl.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,8 +273,9 @@ describe('sdk-client object', () => {
273273
const carContext: LDContext = { kind: 'car', key: 'test-car' };
274274

275275
await expect(ldc.identify(carContext)).rejects.toThrow('test-error');
276-
expect(logger.error).toHaveBeenCalledTimes(1);
277-
expect(logger.error).toHaveBeenCalledWith(expect.stringMatching(/^error:.*test-error/));
276+
expect(logger.error).toHaveBeenCalledTimes(2);
277+
expect(logger.error).toHaveBeenNthCalledWith(1, expect.stringMatching(/^error:.*test-error/));
278+
expect(logger.error).toHaveBeenNthCalledWith(2, expect.stringContaining('Received error 404'));
278279
});
279280

280281
test('identify change and error listeners', async () => {

0 commit comments

Comments
 (0)