capacitor-launchdarkly 0.0.0-stage → 0.1.1
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/CapacitorLaunchdarkly.podspec +20 -0
- package/LICENSE +21 -0
- package/Package.swift +32 -0
- package/README.md +747 -2
- package/android/build.gradle +68 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/kotlin/io/github/maximbelov/launchdarkly/Json.kt +67 -0
- package/android/src/main/kotlin/io/github/maximbelov/launchdarkly/LaunchDarklyClient.kt +250 -0
- package/android/src/main/kotlin/io/github/maximbelov/launchdarkly/LaunchDarklyPlugin.kt +148 -0
- package/android/src/main/kotlin/io/github/maximbelov/launchdarkly/LdConfigFactory.kt +34 -0
- package/android/src/main/kotlin/io/github/maximbelov/launchdarkly/StartOptions.kt +76 -0
- package/android/src/main/kotlin/io/github/maximbelov/launchdarkly/StoredContext.kt +47 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +1174 -0
- package/dist/esm/definitions.d.ts +520 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +46 -0
- package/dist/esm/web.js +204 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +221 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +224 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/LaunchDarklyPlugin/ConfigFactory.swift +29 -0
- package/ios/Sources/LaunchDarklyPlugin/JSONValues.swift +68 -0
- package/ios/Sources/LaunchDarklyPlugin/LaunchDarklyClient.swift +165 -0
- package/ios/Sources/LaunchDarklyPlugin/LaunchDarklyPlugin.swift +169 -0
- package/ios/Sources/LaunchDarklyPlugin/PrivacyInfo.xcprivacy +23 -0
- package/ios/Sources/LaunchDarklyPlugin/StartOptions.swift +79 -0
- package/ios/Sources/LaunchDarklyPlugin/StoredContext.swift +43 -0
- package/ios/Tests/LaunchDarklyPluginTests/BillingSafetyTests.swift +53 -0
- package/ios/Tests/LaunchDarklyPluginTests/JSONValuesTests.swift +67 -0
- package/ios/Tests/LaunchDarklyPluginTests/LaunchDarklyPluginTests.swift +37 -0
- package/ios/Tests/LaunchDarklyPluginTests/PluginConfigurationTests.swift +52 -0
- package/ios/Tests/LaunchDarklyPluginTests/StartContextTests.swift +36 -0
- package/package.json +105 -4
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
var capacitorLaunchDarkly = (function (exports, core) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const LaunchDarkly = core.registerPlugin('LaunchDarkly', {
|
|
5
|
+
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.LaunchDarklyWeb()),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
/** The key the plugin keeps the context of the last `identify()` under in `localStorage`. */
|
|
9
|
+
const STORED_CONTEXT_KEY = 'CapacitorLaunchDarkly.context';
|
|
10
|
+
const DEFAULT_WAIT_SECONDS = 5;
|
|
11
|
+
const MISSING_SDK_MESSAGE = 'The LaunchDarkly web SDK is not installed. To use this plugin on the web, run: npm install @launchdarkly/js-client-sdk';
|
|
12
|
+
// Imported only when a web app starts the client, so a native-only app never loads it.
|
|
13
|
+
const importSdk = () => import('@launchdarkly/js-client-sdk');
|
|
14
|
+
/**
|
|
15
|
+
* The web implementation, on LaunchDarkly's browser SDK. It follows the native rules: one client per page, a context the
|
|
16
|
+
* caller gave with its key (an anonymous context without a key is refused rather than given a generated one), and a context
|
|
17
|
+
* stored by `identify()` that wins over the one `initialize()` is given.
|
|
18
|
+
*/
|
|
19
|
+
class LaunchDarklyWeb extends core.WebPlugin {
|
|
20
|
+
constructor(loadSdk = importSdk) {
|
|
21
|
+
super();
|
|
22
|
+
this.loadSdk = loadSdk;
|
|
23
|
+
this.options = {};
|
|
24
|
+
this.initialized = false;
|
|
25
|
+
this.lastValues = {};
|
|
26
|
+
}
|
|
27
|
+
async initialize(options) {
|
|
28
|
+
var _a, _b, _c, _d;
|
|
29
|
+
const clientSideId = options === null || options === void 0 ? void 0 : options.clientSideId;
|
|
30
|
+
if (!clientSideId)
|
|
31
|
+
throw new Error('initialize needs a clientSideId on the web.');
|
|
32
|
+
if (this.clientSideId === clientSideId) {
|
|
33
|
+
await this.starting;
|
|
34
|
+
return this.state();
|
|
35
|
+
}
|
|
36
|
+
requireKeys(options.context);
|
|
37
|
+
const sdk = await this.sdk();
|
|
38
|
+
if (this.client)
|
|
39
|
+
await this.close();
|
|
40
|
+
const startOptions = (_a = options.options) !== null && _a !== void 0 ? _a : {};
|
|
41
|
+
// The given context plays the part of initialContext: a context an earlier identify() stored wins over it.
|
|
42
|
+
const context = (_b = readStoredContext()) !== null && _b !== void 0 ? _b : options.context;
|
|
43
|
+
const streaming = ((_c = startOptions.streaming) !== null && _c !== void 0 ? _c : true) && !startOptions.offline;
|
|
44
|
+
const client = sdk.createClient(clientSideId, context, Object.assign({ streaming, withReasons: (_d = startOptions.evaluationReasons) !== null && _d !== void 0 ? _d : false }, (startOptions.application ? { applicationInfo: startOptions.application } : {})));
|
|
45
|
+
this.client = client;
|
|
46
|
+
this.clientSideId = clientSideId;
|
|
47
|
+
this.options = startOptions;
|
|
48
|
+
this.context = context;
|
|
49
|
+
this.initialized = false;
|
|
50
|
+
client.on('change', (_context, keys) => this.flagsChanged(client, keys));
|
|
51
|
+
const bootstrap = startOptions.bootstrap;
|
|
52
|
+
this.starting = client
|
|
53
|
+
.start(Object.assign({ timeout: seconds(startOptions.startWaitSeconds) }, (bootstrap && bootstrap !== 'localStorage' ? { bootstrap } : {})))
|
|
54
|
+
.then((result) => {
|
|
55
|
+
if (this.client !== client)
|
|
56
|
+
return;
|
|
57
|
+
this.initialized = result.status === 'complete';
|
|
58
|
+
this.lastValues = Object.assign({}, client.allFlags());
|
|
59
|
+
});
|
|
60
|
+
await this.starting;
|
|
61
|
+
return this.state();
|
|
62
|
+
}
|
|
63
|
+
async waitForInitialization(options) {
|
|
64
|
+
const client = this.client;
|
|
65
|
+
if (!client)
|
|
66
|
+
return { initialized: false };
|
|
67
|
+
if (!this.initialized) {
|
|
68
|
+
const result = await client.waitForInitialization({ timeout: seconds(options === null || options === void 0 ? void 0 : options.timeoutSeconds) });
|
|
69
|
+
if (this.client === client && result.status === 'complete')
|
|
70
|
+
this.initialized = true;
|
|
71
|
+
}
|
|
72
|
+
return { initialized: this.initialized };
|
|
73
|
+
}
|
|
74
|
+
async getState() {
|
|
75
|
+
await this.starting;
|
|
76
|
+
return this.state();
|
|
77
|
+
}
|
|
78
|
+
async identify(options) {
|
|
79
|
+
const client = this.started();
|
|
80
|
+
requireKeys(options === null || options === void 0 ? void 0 : options.context);
|
|
81
|
+
const result = await client.identify(options.context, { timeout: seconds(options.timeoutSeconds) });
|
|
82
|
+
if (result.status === 'error' || result.status === 'shed') {
|
|
83
|
+
throw new Error(result.status === 'shed' ? 'identify was replaced by a later identify' : 'identify failed');
|
|
84
|
+
}
|
|
85
|
+
// On a timeout the SDK has switched to the context and keeps loading its flags; only the wait is over.
|
|
86
|
+
this.context = options.context;
|
|
87
|
+
writeStoredContext(options.context);
|
|
88
|
+
return { timedOut: result.status === 'timeout' };
|
|
89
|
+
}
|
|
90
|
+
async variation(options) {
|
|
91
|
+
var _a;
|
|
92
|
+
const defaultValue = (_a = options.defaultValue) !== null && _a !== void 0 ? _a : null;
|
|
93
|
+
const value = this.client ? this.client.jsonVariation(options.key, defaultValue) : defaultValue;
|
|
94
|
+
return { value: value !== null && value !== void 0 ? value : null };
|
|
95
|
+
}
|
|
96
|
+
async allFlags() {
|
|
97
|
+
return { flags: this.client ? Object.assign({}, this.client.allFlags()) : {} };
|
|
98
|
+
}
|
|
99
|
+
async setStreaming(options) {
|
|
100
|
+
const client = this.started();
|
|
101
|
+
this.options = Object.assign(Object.assign({}, this.options), { streaming: options.enabled });
|
|
102
|
+
client.setStreaming(options.enabled);
|
|
103
|
+
}
|
|
104
|
+
async setOnline(options) {
|
|
105
|
+
var _a;
|
|
106
|
+
// The browser SDK has no offline mode: offline closes the stream, online opens it again when streaming is on.
|
|
107
|
+
const client = this.started();
|
|
108
|
+
client.setStreaming(options.online ? ((_a = this.options.streaming) !== null && _a !== void 0 ? _a : true) : false);
|
|
109
|
+
}
|
|
110
|
+
async flush() {
|
|
111
|
+
var _a;
|
|
112
|
+
await ((_a = this.client) === null || _a === void 0 ? void 0 : _a.flush());
|
|
113
|
+
}
|
|
114
|
+
async close() {
|
|
115
|
+
const client = this.client;
|
|
116
|
+
this.client = undefined;
|
|
117
|
+
this.clientSideId = undefined;
|
|
118
|
+
this.context = undefined;
|
|
119
|
+
this.initialized = false;
|
|
120
|
+
this.starting = undefined;
|
|
121
|
+
this.lastValues = {};
|
|
122
|
+
await (client === null || client === void 0 ? void 0 : client.close());
|
|
123
|
+
}
|
|
124
|
+
async clearStoredContext() {
|
|
125
|
+
try {
|
|
126
|
+
localStorage.removeItem(STORED_CONTEXT_KEY);
|
|
127
|
+
}
|
|
128
|
+
catch (_a) {
|
|
129
|
+
// No storage (a private window, blocked site data): nothing was stored either.
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async sdk() {
|
|
133
|
+
try {
|
|
134
|
+
return await this.loadSdk();
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
throw new Error(`${MISSING_SDK_MESSAGE} (${error instanceof Error ? error.message : String(error)})`, {
|
|
138
|
+
cause: error,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
started() {
|
|
143
|
+
if (!this.client)
|
|
144
|
+
throw new Error('The client is not started.');
|
|
145
|
+
return this.client;
|
|
146
|
+
}
|
|
147
|
+
state() {
|
|
148
|
+
return Object.assign({ initialized: this.initialized, flags: this.client ? Object.assign({}, this.client.allFlags()) : {} }, (this.context ? { context: this.context } : {}));
|
|
149
|
+
}
|
|
150
|
+
flagsChanged(client, keys) {
|
|
151
|
+
if (this.client !== client)
|
|
152
|
+
return;
|
|
153
|
+
const flags = client.allFlags();
|
|
154
|
+
for (const key of keys) {
|
|
155
|
+
const value = key in flags ? flags[key] : null;
|
|
156
|
+
const hadPrevious = key in this.lastValues;
|
|
157
|
+
const previous = this.lastValues[key];
|
|
158
|
+
if (key in flags)
|
|
159
|
+
this.lastValues[key] = flags[key];
|
|
160
|
+
else
|
|
161
|
+
delete this.lastValues[key];
|
|
162
|
+
this.notifyListeners('flagChange', hadPrevious ? { key, value, previous } : { key, value });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function seconds(value) {
|
|
167
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : DEFAULT_WAIT_SECONDS;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Refuses a context whose key (or the key of any kind in a multi-context) is missing or empty. The browser SDK would generate
|
|
171
|
+
* one for an anonymous context, stored per browser, and every browser would then count as a context of its own.
|
|
172
|
+
*/
|
|
173
|
+
function requireKeys(context) {
|
|
174
|
+
var _a;
|
|
175
|
+
if (!context || typeof context !== 'object')
|
|
176
|
+
throw new Error('A context is needed.');
|
|
177
|
+
const parts = context.kind === 'multi'
|
|
178
|
+
? Object.entries(context).filter(([kind]) => kind !== 'kind')
|
|
179
|
+
: [[String((_a = context.kind) !== null && _a !== void 0 ? _a : 'user'), context]];
|
|
180
|
+
if (parts.length === 0)
|
|
181
|
+
throw new Error('Invalid context: a multi-context needs at least one kind.');
|
|
182
|
+
for (const [kind, part] of parts) {
|
|
183
|
+
const key = part === null || part === void 0 ? void 0 : part.key;
|
|
184
|
+
if (typeof key !== 'string' || key === '') {
|
|
185
|
+
throw new Error(`Invalid context: the ${kind} context has no key. Pass one, also for an anonymous context.`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function readStoredContext() {
|
|
190
|
+
try {
|
|
191
|
+
const json = localStorage.getItem(STORED_CONTEXT_KEY);
|
|
192
|
+
if (!json)
|
|
193
|
+
return undefined;
|
|
194
|
+
const context = JSON.parse(json);
|
|
195
|
+
requireKeys(context);
|
|
196
|
+
return context;
|
|
197
|
+
}
|
|
198
|
+
catch (_a) {
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function writeStoredContext(context) {
|
|
203
|
+
try {
|
|
204
|
+
localStorage.setItem(STORED_CONTEXT_KEY, JSON.stringify(context));
|
|
205
|
+
}
|
|
206
|
+
catch (_a) {
|
|
207
|
+
// No storage: the next start begins with the context initialize() is given.
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
212
|
+
__proto__: null,
|
|
213
|
+
LaunchDarklyWeb: LaunchDarklyWeb,
|
|
214
|
+
MISSING_SDK_MESSAGE: MISSING_SDK_MESSAGE,
|
|
215
|
+
STORED_CONTEXT_KEY: STORED_CONTEXT_KEY,
|
|
216
|
+
requireKeys: requireKeys
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
exports.LaunchDarkly = LaunchDarkly;
|
|
220
|
+
|
|
221
|
+
return exports;
|
|
222
|
+
|
|
223
|
+
})({}, capacitorExports);
|
|
224
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst LaunchDarkly = registerPlugin('LaunchDarkly', {\n web: () => import('./web').then((m) => new m.LaunchDarklyWeb()),\n});\nexport * from './definitions';\nexport { LaunchDarkly };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\n/** The key the plugin keeps the context of the last `identify()` under in `localStorage`. */\nexport const STORED_CONTEXT_KEY = 'CapacitorLaunchDarkly.context';\nconst DEFAULT_WAIT_SECONDS = 5;\nexport const MISSING_SDK_MESSAGE = 'The LaunchDarkly web SDK is not installed. To use this plugin on the web, run: npm install @launchdarkly/js-client-sdk';\n// Imported only when a web app starts the client, so a native-only app never loads it.\nconst importSdk = () => import('@launchdarkly/js-client-sdk');\n/**\n * The web implementation, on LaunchDarkly's browser SDK. It follows the native rules: one client per page, a context the\n * caller gave with its key (an anonymous context without a key is refused rather than given a generated one), and a context\n * stored by `identify()` that wins over the one `initialize()` is given.\n */\nexport class LaunchDarklyWeb extends WebPlugin {\n constructor(loadSdk = importSdk) {\n super();\n this.loadSdk = loadSdk;\n this.options = {};\n this.initialized = false;\n this.lastValues = {};\n }\n async initialize(options) {\n var _a, _b, _c, _d;\n const clientSideId = options === null || options === void 0 ? void 0 : options.clientSideId;\n if (!clientSideId)\n throw new Error('initialize needs a clientSideId on the web.');\n if (this.clientSideId === clientSideId) {\n await this.starting;\n return this.state();\n }\n requireKeys(options.context);\n const sdk = await this.sdk();\n if (this.client)\n await this.close();\n const startOptions = (_a = options.options) !== null && _a !== void 0 ? _a : {};\n // The given context plays the part of initialContext: a context an earlier identify() stored wins over it.\n const context = (_b = readStoredContext()) !== null && _b !== void 0 ? _b : options.context;\n const streaming = ((_c = startOptions.streaming) !== null && _c !== void 0 ? _c : true) && !startOptions.offline;\n const client = sdk.createClient(clientSideId, context, Object.assign({ streaming, withReasons: (_d = startOptions.evaluationReasons) !== null && _d !== void 0 ? _d : false }, (startOptions.application ? { applicationInfo: startOptions.application } : {})));\n this.client = client;\n this.clientSideId = clientSideId;\n this.options = startOptions;\n this.context = context;\n this.initialized = false;\n client.on('change', (_context, keys) => this.flagsChanged(client, keys));\n const bootstrap = startOptions.bootstrap;\n this.starting = client\n .start(Object.assign({ timeout: seconds(startOptions.startWaitSeconds) }, (bootstrap && bootstrap !== 'localStorage' ? { bootstrap } : {})))\n .then((result) => {\n if (this.client !== client)\n return;\n this.initialized = result.status === 'complete';\n this.lastValues = Object.assign({}, client.allFlags());\n });\n await this.starting;\n return this.state();\n }\n async waitForInitialization(options) {\n const client = this.client;\n if (!client)\n return { initialized: false };\n if (!this.initialized) {\n const result = await client.waitForInitialization({ timeout: seconds(options === null || options === void 0 ? void 0 : options.timeoutSeconds) });\n if (this.client === client && result.status === 'complete')\n this.initialized = true;\n }\n return { initialized: this.initialized };\n }\n async getState() {\n await this.starting;\n return this.state();\n }\n async identify(options) {\n const client = this.started();\n requireKeys(options === null || options === void 0 ? void 0 : options.context);\n const result = await client.identify(options.context, { timeout: seconds(options.timeoutSeconds) });\n if (result.status === 'error' || result.status === 'shed') {\n throw new Error(result.status === 'shed' ? 'identify was replaced by a later identify' : 'identify failed');\n }\n // On a timeout the SDK has switched to the context and keeps loading its flags; only the wait is over.\n this.context = options.context;\n writeStoredContext(options.context);\n return { timedOut: result.status === 'timeout' };\n }\n async variation(options) {\n var _a;\n const defaultValue = (_a = options.defaultValue) !== null && _a !== void 0 ? _a : null;\n const value = this.client ? this.client.jsonVariation(options.key, defaultValue) : defaultValue;\n return { value: value !== null && value !== void 0 ? value : null };\n }\n async allFlags() {\n return { flags: this.client ? Object.assign({}, this.client.allFlags()) : {} };\n }\n async setStreaming(options) {\n const client = this.started();\n this.options = Object.assign(Object.assign({}, this.options), { streaming: options.enabled });\n client.setStreaming(options.enabled);\n }\n async setOnline(options) {\n var _a;\n // The browser SDK has no offline mode: offline closes the stream, online opens it again when streaming is on.\n const client = this.started();\n client.setStreaming(options.online ? ((_a = this.options.streaming) !== null && _a !== void 0 ? _a : true) : false);\n }\n async flush() {\n var _a;\n await ((_a = this.client) === null || _a === void 0 ? void 0 : _a.flush());\n }\n async close() {\n const client = this.client;\n this.client = undefined;\n this.clientSideId = undefined;\n this.context = undefined;\n this.initialized = false;\n this.starting = undefined;\n this.lastValues = {};\n await (client === null || client === void 0 ? void 0 : client.close());\n }\n async clearStoredContext() {\n try {\n localStorage.removeItem(STORED_CONTEXT_KEY);\n }\n catch (_a) {\n // No storage (a private window, blocked site data): nothing was stored either.\n }\n }\n async sdk() {\n try {\n return await this.loadSdk();\n }\n catch (error) {\n throw new Error(`${MISSING_SDK_MESSAGE} (${error instanceof Error ? error.message : String(error)})`, {\n cause: error,\n });\n }\n }\n started() {\n if (!this.client)\n throw new Error('The client is not started.');\n return this.client;\n }\n state() {\n return Object.assign({ initialized: this.initialized, flags: this.client ? Object.assign({}, this.client.allFlags()) : {} }, (this.context ? { context: this.context } : {}));\n }\n flagsChanged(client, keys) {\n if (this.client !== client)\n return;\n const flags = client.allFlags();\n for (const key of keys) {\n const value = key in flags ? flags[key] : null;\n const hadPrevious = key in this.lastValues;\n const previous = this.lastValues[key];\n if (key in flags)\n this.lastValues[key] = flags[key];\n else\n delete this.lastValues[key];\n this.notifyListeners('flagChange', hadPrevious ? { key, value, previous } : { key, value });\n }\n }\n}\nfunction seconds(value) {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : DEFAULT_WAIT_SECONDS;\n}\n/**\n * Refuses a context whose key (or the key of any kind in a multi-context) is missing or empty. The browser SDK would generate\n * one for an anonymous context, stored per browser, and every browser would then count as a context of its own.\n */\nexport function requireKeys(context) {\n var _a;\n if (!context || typeof context !== 'object')\n throw new Error('A context is needed.');\n const parts = context.kind === 'multi'\n ? Object.entries(context).filter(([kind]) => kind !== 'kind')\n : [[String((_a = context.kind) !== null && _a !== void 0 ? _a : 'user'), context]];\n if (parts.length === 0)\n throw new Error('Invalid context: a multi-context needs at least one kind.');\n for (const [kind, part] of parts) {\n const key = part === null || part === void 0 ? void 0 : part.key;\n if (typeof key !== 'string' || key === '') {\n throw new Error(`Invalid context: the ${kind} context has no key. Pass one, also for an anonymous context.`);\n }\n }\n}\nfunction readStoredContext() {\n try {\n const json = localStorage.getItem(STORED_CONTEXT_KEY);\n if (!json)\n return undefined;\n const context = JSON.parse(json);\n requireKeys(context);\n return context;\n }\n catch (_a) {\n return undefined;\n }\n}\nfunction writeStoredContext(context) {\n try {\n localStorage.setItem(STORED_CONTEXT_KEY, JSON.stringify(context));\n }\n catch (_a) {\n // No storage: the next start begins with the context initialize() is given.\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import LaunchDarkly
|
|
3
|
+
|
|
4
|
+
/// Builds the SDK configuration. Two settings here are not options, because LaunchDarkly bills client-side SDKs by the number
|
|
5
|
+
/// of unique context keys (of the kind that has the most):
|
|
6
|
+
///
|
|
7
|
+
/// - Automatic environment attributes are disabled. Enabled, the SDK adds `ld_application` and `ld_device` contexts to every
|
|
8
|
+
/// context it evaluates (`AutoEnvContextModifier`), and the `ld_device` key is generated per install, which counts every
|
|
9
|
+
/// device as a context of its own.
|
|
10
|
+
/// - Keys are never generated. The iOS SDK generates a key only for a context built without one (`LDContextBuilder()` with no
|
|
11
|
+
/// key, or a start with no context at all, which falls back to `LDContext()`); it has no setting that replaces the key of an
|
|
12
|
+
/// anonymous context. The plugin always builds contexts from JSON with the caller's key and always starts with a context.
|
|
13
|
+
public enum ConfigFactory {
|
|
14
|
+
public static func make(_ options: StartOptions) -> LDConfig {
|
|
15
|
+
var config = LDConfig(mobileKey: options.mobileKey, autoEnvAttributes: .disabled)
|
|
16
|
+
config.streamingMode = options.streaming ? .streaming : .polling
|
|
17
|
+
config.evaluationReasons = options.evaluationReasons
|
|
18
|
+
config.startOnline = !options.offline
|
|
19
|
+
if let app = options.application {
|
|
20
|
+
var info = ApplicationInfo()
|
|
21
|
+
info.applicationIdentifier(app.id)
|
|
22
|
+
info.applicationName(app.name)
|
|
23
|
+
info.applicationVersion(app.version)
|
|
24
|
+
info.applicationVersionName(app.versionName)
|
|
25
|
+
config.applicationInfo = info
|
|
26
|
+
}
|
|
27
|
+
return config
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import LaunchDarkly
|
|
3
|
+
|
|
4
|
+
public enum PluginError: LocalizedError, Equatable {
|
|
5
|
+
case invalidContext(String)
|
|
6
|
+
case notStarted
|
|
7
|
+
|
|
8
|
+
public var errorDescription: String? {
|
|
9
|
+
switch self {
|
|
10
|
+
case .invalidContext(let reason): return "Invalid context: \(reason)"
|
|
11
|
+
case .notStarted: return "The client is not started."
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// JSON text from what Capacitor hands over (dictionaries, arrays, strings, numbers, booleans, NSNull).
|
|
17
|
+
enum JSONText {
|
|
18
|
+
static func from(_ value: Any) -> String? {
|
|
19
|
+
guard JSONSerialization.isValidJSONObject([value]),
|
|
20
|
+
let data = try? JSONSerialization.data(withJSONObject: value, options: [.fragmentsAllowed, .sortedKeys]) else { return nil }
|
|
21
|
+
return String(data: data, encoding: .utf8)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// Contexts in the JSON shape LaunchDarkly's SDKs use, read by the SDK's own decoder, so custom attributes of any JSON type,
|
|
26
|
+
/// nested objects, `anonymous`, `name`, multi-contexts and `_meta.privateAttributes` mean what they mean to LaunchDarkly. The
|
|
27
|
+
/// key is taken as given: nothing here generates or replaces one.
|
|
28
|
+
public enum Contexts {
|
|
29
|
+
public static func parse(_ json: String) throws -> LDContext {
|
|
30
|
+
do {
|
|
31
|
+
return try JSONDecoder().decode(LDContext.self, from: Data(json.utf8))
|
|
32
|
+
} catch {
|
|
33
|
+
throw PluginError.invalidContext(String(describing: error))
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public static func parseOrNil(_ json: String?) -> LDContext? {
|
|
38
|
+
guard let json else { return nil }
|
|
39
|
+
return try? parse(json)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// The JSON of a context Capacitor handed over as a dictionary.
|
|
43
|
+
public static func json(from object: [String: Any]) throws -> String {
|
|
44
|
+
guard let json = JSONText.from(object) else { throw PluginError.invalidContext("not JSON") }
|
|
45
|
+
return json
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Flag values between JavaScript and the SDK's `LDValue`, through JSON.
|
|
50
|
+
public enum JSValues {
|
|
51
|
+
public static func toLDValue(_ value: Any?) -> LDValue {
|
|
52
|
+
guard let value, !(value is NSNull), let json = JSONText.from(value),
|
|
53
|
+
let decoded = try? JSONDecoder().decode(LDValue.self, from: Data(json.utf8)) else { return .null }
|
|
54
|
+
return decoded
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// An `LDValue` as a value Capacitor can send to JavaScript; JSON null is `NSNull`.
|
|
58
|
+
public static func toJS(_ value: LDValue?) -> Any {
|
|
59
|
+
guard let value, value != .null,
|
|
60
|
+
let data = try? JSONEncoder().encode(value),
|
|
61
|
+
let object = try? JSONSerialization.jsonObject(with: data, options: .fragmentsAllowed) else { return NSNull() }
|
|
62
|
+
return object
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public static func flags(_ values: [String: LDValue]) -> [String: Any] {
|
|
66
|
+
values.mapValues { toJS($0) }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import LaunchDarkly
|
|
3
|
+
|
|
4
|
+
/// Where the client's events go: the plugin instance of the current bridge.
|
|
5
|
+
protocol ClientEvents: AnyObject {
|
|
6
|
+
func flagChanged(key: String, value: LDValue, previous: LDValue?)
|
|
7
|
+
func connectionModeChanged(_ mode: String)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/// The one LaunchDarkly client of the process. It outlives a plugin instance, while the SDK keeps one client per process.
|
|
11
|
+
///
|
|
12
|
+
/// Every SDK call and every piece of state here is on the main queue: the SDK delivers its completions and observer callbacks
|
|
13
|
+
/// there, so keeping to it leaves nothing to race. Capacitor calls plugin methods on a queue of its own, and the plugin hops to
|
|
14
|
+
/// main with `LaunchDarklyClient.onMain`.
|
|
15
|
+
final class LaunchDarklyClient {
|
|
16
|
+
static let shared = LaunchDarklyClient()
|
|
17
|
+
|
|
18
|
+
weak var events: ClientEvents?
|
|
19
|
+
|
|
20
|
+
private(set) var options: StartOptions?
|
|
21
|
+
private(set) var contextJSON: String?
|
|
22
|
+
private var starting = false
|
|
23
|
+
private var afterStartQueue: [() -> Void] = []
|
|
24
|
+
/// The owner of the SDK observers; replaced on every start so a closed client's observers are dropped with it.
|
|
25
|
+
private var observerOwner = NSObject()
|
|
26
|
+
|
|
27
|
+
private init() {}
|
|
28
|
+
|
|
29
|
+
static func onMain(_ work: @escaping () -> Void) {
|
|
30
|
+
if Thread.isMainThread { work() } else { DispatchQueue.main.async(execute: work) }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private var client: LDClient? { options == nil ? nil : LDClient.get() }
|
|
34
|
+
|
|
35
|
+
/// Starts the client unless one with the same mobile key is running or starting; one with another key is closed first.
|
|
36
|
+
/// `done` runs once the start has finished: the first flags are in or the wait is over.
|
|
37
|
+
func start(_ options: StartOptions, context: ContextChoice, done: (() -> Void)? = nil) {
|
|
38
|
+
dispatchPrecondition(condition: .onQueue(.main))
|
|
39
|
+
if self.options?.mobileKey == options.mobileKey {
|
|
40
|
+
afterStart { done?() }
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
startNow(options, context: context, done: done)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private func startNow(_ options: StartOptions, context: ContextChoice, done: (() -> Void)?) {
|
|
47
|
+
if self.options != nil { closeNow() }
|
|
48
|
+
self.options = options
|
|
49
|
+
contextJSON = context.json
|
|
50
|
+
starting = true
|
|
51
|
+
if let done { afterStartQueue.append(done) }
|
|
52
|
+
let owner = NSObject()
|
|
53
|
+
observerOwner = owner
|
|
54
|
+
LDClient.start(config: ConfigFactory.make(options), context: context.context, startWaitSeconds: options.startWaitSeconds) { [weak self] _ in
|
|
55
|
+
guard let self, self.observerOwner === owner else { return }
|
|
56
|
+
self.starting = false
|
|
57
|
+
let waiting = self.afterStartQueue
|
|
58
|
+
self.afterStartQueue = []
|
|
59
|
+
waiting.forEach { $0() }
|
|
60
|
+
}
|
|
61
|
+
// start() creates the client before it returns, so the observers are in place before the first flags can arrive.
|
|
62
|
+
observe(owner: owner)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private func observe(owner: NSObject) {
|
|
66
|
+
guard let client else { return }
|
|
67
|
+
client.observeAll(owner: owner) { [weak self] changes in
|
|
68
|
+
for (key, change) in changes.sorted(by: { $0.key < $1.key }) {
|
|
69
|
+
self?.events?.flagChanged(key: key, value: change.newValue, previous: change.oldValue == .null ? nil : change.oldValue)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
client.observeCurrentConnectionMode(owner: owner) { [weak self] mode in
|
|
73
|
+
self?.events?.connectionModeChanged(mode.rawValue)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Restarts the running client with `change` applied to its options, keeping the current context.
|
|
78
|
+
func restart(_ change: (StartOptions) -> StartOptions, done: @escaping (Error?) -> Void) {
|
|
79
|
+
dispatchPrecondition(condition: .onQueue(.main))
|
|
80
|
+
guard let options, let contextJSON else { return done(PluginError.notStarted) }
|
|
81
|
+
do {
|
|
82
|
+
let context = ContextChoice(context: try Contexts.parse(contextJSON), json: contextJSON)
|
|
83
|
+
startNow(change(options), context: context) { done(nil) }
|
|
84
|
+
} catch {
|
|
85
|
+
done(error)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Runs `work` once the start has finished, or straight away when nothing is starting.
|
|
90
|
+
func afterStart(_ work: @escaping () -> Void) {
|
|
91
|
+
dispatchPrecondition(condition: .onQueue(.main))
|
|
92
|
+
if starting { afterStartQueue.append(work) } else { work() }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Switches the context; `done` gets whether the wait for its flags timed out, or the error.
|
|
96
|
+
func identify(_ context: ContextChoice, timeout: TimeInterval, store: ContextStore, done: @escaping (Result<Bool, Error>) -> Void) {
|
|
97
|
+
dispatchPrecondition(condition: .onQueue(.main))
|
|
98
|
+
afterStart { [weak self] in
|
|
99
|
+
guard let self, let client = self.client else { return done(.failure(PluginError.notStarted)) }
|
|
100
|
+
client.identify(context: context.context, timeout: timeout) { result in
|
|
101
|
+
Self.onMain {
|
|
102
|
+
switch result {
|
|
103
|
+
case .complete, .timeout:
|
|
104
|
+
// On a timeout the SDK has switched to the context and keeps loading its flags; only the wait is over.
|
|
105
|
+
self.contextJSON = context.json
|
|
106
|
+
store.write(context.json)
|
|
107
|
+
done(.success(result == .timeout))
|
|
108
|
+
case .shed:
|
|
109
|
+
done(.failure(NSError(domain: "LaunchDarkly", code: 1, userInfo: [NSLocalizedDescriptionKey: "identify was replaced by a later identify"])))
|
|
110
|
+
case .error:
|
|
111
|
+
done(.failure(NSError(domain: "LaunchDarkly", code: 2, userInfo: [NSLocalizedDescriptionKey: "identify failed"])))
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Answers whether the client has its first flags, waiting up to `timeout` for them.
|
|
119
|
+
func waitForInitialization(timeout: TimeInterval, answer: @escaping (Bool) -> Void) {
|
|
120
|
+
dispatchPrecondition(condition: .onQueue(.main))
|
|
121
|
+
guard options != nil else { return answer(false) }
|
|
122
|
+
let deadline = Date().addingTimeInterval(timeout)
|
|
123
|
+
func check() {
|
|
124
|
+
if client?.isInitialized == true { return answer(true) }
|
|
125
|
+
if options == nil || Date() >= deadline { return answer(false) }
|
|
126
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05, execute: check)
|
|
127
|
+
}
|
|
128
|
+
check()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
var isInitialized: Bool { client?.isInitialized == true }
|
|
132
|
+
|
|
133
|
+
/// Every flag from the SDK's store; reading them this way records no evaluation event.
|
|
134
|
+
var allFlags: [String: LDValue] { client?.allFlags ?? [:] }
|
|
135
|
+
|
|
136
|
+
func variation(_ key: String, defaultValue: LDValue) -> LDValue {
|
|
137
|
+
client?.jsonVariation(forKey: key, defaultValue: defaultValue) ?? defaultValue
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
func setOnline(_ online: Bool, done: @escaping () -> Void) {
|
|
141
|
+
guard let client else { return done() }
|
|
142
|
+
client.setOnline(online) { done() }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
func flush() {
|
|
146
|
+
client?.flush()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
func close() {
|
|
150
|
+
dispatchPrecondition(condition: .onQueue(.main))
|
|
151
|
+
closeNow()
|
|
152
|
+
let waiting = afterStartQueue
|
|
153
|
+
afterStartQueue = []
|
|
154
|
+
starting = false
|
|
155
|
+
waiting.forEach { $0() }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private func closeNow() {
|
|
159
|
+
LDClient.get()?.stopObserving(owner: observerOwner)
|
|
160
|
+
LDClient.get()?.close()
|
|
161
|
+
observerOwner = NSObject()
|
|
162
|
+
options = nil
|
|
163
|
+
contextJSON = nil
|
|
164
|
+
}
|
|
165
|
+
}
|