@schematichq/schematic-vue 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.
- package/LICENSE +22 -0
- package/README.md +281 -0
- package/dist/schematic-vue.cjs.js +1692 -0
- package/dist/schematic-vue.d.ts +239 -0
- package/dist/schematic-vue.esm.js +1669 -0
- package/package.json +60 -0
|
@@ -0,0 +1,1692 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
RuleType: () => RuleType,
|
|
24
|
+
Schematic: () => Schematic,
|
|
25
|
+
SchematicPlugin: () => SchematicPlugin,
|
|
26
|
+
UsagePeriod: () => UsagePeriod,
|
|
27
|
+
useSchematic: () => useSchematic,
|
|
28
|
+
useSchematicClient: () => useSchematicClient,
|
|
29
|
+
useSchematicContext: () => useSchematicContext,
|
|
30
|
+
useSchematicEntitlement: () => useSchematicEntitlement,
|
|
31
|
+
useSchematicEvents: () => useSchematicEvents,
|
|
32
|
+
useSchematicFlag: () => useSchematicFlag,
|
|
33
|
+
useSchematicIsPending: () => useSchematicIsPending
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// node_modules/@schematichq/schematic-js/dist/schematic.esm.js
|
|
38
|
+
var __create = Object.create;
|
|
39
|
+
var __defProp2 = Object.defineProperty;
|
|
40
|
+
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
|
|
41
|
+
var __getOwnPropNames2 = Object.getOwnPropertyNames;
|
|
42
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
43
|
+
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
|
|
44
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
45
|
+
return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
46
|
+
};
|
|
47
|
+
var __copyProps2 = (to, from, except, desc) => {
|
|
48
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
49
|
+
for (let key of __getOwnPropNames2(from))
|
|
50
|
+
if (!__hasOwnProp2.call(to, key) && key !== except)
|
|
51
|
+
__defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
|
|
52
|
+
}
|
|
53
|
+
return to;
|
|
54
|
+
};
|
|
55
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps2(
|
|
56
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
57
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
58
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
59
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
60
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp2(target, "default", { value: mod, enumerable: true }) : target,
|
|
61
|
+
mod
|
|
62
|
+
));
|
|
63
|
+
var require_browser_polyfill = __commonJS({
|
|
64
|
+
"node_modules/cross-fetch/dist/browser-polyfill.js"(exports) {
|
|
65
|
+
(function(self2) {
|
|
66
|
+
var irrelevant = (function(exports2) {
|
|
67
|
+
var g = typeof globalThis !== "undefined" && globalThis || typeof self2 !== "undefined" && self2 || // eslint-disable-next-line no-undef
|
|
68
|
+
typeof global !== "undefined" && global || {};
|
|
69
|
+
var support = {
|
|
70
|
+
searchParams: "URLSearchParams" in g,
|
|
71
|
+
iterable: "Symbol" in g && "iterator" in Symbol,
|
|
72
|
+
blob: "FileReader" in g && "Blob" in g && (function() {
|
|
73
|
+
try {
|
|
74
|
+
new Blob();
|
|
75
|
+
return true;
|
|
76
|
+
} catch (e) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
})(),
|
|
80
|
+
formData: "FormData" in g,
|
|
81
|
+
arrayBuffer: "ArrayBuffer" in g
|
|
82
|
+
};
|
|
83
|
+
function isDataView(obj) {
|
|
84
|
+
return obj && DataView.prototype.isPrototypeOf(obj);
|
|
85
|
+
}
|
|
86
|
+
if (support.arrayBuffer) {
|
|
87
|
+
var viewClasses = [
|
|
88
|
+
"[object Int8Array]",
|
|
89
|
+
"[object Uint8Array]",
|
|
90
|
+
"[object Uint8ClampedArray]",
|
|
91
|
+
"[object Int16Array]",
|
|
92
|
+
"[object Uint16Array]",
|
|
93
|
+
"[object Int32Array]",
|
|
94
|
+
"[object Uint32Array]",
|
|
95
|
+
"[object Float32Array]",
|
|
96
|
+
"[object Float64Array]"
|
|
97
|
+
];
|
|
98
|
+
var isArrayBufferView = ArrayBuffer.isView || function(obj) {
|
|
99
|
+
return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function normalizeName(name) {
|
|
103
|
+
if (typeof name !== "string") {
|
|
104
|
+
name = String(name);
|
|
105
|
+
}
|
|
106
|
+
if (/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(name) || name === "") {
|
|
107
|
+
throw new TypeError('Invalid character in header field name: "' + name + '"');
|
|
108
|
+
}
|
|
109
|
+
return name.toLowerCase();
|
|
110
|
+
}
|
|
111
|
+
function normalizeValue(value) {
|
|
112
|
+
if (typeof value !== "string") {
|
|
113
|
+
value = String(value);
|
|
114
|
+
}
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
function iteratorFor(items) {
|
|
118
|
+
var iterator = {
|
|
119
|
+
next: function() {
|
|
120
|
+
var value = items.shift();
|
|
121
|
+
return { done: value === void 0, value };
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
if (support.iterable) {
|
|
125
|
+
iterator[Symbol.iterator] = function() {
|
|
126
|
+
return iterator;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return iterator;
|
|
130
|
+
}
|
|
131
|
+
function Headers(headers) {
|
|
132
|
+
this.map = {};
|
|
133
|
+
if (headers instanceof Headers) {
|
|
134
|
+
headers.forEach(function(value, name) {
|
|
135
|
+
this.append(name, value);
|
|
136
|
+
}, this);
|
|
137
|
+
} else if (Array.isArray(headers)) {
|
|
138
|
+
headers.forEach(function(header) {
|
|
139
|
+
if (header.length != 2) {
|
|
140
|
+
throw new TypeError("Headers constructor: expected name/value pair to be length 2, found" + header.length);
|
|
141
|
+
}
|
|
142
|
+
this.append(header[0], header[1]);
|
|
143
|
+
}, this);
|
|
144
|
+
} else if (headers) {
|
|
145
|
+
Object.getOwnPropertyNames(headers).forEach(function(name) {
|
|
146
|
+
this.append(name, headers[name]);
|
|
147
|
+
}, this);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
Headers.prototype.append = function(name, value) {
|
|
151
|
+
name = normalizeName(name);
|
|
152
|
+
value = normalizeValue(value);
|
|
153
|
+
var oldValue = this.map[name];
|
|
154
|
+
this.map[name] = oldValue ? oldValue + ", " + value : value;
|
|
155
|
+
};
|
|
156
|
+
Headers.prototype["delete"] = function(name) {
|
|
157
|
+
delete this.map[normalizeName(name)];
|
|
158
|
+
};
|
|
159
|
+
Headers.prototype.get = function(name) {
|
|
160
|
+
name = normalizeName(name);
|
|
161
|
+
return this.has(name) ? this.map[name] : null;
|
|
162
|
+
};
|
|
163
|
+
Headers.prototype.has = function(name) {
|
|
164
|
+
return this.map.hasOwnProperty(normalizeName(name));
|
|
165
|
+
};
|
|
166
|
+
Headers.prototype.set = function(name, value) {
|
|
167
|
+
this.map[normalizeName(name)] = normalizeValue(value);
|
|
168
|
+
};
|
|
169
|
+
Headers.prototype.forEach = function(callback, thisArg) {
|
|
170
|
+
for (var name in this.map) {
|
|
171
|
+
if (this.map.hasOwnProperty(name)) {
|
|
172
|
+
callback.call(thisArg, this.map[name], name, this);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
Headers.prototype.keys = function() {
|
|
177
|
+
var items = [];
|
|
178
|
+
this.forEach(function(value, name) {
|
|
179
|
+
items.push(name);
|
|
180
|
+
});
|
|
181
|
+
return iteratorFor(items);
|
|
182
|
+
};
|
|
183
|
+
Headers.prototype.values = function() {
|
|
184
|
+
var items = [];
|
|
185
|
+
this.forEach(function(value) {
|
|
186
|
+
items.push(value);
|
|
187
|
+
});
|
|
188
|
+
return iteratorFor(items);
|
|
189
|
+
};
|
|
190
|
+
Headers.prototype.entries = function() {
|
|
191
|
+
var items = [];
|
|
192
|
+
this.forEach(function(value, name) {
|
|
193
|
+
items.push([name, value]);
|
|
194
|
+
});
|
|
195
|
+
return iteratorFor(items);
|
|
196
|
+
};
|
|
197
|
+
if (support.iterable) {
|
|
198
|
+
Headers.prototype[Symbol.iterator] = Headers.prototype.entries;
|
|
199
|
+
}
|
|
200
|
+
function consumed(body) {
|
|
201
|
+
if (body._noBody) return;
|
|
202
|
+
if (body.bodyUsed) {
|
|
203
|
+
return Promise.reject(new TypeError("Already read"));
|
|
204
|
+
}
|
|
205
|
+
body.bodyUsed = true;
|
|
206
|
+
}
|
|
207
|
+
function fileReaderReady(reader) {
|
|
208
|
+
return new Promise(function(resolve, reject) {
|
|
209
|
+
reader.onload = function() {
|
|
210
|
+
resolve(reader.result);
|
|
211
|
+
};
|
|
212
|
+
reader.onerror = function() {
|
|
213
|
+
reject(reader.error);
|
|
214
|
+
};
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
function readBlobAsArrayBuffer(blob) {
|
|
218
|
+
var reader = new FileReader();
|
|
219
|
+
var promise = fileReaderReady(reader);
|
|
220
|
+
reader.readAsArrayBuffer(blob);
|
|
221
|
+
return promise;
|
|
222
|
+
}
|
|
223
|
+
function readBlobAsText(blob) {
|
|
224
|
+
var reader = new FileReader();
|
|
225
|
+
var promise = fileReaderReady(reader);
|
|
226
|
+
var match = /charset=([A-Za-z0-9_-]+)/.exec(blob.type);
|
|
227
|
+
var encoding = match ? match[1] : "utf-8";
|
|
228
|
+
reader.readAsText(blob, encoding);
|
|
229
|
+
return promise;
|
|
230
|
+
}
|
|
231
|
+
function readArrayBufferAsText(buf) {
|
|
232
|
+
var view = new Uint8Array(buf);
|
|
233
|
+
var chars = new Array(view.length);
|
|
234
|
+
for (var i = 0; i < view.length; i++) {
|
|
235
|
+
chars[i] = String.fromCharCode(view[i]);
|
|
236
|
+
}
|
|
237
|
+
return chars.join("");
|
|
238
|
+
}
|
|
239
|
+
function bufferClone(buf) {
|
|
240
|
+
if (buf.slice) {
|
|
241
|
+
return buf.slice(0);
|
|
242
|
+
} else {
|
|
243
|
+
var view = new Uint8Array(buf.byteLength);
|
|
244
|
+
view.set(new Uint8Array(buf));
|
|
245
|
+
return view.buffer;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function Body() {
|
|
249
|
+
this.bodyUsed = false;
|
|
250
|
+
this._initBody = function(body) {
|
|
251
|
+
this.bodyUsed = this.bodyUsed;
|
|
252
|
+
this._bodyInit = body;
|
|
253
|
+
if (!body) {
|
|
254
|
+
this._noBody = true;
|
|
255
|
+
this._bodyText = "";
|
|
256
|
+
} else if (typeof body === "string") {
|
|
257
|
+
this._bodyText = body;
|
|
258
|
+
} else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
|
|
259
|
+
this._bodyBlob = body;
|
|
260
|
+
} else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
|
|
261
|
+
this._bodyFormData = body;
|
|
262
|
+
} else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
|
|
263
|
+
this._bodyText = body.toString();
|
|
264
|
+
} else if (support.arrayBuffer && support.blob && isDataView(body)) {
|
|
265
|
+
this._bodyArrayBuffer = bufferClone(body.buffer);
|
|
266
|
+
this._bodyInit = new Blob([this._bodyArrayBuffer]);
|
|
267
|
+
} else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
|
|
268
|
+
this._bodyArrayBuffer = bufferClone(body);
|
|
269
|
+
} else {
|
|
270
|
+
this._bodyText = body = Object.prototype.toString.call(body);
|
|
271
|
+
}
|
|
272
|
+
if (!this.headers.get("content-type")) {
|
|
273
|
+
if (typeof body === "string") {
|
|
274
|
+
this.headers.set("content-type", "text/plain;charset=UTF-8");
|
|
275
|
+
} else if (this._bodyBlob && this._bodyBlob.type) {
|
|
276
|
+
this.headers.set("content-type", this._bodyBlob.type);
|
|
277
|
+
} else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
|
|
278
|
+
this.headers.set("content-type", "application/x-www-form-urlencoded;charset=UTF-8");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
if (support.blob) {
|
|
283
|
+
this.blob = function() {
|
|
284
|
+
var rejected = consumed(this);
|
|
285
|
+
if (rejected) {
|
|
286
|
+
return rejected;
|
|
287
|
+
}
|
|
288
|
+
if (this._bodyBlob) {
|
|
289
|
+
return Promise.resolve(this._bodyBlob);
|
|
290
|
+
} else if (this._bodyArrayBuffer) {
|
|
291
|
+
return Promise.resolve(new Blob([this._bodyArrayBuffer]));
|
|
292
|
+
} else if (this._bodyFormData) {
|
|
293
|
+
throw new Error("could not read FormData body as blob");
|
|
294
|
+
} else {
|
|
295
|
+
return Promise.resolve(new Blob([this._bodyText]));
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
this.arrayBuffer = function() {
|
|
300
|
+
if (this._bodyArrayBuffer) {
|
|
301
|
+
var isConsumed = consumed(this);
|
|
302
|
+
if (isConsumed) {
|
|
303
|
+
return isConsumed;
|
|
304
|
+
} else if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
|
|
305
|
+
return Promise.resolve(
|
|
306
|
+
this._bodyArrayBuffer.buffer.slice(
|
|
307
|
+
this._bodyArrayBuffer.byteOffset,
|
|
308
|
+
this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
|
|
309
|
+
)
|
|
310
|
+
);
|
|
311
|
+
} else {
|
|
312
|
+
return Promise.resolve(this._bodyArrayBuffer);
|
|
313
|
+
}
|
|
314
|
+
} else if (support.blob) {
|
|
315
|
+
return this.blob().then(readBlobAsArrayBuffer);
|
|
316
|
+
} else {
|
|
317
|
+
throw new Error("could not read as ArrayBuffer");
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
this.text = function() {
|
|
321
|
+
var rejected = consumed(this);
|
|
322
|
+
if (rejected) {
|
|
323
|
+
return rejected;
|
|
324
|
+
}
|
|
325
|
+
if (this._bodyBlob) {
|
|
326
|
+
return readBlobAsText(this._bodyBlob);
|
|
327
|
+
} else if (this._bodyArrayBuffer) {
|
|
328
|
+
return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer));
|
|
329
|
+
} else if (this._bodyFormData) {
|
|
330
|
+
throw new Error("could not read FormData body as text");
|
|
331
|
+
} else {
|
|
332
|
+
return Promise.resolve(this._bodyText);
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
if (support.formData) {
|
|
336
|
+
this.formData = function() {
|
|
337
|
+
return this.text().then(decode);
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
this.json = function() {
|
|
341
|
+
return this.text().then(JSON.parse);
|
|
342
|
+
};
|
|
343
|
+
return this;
|
|
344
|
+
}
|
|
345
|
+
var methods = ["CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "TRACE"];
|
|
346
|
+
function normalizeMethod(method) {
|
|
347
|
+
var upcased = method.toUpperCase();
|
|
348
|
+
return methods.indexOf(upcased) > -1 ? upcased : method;
|
|
349
|
+
}
|
|
350
|
+
function Request(input, options) {
|
|
351
|
+
if (!(this instanceof Request)) {
|
|
352
|
+
throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');
|
|
353
|
+
}
|
|
354
|
+
options = options || {};
|
|
355
|
+
var body = options.body;
|
|
356
|
+
if (input instanceof Request) {
|
|
357
|
+
if (input.bodyUsed) {
|
|
358
|
+
throw new TypeError("Already read");
|
|
359
|
+
}
|
|
360
|
+
this.url = input.url;
|
|
361
|
+
this.credentials = input.credentials;
|
|
362
|
+
if (!options.headers) {
|
|
363
|
+
this.headers = new Headers(input.headers);
|
|
364
|
+
}
|
|
365
|
+
this.method = input.method;
|
|
366
|
+
this.mode = input.mode;
|
|
367
|
+
this.signal = input.signal;
|
|
368
|
+
if (!body && input._bodyInit != null) {
|
|
369
|
+
body = input._bodyInit;
|
|
370
|
+
input.bodyUsed = true;
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
this.url = String(input);
|
|
374
|
+
}
|
|
375
|
+
this.credentials = options.credentials || this.credentials || "same-origin";
|
|
376
|
+
if (options.headers || !this.headers) {
|
|
377
|
+
this.headers = new Headers(options.headers);
|
|
378
|
+
}
|
|
379
|
+
this.method = normalizeMethod(options.method || this.method || "GET");
|
|
380
|
+
this.mode = options.mode || this.mode || null;
|
|
381
|
+
this.signal = options.signal || this.signal || (function() {
|
|
382
|
+
if ("AbortController" in g) {
|
|
383
|
+
var ctrl = new AbortController();
|
|
384
|
+
return ctrl.signal;
|
|
385
|
+
}
|
|
386
|
+
})();
|
|
387
|
+
this.referrer = null;
|
|
388
|
+
if ((this.method === "GET" || this.method === "HEAD") && body) {
|
|
389
|
+
throw new TypeError("Body not allowed for GET or HEAD requests");
|
|
390
|
+
}
|
|
391
|
+
this._initBody(body);
|
|
392
|
+
if (this.method === "GET" || this.method === "HEAD") {
|
|
393
|
+
if (options.cache === "no-store" || options.cache === "no-cache") {
|
|
394
|
+
var reParamSearch = /([?&])_=[^&]*/;
|
|
395
|
+
if (reParamSearch.test(this.url)) {
|
|
396
|
+
this.url = this.url.replace(reParamSearch, "$1_=" + (/* @__PURE__ */ new Date()).getTime());
|
|
397
|
+
} else {
|
|
398
|
+
var reQueryString = /\?/;
|
|
399
|
+
this.url += (reQueryString.test(this.url) ? "&" : "?") + "_=" + (/* @__PURE__ */ new Date()).getTime();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
Request.prototype.clone = function() {
|
|
405
|
+
return new Request(this, { body: this._bodyInit });
|
|
406
|
+
};
|
|
407
|
+
function decode(body) {
|
|
408
|
+
var form = new FormData();
|
|
409
|
+
body.trim().split("&").forEach(function(bytes) {
|
|
410
|
+
if (bytes) {
|
|
411
|
+
var split = bytes.split("=");
|
|
412
|
+
var name = split.shift().replace(/\+/g, " ");
|
|
413
|
+
var value = split.join("=").replace(/\+/g, " ");
|
|
414
|
+
form.append(decodeURIComponent(name), decodeURIComponent(value));
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
return form;
|
|
418
|
+
}
|
|
419
|
+
function parseHeaders(rawHeaders) {
|
|
420
|
+
var headers = new Headers();
|
|
421
|
+
var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, " ");
|
|
422
|
+
preProcessedHeaders.split("\r").map(function(header) {
|
|
423
|
+
return header.indexOf("\n") === 0 ? header.substr(1, header.length) : header;
|
|
424
|
+
}).forEach(function(line) {
|
|
425
|
+
var parts = line.split(":");
|
|
426
|
+
var key = parts.shift().trim();
|
|
427
|
+
if (key) {
|
|
428
|
+
var value = parts.join(":").trim();
|
|
429
|
+
try {
|
|
430
|
+
headers.append(key, value);
|
|
431
|
+
} catch (error) {
|
|
432
|
+
console.warn("Response " + error.message);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
return headers;
|
|
437
|
+
}
|
|
438
|
+
Body.call(Request.prototype);
|
|
439
|
+
function Response(bodyInit, options) {
|
|
440
|
+
if (!(this instanceof Response)) {
|
|
441
|
+
throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');
|
|
442
|
+
}
|
|
443
|
+
if (!options) {
|
|
444
|
+
options = {};
|
|
445
|
+
}
|
|
446
|
+
this.type = "default";
|
|
447
|
+
this.status = options.status === void 0 ? 200 : options.status;
|
|
448
|
+
if (this.status < 200 || this.status > 599) {
|
|
449
|
+
throw new RangeError("Failed to construct 'Response': The status provided (0) is outside the range [200, 599].");
|
|
450
|
+
}
|
|
451
|
+
this.ok = this.status >= 200 && this.status < 300;
|
|
452
|
+
this.statusText = options.statusText === void 0 ? "" : "" + options.statusText;
|
|
453
|
+
this.headers = new Headers(options.headers);
|
|
454
|
+
this.url = options.url || "";
|
|
455
|
+
this._initBody(bodyInit);
|
|
456
|
+
}
|
|
457
|
+
Body.call(Response.prototype);
|
|
458
|
+
Response.prototype.clone = function() {
|
|
459
|
+
return new Response(this._bodyInit, {
|
|
460
|
+
status: this.status,
|
|
461
|
+
statusText: this.statusText,
|
|
462
|
+
headers: new Headers(this.headers),
|
|
463
|
+
url: this.url
|
|
464
|
+
});
|
|
465
|
+
};
|
|
466
|
+
Response.error = function() {
|
|
467
|
+
var response = new Response(null, { status: 200, statusText: "" });
|
|
468
|
+
response.ok = false;
|
|
469
|
+
response.status = 0;
|
|
470
|
+
response.type = "error";
|
|
471
|
+
return response;
|
|
472
|
+
};
|
|
473
|
+
var redirectStatuses = [301, 302, 303, 307, 308];
|
|
474
|
+
Response.redirect = function(url, status) {
|
|
475
|
+
if (redirectStatuses.indexOf(status) === -1) {
|
|
476
|
+
throw new RangeError("Invalid status code");
|
|
477
|
+
}
|
|
478
|
+
return new Response(null, { status, headers: { location: url } });
|
|
479
|
+
};
|
|
480
|
+
exports2.DOMException = g.DOMException;
|
|
481
|
+
try {
|
|
482
|
+
new exports2.DOMException();
|
|
483
|
+
} catch (err) {
|
|
484
|
+
exports2.DOMException = function(message, name) {
|
|
485
|
+
this.message = message;
|
|
486
|
+
this.name = name;
|
|
487
|
+
var error = Error(message);
|
|
488
|
+
this.stack = error.stack;
|
|
489
|
+
};
|
|
490
|
+
exports2.DOMException.prototype = Object.create(Error.prototype);
|
|
491
|
+
exports2.DOMException.prototype.constructor = exports2.DOMException;
|
|
492
|
+
}
|
|
493
|
+
function fetch2(input, init) {
|
|
494
|
+
return new Promise(function(resolve, reject) {
|
|
495
|
+
var request = new Request(input, init);
|
|
496
|
+
if (request.signal && request.signal.aborted) {
|
|
497
|
+
return reject(new exports2.DOMException("Aborted", "AbortError"));
|
|
498
|
+
}
|
|
499
|
+
var xhr = new XMLHttpRequest();
|
|
500
|
+
function abortXhr() {
|
|
501
|
+
xhr.abort();
|
|
502
|
+
}
|
|
503
|
+
xhr.onload = function() {
|
|
504
|
+
var options = {
|
|
505
|
+
statusText: xhr.statusText,
|
|
506
|
+
headers: parseHeaders(xhr.getAllResponseHeaders() || "")
|
|
507
|
+
};
|
|
508
|
+
if (request.url.indexOf("file://") === 0 && (xhr.status < 200 || xhr.status > 599)) {
|
|
509
|
+
options.status = 200;
|
|
510
|
+
} else {
|
|
511
|
+
options.status = xhr.status;
|
|
512
|
+
}
|
|
513
|
+
options.url = "responseURL" in xhr ? xhr.responseURL : options.headers.get("X-Request-URL");
|
|
514
|
+
var body = "response" in xhr ? xhr.response : xhr.responseText;
|
|
515
|
+
setTimeout(function() {
|
|
516
|
+
resolve(new Response(body, options));
|
|
517
|
+
}, 0);
|
|
518
|
+
};
|
|
519
|
+
xhr.onerror = function() {
|
|
520
|
+
setTimeout(function() {
|
|
521
|
+
reject(new TypeError("Network request failed"));
|
|
522
|
+
}, 0);
|
|
523
|
+
};
|
|
524
|
+
xhr.ontimeout = function() {
|
|
525
|
+
setTimeout(function() {
|
|
526
|
+
reject(new TypeError("Network request timed out"));
|
|
527
|
+
}, 0);
|
|
528
|
+
};
|
|
529
|
+
xhr.onabort = function() {
|
|
530
|
+
setTimeout(function() {
|
|
531
|
+
reject(new exports2.DOMException("Aborted", "AbortError"));
|
|
532
|
+
}, 0);
|
|
533
|
+
};
|
|
534
|
+
function fixUrl(url) {
|
|
535
|
+
try {
|
|
536
|
+
return url === "" && g.location.href ? g.location.href : url;
|
|
537
|
+
} catch (e) {
|
|
538
|
+
return url;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
xhr.open(request.method, fixUrl(request.url), true);
|
|
542
|
+
if (request.credentials === "include") {
|
|
543
|
+
xhr.withCredentials = true;
|
|
544
|
+
} else if (request.credentials === "omit") {
|
|
545
|
+
xhr.withCredentials = false;
|
|
546
|
+
}
|
|
547
|
+
if ("responseType" in xhr) {
|
|
548
|
+
if (support.blob) {
|
|
549
|
+
xhr.responseType = "blob";
|
|
550
|
+
} else if (support.arrayBuffer) {
|
|
551
|
+
xhr.responseType = "arraybuffer";
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (init && typeof init.headers === "object" && !(init.headers instanceof Headers || g.Headers && init.headers instanceof g.Headers)) {
|
|
555
|
+
var names = [];
|
|
556
|
+
Object.getOwnPropertyNames(init.headers).forEach(function(name) {
|
|
557
|
+
names.push(normalizeName(name));
|
|
558
|
+
xhr.setRequestHeader(name, normalizeValue(init.headers[name]));
|
|
559
|
+
});
|
|
560
|
+
request.headers.forEach(function(value, name) {
|
|
561
|
+
if (names.indexOf(name) === -1) {
|
|
562
|
+
xhr.setRequestHeader(name, value);
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
} else {
|
|
566
|
+
request.headers.forEach(function(value, name) {
|
|
567
|
+
xhr.setRequestHeader(name, value);
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
if (request.signal) {
|
|
571
|
+
request.signal.addEventListener("abort", abortXhr);
|
|
572
|
+
xhr.onreadystatechange = function() {
|
|
573
|
+
if (xhr.readyState === 4) {
|
|
574
|
+
request.signal.removeEventListener("abort", abortXhr);
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
xhr.send(typeof request._bodyInit === "undefined" ? null : request._bodyInit);
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
fetch2.polyfill = true;
|
|
582
|
+
if (!g.fetch) {
|
|
583
|
+
g.fetch = fetch2;
|
|
584
|
+
g.Headers = Headers;
|
|
585
|
+
g.Request = Request;
|
|
586
|
+
g.Response = Response;
|
|
587
|
+
}
|
|
588
|
+
exports2.Headers = Headers;
|
|
589
|
+
exports2.Request = Request;
|
|
590
|
+
exports2.Response = Response;
|
|
591
|
+
exports2.fetch = fetch2;
|
|
592
|
+
return exports2;
|
|
593
|
+
})({});
|
|
594
|
+
})(typeof self !== "undefined" ? self : exports);
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
var byteToHex = [];
|
|
598
|
+
for (let i = 0; i < 256; ++i) {
|
|
599
|
+
byteToHex.push((i + 256).toString(16).slice(1));
|
|
600
|
+
}
|
|
601
|
+
function unsafeStringify(arr, offset = 0) {
|
|
602
|
+
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
|
603
|
+
}
|
|
604
|
+
var getRandomValues;
|
|
605
|
+
var rnds8 = new Uint8Array(16);
|
|
606
|
+
function rng() {
|
|
607
|
+
if (!getRandomValues) {
|
|
608
|
+
if (typeof crypto === "undefined" || !crypto.getRandomValues) {
|
|
609
|
+
throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
|
|
610
|
+
}
|
|
611
|
+
getRandomValues = crypto.getRandomValues.bind(crypto);
|
|
612
|
+
}
|
|
613
|
+
return getRandomValues(rnds8);
|
|
614
|
+
}
|
|
615
|
+
var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
|
|
616
|
+
var native_default = { randomUUID };
|
|
617
|
+
function _v4(options, buf, offset) {
|
|
618
|
+
options = options || {};
|
|
619
|
+
const rnds = options.random ?? options.rng?.() ?? rng();
|
|
620
|
+
if (rnds.length < 16) {
|
|
621
|
+
throw new Error("Random bytes length must be >= 16");
|
|
622
|
+
}
|
|
623
|
+
rnds[6] = rnds[6] & 15 | 64;
|
|
624
|
+
rnds[8] = rnds[8] & 63 | 128;
|
|
625
|
+
if (buf) {
|
|
626
|
+
offset = offset || 0;
|
|
627
|
+
if (offset < 0 || offset + 16 > buf.length) {
|
|
628
|
+
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
|
|
629
|
+
}
|
|
630
|
+
for (let i = 0; i < 16; ++i) {
|
|
631
|
+
buf[offset + i] = rnds[i];
|
|
632
|
+
}
|
|
633
|
+
return buf;
|
|
634
|
+
}
|
|
635
|
+
return unsafeStringify(rnds);
|
|
636
|
+
}
|
|
637
|
+
function v4(options, buf, offset) {
|
|
638
|
+
if (native_default.randomUUID && !buf && !options) {
|
|
639
|
+
return native_default.randomUUID();
|
|
640
|
+
}
|
|
641
|
+
return _v4(options, buf, offset);
|
|
642
|
+
}
|
|
643
|
+
var v4_default = v4;
|
|
644
|
+
var import_polyfill = __toESM(require_browser_polyfill());
|
|
645
|
+
function CheckFlagResponseDataFromJSON(json) {
|
|
646
|
+
return CheckFlagResponseDataFromJSONTyped(json, false);
|
|
647
|
+
}
|
|
648
|
+
function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
|
|
649
|
+
if (json == null) {
|
|
650
|
+
return json;
|
|
651
|
+
}
|
|
652
|
+
return {
|
|
653
|
+
companyId: json["company_id"] == null ? void 0 : json["company_id"],
|
|
654
|
+
error: json["error"] == null ? void 0 : json["error"],
|
|
655
|
+
featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
|
|
656
|
+
featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
|
|
657
|
+
featureUsageEvent: json["feature_usage_event"] == null ? void 0 : json["feature_usage_event"],
|
|
658
|
+
featureUsagePeriod: json["feature_usage_period"] == null ? void 0 : json["feature_usage_period"],
|
|
659
|
+
featureUsageResetAt: json["feature_usage_reset_at"] == null ? void 0 : new Date(json["feature_usage_reset_at"]),
|
|
660
|
+
flag: json["flag"],
|
|
661
|
+
flagId: json["flag_id"] == null ? void 0 : json["flag_id"],
|
|
662
|
+
reason: json["reason"],
|
|
663
|
+
ruleId: json["rule_id"] == null ? void 0 : json["rule_id"],
|
|
664
|
+
ruleType: json["rule_type"] == null ? void 0 : json["rule_type"],
|
|
665
|
+
userId: json["user_id"] == null ? void 0 : json["user_id"],
|
|
666
|
+
value: json["value"]
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
function EventBodyFlagCheckToJSON(json) {
|
|
670
|
+
return EventBodyFlagCheckToJSONTyped(json, false);
|
|
671
|
+
}
|
|
672
|
+
function EventBodyFlagCheckToJSONTyped(value, ignoreDiscriminator = false) {
|
|
673
|
+
if (value == null) {
|
|
674
|
+
return value;
|
|
675
|
+
}
|
|
676
|
+
return {
|
|
677
|
+
company_id: value["companyId"],
|
|
678
|
+
error: value["error"],
|
|
679
|
+
flag_id: value["flagId"],
|
|
680
|
+
flag_key: value["flagKey"],
|
|
681
|
+
reason: value["reason"],
|
|
682
|
+
req_company: value["reqCompany"],
|
|
683
|
+
req_user: value["reqUser"],
|
|
684
|
+
rule_id: value["ruleId"],
|
|
685
|
+
user_id: value["userId"],
|
|
686
|
+
value: value["value"]
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
function CheckFlagResponseFromJSON(json) {
|
|
690
|
+
return CheckFlagResponseFromJSONTyped(json, false);
|
|
691
|
+
}
|
|
692
|
+
function CheckFlagResponseFromJSONTyped(json, ignoreDiscriminator) {
|
|
693
|
+
if (json == null) {
|
|
694
|
+
return json;
|
|
695
|
+
}
|
|
696
|
+
return {
|
|
697
|
+
data: CheckFlagResponseDataFromJSON(json["data"]),
|
|
698
|
+
params: json["params"]
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
function CheckFlagsResponseDataFromJSON(json) {
|
|
702
|
+
return CheckFlagsResponseDataFromJSONTyped(json, false);
|
|
703
|
+
}
|
|
704
|
+
function CheckFlagsResponseDataFromJSONTyped(json, ignoreDiscriminator) {
|
|
705
|
+
if (json == null) {
|
|
706
|
+
return json;
|
|
707
|
+
}
|
|
708
|
+
return {
|
|
709
|
+
flags: json["flags"].map(CheckFlagResponseDataFromJSON)
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
function CheckFlagsResponseFromJSON(json) {
|
|
713
|
+
return CheckFlagsResponseFromJSONTyped(json, false);
|
|
714
|
+
}
|
|
715
|
+
function CheckFlagsResponseFromJSONTyped(json, ignoreDiscriminator) {
|
|
716
|
+
if (json == null) {
|
|
717
|
+
return json;
|
|
718
|
+
}
|
|
719
|
+
return {
|
|
720
|
+
data: CheckFlagsResponseDataFromJSON(json["data"]),
|
|
721
|
+
params: json["params"]
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
var RuleType = /* @__PURE__ */ ((RuleType2) => {
|
|
725
|
+
RuleType2["GLOBAL_OVERRIDE"] = "global_override";
|
|
726
|
+
RuleType2["COMPANY_OVERRIDE"] = "company_override";
|
|
727
|
+
RuleType2["COMPANY_OVERRIDE_USAGE_EXCEEDED"] = "company_override_usage_exceeded";
|
|
728
|
+
RuleType2["PLAN_ENTITLEMENT"] = "plan_entitlement";
|
|
729
|
+
RuleType2["PLAN_ENTITLEMENT_USAGE_EXCEEDED"] = "plan_entitlement_usage_exceeded";
|
|
730
|
+
RuleType2["STANDARD"] = "standard";
|
|
731
|
+
RuleType2["DEFAULT"] = "default";
|
|
732
|
+
return RuleType2;
|
|
733
|
+
})(RuleType || {});
|
|
734
|
+
var UsagePeriod = /* @__PURE__ */ ((UsagePeriod2) => {
|
|
735
|
+
UsagePeriod2["ALL_TIME"] = "all_time";
|
|
736
|
+
UsagePeriod2["CURRENT_DAY"] = "current_day";
|
|
737
|
+
UsagePeriod2["CURRENT_MONTH"] = "current_month";
|
|
738
|
+
UsagePeriod2["CURRENT_WEEK"] = "current_week";
|
|
739
|
+
return UsagePeriod2;
|
|
740
|
+
})(UsagePeriod || {});
|
|
741
|
+
var CheckFlagReturnFromJSON = (json) => {
|
|
742
|
+
const {
|
|
743
|
+
companyId,
|
|
744
|
+
error,
|
|
745
|
+
featureAllocation,
|
|
746
|
+
featureUsage,
|
|
747
|
+
featureUsageEvent,
|
|
748
|
+
featureUsagePeriod,
|
|
749
|
+
featureUsageResetAt,
|
|
750
|
+
flag,
|
|
751
|
+
flagId,
|
|
752
|
+
reason,
|
|
753
|
+
ruleId,
|
|
754
|
+
ruleType,
|
|
755
|
+
userId,
|
|
756
|
+
value
|
|
757
|
+
} = CheckFlagResponseDataFromJSON(json);
|
|
758
|
+
const featureUsageExceeded = !value && // if flag is not false, then we haven't exceeded usage
|
|
759
|
+
(ruleType == "company_override_usage_exceeded" || // if the rule type is one of these, then we have exceeded usage
|
|
760
|
+
ruleType == "plan_entitlement_usage_exceeded");
|
|
761
|
+
return {
|
|
762
|
+
featureUsageExceeded,
|
|
763
|
+
companyId: companyId == null ? void 0 : companyId,
|
|
764
|
+
error: error == null ? void 0 : error,
|
|
765
|
+
featureAllocation: featureAllocation == null ? void 0 : featureAllocation,
|
|
766
|
+
featureUsage: featureUsage == null ? void 0 : featureUsage,
|
|
767
|
+
featureUsageEvent: featureUsageEvent === null ? void 0 : featureUsageEvent,
|
|
768
|
+
featureUsagePeriod: featureUsagePeriod == null ? void 0 : featureUsagePeriod,
|
|
769
|
+
featureUsageResetAt: featureUsageResetAt == null ? void 0 : featureUsageResetAt,
|
|
770
|
+
flag,
|
|
771
|
+
flagId: flagId == null ? void 0 : flagId,
|
|
772
|
+
reason,
|
|
773
|
+
ruleId: ruleId == null ? void 0 : ruleId,
|
|
774
|
+
ruleType: ruleType == null ? void 0 : ruleType,
|
|
775
|
+
userId: userId == null ? void 0 : userId,
|
|
776
|
+
value
|
|
777
|
+
};
|
|
778
|
+
};
|
|
779
|
+
function contextString(context) {
|
|
780
|
+
const sortedContext = Object.keys(context).reduce((acc, key) => {
|
|
781
|
+
const sortedKeys = Object.keys(
|
|
782
|
+
context[key] || {}
|
|
783
|
+
).sort();
|
|
784
|
+
const sortedObj = sortedKeys.reduce((obj, sortedKey) => {
|
|
785
|
+
obj[sortedKey] = context[key][sortedKey];
|
|
786
|
+
return obj;
|
|
787
|
+
}, {});
|
|
788
|
+
acc[key] = sortedObj;
|
|
789
|
+
return acc;
|
|
790
|
+
}, {});
|
|
791
|
+
return JSON.stringify(sortedContext);
|
|
792
|
+
}
|
|
793
|
+
var version = "1.2.6";
|
|
794
|
+
var anonymousIdKey = "schematicId";
|
|
795
|
+
var Schematic = class {
|
|
796
|
+
additionalHeaders = {};
|
|
797
|
+
apiKey;
|
|
798
|
+
apiUrl = "https://api.schematichq.com";
|
|
799
|
+
conn = null;
|
|
800
|
+
context = {};
|
|
801
|
+
debugEnabled = false;
|
|
802
|
+
offlineEnabled = false;
|
|
803
|
+
eventQueue;
|
|
804
|
+
contextDependentEventQueue;
|
|
805
|
+
eventUrl = "https://c.schematichq.com";
|
|
806
|
+
flagCheckListeners = {};
|
|
807
|
+
flagValueListeners = {};
|
|
808
|
+
isPending = true;
|
|
809
|
+
isPendingListeners = /* @__PURE__ */ new Set();
|
|
810
|
+
storage;
|
|
811
|
+
useWebSocket = false;
|
|
812
|
+
checks = {};
|
|
813
|
+
featureUsageEventMap = {};
|
|
814
|
+
webSocketUrl = "wss://api.schematichq.com";
|
|
815
|
+
constructor(apiKey, options) {
|
|
816
|
+
this.apiKey = apiKey;
|
|
817
|
+
this.eventQueue = [];
|
|
818
|
+
this.contextDependentEventQueue = [];
|
|
819
|
+
this.useWebSocket = options?.useWebSocket ?? false;
|
|
820
|
+
this.debugEnabled = options?.debug ?? false;
|
|
821
|
+
this.offlineEnabled = options?.offline ?? false;
|
|
822
|
+
if (typeof window !== "undefined" && typeof window.location !== "undefined") {
|
|
823
|
+
const params = new URLSearchParams(window.location.search);
|
|
824
|
+
const debugParam = params.get("schematic_debug");
|
|
825
|
+
if (debugParam !== null && (debugParam === "" || debugParam === "true" || debugParam === "1")) {
|
|
826
|
+
this.debugEnabled = true;
|
|
827
|
+
}
|
|
828
|
+
const offlineParam = params.get("schematic_offline");
|
|
829
|
+
if (offlineParam !== null && (offlineParam === "" || offlineParam === "true" || offlineParam === "1")) {
|
|
830
|
+
this.offlineEnabled = true;
|
|
831
|
+
this.debugEnabled = true;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (this.offlineEnabled && options?.debug !== false) {
|
|
835
|
+
this.debugEnabled = true;
|
|
836
|
+
}
|
|
837
|
+
if (this.offlineEnabled) {
|
|
838
|
+
this.setIsPending(false);
|
|
839
|
+
}
|
|
840
|
+
this.additionalHeaders = {
|
|
841
|
+
"X-Schematic-Client-Version": `schematic-js@${version}`,
|
|
842
|
+
...options?.additionalHeaders ?? {}
|
|
843
|
+
};
|
|
844
|
+
if (options?.storage) {
|
|
845
|
+
this.storage = options.storage;
|
|
846
|
+
} else if (typeof localStorage !== "undefined") {
|
|
847
|
+
this.storage = localStorage;
|
|
848
|
+
}
|
|
849
|
+
if (options?.apiUrl !== void 0) {
|
|
850
|
+
this.apiUrl = options.apiUrl;
|
|
851
|
+
}
|
|
852
|
+
if (options?.eventUrl !== void 0) {
|
|
853
|
+
this.eventUrl = options.eventUrl;
|
|
854
|
+
}
|
|
855
|
+
if (options?.webSocketUrl !== void 0) {
|
|
856
|
+
this.webSocketUrl = options.webSocketUrl;
|
|
857
|
+
}
|
|
858
|
+
if (typeof window !== "undefined" && window?.addEventListener) {
|
|
859
|
+
window.addEventListener("beforeunload", () => {
|
|
860
|
+
this.flushEventQueue();
|
|
861
|
+
this.flushContextDependentEventQueue();
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
if (this.offlineEnabled) {
|
|
865
|
+
this.debug(
|
|
866
|
+
"Initialized with offline mode enabled - no network requests will be made"
|
|
867
|
+
);
|
|
868
|
+
} else if (this.debugEnabled) {
|
|
869
|
+
this.debug("Initialized with debug mode enabled");
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Get value for a single flag.
|
|
874
|
+
* In WebSocket mode, returns cached values if connection is active, otherwise establishes
|
|
875
|
+
* new connection and then returns the requestedvalue. Falls back to REST API if WebSocket
|
|
876
|
+
* connection fails.
|
|
877
|
+
* In REST mode, makes an API call for each check.
|
|
878
|
+
*/
|
|
879
|
+
async checkFlag(options) {
|
|
880
|
+
const { fallback = false, key } = options;
|
|
881
|
+
const context = options.context || this.context;
|
|
882
|
+
const contextStr = contextString(context);
|
|
883
|
+
this.debug(`checkFlag: ${key}`, { context, fallback });
|
|
884
|
+
if (this.isOffline()) {
|
|
885
|
+
this.debug(`checkFlag offline result: ${key}`, {
|
|
886
|
+
value: fallback,
|
|
887
|
+
offlineMode: true
|
|
888
|
+
});
|
|
889
|
+
return fallback;
|
|
890
|
+
}
|
|
891
|
+
if (!this.useWebSocket) {
|
|
892
|
+
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
893
|
+
return fetch(requestUrl, {
|
|
894
|
+
method: "POST",
|
|
895
|
+
headers: {
|
|
896
|
+
...this.additionalHeaders ?? {},
|
|
897
|
+
"Content-Type": "application/json;charset=UTF-8",
|
|
898
|
+
"X-Schematic-Api-Key": this.apiKey
|
|
899
|
+
},
|
|
900
|
+
body: JSON.stringify(context)
|
|
901
|
+
}).then((response) => {
|
|
902
|
+
if (!response.ok) {
|
|
903
|
+
throw new Error("Network response was not ok");
|
|
904
|
+
}
|
|
905
|
+
return response.json();
|
|
906
|
+
}).then((response) => {
|
|
907
|
+
const parsedResponse = CheckFlagResponseFromJSON(response);
|
|
908
|
+
this.debug(`checkFlag result: ${key}`, parsedResponse);
|
|
909
|
+
const result = CheckFlagReturnFromJSON(parsedResponse.data);
|
|
910
|
+
if (typeof result.featureUsageEvent === "string") {
|
|
911
|
+
this.updateFeatureUsageEventMap(result);
|
|
912
|
+
}
|
|
913
|
+
this.submitFlagCheckEvent(key, result, context);
|
|
914
|
+
return result.value;
|
|
915
|
+
}).catch((error) => {
|
|
916
|
+
console.error("There was a problem with the fetch operation:", error);
|
|
917
|
+
const errorResult = {
|
|
918
|
+
flag: key,
|
|
919
|
+
value: fallback,
|
|
920
|
+
reason: "API request failed",
|
|
921
|
+
error: error instanceof Error ? error.message : String(error)
|
|
922
|
+
};
|
|
923
|
+
this.submitFlagCheckEvent(key, errorResult, context);
|
|
924
|
+
return fallback;
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
try {
|
|
928
|
+
const existingVals = this.checks[contextStr];
|
|
929
|
+
if (this.conn !== null && typeof existingVals !== "undefined" && typeof existingVals[key] !== "undefined") {
|
|
930
|
+
this.debug(`checkFlag cached result: ${key}`, existingVals[key]);
|
|
931
|
+
return existingVals[key].value;
|
|
932
|
+
}
|
|
933
|
+
if (this.isOffline()) {
|
|
934
|
+
return fallback;
|
|
935
|
+
}
|
|
936
|
+
try {
|
|
937
|
+
await this.setContext(context);
|
|
938
|
+
} catch (error) {
|
|
939
|
+
console.error(
|
|
940
|
+
"WebSocket connection failed, falling back to REST:",
|
|
941
|
+
error
|
|
942
|
+
);
|
|
943
|
+
return this.fallbackToRest(key, context, fallback);
|
|
944
|
+
}
|
|
945
|
+
const contextVals = this.checks[contextStr] ?? {};
|
|
946
|
+
const flagCheck = contextVals[key];
|
|
947
|
+
const result = flagCheck?.value ?? fallback;
|
|
948
|
+
this.debug(
|
|
949
|
+
`checkFlag WebSocket result: ${key}`,
|
|
950
|
+
typeof flagCheck !== "undefined" ? flagCheck : { value: fallback, fallbackUsed: true }
|
|
951
|
+
);
|
|
952
|
+
if (typeof flagCheck !== "undefined") {
|
|
953
|
+
this.submitFlagCheckEvent(key, flagCheck, context);
|
|
954
|
+
}
|
|
955
|
+
return result;
|
|
956
|
+
} catch (error) {
|
|
957
|
+
console.error("Unexpected error in checkFlag:", error);
|
|
958
|
+
const errorResult = {
|
|
959
|
+
flag: key,
|
|
960
|
+
value: fallback,
|
|
961
|
+
reason: "Unexpected error in flag check",
|
|
962
|
+
error: error instanceof Error ? error.message : String(error)
|
|
963
|
+
};
|
|
964
|
+
this.submitFlagCheckEvent(key, errorResult, context);
|
|
965
|
+
return fallback;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Helper function to log debug messages
|
|
970
|
+
* Only logs if debug mode is enabled
|
|
971
|
+
*/
|
|
972
|
+
debug(message, ...args) {
|
|
973
|
+
if (this.debugEnabled) {
|
|
974
|
+
console.log(`[Schematic] ${message}`, ...args);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Helper function to check if client is in offline mode
|
|
979
|
+
*/
|
|
980
|
+
isOffline() {
|
|
981
|
+
return this.offlineEnabled;
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Submit a flag check event
|
|
985
|
+
* Records data about a flag check for analytics
|
|
986
|
+
*/
|
|
987
|
+
submitFlagCheckEvent(flagKey, result, context) {
|
|
988
|
+
const eventBody = {
|
|
989
|
+
flagKey,
|
|
990
|
+
value: result.value,
|
|
991
|
+
reason: result.reason,
|
|
992
|
+
flagId: result.flagId,
|
|
993
|
+
ruleId: result.ruleId,
|
|
994
|
+
companyId: result.companyId,
|
|
995
|
+
userId: result.userId,
|
|
996
|
+
error: result.error,
|
|
997
|
+
reqCompany: context.company,
|
|
998
|
+
reqUser: context.user
|
|
999
|
+
};
|
|
1000
|
+
this.debug(`submitting flag check event:`, eventBody);
|
|
1001
|
+
return this.handleEvent("flag_check", EventBodyFlagCheckToJSON(eventBody));
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Helper method for falling back to REST API when WebSocket connection fails
|
|
1005
|
+
*/
|
|
1006
|
+
async fallbackToRest(key, context, fallback) {
|
|
1007
|
+
if (this.isOffline()) {
|
|
1008
|
+
this.debug(`fallbackToRest offline result: ${key}`, {
|
|
1009
|
+
value: fallback,
|
|
1010
|
+
offlineMode: true
|
|
1011
|
+
});
|
|
1012
|
+
return fallback;
|
|
1013
|
+
}
|
|
1014
|
+
try {
|
|
1015
|
+
const requestUrl = `${this.apiUrl}/flags/${key}/check`;
|
|
1016
|
+
const response = await fetch(requestUrl, {
|
|
1017
|
+
method: "POST",
|
|
1018
|
+
headers: {
|
|
1019
|
+
...this.additionalHeaders ?? {},
|
|
1020
|
+
"Content-Type": "application/json;charset=UTF-8",
|
|
1021
|
+
"X-Schematic-Api-Key": this.apiKey
|
|
1022
|
+
},
|
|
1023
|
+
body: JSON.stringify(context)
|
|
1024
|
+
});
|
|
1025
|
+
if (!response.ok) {
|
|
1026
|
+
throw new Error("Network response was not ok");
|
|
1027
|
+
}
|
|
1028
|
+
const responseJson = await response.json();
|
|
1029
|
+
const data = CheckFlagResponseFromJSON(responseJson);
|
|
1030
|
+
this.debug(`fallbackToRest result: ${key}`, data);
|
|
1031
|
+
const result = CheckFlagReturnFromJSON(data.data);
|
|
1032
|
+
if (typeof result.featureUsageEvent === "string") {
|
|
1033
|
+
this.updateFeatureUsageEventMap(result);
|
|
1034
|
+
}
|
|
1035
|
+
this.submitFlagCheckEvent(key, result, context);
|
|
1036
|
+
return result.value;
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
console.error("REST API call failed, using fallback value:", error);
|
|
1039
|
+
const errorResult = {
|
|
1040
|
+
flag: key,
|
|
1041
|
+
value: fallback,
|
|
1042
|
+
reason: "API request failed (fallback)",
|
|
1043
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1044
|
+
};
|
|
1045
|
+
this.submitFlagCheckEvent(key, errorResult, context);
|
|
1046
|
+
return fallback;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Make an API call to fetch all flag values for a given context.
|
|
1051
|
+
* Recommended for use in REST mode only.
|
|
1052
|
+
* In offline mode, returns an empty object.
|
|
1053
|
+
*/
|
|
1054
|
+
checkFlags = async (context) => {
|
|
1055
|
+
context = context || this.context;
|
|
1056
|
+
this.debug(`checkFlags`, { context });
|
|
1057
|
+
if (this.isOffline()) {
|
|
1058
|
+
this.debug(`checkFlags offline result: returning empty object`);
|
|
1059
|
+
return {};
|
|
1060
|
+
}
|
|
1061
|
+
const requestUrl = `${this.apiUrl}/flags/check`;
|
|
1062
|
+
const requestBody = JSON.stringify(context);
|
|
1063
|
+
return fetch(requestUrl, {
|
|
1064
|
+
method: "POST",
|
|
1065
|
+
headers: {
|
|
1066
|
+
...this.additionalHeaders ?? {},
|
|
1067
|
+
"Content-Type": "application/json;charset=UTF-8",
|
|
1068
|
+
"X-Schematic-Api-Key": this.apiKey
|
|
1069
|
+
},
|
|
1070
|
+
body: requestBody
|
|
1071
|
+
}).then((response) => {
|
|
1072
|
+
if (!response.ok) {
|
|
1073
|
+
throw new Error("Network response was not ok");
|
|
1074
|
+
}
|
|
1075
|
+
return response.json();
|
|
1076
|
+
}).then((responseJson) => {
|
|
1077
|
+
const resp = CheckFlagsResponseFromJSON(responseJson);
|
|
1078
|
+
this.debug(`checkFlags result:`, resp);
|
|
1079
|
+
return (resp?.data?.flags ?? []).reduce(
|
|
1080
|
+
(accum, flag) => {
|
|
1081
|
+
accum[flag.flag] = flag.value;
|
|
1082
|
+
return accum;
|
|
1083
|
+
},
|
|
1084
|
+
{}
|
|
1085
|
+
);
|
|
1086
|
+
}).catch((error) => {
|
|
1087
|
+
console.error("There was a problem with the fetch operation:", error);
|
|
1088
|
+
return {};
|
|
1089
|
+
});
|
|
1090
|
+
};
|
|
1091
|
+
/**
|
|
1092
|
+
* Send an identify event.
|
|
1093
|
+
* This will set the context for subsequent flag evaluation and events, and will also
|
|
1094
|
+
* send an identify event to the Schematic API which will upsert a user and company.
|
|
1095
|
+
*/
|
|
1096
|
+
identify = (body) => {
|
|
1097
|
+
this.debug(`identify:`, body);
|
|
1098
|
+
try {
|
|
1099
|
+
this.setContext({
|
|
1100
|
+
company: body.company?.keys,
|
|
1101
|
+
user: body.keys
|
|
1102
|
+
});
|
|
1103
|
+
} catch (error) {
|
|
1104
|
+
console.error("Error setting context:", error);
|
|
1105
|
+
}
|
|
1106
|
+
return this.handleEvent("identify", body);
|
|
1107
|
+
};
|
|
1108
|
+
/**
|
|
1109
|
+
* Set the flag evaluation context.
|
|
1110
|
+
* In WebSocket mode, this will:
|
|
1111
|
+
* 1. Open a websocket connection if not already open
|
|
1112
|
+
* 2. Send the context to the server
|
|
1113
|
+
* 3. Wait for initial flag values to be returned
|
|
1114
|
+
* The promise resolves when initial flag values are received.
|
|
1115
|
+
* In offline mode, this will just set the context locally without connecting.
|
|
1116
|
+
*/
|
|
1117
|
+
setContext = async (context) => {
|
|
1118
|
+
if (this.isOffline() || !this.useWebSocket) {
|
|
1119
|
+
this.context = context;
|
|
1120
|
+
this.flushContextDependentEventQueue();
|
|
1121
|
+
this.setIsPending(false);
|
|
1122
|
+
return Promise.resolve();
|
|
1123
|
+
}
|
|
1124
|
+
try {
|
|
1125
|
+
this.setIsPending(true);
|
|
1126
|
+
if (!this.conn) {
|
|
1127
|
+
this.conn = this.wsConnect();
|
|
1128
|
+
}
|
|
1129
|
+
const socket = await this.conn;
|
|
1130
|
+
await this.wsSendMessage(socket, context);
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
console.error("Failed to establish WebSocket connection:", error);
|
|
1133
|
+
throw error;
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
/**
|
|
1137
|
+
* Send a track event
|
|
1138
|
+
* Track usage for a company and/or user.
|
|
1139
|
+
* Optimistically updates feature usage flags if tracking a featureUsageEvent.
|
|
1140
|
+
*/
|
|
1141
|
+
track = (body) => {
|
|
1142
|
+
const { company, user, event, traits, quantity = 1 } = body;
|
|
1143
|
+
if (!this.hasContext(company, user)) {
|
|
1144
|
+
this.debug(`track: queuing event "${event}" until context is available`);
|
|
1145
|
+
const queuedEvent = {
|
|
1146
|
+
api_key: this.apiKey,
|
|
1147
|
+
body: {
|
|
1148
|
+
company,
|
|
1149
|
+
event,
|
|
1150
|
+
traits: traits ?? {},
|
|
1151
|
+
user,
|
|
1152
|
+
quantity
|
|
1153
|
+
},
|
|
1154
|
+
sent_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1155
|
+
tracker_event_id: v4_default(),
|
|
1156
|
+
tracker_user_id: this.getAnonymousId(),
|
|
1157
|
+
type: "track"
|
|
1158
|
+
};
|
|
1159
|
+
this.contextDependentEventQueue.push(queuedEvent);
|
|
1160
|
+
return Promise.resolve();
|
|
1161
|
+
}
|
|
1162
|
+
const trackData = {
|
|
1163
|
+
company: company ?? this.context.company,
|
|
1164
|
+
event,
|
|
1165
|
+
traits: traits ?? {},
|
|
1166
|
+
user: user ?? this.context.user,
|
|
1167
|
+
quantity
|
|
1168
|
+
};
|
|
1169
|
+
this.debug(`track:`, trackData);
|
|
1170
|
+
if (event in this.featureUsageEventMap) {
|
|
1171
|
+
this.optimisticallyUpdateFeatureUsage(event, quantity);
|
|
1172
|
+
}
|
|
1173
|
+
return this.handleEvent("track", trackData);
|
|
1174
|
+
};
|
|
1175
|
+
/**
|
|
1176
|
+
* Optimistically update feature usage flags associated with a tracked event
|
|
1177
|
+
* This updates flags in memory with updated usage counts and value/featureUsageExceeded flags
|
|
1178
|
+
* before the network request completes
|
|
1179
|
+
*/
|
|
1180
|
+
optimisticallyUpdateFeatureUsage = (eventName, quantity = 1) => {
|
|
1181
|
+
const flagsForEvent = this.featureUsageEventMap[eventName];
|
|
1182
|
+
if (flagsForEvent === void 0 || flagsForEvent === null) return;
|
|
1183
|
+
this.debug(
|
|
1184
|
+
`Optimistically updating feature usage for event: ${eventName}`,
|
|
1185
|
+
{ quantity }
|
|
1186
|
+
);
|
|
1187
|
+
Object.entries(flagsForEvent).forEach(([flagKey, check]) => {
|
|
1188
|
+
if (check === void 0) return;
|
|
1189
|
+
const updatedCheck = { ...check };
|
|
1190
|
+
if (typeof updatedCheck.featureUsage === "number") {
|
|
1191
|
+
updatedCheck.featureUsage += quantity;
|
|
1192
|
+
if (typeof updatedCheck.featureAllocation === "number") {
|
|
1193
|
+
const wasExceeded = updatedCheck.featureUsageExceeded === true;
|
|
1194
|
+
const nowExceeded = updatedCheck.featureUsage >= updatedCheck.featureAllocation;
|
|
1195
|
+
if (nowExceeded !== wasExceeded) {
|
|
1196
|
+
updatedCheck.featureUsageExceeded = nowExceeded;
|
|
1197
|
+
if (nowExceeded) {
|
|
1198
|
+
updatedCheck.value = false;
|
|
1199
|
+
}
|
|
1200
|
+
this.debug(`Usage limit status changed for flag: ${flagKey}`, {
|
|
1201
|
+
was: wasExceeded ? "exceeded" : "within limits",
|
|
1202
|
+
now: nowExceeded ? "exceeded" : "within limits",
|
|
1203
|
+
featureUsage: updatedCheck.featureUsage,
|
|
1204
|
+
featureAllocation: updatedCheck.featureAllocation,
|
|
1205
|
+
value: updatedCheck.value
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
if (this.featureUsageEventMap[eventName] !== void 0) {
|
|
1210
|
+
this.featureUsageEventMap[eventName][flagKey] = updatedCheck;
|
|
1211
|
+
}
|
|
1212
|
+
const contextStr = contextString(this.context);
|
|
1213
|
+
if (this.checks[contextStr] !== void 0 && this.checks[contextStr] !== null) {
|
|
1214
|
+
this.checks[contextStr][flagKey] = updatedCheck;
|
|
1215
|
+
}
|
|
1216
|
+
this.notifyFlagCheckListeners(flagKey, updatedCheck);
|
|
1217
|
+
this.notifyFlagValueListeners(flagKey, updatedCheck.value);
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
};
|
|
1221
|
+
/**
|
|
1222
|
+
* Event processing
|
|
1223
|
+
*/
|
|
1224
|
+
hasContext = (company, user) => {
|
|
1225
|
+
const hasProvidedContext = company !== void 0 && company !== null && Object.keys(company).length > 0 || user !== void 0 && user !== null && Object.keys(user).length > 0;
|
|
1226
|
+
const hasInstanceContext = this.context.company !== void 0 && this.context.company !== null && Object.keys(this.context.company).length > 0 || this.context.user !== void 0 && this.context.user !== null && Object.keys(this.context.user).length > 0;
|
|
1227
|
+
return hasProvidedContext || hasInstanceContext;
|
|
1228
|
+
};
|
|
1229
|
+
flushContextDependentEventQueue = () => {
|
|
1230
|
+
this.debug(
|
|
1231
|
+
`flushing ${this.contextDependentEventQueue.length} context-dependent events`
|
|
1232
|
+
);
|
|
1233
|
+
while (this.contextDependentEventQueue.length > 0) {
|
|
1234
|
+
const event = this.contextDependentEventQueue.shift();
|
|
1235
|
+
if (event) {
|
|
1236
|
+
if (event.type === "track" && typeof event.body === "object" && event.body !== null) {
|
|
1237
|
+
const trackBody = event.body;
|
|
1238
|
+
const updatedBody = {
|
|
1239
|
+
...trackBody,
|
|
1240
|
+
company: trackBody.company ?? this.context.company,
|
|
1241
|
+
user: trackBody.user ?? this.context.user
|
|
1242
|
+
};
|
|
1243
|
+
const updatedEvent = {
|
|
1244
|
+
...event,
|
|
1245
|
+
body: updatedBody,
|
|
1246
|
+
sent_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1247
|
+
// Update timestamp to actual send time
|
|
1248
|
+
};
|
|
1249
|
+
this.sendEvent(updatedEvent);
|
|
1250
|
+
} else {
|
|
1251
|
+
this.sendEvent(event);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
flushEventQueue = () => {
|
|
1257
|
+
while (this.eventQueue.length > 0) {
|
|
1258
|
+
const event = this.eventQueue.shift();
|
|
1259
|
+
if (event) {
|
|
1260
|
+
this.sendEvent(event);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
getAnonymousId = () => {
|
|
1265
|
+
if (!this.storage) {
|
|
1266
|
+
return v4_default();
|
|
1267
|
+
}
|
|
1268
|
+
const storedAnonymousId = this.storage.getItem(anonymousIdKey);
|
|
1269
|
+
if (typeof storedAnonymousId !== "undefined") {
|
|
1270
|
+
return storedAnonymousId;
|
|
1271
|
+
}
|
|
1272
|
+
const generatedAnonymousId = v4_default();
|
|
1273
|
+
this.storage.setItem(anonymousIdKey, generatedAnonymousId);
|
|
1274
|
+
return generatedAnonymousId;
|
|
1275
|
+
};
|
|
1276
|
+
handleEvent = (eventType, eventBody) => {
|
|
1277
|
+
const event = {
|
|
1278
|
+
api_key: this.apiKey,
|
|
1279
|
+
body: eventBody,
|
|
1280
|
+
sent_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1281
|
+
tracker_event_id: v4_default(),
|
|
1282
|
+
tracker_user_id: this.getAnonymousId(),
|
|
1283
|
+
type: eventType
|
|
1284
|
+
};
|
|
1285
|
+
if (typeof document !== "undefined" && document?.hidden) {
|
|
1286
|
+
return this.storeEvent(event);
|
|
1287
|
+
} else {
|
|
1288
|
+
return this.sendEvent(event);
|
|
1289
|
+
}
|
|
1290
|
+
};
|
|
1291
|
+
sendEvent = async (event) => {
|
|
1292
|
+
const captureUrl = `${this.eventUrl}/e`;
|
|
1293
|
+
const payload = JSON.stringify(event);
|
|
1294
|
+
this.debug(`sending event:`, { url: captureUrl, event });
|
|
1295
|
+
if (this.isOffline()) {
|
|
1296
|
+
this.debug(`event not sent (offline mode):`, { event });
|
|
1297
|
+
return Promise.resolve();
|
|
1298
|
+
}
|
|
1299
|
+
try {
|
|
1300
|
+
const response = await fetch(captureUrl, {
|
|
1301
|
+
method: "POST",
|
|
1302
|
+
headers: {
|
|
1303
|
+
...this.additionalHeaders ?? {},
|
|
1304
|
+
"Content-Type": "application/json;charset=UTF-8"
|
|
1305
|
+
},
|
|
1306
|
+
body: payload
|
|
1307
|
+
});
|
|
1308
|
+
this.debug(`event sent:`, {
|
|
1309
|
+
status: response.status,
|
|
1310
|
+
statusText: response.statusText
|
|
1311
|
+
});
|
|
1312
|
+
} catch (error) {
|
|
1313
|
+
console.error("Error sending Schematic event: ", error);
|
|
1314
|
+
}
|
|
1315
|
+
return Promise.resolve();
|
|
1316
|
+
};
|
|
1317
|
+
storeEvent = (event) => {
|
|
1318
|
+
this.eventQueue.push(event);
|
|
1319
|
+
return Promise.resolve();
|
|
1320
|
+
};
|
|
1321
|
+
/**
|
|
1322
|
+
* Websocket management
|
|
1323
|
+
*/
|
|
1324
|
+
/**
|
|
1325
|
+
* If using websocket mode, close the connection when done.
|
|
1326
|
+
* In offline mode, this is a no-op.
|
|
1327
|
+
*/
|
|
1328
|
+
cleanup = async () => {
|
|
1329
|
+
if (this.isOffline()) {
|
|
1330
|
+
this.debug("cleanup: skipped (offline mode)");
|
|
1331
|
+
return Promise.resolve();
|
|
1332
|
+
}
|
|
1333
|
+
if (this.conn) {
|
|
1334
|
+
try {
|
|
1335
|
+
const socket = await this.conn;
|
|
1336
|
+
socket.close();
|
|
1337
|
+
} catch (error) {
|
|
1338
|
+
console.error("Error during cleanup:", error);
|
|
1339
|
+
} finally {
|
|
1340
|
+
this.conn = null;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
// Open a websocket connection
|
|
1345
|
+
wsConnect = () => {
|
|
1346
|
+
if (this.isOffline()) {
|
|
1347
|
+
this.debug("wsConnect: skipped (offline mode)");
|
|
1348
|
+
return Promise.reject(
|
|
1349
|
+
new Error("WebSocket connection skipped in offline mode")
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
return new Promise((resolve, reject) => {
|
|
1353
|
+
const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
|
|
1354
|
+
this.debug(`connecting to WebSocket:`, wsUrl);
|
|
1355
|
+
const webSocket = new WebSocket(wsUrl);
|
|
1356
|
+
webSocket.onopen = () => {
|
|
1357
|
+
this.debug(`WebSocket connection opened`);
|
|
1358
|
+
resolve(webSocket);
|
|
1359
|
+
};
|
|
1360
|
+
webSocket.onerror = (error) => {
|
|
1361
|
+
this.debug(`WebSocket connection error:`, error);
|
|
1362
|
+
reject(error);
|
|
1363
|
+
};
|
|
1364
|
+
webSocket.onclose = () => {
|
|
1365
|
+
this.debug(`WebSocket connection closed`);
|
|
1366
|
+
this.conn = null;
|
|
1367
|
+
};
|
|
1368
|
+
});
|
|
1369
|
+
};
|
|
1370
|
+
// Send a message on the websocket indicating interest in a particular evaluation context
|
|
1371
|
+
// and wait for the initial set of flag values to be returned
|
|
1372
|
+
wsSendMessage = (socket, context) => {
|
|
1373
|
+
if (this.isOffline()) {
|
|
1374
|
+
this.debug("wsSendMessage: skipped (offline mode)");
|
|
1375
|
+
this.setIsPending(false);
|
|
1376
|
+
return Promise.resolve();
|
|
1377
|
+
}
|
|
1378
|
+
return new Promise((resolve, reject) => {
|
|
1379
|
+
if (contextString(context) == contextString(this.context)) {
|
|
1380
|
+
this.debug(`WebSocket context unchanged, skipping update`);
|
|
1381
|
+
return resolve(this.setIsPending(false));
|
|
1382
|
+
}
|
|
1383
|
+
this.debug(`WebSocket context updated:`, context);
|
|
1384
|
+
this.context = context;
|
|
1385
|
+
const sendMessage = () => {
|
|
1386
|
+
let resolved = false;
|
|
1387
|
+
const messageHandler = (event) => {
|
|
1388
|
+
const message = JSON.parse(event.data);
|
|
1389
|
+
this.debug(`WebSocket message received:`, message);
|
|
1390
|
+
if (!(contextString(context) in this.checks)) {
|
|
1391
|
+
this.checks[contextString(context)] = {};
|
|
1392
|
+
}
|
|
1393
|
+
(message.flags ?? []).forEach((flag) => {
|
|
1394
|
+
const flagCheck = CheckFlagReturnFromJSON(flag);
|
|
1395
|
+
const contextStr = contextString(context);
|
|
1396
|
+
if (this.checks[contextStr] === void 0) {
|
|
1397
|
+
this.checks[contextStr] = {};
|
|
1398
|
+
}
|
|
1399
|
+
this.checks[contextStr][flagCheck.flag] = flagCheck;
|
|
1400
|
+
this.debug(`WebSocket flag update:`, {
|
|
1401
|
+
flag: flagCheck.flag,
|
|
1402
|
+
value: flagCheck.value,
|
|
1403
|
+
flagCheck
|
|
1404
|
+
});
|
|
1405
|
+
if (typeof flagCheck.featureUsageEvent === "string") {
|
|
1406
|
+
this.updateFeatureUsageEventMap(flagCheck);
|
|
1407
|
+
}
|
|
1408
|
+
if (this.flagCheckListeners[flag.flag]?.size > 0 || this.flagValueListeners[flag.flag]?.size > 0) {
|
|
1409
|
+
this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
|
|
1410
|
+
}
|
|
1411
|
+
this.notifyFlagCheckListeners(flag.flag, flagCheck);
|
|
1412
|
+
this.notifyFlagValueListeners(flag.flag, flagCheck.value);
|
|
1413
|
+
});
|
|
1414
|
+
this.flushContextDependentEventQueue();
|
|
1415
|
+
this.setIsPending(false);
|
|
1416
|
+
if (!resolved) {
|
|
1417
|
+
resolved = true;
|
|
1418
|
+
resolve();
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1421
|
+
socket.addEventListener("message", messageHandler);
|
|
1422
|
+
const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
|
|
1423
|
+
const messagePayload = {
|
|
1424
|
+
apiKey: this.apiKey,
|
|
1425
|
+
clientVersion,
|
|
1426
|
+
data: context
|
|
1427
|
+
};
|
|
1428
|
+
this.debug(`WebSocket sending message:`, messagePayload);
|
|
1429
|
+
socket.send(JSON.stringify(messagePayload));
|
|
1430
|
+
};
|
|
1431
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
1432
|
+
this.debug(`WebSocket already open, sending message`);
|
|
1433
|
+
sendMessage();
|
|
1434
|
+
} else if (socket.readyState === WebSocket.CONNECTING) {
|
|
1435
|
+
this.debug(`WebSocket connecting, waiting for open to send message`);
|
|
1436
|
+
socket.addEventListener("open", sendMessage);
|
|
1437
|
+
} else {
|
|
1438
|
+
this.debug(`WebSocket is closed, cannot send message`);
|
|
1439
|
+
reject("WebSocket is not open or connecting");
|
|
1440
|
+
}
|
|
1441
|
+
});
|
|
1442
|
+
};
|
|
1443
|
+
/**
|
|
1444
|
+
* State management
|
|
1445
|
+
*/
|
|
1446
|
+
// isPending state
|
|
1447
|
+
getIsPending = () => {
|
|
1448
|
+
return this.isPending;
|
|
1449
|
+
};
|
|
1450
|
+
addIsPendingListener = (listener) => {
|
|
1451
|
+
this.isPendingListeners.add(listener);
|
|
1452
|
+
return () => {
|
|
1453
|
+
this.isPendingListeners.delete(listener);
|
|
1454
|
+
};
|
|
1455
|
+
};
|
|
1456
|
+
setIsPending = (isPending) => {
|
|
1457
|
+
this.isPending = isPending;
|
|
1458
|
+
this.isPendingListeners.forEach(
|
|
1459
|
+
(listener) => notifyPendingListener(listener, isPending)
|
|
1460
|
+
);
|
|
1461
|
+
};
|
|
1462
|
+
// flag checks state
|
|
1463
|
+
getFlagCheck = (flagKey) => {
|
|
1464
|
+
const contextStr = contextString(this.context);
|
|
1465
|
+
const checks = this.checks[contextStr] ?? {};
|
|
1466
|
+
return checks[flagKey];
|
|
1467
|
+
};
|
|
1468
|
+
// flagValues state
|
|
1469
|
+
getFlagValue = (flagKey) => {
|
|
1470
|
+
const check = this.getFlagCheck(flagKey);
|
|
1471
|
+
return check?.value;
|
|
1472
|
+
};
|
|
1473
|
+
/** Register an event listener that will be notified with the boolean value for a given flag when this value changes */
|
|
1474
|
+
addFlagValueListener = (flagKey, listener) => {
|
|
1475
|
+
if (!(flagKey in this.flagValueListeners)) {
|
|
1476
|
+
this.flagValueListeners[flagKey] = /* @__PURE__ */ new Set();
|
|
1477
|
+
}
|
|
1478
|
+
this.flagValueListeners[flagKey].add(listener);
|
|
1479
|
+
return () => {
|
|
1480
|
+
this.flagValueListeners[flagKey].delete(listener);
|
|
1481
|
+
};
|
|
1482
|
+
};
|
|
1483
|
+
/** Register an event listener that will be notified with the full flag check response for a given flag whenever this value changes */
|
|
1484
|
+
addFlagCheckListener = (flagKey, listener) => {
|
|
1485
|
+
if (!(flagKey in this.flagCheckListeners)) {
|
|
1486
|
+
this.flagCheckListeners[flagKey] = /* @__PURE__ */ new Set();
|
|
1487
|
+
}
|
|
1488
|
+
this.flagCheckListeners[flagKey].add(listener);
|
|
1489
|
+
return () => {
|
|
1490
|
+
this.flagCheckListeners[flagKey].delete(listener);
|
|
1491
|
+
};
|
|
1492
|
+
};
|
|
1493
|
+
notifyFlagCheckListeners = (flagKey, check) => {
|
|
1494
|
+
const listeners = this.flagCheckListeners?.[flagKey] ?? [];
|
|
1495
|
+
if (listeners.size > 0) {
|
|
1496
|
+
this.debug(
|
|
1497
|
+
`Notifying ${listeners.size} flag check listeners for ${flagKey}`,
|
|
1498
|
+
check
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1501
|
+
if (typeof check.featureUsageEvent === "string") {
|
|
1502
|
+
this.updateFeatureUsageEventMap(check);
|
|
1503
|
+
}
|
|
1504
|
+
listeners.forEach((listener) => notifyFlagCheckListener(listener, check));
|
|
1505
|
+
};
|
|
1506
|
+
/** Add or update a CheckFlagReturn in the featureUsageEventMap */
|
|
1507
|
+
updateFeatureUsageEventMap = (check) => {
|
|
1508
|
+
if (typeof check.featureUsageEvent !== "string") return;
|
|
1509
|
+
const eventName = check.featureUsageEvent;
|
|
1510
|
+
if (this.featureUsageEventMap[eventName] === void 0 || this.featureUsageEventMap[eventName] === null) {
|
|
1511
|
+
this.featureUsageEventMap[eventName] = {};
|
|
1512
|
+
}
|
|
1513
|
+
if (this.featureUsageEventMap[eventName] !== void 0) {
|
|
1514
|
+
this.featureUsageEventMap[eventName][check.flag] = check;
|
|
1515
|
+
}
|
|
1516
|
+
this.debug(
|
|
1517
|
+
`Updated featureUsageEventMap for event: ${eventName}, flag: ${check.flag}`,
|
|
1518
|
+
check
|
|
1519
|
+
);
|
|
1520
|
+
};
|
|
1521
|
+
notifyFlagValueListeners = (flagKey, value) => {
|
|
1522
|
+
const listeners = this.flagValueListeners?.[flagKey] ?? [];
|
|
1523
|
+
if (listeners.size > 0) {
|
|
1524
|
+
this.debug(
|
|
1525
|
+
`Notifying ${listeners.size} flag value listeners for ${flagKey}`,
|
|
1526
|
+
{ value }
|
|
1527
|
+
);
|
|
1528
|
+
}
|
|
1529
|
+
listeners.forEach((listener) => notifyFlagValueListener(listener, value));
|
|
1530
|
+
};
|
|
1531
|
+
};
|
|
1532
|
+
var notifyPendingListener = (listener, value) => {
|
|
1533
|
+
if (listener.length > 0) {
|
|
1534
|
+
listener(value);
|
|
1535
|
+
} else {
|
|
1536
|
+
listener();
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1539
|
+
var notifyFlagCheckListener = (listener, value) => {
|
|
1540
|
+
if (listener.length > 0) {
|
|
1541
|
+
listener(value);
|
|
1542
|
+
} else {
|
|
1543
|
+
listener();
|
|
1544
|
+
}
|
|
1545
|
+
};
|
|
1546
|
+
var notifyFlagValueListener = (listener, value) => {
|
|
1547
|
+
if (listener.length > 0) {
|
|
1548
|
+
listener(value);
|
|
1549
|
+
} else {
|
|
1550
|
+
listener();
|
|
1551
|
+
}
|
|
1552
|
+
};
|
|
1553
|
+
|
|
1554
|
+
// src/context/schematic.ts
|
|
1555
|
+
var import_vue = require("vue");
|
|
1556
|
+
|
|
1557
|
+
// src/version.ts
|
|
1558
|
+
var version2 = "0.1.0";
|
|
1559
|
+
|
|
1560
|
+
// src/context/schematic.ts
|
|
1561
|
+
var SchematicSymbol = Symbol("schematic");
|
|
1562
|
+
var SchematicPlugin = {
|
|
1563
|
+
install(app, options) {
|
|
1564
|
+
let client;
|
|
1565
|
+
if ("client" in options && options.client) {
|
|
1566
|
+
client = options.client;
|
|
1567
|
+
} else if ("publishableKey" in options && options.publishableKey) {
|
|
1568
|
+
const { publishableKey, ...clientOpts } = options;
|
|
1569
|
+
client = new Schematic(publishableKey, {
|
|
1570
|
+
useWebSocket: clientOpts.useWebSocket ?? true,
|
|
1571
|
+
additionalHeaders: {
|
|
1572
|
+
"X-Schematic-Client-Version": `schematic-vue@${version2}`
|
|
1573
|
+
},
|
|
1574
|
+
...clientOpts
|
|
1575
|
+
});
|
|
1576
|
+
} else {
|
|
1577
|
+
throw new Error(
|
|
1578
|
+
"SchematicPlugin requires either 'publishableKey' or 'client' option"
|
|
1579
|
+
);
|
|
1580
|
+
}
|
|
1581
|
+
const contextValue = {
|
|
1582
|
+
client
|
|
1583
|
+
};
|
|
1584
|
+
app.provide(SchematicSymbol, contextValue);
|
|
1585
|
+
}
|
|
1586
|
+
};
|
|
1587
|
+
var useSchematic = () => {
|
|
1588
|
+
const context = (0, import_vue.inject)(SchematicSymbol);
|
|
1589
|
+
if (!context) {
|
|
1590
|
+
throw new Error(
|
|
1591
|
+
"useSchematic must be used within a component where SchematicPlugin is installed"
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
1594
|
+
return context;
|
|
1595
|
+
};
|
|
1596
|
+
|
|
1597
|
+
// src/composables/schematic.ts
|
|
1598
|
+
var import_vue2 = require("vue");
|
|
1599
|
+
var useSchematicClient = (opts) => {
|
|
1600
|
+
const schematic = useSchematic();
|
|
1601
|
+
const { client } = opts ?? {};
|
|
1602
|
+
return client ?? schematic.client;
|
|
1603
|
+
};
|
|
1604
|
+
var useSchematicContext = (opts) => {
|
|
1605
|
+
const client = useSchematicClient(opts);
|
|
1606
|
+
return {
|
|
1607
|
+
setContext: (context) => client.setContext(context)
|
|
1608
|
+
};
|
|
1609
|
+
};
|
|
1610
|
+
var useSchematicEvents = (opts) => {
|
|
1611
|
+
const client = useSchematicClient(opts);
|
|
1612
|
+
const track = (body) => client.track(body);
|
|
1613
|
+
const identify = (body) => {
|
|
1614
|
+
client.identify(body);
|
|
1615
|
+
};
|
|
1616
|
+
return {
|
|
1617
|
+
track,
|
|
1618
|
+
identify
|
|
1619
|
+
};
|
|
1620
|
+
};
|
|
1621
|
+
var useSchematicFlag = (key, opts) => {
|
|
1622
|
+
const client = useSchematicClient(opts);
|
|
1623
|
+
const fallback = opts?.fallback ?? false;
|
|
1624
|
+
const flagValue = (0, import_vue2.ref)(client.getFlagValue(key) ?? fallback);
|
|
1625
|
+
let unsubscribe = null;
|
|
1626
|
+
(0, import_vue2.onMounted)(() => {
|
|
1627
|
+
unsubscribe = client.addFlagValueListener(key, (value) => {
|
|
1628
|
+
flagValue.value = value;
|
|
1629
|
+
});
|
|
1630
|
+
});
|
|
1631
|
+
(0, import_vue2.onScopeDispose)(() => {
|
|
1632
|
+
unsubscribe?.();
|
|
1633
|
+
});
|
|
1634
|
+
return flagValue;
|
|
1635
|
+
};
|
|
1636
|
+
var useSchematicEntitlement = (key, opts) => {
|
|
1637
|
+
const client = useSchematicClient(opts);
|
|
1638
|
+
const fallback = opts?.fallback ?? false;
|
|
1639
|
+
const fallbackCheck = {
|
|
1640
|
+
flag: key,
|
|
1641
|
+
reason: "Fallback",
|
|
1642
|
+
value: fallback
|
|
1643
|
+
};
|
|
1644
|
+
const flagCheck = (0, import_vue2.ref)(
|
|
1645
|
+
client.getFlagCheck(key) ?? fallbackCheck
|
|
1646
|
+
);
|
|
1647
|
+
let unsubscribe = null;
|
|
1648
|
+
(0, import_vue2.onMounted)(() => {
|
|
1649
|
+
unsubscribe = client.addFlagCheckListener(key, (check) => {
|
|
1650
|
+
flagCheck.value = check;
|
|
1651
|
+
});
|
|
1652
|
+
});
|
|
1653
|
+
(0, import_vue2.onScopeDispose)(() => {
|
|
1654
|
+
unsubscribe?.();
|
|
1655
|
+
});
|
|
1656
|
+
return {
|
|
1657
|
+
// Full check object
|
|
1658
|
+
check: (0, import_vue2.computed)(() => flagCheck.value),
|
|
1659
|
+
// Individual properties as computed refs
|
|
1660
|
+
value: (0, import_vue2.computed)(() => flagCheck.value.value),
|
|
1661
|
+
flag: (0, import_vue2.computed)(() => flagCheck.value.flag),
|
|
1662
|
+
reason: (0, import_vue2.computed)(() => flagCheck.value.reason),
|
|
1663
|
+
featureUsage: (0, import_vue2.computed)(() => flagCheck.value.featureUsage),
|
|
1664
|
+
featureAllocation: (0, import_vue2.computed)(() => flagCheck.value.featureAllocation),
|
|
1665
|
+
featureUsageExceeded: (0, import_vue2.computed)(() => flagCheck.value.featureUsageExceeded),
|
|
1666
|
+
featureUsageEvent: (0, import_vue2.computed)(() => flagCheck.value.featureUsageEvent),
|
|
1667
|
+
featureUsagePeriod: (0, import_vue2.computed)(() => flagCheck.value.featureUsagePeriod),
|
|
1668
|
+
flagId: (0, import_vue2.computed)(() => flagCheck.value.flagId),
|
|
1669
|
+
ruleId: (0, import_vue2.computed)(() => flagCheck.value.ruleId),
|
|
1670
|
+
companyId: (0, import_vue2.computed)(() => flagCheck.value.companyId),
|
|
1671
|
+
userId: (0, import_vue2.computed)(() => flagCheck.value.userId)
|
|
1672
|
+
};
|
|
1673
|
+
};
|
|
1674
|
+
var useSchematicIsPending = (opts) => {
|
|
1675
|
+
const client = useSchematicClient(opts);
|
|
1676
|
+
const isPending = (0, import_vue2.ref)(client.getIsPending());
|
|
1677
|
+
let unsubscribe = null;
|
|
1678
|
+
(0, import_vue2.onMounted)(() => {
|
|
1679
|
+
unsubscribe = client.addIsPendingListener((value) => {
|
|
1680
|
+
isPending.value = value;
|
|
1681
|
+
});
|
|
1682
|
+
});
|
|
1683
|
+
(0, import_vue2.onScopeDispose)(() => {
|
|
1684
|
+
unsubscribe?.();
|
|
1685
|
+
});
|
|
1686
|
+
return isPending;
|
|
1687
|
+
};
|
|
1688
|
+
/*! Bundled license information:
|
|
1689
|
+
|
|
1690
|
+
@schematichq/schematic-js/dist/schematic.esm.js:
|
|
1691
|
+
(* @preserve *)
|
|
1692
|
+
*/
|