launchdarkly-js-sdk-common 5.1.0 → 5.3.0

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.
@@ -2,7 +2,7 @@ version: 2
2
2
  jobs:
3
3
  build:
4
4
  docker:
5
- - image: cimg/node:12.22
5
+ - image: cimg/node:22.2.0
6
6
  steps:
7
7
  - checkout
8
8
 
@@ -0,0 +1,16 @@
1
+ name: Publish Documentation
2
+ description: 'Publish documentation to github pages.'
3
+
4
+ inputs:
5
+ github_token:
6
+ description: 'The github token to use for committing'
7
+ required: true
8
+
9
+ runs:
10
+ using: composite
11
+ steps:
12
+ - uses: launchdarkly/gh-actions/actions/publish-pages@publish-pages-v1.0.2
13
+ name: 'Publish to Github pages'
14
+ with:
15
+ docs_path: docs
16
+ github_token: ${{ inputs.github_token }}
@@ -0,0 +1,20 @@
1
+ name: Publish to NPM
2
+ description: Publish an npm package.
3
+ inputs:
4
+ prerelease:
5
+ description: 'Is this a prerelease. If so, then the latest tag will not be updated in npm.'
6
+ required: false
7
+ dry-run:
8
+ description: 'Is this a dry run. If so no package will be published.'
9
+ required: false
10
+
11
+ runs:
12
+ using: composite
13
+ steps:
14
+ - name: Publish
15
+ shell: bash
16
+ run: |
17
+ ./scripts/publish-npm.sh
18
+ env:
19
+ LD_RELEASE_IS_PRERELEASE: ${{ inputs.prerelease }}
20
+ LD_RELEASE_IS_DRYRUN: ${{ inputs.dry-run }}
@@ -0,0 +1,41 @@
1
+ name: Build and Test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths-ignore:
7
+ - '**.md' #Do not need to run CI for markdown changes.
8
+ pull_request:
9
+ branches: [main]
10
+ paths-ignore:
11
+ - '**.md'
12
+
13
+ jobs:
14
+ build-test:
15
+ strategy:
16
+ matrix:
17
+ variations: [
18
+ {os: ubuntu-latest, node: latest},
19
+ {os: ubuntu-latest, node: 18}
20
+ ]
21
+
22
+ runs-on: ${{ matrix.variations.os }}
23
+
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ - uses: actions/setup-node@v4
27
+ with:
28
+ node-version: ${{ matrix.variations.node }}
29
+ registry-url: 'https://registry.npmjs.org'
30
+ - name: Install
31
+ run: npm install
32
+ - name: Test
33
+ run: npm test
34
+ env:
35
+ JEST_JUNIT_OUTPUT_FILE: "reports/junit/js-test-results.xml"
36
+ - name: Lint
37
+ run: npm run lint:all
38
+ - name: Check typescript
39
+ run: npm run check-typescript
40
+ - name: Build Docs
41
+ run: npm run doc
@@ -0,0 +1,12 @@
1
+ name: Lint PR title
2
+
3
+ on:
4
+ pull_request_target:
5
+ types:
6
+ - opened
7
+ - edited
8
+ - synchronize
9
+
10
+ jobs:
11
+ lint-pr-title:
12
+ uses: launchdarkly/gh-actions/.github/workflows/lint-pr-title.yml@main
@@ -0,0 +1,57 @@
1
+ name: Release Please
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ release-please:
10
+ runs-on: ubuntu-latest
11
+ outputs:
12
+ release_created: ${{ steps.release.outputs.release_created }}
13
+ steps:
14
+ - uses: googleapis/release-please-action@v4
15
+ id: release
16
+ with:
17
+ token: ${{secrets.GITHUB_TOKEN}}
18
+
19
+ publish-package:
20
+ runs-on: ubuntu-latest
21
+ needs: ['release-please']
22
+ permissions:
23
+ id-token: write
24
+ contents: write
25
+ if: ${{ needs.release-please.outputs.release_created == 'true' }}
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+
29
+ - uses: actions/setup-node@v4
30
+ with:
31
+ node-version: 20.x
32
+ registry-url: 'https://registry.npmjs.org'
33
+
34
+ - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.2.0
35
+ name: 'Get NPM token'
36
+ with:
37
+ aws_assume_role: ${{ vars.AWS_ROLE_ARN }}
38
+ ssm_parameter_pairs: '/production/common/releasing/npm/token = NODE_AUTH_TOKEN'
39
+
40
+ - name: Install Dependencies
41
+ run: npm install
42
+
43
+ - id: publish-npm
44
+ name: Publish NPM Package
45
+ uses: ./.github/actions/publish-npm
46
+ with:
47
+ dry-run: 'false'
48
+ prerelease: 'false'
49
+
50
+ - name: Build Documentation
51
+ run: npm run doc
52
+
53
+ - id: publish-docs
54
+ name: Publish Documentation
55
+ uses: ./.github/actions/publish-docs
56
+ with:
57
+ github_token: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "5.3.0"
3
+ }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,35 @@
2
2
 
