capacitor-launchdarkly 0.0.0-stage → 0.1.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.
Files changed (40) hide show
  1. package/CapacitorLaunchdarkly.podspec +20 -0
  2. package/LICENSE +21 -0
  3. package/Package.swift +32 -0
  4. package/README.md +747 -2
  5. package/android/build.gradle +68 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/kotlin/io/github/maximbelov/launchdarkly/Json.kt +67 -0
  8. package/android/src/main/kotlin/io/github/maximbelov/launchdarkly/LaunchDarklyClient.kt +250 -0
  9. package/android/src/main/kotlin/io/github/maximbelov/launchdarkly/LaunchDarklyPlugin.kt +148 -0
  10. package/android/src/main/kotlin/io/github/maximbelov/launchdarkly/LdConfigFactory.kt +34 -0
  11. package/android/src/main/kotlin/io/github/maximbelov/launchdarkly/StartOptions.kt +76 -0
  12. package/android/src/main/kotlin/io/github/maximbelov/launchdarkly/StoredContext.kt +47 -0
  13. package/android/src/main/res/.gitkeep +0 -0
  14. package/dist/docs.json +1174 -0
  15. package/dist/esm/definitions.d.ts +520 -0
  16. package/dist/esm/definitions.js +2 -0
  17. package/dist/esm/definitions.js.map +1 -0
  18. package/dist/esm/index.d.ts +4 -0
  19. package/dist/esm/index.js +7 -0
  20. package/dist/esm/index.js.map +1 -0
  21. package/dist/esm/web.d.ts +46 -0
  22. package/dist/esm/web.js +204 -0
  23. package/dist/esm/web.js.map +1 -0
  24. package/dist/plugin.cjs.js +221 -0
  25. package/dist/plugin.cjs.js.map +1 -0
  26. package/dist/plugin.js +224 -0
  27. package/dist/plugin.js.map +1 -0
  28. package/ios/Sources/LaunchDarklyPlugin/ConfigFactory.swift +29 -0
  29. package/ios/Sources/LaunchDarklyPlugin/JSONValues.swift +68 -0
  30. package/ios/Sources/LaunchDarklyPlugin/LaunchDarklyClient.swift +165 -0
  31. package/ios/Sources/LaunchDarklyPlugin/LaunchDarklyPlugin.swift +169 -0
  32. package/ios/Sources/LaunchDarklyPlugin/PrivacyInfo.xcprivacy +23 -0
  33. package/ios/Sources/LaunchDarklyPlugin/StartOptions.swift +79 -0
  34. package/ios/Sources/LaunchDarklyPlugin/StoredContext.swift +43 -0
  35. package/ios/Tests/LaunchDarklyPluginTests/BillingSafetyTests.swift +53 -0
  36. package/ios/Tests/LaunchDarklyPluginTests/JSONValuesTests.swift +67 -0
  37. package/ios/Tests/LaunchDarklyPluginTests/LaunchDarklyPluginTests.swift +37 -0
  38. package/ios/Tests/LaunchDarklyPluginTests/PluginConfigurationTests.swift +52 -0
  39. package/ios/Tests/LaunchDarklyPluginTests/StartContextTests.swift +36 -0
  40. package/package.json +105 -4
