launchdarkly-js-sdk-common 5.7.0-beta.2 → 5.7.0-beta.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "launchdarkly-js-sdk-common",
3
- "version": "5.7.0-beta.2",
3
+ "version": "5.7.0-beta.4",
4
4
  "description": "LaunchDarkly SDK for JavaScript - common code",
5
5
  "author": "LaunchDarkly <team@launchdarkly.com>",
6
6
  "license": "Apache-2.0",
@@ -5,7 +5,6 @@ const errors = require('./errors');
5
5
  const messages = require('./messages');
6
6
  const utils = require('./utils');
7
7
  const { getContextKeys } = require('./context');
8
- const EventSummarizer = require('./EventSummarizer');
9
8
 
10
9
  function EventProcessor(
11
10
  platform,
@@ -19,12 +18,7 @@ function EventProcessor(
19
18
  const eventSender = sender || EventSender(platform, environmentId, options);
20
19
  const mainEventsUrl = utils.appendUrlPath(options.eventsUrl, '/events/bulk/' + environmentId);
21
20
  const contextFilter = ContextFilter(options);
22
- // If the platform has a hasherFactory, use the MultiEventSummarizer, otherwise use the EventSummarizer.
23
- // Generally packages should be pinning a specific version of the common SDK, but this will handle them potentially
24
- // being mis-matched.
25
- const summarizer = platform.hasherFactory
26
- ? MultiEventSummarizer(contextFilter, () => platform.hasherFactory('sha256'))
27
- : new EventSummarizer();
21
+ const summarizer = MultiEventSummarizer(contextFilter);
28
22
  const samplingInterval = options.samplingInterval;
29
23
  const eventCapacity = options.eventCapacity;
30
24
  const flushInterval = options.flushInterval;
@@ -128,7 +122,7 @@ function EventProcessor(
128
122
  return Promise.resolve();
129
123
  }
130
124
  const eventsToSend = queue;
131
- const summaries = await summarizer.getSummaries();
125
+ const summaries = summarizer.getSummaries();
132
126
 
133
127
  summaries.forEach(summary => {
134
128
  if (Object.keys(summary.features).length) {
@@ -93,13 +93,6 @@ function EventSummarizer() {
93
93
  };
94
94
  };
95
95
 
96
- es.getSummaries = () => {
97
- const summary = es.getSummary();
98
- const summaries = summary ? [summary] : [];
99
- es.clearSummary();
100
- return summaries;
101
- };
102
-
103
96
  es.clearSummary = () => {
104
97
  startDate = 0;
105
98
  endDate = 0;
@@ -1,14 +1,14 @@
1
- import { hashContext } from './context';
2
- import EventSummarizer from './EventSummarizer';
1
+ const canonicalize = require('./canonicalize');
2
+ const EventSummarizer = require('./EventSummarizer');
3
+
3
4
  /**
4
5
  *
5
6
  * @param {{filter: (context: any) => any}} contextFilter
6
7
  * @param {() => {update: (value: string) => void, digest: (format: string) => Promise<string>}} hasherFactory
7
8
  */
8
- function MultiEventSummarizer(contextFilter, hasherFactory) {
9
+ function MultiEventSummarizer(contextFilter) {
9
10
  let summarizers = {};
10
11
  let contexts = {};
11
- const pendingPromises = [];
12
12
 
13
13
  /**
14
14
  * Summarize the given event.
@@ -18,51 +18,36 @@ function MultiEventSummarizer(contextFilter, hasherFactory) {
18
18
  * }} event
19
19
  */
20
20
  function summarizeEvent(event) {
21
- // This will execute asynchronously, which means that a flush could happen before the event
22
- // is summarized. When that happens, then the event will just be in the next batch of summaries.
23
- const promise = (async () => {
24
- if (event.kind === 'feature') {
25
- const hash = await hashContext(event.context, hasherFactory());
26
- if (!hash) {
27
- return;
28
- }
29
-
30
- let summarizer = summarizers[hash];
31
- if (!summarizer) {
32
- summarizers[hash] = EventSummarizer();
33
- summarizer = summarizers[hash];
34
- contexts[hash] = event.context;
35
- }
36
-
37
- summarizer.summarizeEvent(event);
21
+ if (event.kind === 'feature') {
22
+ const key = canonicalize(event.context);
23
+ if (!key) {
24
+ return;
38
25
  }
39
- })();
40
- pendingPromises.push(promise);
41
- promise.finally(() => {
42
- const index = pendingPromises.indexOf(promise);
43
- if (index !== -1) {
44
- pendingPromises.splice(index, 1);
26
+
27
+ let summarizer = summarizers[key];
28
+ if (!summarizer) {
29
+ summarizers[key] = EventSummarizer();
30
+ summarizer = summarizers[key];
31
+ contexts[key] = event.context;
45
32
  }
46
- });
33
+
34
+ summarizer.summarizeEvent(event);
35
+ }
47
36
  }
48
37
 
49
38
  /**
50
39
  * Get the summaries of the events that have been summarized.
51
40
  * @returns {any[]}
52
41
  */
53
- async function getSummaries() {
54
- // Wait for any pending summarizations to complete
55
- // Additional tasks queued while waiting will not be waited for.
56
- await Promise.all([...pendingPromises]);
57
-
42
+ function getSummaries() {
58
43
  const summarizersToFlush = summarizers;
59
44
  const contextsForSummaries = contexts;
60
45
 
61
46
  summarizers = {};
62
47
  contexts = {};
63
- return Object.entries(summarizersToFlush).map(([hash, summarizer]) => {
48
+ return Object.entries(summarizersToFlush).map(([key, summarizer]) => {
64
49
  const summary = summarizer.getSummary();
65
- summary.context = contextFilter.filter(contextsForSummaries[hash]);
50
+ summary.context = contextFilter.filter(contextsForSummaries[key]);
66
51
  return summary;
67
52
  });
68
53
  }
@@ -485,6 +485,73 @@ describe.each([
485
485
  });
486
486
  });
487
487
 
488
+ it('generates separate summary events for different contexts', async () => {
489
+ await withProcessorAndSender(defaultConfig, async (ep, mockEventSender) => {
490
+ const context1 = { key: 'user1', kind: 'user' };
491
+ const context2 = { key: 'user2', kind: 'user' };
492
+
493
+ // Create feature events for two different contexts
494
+ const event1 = {
495
+ kind: 'feature',
496
+ creationDate: 1000,
497
+ context: context1,
498
+ key: 'flag1',
499
+ version: 11,
500
+ variation: 1,
501
+ value: 'value1',
502
+ default: 'default1',
503
+ trackEvents: false,
504
+ };
505
+
506
+ const event2 = {
507
+ kind: 'feature',
508
+ creationDate: 1000,
509
+ context: context2,
510
+ key: 'flag2',
511
+ version: 22,
512
+ variation: 2,
513
+ value: 'value2',
514
+ default: 'default2',
515
+ trackEvents: false,
516
+ };
517
+
518
+ ep.enqueue(event1);
519
+ ep.enqueue(event2);
520
+ await ep.flush();
521
+
522
+ expect(mockEventSender.calls.length()).toEqual(1);
523
+ const output = (await mockEventSender.calls.take()).events;
524
+
525
+ // Should have two summary events, one for each context
526
+ expect(output.length).toEqual(2);
527
+
528
+ // Find the summary event for each context
529
+ const summary1 = output.find(e => e.context.key === 'user1');
530
+ const summary2 = output.find(e => e.context.key === 'user2');
531
+
532
+ // Verify each summary event has the correct context and flag data
533
+ expect(summary1).toBeDefined();
534
+ expect(summary1.context).toEqual(context1);
535
+ expect(summary1.features).toEqual({
536
+ flag1: {
537
+ contextKinds: ['user'],
538
+ default: 'default1',
539
+ counters: [{ version: 11, variation: 1, value: 'value1', count: 1 }],
540
+ },
541
+ });
542
+
543
+ expect(summary2).toBeDefined();
544
+ expect(summary2.context).toEqual(context2);
545
+ expect(summary2.features).toEqual({
546
+ flag2: {
547
+ contextKinds: ['user'],
548
+ default: 'default2',
549
+ counters: [{ version: 22, variation: 2, value: 'value2', count: 1 }],
550
+ },
551
+ });
552
+ });
553
+ });
554
+
488
555
  describe('interaction with diagnostic events', () => {
489
556
  it('sets eventsInLastBatch on flush', async () => {
490
557
  const e0 = { kind: 'custom', creationDate: 1000, context: eventContext, key: 'key0' };
@@ -526,92 +593,7 @@ describe.each([
526
593
  });
527
594
  });
528
595
 
529
- it('uses EventSummarizer when platform has no hasherFactory', async () => {
530
- const event = {
531
- kind: 'feature',
532
- creationDate: 1000,
533
- context: eventContext,
534
- key: 'flagkey',
535
- version: 11,
536
- variation: 1,
537
- value: 'value',
538
- default: 'default',
539
- trackEvents: true,
540
- };
541
-
542
- const platformWithoutHasher = { ...platform };
543
- delete platformWithoutHasher.hasherFactory;
544
-
545
- const sender = MockEventSender();
546
- const ep = EventProcessor(platformWithoutHasher, defaultConfig, envId, null, null, sender);
547
- try {
548
- ep.enqueue(event);
549
- await ep.flush();
550
-
551
- expect(sender.calls.length()).toEqual(1);
552
- const output = (await sender.calls.take()).events;
553
- expect(output.length).toEqual(2);
554
- checkFeatureEvent(output[0], event, false, eventContext);
555
- checkSummaryEvent(output[1]);
556
-
557
- // Verify the summary event doesn't have a context field when there's no hasherFactory
558
- const summaryEvent = output[1];
559
- expect(summaryEvent.context).toBeUndefined();
560
- expect(summaryEvent.features).toEqual({
561
- flagkey: {
562
- contextKinds: ['user'],
563
- default: 'default',
564
- counters: [{ version: 11, variation: 1, value: 'value', count: 1 }],
565
- },
566
- });
567
- } finally {
568
- ep.stop();
569
- }
570
- });
571
-
572
- it('uses MultiEventSummarizer when platform has hasherFactory', async () => {
573
- const event = {
574
- kind: 'feature',
575
- creationDate: 1000,
576
- context: eventContext,
577
- key: 'flagkey',
578
- version: 11,
579
- variation: 1,
580
- value: 'value',
581
- default: 'default',
582
- trackEvents: true,
583
- };
584
-
585
- // Stub platform hash a hasher factory.
586
-
587
- const sender = MockEventSender();
588
- const ep = EventProcessor(platform, defaultConfig, envId, null, null, sender);
589
- try {
590
- ep.enqueue(event);
591
- await ep.flush();
592
-
593
- expect(sender.calls.length()).toEqual(1);
594
- const output = (await sender.calls.take()).events;
595
- expect(output.length).toEqual(2);
596
- checkFeatureEvent(output[0], event, false, eventContext);
597
- checkSummaryEvent(output[1]);
598
-
599
- // Verify that when MultiEventSummarizer is used, context is included in the summary
600
- const summaryEvent = output[1];
601
- expect(summaryEvent.context).toEqual(eventContext);
602
- expect(summaryEvent.features).toEqual({
603
- flagkey: {
604
- contextKinds: ['user'],
605
- default: 'default',
606
- counters: [{ version: 11, variation: 1, value: 'value', count: 1 }],
607
- },
608
- });
609
- } finally {
610
- ep.stop();
611
- }
612
- });
613
-
614
- it('filters context in summary events when using MultiEventSummarizer', async () => {
596
+ it('filters context in summary events', async () => {
615
597
  const event = {
616
598
  kind: 'feature',
617
599
  creationDate: 1000,
@@ -33,38 +33,6 @@ describe('EventSummarizer', () => {
33
33
  expect(data.endDate).toEqual(2000);
34
34
  });
35
35
 
36
- it('returns summaries for feature events', () => {
37
- // getSummaries returns the single summary from getSummary(), but wrapped in an array.
38
- const es = EventSummarizer();
39
- const event1 = { kind: 'feature', creationDate: 2000, key: 'key', user: user };
40
- const event2 = { kind: 'feature', creationDate: 1000, key: 'key', user: user };
41
- const event3 = { kind: 'feature', creationDate: 1500, key: 'key', user: user };
42
- es.summarizeEvent(event1);
43
- es.summarizeEvent(event2);
44
- es.summarizeEvent(event3);
45
- const summaries = es.getSummaries();
46
-
47
- expect(summaries[0].startDate).toEqual(1000);
48
- expect(summaries[0].endDate).toEqual(2000);
49
- });
50
-
51
- it('clears summaries when getSummaries is called', () => {
52
- const es = EventSummarizer();
53
- const event1 = { kind: 'feature', creationDate: 2000, key: 'key', user: user };
54
- const event2 = { kind: 'feature', creationDate: 1000, key: 'key', user: user };
55
- es.summarizeEvent(event1);
56
- es.summarizeEvent(event2);
57
- const summaries = es.getSummaries();
58
- expect(summaries[0].startDate).toEqual(1000);
59
- expect(summaries[0].endDate).toEqual(2000);
60
- expect(es.getSummaries()).toEqual([]);
61
- });
62
-
63
- it('returns empty array if no summaries are available', () => {
64
- const es = EventSummarizer();
65
- expect(es.getSummaries()).toEqual([]);
66
- });
67
-
68
36
  function makeEvent(key, version, variation, value, defaultVal) {
69
37
  return {
70
38
  kind: 'feature',
@@ -1,4 +1,4 @@
1
- const { checkContext, getContextKeys, getContextKinds, getCanonicalKey, hashContext } = require('../context');
1
+ const { checkContext, getContextKeys, getContextKinds, getCanonicalKey } = require('../context');
2
2
 
3
3
  describe.each([{ key: 'test' }, { kind: 'user', key: 'test' }, { kind: 'multi', user: { key: 'test' } }])(
4
4
  'given a context which contains a single kind',
@@ -199,332 +199,3 @@ describe('getContextKeys', () => {
199
199
  expect(keys).toEqual({});
200
200
  });
201
201
  });
202
-
203
- function mockHasher() {
204
- let state = '';
205
- return {
206
- update: input => {
207
- state += input;
208
- },
209
- digest: () => state,
210
- };
211
- }
212
-
213
- it('hashes two equal contexts the same', async () => {
214
- const a = {
215
- kind: 'multi',
216
- org: {
217
- key: 'testKey',
218
- name: 'testName',
219
- cat: 'calico',
220
- dog: 'lab',
221
- anonymous: true,
222
- _meta: {
223
- privateAttributes: ['/a/b/c', 'cat', 'custom/dog'],
224
- },
225
- },
226
- customer: {
227
- key: 'testKey',
228
- name: 'testName',
229
- bird: 'party parrot',
230
- chicken: 'hen',
231
- },
232
- };
233
-
234
- const b = {
235
- kind: 'multi',
236
- org: {
237
- key: 'testKey',
238
- name: 'testName',
239
- cat: 'calico',
240
- dog: 'lab',
241
- anonymous: true,
242
- _meta: {
243
- privateAttributes: ['/a/b/c', 'cat', 'custom/dog'],
244
- },
245
- },
246
- customer: {
247
- key: 'testKey',
248
- name: 'testName',
249
- bird: 'party parrot',
250
- chicken: 'hen',
251
- },
252
- };
253
- expect(await hashContext(a, mockHasher())).toEqual(await hashContext(b, mockHasher()));
254
- });
255
-
256
- it('handles shared references without getting stuck', async () => {
257
- const sharedObject = { value: 'shared' };
258
- const context = {
259
- kind: 'multi',
260
- org: {
261
- key: 'testKey',
262
- shared: sharedObject,
263
- },
264
- user: {
265
- key: 'testKey',
266
- shared: sharedObject,
267
- },
268
- };
269
-
270
- const hash = await hashContext(context, mockHasher());
271
- expect(hash).toBeDefined();
272
- });
273
-
274
- it('returns undefined for contexts with cycles', async () => {
275
- const cyclicObject = { value: 'cyclic' };
276
- cyclicObject.self = cyclicObject;
277
-
278
- const context = {
279
- kind: 'user',
280
- key: 'testKey',
281
- cyclic: cyclicObject,
282
- };
283
-
284
- expect(await hashContext(context, mockHasher())).toBeUndefined();
285
- });
286
-
287
- it('handles nested objects correctly', async () => {
288
- const context = {
289
- kind: 'user',
290
- key: 'testKey',
291
- nested: {
292
- level1: {
293
- level2: {
294
- value: 'deep',
295
- },
296
- },
297
- },
298
- };
299
-
300
- const hash = await hashContext(context, mockHasher());
301
- expect(hash).toBeDefined();
302
- });
303
-
304
- it('handles arrays correctly', async () => {
305
- const context = {
306
- kind: 'user',
307
- key: 'testKey',
308
- array: [1, 2, 3],
309
- nestedArray: [
310
- [1, 2],
311
- [3, 4],
312
- ],
313
- };
314
-
315
- const hash = await hashContext(context, mockHasher());
316
- expect(hash).toBeDefined();
317
- });
318
-
319
- it('handles primitive values correctly', async () => {
320
- const context = {
321
- kind: 'user',
322
- key: 'testKey',
323
- string: 'test',
324
- number: 42,
325
- boolean: true,
326
- nullValue: null,
327
- undefinedValue: undefined,
328
- };
329
-
330
- const hash = await hashContext(context, mockHasher());
331
- expect(hash).toBeDefined();
332
- });
333
-
334
- it('includes private attributes in hash calculation', async () => {
335
- const baseContext = {
336
- kind: 'user',
337
- key: 'testKey',
338
- name: 'testName',
339
- nested: {
340
- value: 'testValue',
341
- },
342
- };
343
-
344
- const contextWithPrivate = {
345
- ...baseContext,
346
- _meta: {
347
- privateAttributes: ['name', 'nested/value'],
348
- },
349
- };
350
-
351
- const hashWithPrivate = await hashContext(contextWithPrivate, mockHasher());
352
- const hashWithoutPrivate = await hashContext(baseContext, mockHasher());
353
-
354
- // The hashes should be different because private attributes are included in the hash
355
- expect(hashWithPrivate).not.toEqual(hashWithoutPrivate);
356
- });
357
-
358
- it('uses the keys of attributes in the hash', async () => {
359
- const a = {
360
- kind: 'user',
361
- key: 'testKey',
362
- a: 'b',
363
- };
364
-
365
- const b = {
366
- kind: 'user',
367
- key: 'testKey',
368
- b: 'b',
369
- };
370
-
371
- const hashA = await hashContext(a, mockHasher());
372
- const hashB = await hashContext(b, mockHasher());
373
- expect(hashA).not.toBe(hashB);
374
- });
375
-
376
- it('uses the keys of nested objects inside the hash', async () => {
377
- const a = {
378
- kind: 'user',
379
- key: 'testKey',
380
- nested: {
381
- level1: {
382
- level2: {
383
- value: 'deep',
384
- },
385
- },
386
- },
387
- };
388
-
389
- const b = {
390
- kind: 'user',
391
- key: 'testKey',
392
- nested: {
393
- sub1: {
394
- sub2: {
395
- value: 'deep',
396
- },
397
- },
398
- },
399
- };
400
-
401
- const hashA = await hashContext(a, mockHasher());
402
- const hashB = await hashContext(b, mockHasher());
403
- expect(hashA).not.toBe(hashB);
404
- });
405
-
406
- it('uses the values of nested array in calculations', async () => {
407
- const a = {
408
- kind: 'user',
409
- key: 'testKey',
410
- array: [1, 2, 3],
411
- nestedArray: [
412
- [1, 2],
413
- [3, 4],
414
- ],
415
- };
416
-
417
- const b = {
418
- kind: 'user',
419
- key: 'testKey',
420
- array: [1, 2, 3],
421
- nestedArray: [
422
- [2, 1],
423
- [3, 4],
424
- ],
425
- };
426
-
427
- const hashA = await hashContext(a, mockHasher());
428
- const hashB = await hashContext(b, mockHasher());
429
- expect(hashA).not.toBe(hashB);
430
- });
431
-
432
- it('uses the values of nested objects inside the hash', async () => {
433
- const a = {
434
- kind: 'user',
435
- key: 'testKey',
436
- nested: {
437
- level1: {
438
- level2: {
439
- value: 'deep',
440
- },
441
- },
442
- },
443
- };
444
-
445
- const b = {
446
- kind: 'user',
447
- key: 'testKey',
448
- nested: {
449
- level1: {
450
- level2: {
451
- value: 'deeper',
452
- },
453
- },
454
- },
455
- };
456
-
457
- const hashA = await hashContext(a, mockHasher());
458
- const hashB = await hashContext(b, mockHasher());
459
- expect(hashA).not.toBe(hashB);
460
- });
461
-
462
- it('hashes _meta in attributes', async () => {
463
- const a = {
464
- kind: 'user',
465
- key: 'testKey',
466
- nested: {
467
- level1: {
468
- level2: {
469
- _meta: { test: 'a' },
470
- },
471
- },
472
- },
473
- };
474
-
475
- const b = {
476
- kind: 'user',
477
- key: 'testKey',
478
- nested: {
479
- level1: {
480
- level2: {
481
- _meta: { test: 'b' },
482
- },
483
- },
484
- },
485
- };
486
-
487
- const hashA = await hashContext(a, mockHasher());
488
- const hashB = await hashContext(b, mockHasher());
489
- expect(hashA).not.toBe(hashB);
490
- });
491
-
492
- it('produces the same value for the given context', async () => {
493
- // This isn't so much a test as it is a detection of change.
494
- // If this test failed, and you didn't expect it, then you probably need to make sure your
495
- // change makes sense.
496
- const complexContext = {
497
- kind: 'multi',
498
- org: {
499
- key: 'testKey',
500
- name: 'testName',
501
- cat: 'calico',
502
- dog: 'lab',
503
- anonymous: true,
504
- nestedArray: [
505
- [1, 2],
506
- [3, 4],
507
- ],
508
- _meta: {
509
- privateAttributes: ['/a/b/c', 'cat', 'custom/dog'],
510
- },
511
- },
512
- customer: {
513
- key: 'testKey',
514
- name: 'testName',
515
- bird: 'party parrot',
516
- chicken: 'hen',
517
- nested: {
518
- level1: {
519
- level2: {
520
- value: 'deep',
521
- _meta: { thisShouldBeInTheHash: true },
522
- },
523
- },
524
- },
525
- },
526
- };
527
- expect(await hashContext(complexContext, mockHasher())).toBe(
528
- '{"customer":{"bird":"party parrot","chicken":"hen","key":"testKey","name":"testName","nested":{"level1":{"level2":{"_meta":{"thisShouldBeInTheHash":true},"value":"deep"}}}},"kind":"multi","org":{"_meta":{"privateAttributes":["/a/b/c","cat","custom/dog"]},"anonymous":true,"cat":"calico","dog":"lab","key":"testKey","name":"testName","nestedArray":[[1,2],[3,4]]}}'
529
- );
530
- });
@@ -45,15 +45,6 @@ export function defaults() {
45
45
  diagnosticPlatformData: { name: 'stub-platform' },
46
46
  getCurrentUrl: () => currentUrl,
47
47
  isDoNotTrack: () => doNotTrack,
48
- hasherFactory: (/*algorithm*/) => {
49
- let content = '';
50
- return {
51
- update: value => {
52
- content += value;
53
- },
54
- digest: (/*format*/) => content,
55
- };
56
- },
57
48
  eventSourceFactory: (url, options) => {
58
49
  const es = new EventSource(url);
59
50
  es.options = options;
package/src/context.js CHANGED
@@ -1,5 +1,4 @@
1
1
  const { commonBasicLogger } = require('./loggers');
2
- const canonicalize = require('./canonicalize');
3
2
 
4
3
  /**
5
4
  * Validate a context kind.
@@ -127,45 +126,9 @@ function getContextKeys(context, logger = commonBasicLogger()) {
127
126
  return keys;
128
127
  }
129
128
 
130
- /**
131
- * Hash the given context using the provided hasher.
132
- * This implementation can produce different hashes for equivalent contexts.
133
- *
134
- * For example:
135
- * A legacy user and a single-kind context of user kind that are equivalent, will hash differently.
136
- * A multi-context with one kind, and the single context with that kind are equivalent, but will hash differently.
137
- * Two equivalent contexts, with private attributes that are defined in different orders, will hash differently.
138
- *
139
- * @param {Object} context
140
- * @param {{update: (value: string) => void, digest: (format: string) => Promise<string>}} hasher
141
- * @returns {Promise<string | undefined>} The hash of the context, or undefined if the context is invalid.
142
- */
143
- function hashContext(context, hasher) {
144
- // In js-core we have legacy and non-legacy contexts hash the same. This implementation does not support that.
145
- // Because this implementation directly uses the user-provided context and doesn't manipulate it.
146
- // The js-core implementation is more conceptually correct, but it isn't a practical requirement.
147
-
148
- // This implementation additionally doesn't produce the same hash for an equivalent multi-context with one kind, and
149
- // the single context with that kind.
150
- if (!checkContext(context)) {
151
- return undefined;
152
- }
153
-
154
- try {
155
- const canonicalized = canonicalize(context);
156
-
157
- hasher.update(canonicalized);
158
-
159
- return hasher.digest('hex');
160
- } catch {
161
- return undefined;
162
- }
163
- }
164
-
165
129
  module.exports = {
166
130
  checkContext,
167
131
  getContextKeys,
168
132
  getContextKinds,
169
133
  getCanonicalKey,
170
- hashContext,
171
134
  };