3
3
  All notable changes to the `launchdarkly-js-sdk-common` package will be documented in this file. Changes that affect the dependent SDKs such as `launchdarkly-js-client-sdk` should also be logged in those projects, in the next release that uses the updated version of this package. This project adheres to [Semantic Versioning](http://semver.org).
4
4
 
5
+ ## [5.3.0](https://github.com/launchdarkly/js-sdk-common/compare/launchdarkly-js-sdk-common-v5.2.0...launchdarkly-js-sdk-common-v5.3.0) (2024-06-18)
6
+
7
+
8
+ ### Features
9
+
10
+ * Add inExperiment to evaluation reason. ([#105](https://github.com/launchdarkly/js-sdk-common/issues/105)) ([cf69770](https://github.com/launchdarkly/js-sdk-common/commit/cf6977080e67e4e54f773df410a671764dbcb304))
11
+ * Allow for synchronous inspectors. ([#103](https://github.com/launchdarkly/js-sdk-common/issues/103)) ([7e490f4](https://github.com/launchdarkly/js-sdk-common/commit/7e490f479299f772a9db78efd1c2235645785250))
12
+
13
+ ## [5.2.0] - 2024-05-01
14
+ ### Added:
15
+ - Added an optional timeout to the `waitForInitialization` method. When a timeout is specified the returned promise will be rejected after the timeout elapses if the client has not finished initializing within that time. When no timeout is specified the returned promise will not be resolved or rejected until the initialization either completes or fails.
16
+
17
+ ### Changed:
18
+ - The track method now validates that the provided metricValue is a number. If a metric value is provided, and it is not a number, then a warning will be logged.
19
+
20
+ ### Fixed:
21
+ - Fixed the documentation for `evaluationReasons` for the `identify` method.
22
+
23
+ ## [5.1.0] - 2024-03-19
24
+ ### Changed:
25
+ - Redact anonymous attributes within feature events
26
+ - Always inline contexts for feature events
27
+
28
+ ### Fixed:
29
+ - Pin dev version of node to compatible types.
30
+
31
+ ### Removed:
32
+ - HTTP fallback ping
33
+
5
34
  ## [5.0.3] - 2023-03-21
6
35
  ### Changed:
7
36
  - Update `LDContext` to allow for key to be optional. This is used when making an anonymous context with a generated key.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # LaunchDarkly Javascript SDK Core Components
2
2
 
3
- [![Circle CI](https://circleci.com/gh/launchdarkly/js-sdk-common/tree/master.svg?style=svg)](https://circleci.com/gh/launchdarkly/js-sdk-common/tree/master)
3
+ [![Actions Status][ci-badge]][ci]
4
4
 
5
5
  ## LaunchDarkly overview
6
6
 
@@ -33,3 +33,6 @@ We encourage pull requests and other contributions from the community. Check out
33
33
  * [docs.launchdarkly.com](https://docs.launchdarkly.com/ "LaunchDarkly Documentation") for our documentation and SDK reference guides
34
34
  * [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ "LaunchDarkly API Documentation") for our API documentation
35
35
  * [blog.launchdarkly.com](https://blog.launchdarkly.com/ "LaunchDarkly Blog Documentation") for the latest product updates
36
+
37
+ [ci-badge]: https://github.com/launchdarkly/js-sdk-common/actions/workflows/ci.yml/badge.svg
38
+ [ci]: https://github.com/launchdarkly/js-sdk-common/actions/workflows/ci.yml
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "launchdarkly-js-sdk-common",
3
- "version": "5.1.0",
3
+ "version": "5.3.0",
4
4
  "description": "LaunchDarkly SDK for JavaScript - common code",
5
5
  "author": "LaunchDarkly <team@launchdarkly.com>",
6
6
  "license": "Apache-2.0",
@@ -17,7 +17,8 @@
17
17
  "format:test:md": "prettier --parser markdown --ignore-path .prettierignore --list-different '*.md'",
18
18
  "format:test:js": "prettier --ignore-path .prettierignore --list-different 'src/**/*.js'",
19
19
  "test": "cross-env NODE_ENV=test jest",
20
- "check-typescript": "node_modules/typescript/bin/tsc"
20
+ "check-typescript": "tsc",
21
+ "doc": "typedoc"
21
22
  },
22
23
  "devDependencies": {
23
24
  "@babel/cli": "^7.8.4",
@@ -38,12 +39,13 @@
38
39
  "eslint-formatter-pretty": "^1.3.0",
39
40
  "eslint-plugin-babel": "^5.0.0",
40
41
  "eslint-plugin-prettier": "^2.6.0",
41
- "jest": "^25.5.4",
42
+ "jest": "^26.6.3",
42
43
  "jsdom": "^11.11.0",
43
44
  "launchdarkly-js-test-helpers": "1.1.0",
44
- "prettier": "1.11.1",
45
+ "prettier": "1.19.1",
45
46
  "readline-sync": "^1.4.9",
46
- "typescript": "~4.4.4"
47
+ "typescript": "~5.4.5",
48
+ "typedoc": "^0.25.13"
47
49
  },
48
50
  "dependencies": {
49
51
  "base64-js": "^1.3.0",
@@ -0,0 +1,8 @@
1
+ {
2
+ "bootstrap-sha": "d49ca41718a593c071874950d301f2f00c71a371",
3
+ "packages": {
4
+ ".": {
5
+ "release-type": "node"
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env bash
2
+ if $LD_RELEASE_IS_DRYRUN ; then
3
+ echo "Doing a dry run of publishing."
4
+ else
5
+ if $LD_RELEASE_IS_PRERELEASE ; then
6
+ echo "Publishing with prerelease tag."
7
+ npm publish --tag prerelease --provenance --access public || { echo "npm publish failed" >&2; exit 1; }
8
+ else
9
+ npm publish --provenance --access public || { echo "npm publish failed" >&2; exit 1; }
10
+ fi
11
+ fi
@@ -118,8 +118,8 @@ function ContextFilter(config) {
118
118
  filtered._meta = filtered._meta || {};
119
119
  // If any private attributes started with '/' we need to convert them to references, otherwise the '/' will
120
120
  // cause the literal to incorrectly be treated as a reference.
121
- filtered._meta.privateAttributes = user.privateAttributeNames.map(
122
- literal => (literal.startsWith('/') ? AttributeReference.literalToReference(literal) : literal)
121
+ filtered._meta.privateAttributes = user.privateAttributeNames.map(literal =>
122
+ literal.startsWith('/') ? AttributeReference.literalToReference(literal) : literal
123
123
  );
124
124
  }
125
125
 
@@ -22,6 +22,9 @@ function InspectorManager(inspectors, logger) {
22
22
 
23
23
  /**
24
24
  * Collection of inspectors keyed by type.
25
+ *
26
+ * Inspectors are async by default.
27
+ *
25
28
  * @type {{[type: string]: object[]}}
26
29
  */
27
30
  const inspectorsByType = {
@@ -30,14 +33,30 @@ function InspectorManager(inspectors, logger) {
30
33
  [InspectorTypes.flagDetailChanged]: [],
31
34
  [InspectorTypes.clientIdentityChanged]: [],
32
35
  };
36
+ /**
37
+ * Collection synchronous of inspectors keyed by type.
38
+ *
39
+ * @type {{[type: string]: object[]}}
40
+ */
41
+ const synchronousInspectorsByType = {
42
+ [InspectorTypes.flagUsed]: [],
43
+ [InspectorTypes.flagDetailsChanged]: [],
44
+ [InspectorTypes.flagDetailChanged]: [],
45
+ [InspectorTypes.clientIdentityChanged]: [],
46
+ };
33
47
 
34
48
  const safeInspectors = inspectors && inspectors.map(inspector => SafeInspector(inspector, logger));
35
49
 
36
50
  safeInspectors &&
37
51
  safeInspectors.forEach(safeInspector => {
38
52
  // Only add inspectors of supported types.
39
- if (Object.prototype.hasOwnProperty.call(inspectorsByType, safeInspector.type)) {
53
+ if (Object.prototype.hasOwnProperty.call(inspectorsByType, safeInspector.type) && !safeInspector.synchronous) {
40
54
  inspectorsByType[safeInspector.type].push(safeInspector);
55
+ } else if (
56
+ Object.prototype.hasOwnProperty.call(synchronousInspectorsByType, safeInspector.type) &&
57
+ safeInspector.synchronous
58
+ ) {
59
+ synchronousInspectorsByType[safeInspector.type].push(safeInspector);
41
60
  } else {
42
61
  logger.warn(messages.invalidInspector(safeInspector.type, safeInspector.name));
43
62
  }
@@ -49,7 +68,9 @@ function InspectorManager(inspectors, logger) {
49
68
  * @param {string} type The type of the inspector to check.
50
69
  * @returns True if there are any inspectors of that type registered.
51
70
  */
52
- manager.hasListeners = type => inspectorsByType[type] && inspectorsByType[type].length;
71
+ manager.hasListeners = type =>
72
+ (inspectorsByType[type] && inspectorsByType[type].length) ||
73
+ (synchronousInspectorsByType[type] && synchronousInspectorsByType[type].length);
53
74
 
54
75
  /**
55
76
  * Notify registered inspectors of a flag being used.
@@ -61,9 +82,13 @@ function InspectorManager(inspectors, logger) {
61
82
  * @param {Object} context The LDContext for the flag.
62
83
  */
63
84
  manager.onFlagUsed = (flagKey, detail, context) => {
64
- if (inspectorsByType[InspectorTypes.flagUsed].length) {
85
+ const type = InspectorTypes.flagUsed;
86
+ if (synchronousInspectorsByType[type].length) {
87
+ synchronousInspectorsByType[type].forEach(inspector => inspector.method(flagKey, detail, context));
88
+ }
89
+ if (inspectorsByType[type].length) {
65
90
  onNextTick(() => {
66
- inspectorsByType[InspectorTypes.flagUsed].forEach(inspector => inspector.method(flagKey, detail, context));
91
+ inspectorsByType[type].forEach(inspector => inspector.method(flagKey, detail, context));
67
92
  });
68
93
  }
69
94
  };
@@ -76,9 +101,13 @@ function InspectorManager(inspectors, logger) {
76
101
  * @param {Record<string, Object>} flags The current flags as a Record<string, LDEvaluationDetail>.
77
102
  */
78
103
  manager.onFlags = flags => {
79
- if (inspectorsByType[InspectorTypes.flagDetailsChanged].length) {
104
+ const type = InspectorTypes.flagDetailsChanged;
105
+ if (synchronousInspectorsByType[type].length) {
106
+ synchronousInspectorsByType[type].forEach(inspector => inspector.method(flags));
107
+ }
108
+ if (inspectorsByType[type].length) {
80
109
  onNextTick(() => {
81
- inspectorsByType[InspectorTypes.flagDetailsChanged].forEach(inspector => inspector.method(flags));
110
+ inspectorsByType[type].forEach(inspector => inspector.method(flags));
82
111
  });
83
112
  }
84
113
  };
@@ -92,9 +121,13 @@ function InspectorManager(inspectors, logger) {
92
121
  * @param {Object} flag An `LDEvaluationDetail` for the flag.
93
122
  */
94
123
  manager.onFlagChanged = (flagKey, flag) => {
95
- if (inspectorsByType[InspectorTypes.flagDetailChanged].length) {
124
+ const type = InspectorTypes.flagDetailChanged;
125
+ if (synchronousInspectorsByType[type].length) {
126
+ synchronousInspectorsByType[type].forEach(inspector => inspector.method(flagKey, flag));
127
+ }
128
+ if (inspectorsByType[type].length) {
96
129
  onNextTick(() => {
97
- inspectorsByType[InspectorTypes.flagDetailChanged].forEach(inspector => inspector.method(flagKey, flag));
130
+ inspectorsByType[type].forEach(inspector => inspector.method(flagKey, flag));
98
131
  });
99
132
  }
100
133
  };
@@ -107,9 +140,13 @@ function InspectorManager(inspectors, logger) {
107
140
  * @param {Object} context The `LDContext` which is now identified.
108
141
  */
109
142
  manager.onIdentityChanged = context => {
110
- if (inspectorsByType[InspectorTypes.clientIdentityChanged].length) {
143
+ const type = InspectorTypes.clientIdentityChanged;
144
+ if (synchronousInspectorsByType[type].length) {
145
+ synchronousInspectorsByType[type].forEach(inspector => inspector.method(context));
146
+ }
147
+ if (inspectorsByType[type].length) {
111
148
  onNextTick(() => {
112
- inspectorsByType[InspectorTypes.clientIdentityChanged].forEach(inspector => inspector.method(context));
149
+ inspectorsByType[type].forEach(inspector => inspector.method(context));
113
150
  });
114
151
  }
115
152
  };
@@ -9,6 +9,7 @@ function SafeInspector(inspector, logger) {
9
9
  const wrapper = {
10
10
  type: inspector.type,
11
11
  name: inspector.name,
12
+ synchronous: inspector.synchronous,
12
13
  };
13
14
 
14
15
  wrapper.method = (...args) => {
@@ -10,7 +10,10 @@ import { MockEventSender } from './testUtils';
10
10
  // tests; here, we use a mock EventSender.
11
11
 
12
12
  describe.each([
13
- [{ key: 'userKey', name: 'Red' }, { key: 'userKey', kind: 'user', _meta: { redactedAttributes: ['/name'] } }],
13
+ [
14
+ { key: 'userKey', name: 'Red' },
15
+ { key: 'userKey', kind: 'user', _meta: { redactedAttributes: ['/name'] } },
16
+ ],
14
17
  [
15
18
  { kind: 'user', key: 'userKey', name: 'Red' },
16
19
  { key: 'userKey', kind: 'user', _meta: { redactedAttributes: ['/name'] } },
@@ -99,7 +99,10 @@ describe('EventSummarizer', () => {
99
99
  key1: {
100
100
  contextKinds: ['user'],
101
101
  default: 111,
102
- counters: [{ variation: 0, value: 100, version: 11, count: 1 }, { value: 111, version: 11, count: 2 }],
102
+ counters: [
103
+ { variation: 0, value: 100, version: 11, count: 1 },
104
+ { value: 111, version: 11, count: 2 },
105
+ ],
103
106
  },
104
107
  };
105
108
  expect(data.features).toEqual(expectedFeatures);
@@ -28,7 +28,7 @@ describe('given an inspector manager with no registered inspectors', () => {
28
28
  });
29
29
  });
30
30
 
31
- describe('given an inspector with callbacks of every type', () => {
31
+ describe.each([true, false])('given an inspector with callbacks of every type: synchronous: %p', synchronous => {
32
32
  /**
33
33
  * @type {AsyncQueue}
34
34
  */
@@ -39,6 +39,7 @@ describe('given an inspector with callbacks of every type', () => {
39
39
  {
40
40
  type: 'flag-used',
41
41
  name: 'my-flag-used-inspector',
42
+ synchronous,
42
43
  method: (flagKey, flagDetail, context) => {
43
44
  eventQueue.add({ type: 'flag-used', flagKey, flagDetail, context });
44
45
  },
@@ -47,6 +48,7 @@ describe('given an inspector with callbacks of every type', () => {
47
48
  {
48
49
  type: 'flag-used',
49
50
  name: 'my-other-flag-used-inspector',
51
+ synchronous,
50
52
  method: (flagKey, flagDetail, context) => {
51
53
  eventQueue.add({ type: 'flag-used', flagKey, flagDetail, context });
52
54
  },
@@ -54,6 +56,7 @@ describe('given an inspector with callbacks of every type', () => {
54
56
  {
55
57
  type: 'flag-details-changed',
56
58
  name: 'my-flag-details-inspector',
59
+ synchronous,
57
60
  method: details => {
58
61
  eventQueue.add({
59
62
  type: 'flag-details-changed',
@@ -64,6 +67,7 @@ describe('given an inspector with callbacks of every type', () => {
64
67
  {
65
68
  type: 'flag-detail-changed',
66
69
  name: 'my-flag-detail-inspector',
70
+ synchronous,
67
71
  method: (flagKey, flagDetail) => {
68
72
  eventQueue.add({
69
73
  type: 'flag-detail-changed',
@@ -75,6 +79,7 @@ describe('given an inspector with callbacks of every type', () => {
75
79
  {
76
80
  type: 'client-identity-changed',
77
81
  name: 'my-identity-inspector',
82
+ synchronous,
78
83
  method: context => {
79
84
  eventQueue.add({
80
85
  type: 'client-identity-changed',
@@ -85,6 +90,7 @@ describe('given an inspector with callbacks of every type', () => {
85
90
  // Invalid inspector shouldn't have an effect.
86
91
  {
87
92
  type: 'potato',
93
+ synchronous,
88
94
  name: 'my-potato-inspector',
89
95
  method: () => {},
90
96
  },