package/README.md CHANGED
@@ -1,3 +1,748 @@
1
- # Temporary Holding Version
1
+ # capacitor-launchdarkly
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [LaunchDarkly](https://launchdarkly.com) feature flags for Capacitor apps, with one API on iOS, Android and the web. On iOS and
4
+ Android it runs LaunchDarkly's native client SDKs and can start the client natively as the app launches, before the web view
5
+ runs any of your code; on the web it runs LaunchDarkly's browser SDK.
6
+
7
+ | | iOS | Android | Web |
8
+ | ------------------------- | -------------------------------------- | -------------------------------------------- | ----------------------------------------------- |
9
+ | SDK | LaunchDarkly iOS client SDK 11.8.0 | LaunchDarkly Android client SDK 5.18.0 | `@launchdarkly/js-client-sdk` 4.x (you install) |
10
+ | Key | mobile key (`mob-…`) | mobile key (`mob-…`) | client-side ID |
11
+ | Where the client starts | natively at launch, or `initialize()` | natively at launch, or `initialize()` | `initialize()` |
12
+ | Stored context | `UserDefaults` | `SharedPreferences` | `localStorage` |
13
+ | `connectionModeChange` | yes | yes | never fired |
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install capacitor-launchdarkly
19
+ npx cap sync
20
+ ```
21
+
22
+ Requires Capacitor 8. Android 7.0 (API 24) or newer, iOS 15 or newer. On iOS the plugin is a Swift package; CocoaPods works too.
23
+ The native SDKs come with the plugin, pinned to the versions above. An Android app can move its SDK with
24
+ `launchDarklyAndroidVersion` in `android/variables.gradle`.
25
+
26
+ To use the plugin **on the web** as well, install LaunchDarkly's browser SDK next to it:
27
+
28
+ ```bash
29
+ npm install @launchdarkly/js-client-sdk
30
+ ```
31
+
32
+ It is an optional peer dependency. The plugin imports it only when `initialize()` runs in a browser, so a native-only app never
33
+ loads it. Without it, `initialize()` on the web rejects with an error saying to install it.
34
+
35
+ ## Native start-up
36
+
37
+ Put the mobile key in `capacitor.config` and the client starts as the app launches:
38
+
39
+ ```json
40
+ {
41
+ "plugins": {
42
+ "LaunchDarkly": {
43
+ "mobileKey": "mob-00000000-0000-0000-0000-000000000000",
44
+ "initialContext": { "kind": "user", "key": "anonymous_user", "anonymous": true },
45
+ "startWaitSeconds": 5
46
+ }
47
+ }
48
+ }
49
+ ```
50
+
51
+ Why start natively rather than from JavaScript:
52
+
53
+ - **The flags are there at launch.** The plugin's `load()` runs while Capacitor creates its bridge, before the web view loads
54
+ your app, so the first request to LaunchDarkly is on its way (or already answered) when your code first asks.
55
+ - **No anonymous flip.** Every `identify()` stores the context it switched to, and the next start-up begins with that stored
56
+ context instead of `initialContext`. A signed-in user stays signed in as far as the flags are concerned, from the first frame,
57
+ and the SDK serves that context's cached flags while it fetches fresh ones.
58
+ - **One client per process.** The client belongs to the process, not to the web view: a recreated activity or a reloaded page
59
+ finds it running. A later `initialize()` with the same key changes nothing and answers the current state.
60
+
61
+ The web layer then reads what is already there:
62
+
63
+ ```typescript
64
+ import { LaunchDarkly } from 'capacitor-launchdarkly';
65
+
66
+ const { initialized, context, flags } = await LaunchDarkly.getState(); // waits for the start-up if it is still running
67
+
68
+ await LaunchDarkly.addListener('flagChange', ({ key, value, previous }) => {
69
+ console.log(`${key} changed from ${JSON.stringify(previous)} to ${JSON.stringify(value)}`);
70
+ });
71
+
72
+ const { value } = await LaunchDarkly.variation({ key: 'new-checkout', defaultValue: false });
73
+ ```
74
+
75
+ On sign-in and sign-out, switch the context. The plugin stores it, so the next launch starts with it:
76
+
77
+ ```typescript
78
+ await LaunchDarkly.identify({ context: { kind: 'user', key: user.id, name: user.name } });
79
+ // signing out: back to the shared anonymous context, which becomes the stored one
80
+ await LaunchDarkly.identify({ context: { kind: 'user', key: 'anonymous_user', anonymous: true } });
81
+ ```
82
+
83
+ `clearStoredContext()` forgets the stored context altogether, so the next start-up uses `initialContext` again.
84
+
85
+ Do not commit a real mobile key to a public repository. A mobile key is not a secret in the way a server-side SDK key is (it
86
+ ships inside every copy of your app), but it is still yours: keep it in your app's own configuration.
87
+
88
+ Without a `mobileKey` in the config, nothing starts natively, and the app calls `initialize()` with one, as it does on the web.
89
+
90
+ ## Starting from JavaScript, and on the web
91
+
92
+ ```typescript
93
+ import { Capacitor } from '@capacitor/core';
94
+
95
+ await LaunchDarkly.initialize({
96
+ mobileKey: 'mob-…', // used on iOS and Android
97
+ clientSideId: '…', // used on the web
98
+ context: { kind: 'user', key: 'anonymous_user', anonymous: true },
99
+ options: { startWaitSeconds: 5, bootstrap: 'localStorage' },
100
+ });
101
+ ```
102
+
103
+ `initialize()` accepts both keys, and each platform takes its own. The context you pass plays the part of `initialContext`: a
104
+ context an earlier `identify()` stored wins over it, on the web too.
105
+
106
+ On the web, `options.bootstrap` decides what the client shows before LaunchDarkly answers: `'localStorage'` starts from the flags
107
+ the browser SDK cached for that context on an earlier visit (the SDK does this on its own), and a flag object, for example one
108
+ your server rendered into the page, starts from those values.
109
+
110
+ What has no direct equivalent in the browser SDK:
111
+
112
+ - **`setOnline()`** – the browser SDK has no offline mode. Offline closes the stream, so no flag updates arrive; online opens it
113
+ again when streaming is on. Analytics events are still sent.
114
+ - **`offline: true`** at start – starts without a stream; the first flags are still fetched once.
115
+ - **`setStreaming()`** – the browser SDK switches in place. The native SDKs cannot change their data source while they run, so
116
+ there the plugin closes the client and starts it again with the current context.
117
+ - **`connectionModeChange`** – never fired on the web.
118
+
119
+ ## Contexts, and what LaunchDarkly bills
120
+
121
+ Contexts use the JSON shape of LaunchDarkly's SDKs: a single context `{ kind, key, name?, anonymous?, ...attributes }`, custom
122
+ attributes of any JSON type (nested objects and arrays included), private attributes in `_meta.privateAttributes`, and
123
+ multi-contexts `{ kind: 'multi', user: {...}, organization: {...} }`. The native side hands the JSON to the SDK's own parser
124
+ (`JsonSerialization` on Android, `LDContext`'s `Decodable` on iOS), so every field means what it means to LaunchDarkly.
125
+
126
+ LaunchDarkly bills client-side SDKs by monthly active users: the number of unique context keys, counted for the context kind that
127
+ has the most ([how billing is calculated](https://launchdarkly.com/docs/home/account/calculating-billing)). The plugin makes sure
128
+ that only the keys your app passes are ever counted:
129
+
130
+ - **Automatic environment attributes are off**, on both native platforms (`AutoEnvAttributes.Disabled` / `.disabled`), and the
131
+ browser SDK never adds them. With them on, the SDK adds `ld_application` and `ld_device` contexts to every evaluation, and the
132
+ `ld_device` key is generated per install, which counts every device as a context of its own
133
+ ([automatic environment attributes](https://launchdarkly.com/docs/sdk/features/environment-attributes)). This is not an
134
+ option.
135
+ - **Keys are never generated.** An anonymous context keeps the key you give it, so an app can use one shared key (for example
136
+ `anonymous_user`) for every signed-out user. Android is configured with `generateAnonymousKeys(false)`; the iOS SDK generates a
137
+ key only for a context built without one, which the plugin never does; and a context without a key is refused on every
138
+ platform, because the browser SDK would otherwise generate one for an anonymous context.
139
+
140
+ Native unit tests pin both: the built configuration has environment attributes off and key generation off, and an anonymous
141
+ context comes out of the SDK's own context processing with its key (see "Testing").
142
+
143
+ `sendEventsOnlyForVariation`, an option of the old JavaScript SDK, has no equivalent in either native SDK. `getState()` and
144
+ `allFlags()` read the SDK's store and send no evaluation events on any platform; `variation()` sends one, as any SDK evaluation
145
+ does.
146
+
147
+ ## iOS privacy manifest
148
+
149
+ The plugin keeps the stored context in `UserDefaults`, one of Apple's
150
+ [required-reason APIs](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api),
151
+ and ships its own `PrivacyInfo.xcprivacy` declaring `NSPrivacyAccessedAPICategoryUserDefaults` with reason `CA92.1`. The
152
+ LaunchDarkly SDK ships its own manifest too. Declare the same category in your app's manifest if your app does not already.
153
+
154
+ ## Configuration
155
+
156
+ <docgen-config>
157
+ <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
158
+
159
+ Starts the native LaunchDarkly client as the app launches, before the
160
+ web view runs any app code. Leave `mobileKey` out to start it from
161
+ JavaScript with `initialize()` instead. iOS and Android only.
162
+
163
+ | Prop | Type | Description | Default | Since |
164
+ | ----------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ----- |
165
+ | **`mobileKey`** | <code>string</code> | The mobile key of the LaunchDarkly environment. Without it there is no native start-up, and `initialize()` starts the client instead. | | 0.1.0 |
166
+ | **`initialContext`** | <code><a href="#ldcontext">LDContext</a></code> | The context the native start-up uses when no context was stored by an earlier `identify()`. | <code>{ kind: 'user', key: 'anonymous_user', anonymous: true }</code> | 0.1.0 |
167
+ | **`streaming`** | <code>boolean</code> | Streams flag updates when `true`, polls when `false`. | <code>true</code> | 0.1.0 |
168
+ | **`evaluationReasons`** | <code>boolean</code> | Asks LaunchDarkly for the reason behind each evaluation. | <code>false</code> | 0.1.0 |
169
+ | **`offline`** | <code>boolean</code> | Starts the client offline, until `setOnline({ online: true })`. | <code>false</code> | 0.1.0 |
170
+ | **`startWaitSeconds`** | <code>number</code> | How long the start waits for the first set of flags, in seconds. | <code>5</code> | 0.1.0 |
171
+ | **`application`** | <code><a href="#ldapplicationinfo">LDApplicationInfo</a></code> | Application metadata sent to LaunchDarkly with every request. | | 0.1.0 |
172
+
173
+ ### Examples
174
+
175
+ In `capacitor.config.json`:
176
+
177
+ ```json
178
+ {
179
+ "plugins": {
180
+ "LaunchDarkly": {
181
+ "mobileKey": "mob-00000000-0000-0000-0000-000000000000",
182
+ "initialContext": { "kind": "user", "key": "anonymous_user", "anonymous": true },
183
+ "streaming": true,
184
+ "evaluationReasons": false,
185
+ "offline": false,
186
+ "startWaitSeconds": 5,
187
+ "application": { "id": "com.example.plugin", "version": "1.0.0" }
188
+ }
189
+ }
190
+ }
191
+ ```
192
+
193
+ In `capacitor.config.ts`:
194
+
195
+ ```ts
196
+ /// <reference types="capacitor-launchdarkly" />
197
+
198
+ import { CapacitorConfig } from '@capacitor/cli';
199
+
200
+ const config: CapacitorConfig = {
201
+ plugins: {
202
+ LaunchDarkly: {
203
+ mobileKey: "mob-00000000-0000-0000-0000-000000000000",
204
+ initialContext: { "kind": "user", "key": "anonymous_user", "anonymous": true },
205
+ streaming: true,
206
+ evaluationReasons: false,
207
+ offline: false,
208
+ startWaitSeconds: 5,
209
+ application: { "id": "com.example.plugin", "version": "1.0.0" },
210
+ },
211
+ },
212
+ };
213
+
214
+ export default config;
215
+ ```
216
+
217
+ </docgen-config>
218
+
219
+ ## API
220
+
221
+ <docgen-index>
222
+
223
+ * [`initialize(...)`](#initialize)
224
+ * [`waitForInitialization(...)`](#waitforinitialization)
225
+ * [`getState()`](#getstate)
226
+ * [`identify(...)`](#identify)
227
+ * [`variation(...)`](#variation)
228
+ * [`allFlags()`](#allflags)
229
+ * [`setStreaming(...)`](#setstreaming)
230
+ * [`setOnline(...)`](#setonline)
231
+ * [`flush()`](#flush)
232
+ * [`close()`](#close)
233
+ * [`clearStoredContext()`](#clearstoredcontext)
234
+ * [`addListener('flagChange', ...)`](#addlistenerflagchange-)
235
+ * [`addListener('connectionModeChange', ...)`](#addlistenerconnectionmodechange-)
236
+ * [`removeAllListeners()`](#removealllisteners)
237
+ * [Interfaces](#interfaces)
238
+ * [Type Aliases](#type-aliases)
239
+
240
+ </docgen-index>
241
+
242
+ <docgen-api>
243
+ <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
244
+
245
+ ### initialize(...)
246
+
247
+ ```typescript
248
+ initialize(options: InitializeOptions) => Promise<LaunchDarklyState>
249
+ ```
250
+
251
+ Starts the client from JavaScript and waits for the first set of flags or
252
+ `options.startWaitSeconds` (5 by default), whichever comes first.
253
+
254
+ - **iOS, Android**: takes `mobileKey`. Needed only by an app that does not
255
+ put a `mobileKey` in `capacitor.config`; with one there, the client is
256
+ already running when the app's code starts, and this call only answers
257
+ its state.
258
+ - **Web**: takes `clientSideId`, and is the only way to start the client.
259
+ Needs `@launchdarkly/js-client-sdk` installed (see the README).
260
+
261
+ `context` plays the part of `initialContext`: when an earlier `identify()`
262
+ stored a context, the client starts with the stored one instead, so the
263
+ app does not flip to an anonymous context for a moment after a restart.
264
+
265
+ There is one client per process (per page on the web). A second call with
266
+ the same key, or a call after the native start-up already started a
267
+ client with that key, changes nothing: the client is not created again,
268
+ `context` and `options` are ignored, and the current state is answered.
269
+ Use `identify()` to switch the context. A call with a different key closes
270
+ the running client and starts a new one.
271
+
272
+ | Param | Type |
273
+ | ------------- | --------------------------------------------------------------- |
274
+ | **`options`** | <code><a href="#initializeoptions">InitializeOptions</a></code> |
275
+
276
+ **Returns:** <code>Promise&lt;<a href="#launchdarklystate">LaunchDarklyState</a>&gt;</code>
277
+
278
+ **Since:** 0.1.0
279
+
280
+ --------------------
281
+
282
+
283
+ ### waitForInitialization(...)
284
+
285
+ ```typescript
286
+ waitForInitialization(options?: WaitForInitializationOptions | undefined) => Promise<WaitForInitializationResult>
287
+ ```
288
+
289
+ Answers whether the client has received its first set of flags, waiting
290
+ up to `timeoutSeconds` (5 by default) for that when it has not yet.
291
+
292
+ Resolves `{ initialized: false }` straight away when no client was
293
+ started, neither natively nor with `initialize()`.
294
+
295
+ | Param | Type |
296
+ | ------------- | ------------------------------------------------------------------------------------- |
297
+ | **`options`** | <code><a href="#waitforinitializationoptions">WaitForInitializationOptions</a></code> |
298
+
299
+ **Returns:** <code>Promise&lt;<a href="#waitforinitializationresult">WaitForInitializationResult</a>&gt;</code>
300
+
301
+ **Since:** 0.1.0
302
+
303
+ --------------------
304
+
305
+
306
+ ### getState()
307
+
308
+ ```typescript
309
+ getState() => Promise<LaunchDarklyState>
310
+ ```
311
+
312
+ The state of the native client: whether it has its flags, the current
313
+ context and the value of every flag, read from the native SDK's own
314
+ store. Meant for bootstrapping the web layer at start without a second
315
+ network round trip.
316
+
317
+ While the native start-up is still running, this waits until it is done:
318
+ until the first set of flags is in, or `startWaitSeconds` has passed.
319
+ With no client at all it answers `initialized: false` and no flags.
320
+
321
+ Reading the flags this way sends no evaluation events.
322
+
323
+ **Returns:** <code>Promise&lt;<a href="#launchdarklystate">LaunchDarklyState</a>&gt;</code>
324
+
325
+ **Since:** 0.1.0
326
+
327
+ --------------------
328
+
329
+
330
+ ### identify(...)
331
+
332
+ ```typescript
333
+ identify(options: IdentifyOptions) => Promise<IdentifyResult>
334
+ ```
335
+
336
+ Switches the client to another context and resolves when the flags for
337
+ it are loaded, or when `timeoutSeconds` (5 by default) has passed.
338
+
339
+ The context the SDK accepted is stored natively, under a key of the
340
+ plugin's own, and the next native start-up begins with it instead of
341
+ `initialContext`. So an app logs out by identifying its anonymous
342
+ context, which then becomes the stored one; `clearStoredContext()`
343
+ forgets it altogether.
344
+
345
+ An anonymous context keeps the key it is given. The plugin never asks the
346
+ SDK to generate one.
347
+
348
+ | Param | Type |
349
+ | ------------- | ----------------------------------------------------------- |
350
+ | **`options`** | <code><a href="#identifyoptions">IdentifyOptions</a></code> |
351
+
352
+ **Returns:** <code>Promise&lt;<a href="#identifyresult">IdentifyResult</a>&gt;</code>
353
+
354
+ **Since:** 0.1.0
355
+
356
+ --------------------
357
+
358
+
359
+ ### variation(...)
360
+
361
+ ```typescript
362
+ variation(options: VariationOptions) => Promise<VariationResult>
363
+ ```
364
+
365
+ Evaluates one flag for the current context and answers its value, of any
366
+ JSON type, or `defaultValue` (`null` when left out) when the flag is
367
+ unknown or the client has not started. Goes through the SDK's JSON
368
+ variation call, so it records an evaluation event like any SDK call.
369
+
370
+ | Param | Type |
371
+ | ------------- | ------------------------------------------------------------- |
372
+ | **`options`** | <code><a href="#variationoptions">VariationOptions</a></code> |
373
+
374
+ **Returns:** <code>Promise&lt;<a href="#variationresult">VariationResult</a>&gt;</code>
375
+
376
+ **Since:** 0.1.0
377
+
378
+ --------------------
379
+
380
+
381
+ ### allFlags()
382
+
383
+ ```typescript
384
+ allFlags() => Promise<AllFlagsResult>
385
+ ```
386
+
387
+ The value of every flag for the current context, from the native SDK's
388
+ store. Sends no evaluation events. Empty when the client has not started.
389
+
390
+ **Returns:** <code>Promise&lt;<a href="#allflagsresult">AllFlagsResult</a>&gt;</code>
391
+
392
+ **Since:** 0.1.0
393
+
394
+ --------------------
395
+
396
+
397
+ ### setStreaming(...)
398
+
399
+ ```typescript
400
+ setStreaming(options: SetStreamingOptions) => Promise<void>
401
+ ```
402
+
403
+ Switches between streaming and polling. Neither native SDK can change its
404
+ data source while it runs, so the plugin closes the client and starts it
405
+ again with the same mobile key, options and current context, then waits as
406
+ at start-up. Flag listeners stay registered. The web SDK switches in place
407
+ (`setStreaming()`).
408
+
409
+ | Param | Type |
410
+ | ------------- | ------------------------------------------------------------------- |
411
+ | **`options`** | <code><a href="#setstreamingoptions">SetStreamingOptions</a></code> |
412
+
413
+ **Since:** 0.1.0
414
+
415
+ --------------------
416
+
417
+
418
+ ### setOnline(...)
419
+
420
+ ```typescript
421
+ setOnline(options: SetOnlineOptions) => Promise<void>
422
+ ```
423
+
424
+ Takes the client online or offline (`setOnline()` / `setOffline()` on
425
+ Android, `setOnline(_:)` on iOS). Offline, the client keeps answering the
426
+ values it has.
427
+
428
+ The web SDK has no offline mode. There, going offline closes the stream so
429
+ no flag updates arrive, and going online opens it again if `streaming` is
430
+ on; analytics events are still sent.
431
+
432
+ | Param | Type |
433
+ | ------------- | ------------------------------------------------------------- |
434
+ | **`options`** | <code><a href="#setonlineoptions">SetOnlineOptions</a></code> |
435
+
436
+ **Since:** 0.1.0
437
+
438
+ --------------------
439
+
440
+
441
+ ### flush()
442
+
443
+ ```typescript
444
+ flush() => Promise<void>
445
+ ```
446
+
447
+ Sends the analytics events the client has queued.
448
+
449
+ **Since:** 0.1.0
450
+
451
+ --------------------
452
+
453
+
454
+ ### close()
455
+
456
+ ```typescript
457
+ close() => Promise<void>
458
+ ```
459
+
460
+ Closes the client. It can be started again with `initialize()`; until
461
+ then, `variation()` answers the default value. The stored context is
462
+ kept.
463
+
464
+ **Since:** 0.1.0
465
+
466
+ --------------------
467
+
468
+
469
+ ### clearStoredContext()
470
+
471
+ ```typescript
472
+ clearStoredContext() => Promise<void>
473
+ ```
474
+
475
+ Forgets the context `identify()` stored, so the next native start-up
476
+ begins with `initialContext` again. The running client is not changed.
477
+
478
+ **Since:** 0.1.0
479
+
480
+ --------------------
481
+
482
+
483
+ ### addListener('flagChange', ...)
484
+
485
+ ```typescript
486
+ addListener(eventName: 'flagChange', listenerFunc: (change: FlagChange) => void) => Promise<PluginListenerHandle>
487
+ ```
488
+
489
+ Called for every flag whose value changes: from the stream or a poll, and
490
+ after `identify()` for each flag whose value differs for the new context.
491
+
492
+ | Param | Type |
493
+ | ------------------ | ---------------------------------------------------------------------- |
494
+ | **`eventName`** | <code>'flagChange'</code> |
495
+ | **`listenerFunc`** | <code>(change: <a href="#flagchange">FlagChange</a>) =&gt; void</code> |
496
+
497
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
498
+
499
+ **Since:** 0.1.0
500
+
501
+ --------------------
502
+
503
+
504
+ ### addListener('connectionModeChange', ...)
505
+
506
+ ```typescript
507
+ addListener(eventName: 'connectionModeChange', listenerFunc: (change: ConnectionModeChange) => void) => Promise<PluginListenerHandle>
508
+ ```
509
+
510
+ Called when the SDK's connection mode changes, for example from streaming
511
+ to offline. iOS and Android only: the web SDK has no connection mode, and
512
+ this is never called there.
513
+
514
+ | Param | Type |
515
+ | ------------------ | ------------------------------------------------------------------------------------------ |
516
+ | **`eventName`** | <code>'connectionModeChange'</code> |
517
+ | **`listenerFunc`** | <code>(change: <a href="#connectionmodechange">ConnectionModeChange</a>) =&gt; void</code> |
518
+
519
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
520
+
521
+ **Since:** 0.1.0
522
+
523
+ --------------------
524
+
525
+
526
+ ### removeAllListeners()
527
+
528
+ ```typescript
529
+ removeAllListeners() => Promise<void>
530
+ ```
531
+
532
+ Removes every listener of this plugin.
533
+
534
+ **Since:** 0.1.0
535
+
536
+ --------------------
537
+
538
+
539
+ ### Interfaces
540
+
541
+
542
+ #### LaunchDarklyState
543
+
544
+ | Prop | Type | Description | Since |
545
+ | ----------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ----- |
546
+ | **`initialized`** | <code>boolean</code> | Whether the client has received its first set of flags, from the network or, when it was started offline, from its cache. | 0.1.0 |
547
+ | **`context`** | <code><a href="#ldcontext">LDContext</a></code> | The current context. Absent when no client was started. | 0.1.0 |
548
+ | **`flags`** | <code><a href="#ldflagset">LDFlagSet</a></code> | The value of every flag for the current context. | 0.1.0 |
549
+
550
+
551
+ #### LDSingleKindContext
552
+
553
+ A context of one kind. Any other property is a custom attribute: a string,
554
+ number, boolean, array or object.
555
+
556
+ | Prop | Type | Description | Since |
557
+ | --------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ----- |
558
+ | **`kind`** | <code>string</code> | The context kind, for example `user` or `organization`. | 0.1.0 |
559
+ | **`key`** | <code>string</code> | The key. An anonymous context keeps the key it is given. | 0.1.0 |
560
+ | **`name`** | <code>string</code> | | 0.1.0 |
561
+ | **`anonymous`** | <code>boolean</code> | Leaves the context out of the dashboard's context list. It is still counted for billing by its key. | 0.1.0 |
562
+ | **`_meta`** | <code><a href="#ldcontextmeta">LDContextMeta</a></code> | | 0.1.0 |
563
+
564
+
565
+ #### LDContextMeta
566
+
567
+ | Prop | Type | Description | Since |
568
+ | ----------------------- | --------------------- | ------------------------------------------------------------------------------------------------------ | ----- |
569
+ | **`privateAttributes`** | <code>string[]</code> | Attributes, by name or `/`-separated reference, that are not sent to LaunchDarkly in analytics events. | 0.1.0 |
570
+
571
+
572
+ #### LDMultiKindContext
573
+
574
+ A context made of several kinds: `{ kind: 'multi', user: {...}, organization: {...} }`.
575
+ Each kind's object has no `kind` of its own.
576
+
577
+ | Prop | Type | Since |
578
+ | ---------- | -------------------- | ----- |
579
+ | **`kind`** | <code>'multi'</code> | 0.1.0 |
580
+
581
+
582
+ #### LDFlagSet
583
+
584
+
585
+ #### InitializeOptions
586
+
587
+ | Prop | Type | Description | Since |
588
+ | ------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------------------ | ----- |
589
+ | **`mobileKey`** | <code>string</code> | The mobile key of the LaunchDarkly environment, used on iOS and Android. | 0.1.0 |
590
+ | **`clientSideId`** | <code>string</code> | The client-side ID of the LaunchDarkly environment, used on the web. | 0.1.0 |
591
+ | **`context`** | <code><a href="#ldcontext">LDContext</a></code> | | 0.1.0 |
592
+ | **`options`** | <code><a href="#launchdarklyoptions">LaunchDarklyOptions</a></code> | | 0.1.0 |
593
+
594
+
595
+ #### LaunchDarklyOptions
596
+
597
+ | Prop | Type | Description | Default | Since |
598
+ | ----------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- |
599
+ | **`streaming`** | <code>boolean</code> | Streams flag updates when `true`, polls when `false`. | <code>true</code> | 0.1.0 |
600
+ | **`evaluationReasons`** | <code>boolean</code> | Asks LaunchDarkly for the reason behind each evaluation. The SDKs then attach it to their evaluation events. | <code>false</code> | 0.1.0 |
601
+ | **`offline`** | <code>boolean</code> | Starts the client offline: it answers cached or default values and does not connect until `setOnline({ online: true })`. The web SDK has no offline mode: there `offline: true` starts without a stream, and the first flags are still fetched once. | <code>false</code> | 0.1.0 |
602
+ | **`startWaitSeconds`** | <code>number</code> | How long the start waits for the first set of flags, in seconds. | <code>5</code> | 0.1.0 |
603
+ | **`application`** | <code><a href="#ldapplicationinfo">LDApplicationInfo</a></code> | Application metadata sent to LaunchDarkly with every request. It describes the app; it is not a context, and adds nothing to the contexts counted for billing. | | 0.1.0 |
604
+ | **`bootstrap`** | <code>'localStorage' \| <a href="#ldflagset">LDFlagSet</a></code> | Web only: flags to start with before LaunchDarkly answers. `'localStorage'` starts from the flags the web SDK cached in `localStorage` for the context on an earlier visit, which the SDK does on its own; a flag object (for example rendered by your server) starts from those values. The native SDKs always start from their own cache and ignore this. | | 0.1.0 |
605
+
606
+
607
+ #### LDApplicationInfo
608
+
609
+ | Prop | Type | Since |
610
+ | ----------------- | ------------------- | ----- |
611
+ | **`id`** | <code>string</code> | 0.1.0 |
612
+ | **`name`** | <code>string</code> | 0.1.0 |
613
+ | **`version`** | <code>string</code> | 0.1.0 |
614
+ | **`versionName`** | <code>string</code> | 0.1.0 |
615
+
616
+
617
+ #### WaitForInitializationResult
618
+
619
+ | Prop | Type | Since |
620
+ | ----------------- | -------------------- | ----- |
621
+ | **`initialized`** | <code>boolean</code> | 0.1.0 |
622
+
623
+
624
+ #### WaitForInitializationOptions
625
+
626
+ | Prop | Type | Default | Since |
627
+ | -------------------- | ------------------- | -------------- | ----- |
628
+ | **`timeoutSeconds`** | <code>number</code> | <code>5</code> | 0.1.0 |
629
+
630
+
631
+ #### IdentifyResult
632
+
633
+ | Prop | Type | Description | Since |
634
+ | -------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
635
+ | **`timedOut`** | <code>boolean</code> | `true` when the flags of the new context did not arrive in time. The context is switched all the same, and the flags follow when they arrive. | 0.1.0 |
636
+
637
+
638
+ #### IdentifyOptions
639
+
640
+ | Prop | Type | Description | Default | Since |
641
+ | -------------------- | ----------------------------------------------- | -------------------------------------------------------------- | -------------- | ----- |
642
+ | **`context`** | <code><a href="#ldcontext">LDContext</a></code> | | | 0.1.0 |
643
+ | **`timeoutSeconds`** | <code>number</code> | How long to wait for the flags of the new context, in seconds. | <code>5</code> | 0.1.0 |
644
+
645
+
646
+ #### VariationResult
647
+
648
+ | Prop | Type | Since |
649
+ | ----------- | -------------------- | ----- |
650
+ | **`value`** | <code>unknown</code> | 0.1.0 |
651
+
652
+
653
+ #### VariationOptions
654
+
655
+ | Prop | Type | Default | Since |
656
+ | ------------------ | -------------------- | ----------------- | ----- |
657
+ | **`key`** | <code>string</code> | | 0.1.0 |
658
+ | **`defaultValue`** | <code>unknown</code> | <code>null</code> | 0.1.0 |
659
+
660
+
661
+ #### AllFlagsResult
662
+
663
+ | Prop | Type | Since |
664
+ | ----------- | ----------------------------------------------- | ----- |
665
+ | **`flags`** | <code><a href="#ldflagset">LDFlagSet</a></code> | 0.1.0 |
666
+
667
+
668
+ #### SetStreamingOptions
669
+
670
+ | Prop | Type | Since |
671
+ | ------------- | -------------------- | ----- |
672
+ | **`enabled`** | <code>boolean</code> | 0.1.0 |
673
+
674
+
675
+ #### SetOnlineOptions
676
+
677
+ | Prop | Type | Since |
678
+ | ------------ | -------------------- | ----- |
679
+ | **`online`** | <code>boolean</code> | 0.1.0 |
680
+
681
+
682
+ #### PluginListenerHandle
683
+
684
+ | Prop | Type |
685
+ | ------------ | ----------------------------------------- |
686
+ | **`remove`** | <code>() =&gt; Promise&lt;void&gt;</code> |
687
+
688
+
689
+ #### FlagChange
690
+
691
+ | Prop | Type | Description | Since |
692
+ | -------------- | -------------------- | ------------------------------------------------------ | ----- |
693
+ | **`key`** | <code>string</code> | | 0.1.0 |
694
+ | **`value`** | <code>unknown</code> | The new value; `null` when the flag was deleted. | 0.1.0 |
695
+ | **`previous`** | <code>unknown</code> | The value before the change, when the plugin knew one. | 0.1.0 |
696
+
697
+
698
+ #### ConnectionModeChange
699
+
700
+ | Prop | Type | Description | Since |
701
+ | ---------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
702
+ | **`mode`** | <code>string</code> | The SDK's own name for the mode, which differs between the platforms: on iOS `streaming`, `polling`, `offline` or `establishingStreamingConnection`; on Android the name of the SDK's `ConnectionMode`, such as `STREAMING`, `POLLING`, `OFFLINE`, `SET_OFFLINE` or `BACKGROUND_POLLING`. | 0.1.0 |
703
+
704
+
705
+ ### Type Aliases
706
+
707
+
708
+ #### LDContext
709
+
710
+ A context in the JSON shape LaunchDarkly's SDKs use: a single-kind context
711
+ or a multi-context.
712
+
713
+ <code><a href="#ldsinglekindcontext">LDSingleKindContext</a> | <a href="#ldmultikindcontext">LDMultiKindContext</a></code>
714
+
715
+ </docgen-api>
716
+
717
+ ## Testing
718
+
719
+ ```bash
720
+ npm run test:unit # TypeScript: native calls reach the native plugin; the web implementation against a stand-in browser SDK
721
+ npm run test:parity # the API, the web, the Swift, the Kotlin and this README name the same methods and the same config keys
722
+ npm run test:android # JUnit on the JVM: config parsing, stored-context precedence, the SDK config, contexts and values
723
+ npm run test:ios # XCTest on a simulator: the same on iOS, and the privacy manifest
724
+ ```
725
+
726
+ The billing rules are tested against the SDKs themselves, not only against the plugin's settings. On Android a test runs the
727
+ SDK's own `AnonymousKeyContextModifier`, the step `LDClient.init` and `identify` apply to every context, with the plugin's
728
+ configuration, and checks that `anonymous_user` comes out unchanged, with a control that turns generation on and sees the key
729
+ replaced. On iOS the tests decode contexts with the SDK's decoder and check the key and the `anonymous` attribute of single and
730
+ multi-contexts. On both, the auto-start path is tested to build a configuration with environment attributes off.
731
+
732
+ What the tests do not reach is LaunchDarkly itself: start the example app with a real mobile key to see flags arrive.
733
+
734
+ ## Example app
735
+
736
+ `example-app/` installs the plugin from this repository and has a button for every method. It commits no key: type the mobile key
737
+ (or, in a browser, the client-side ID) into the field, or set `LAUNCHDARKLY_MOBILE_KEY` when you sync to try the native
738
+ start-up:
739
+
740
+ ```bash
741
+ cd example-app
742
+ npm install
743
+ LAUNCHDARKLY_MOBILE_KEY=mob-… npx cap sync
744
+ ```
745
+
746
+ ## License
747
+
748
+ MIT