@schematichq/schematic-js 0.0.13 → 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/dist/schematic.browser.js +1 -1
- package/dist/schematic.cjs.js +190 -72
- package/dist/schematic.d.ts +35 -16
- package/dist/schematic.esm.js +190 -72
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";(()=>{var
|
|
1
|
+
"use strict";(()=>{var p,f=new Uint8Array(16);function y(){if(!p&&(p=typeof crypto<"u"&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto),!p))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return p(f)}var i=[];for(let t=0;t<256;++t)i.push((t+256).toString(16).slice(1));function m(t,e=0){return i[t[e+0]]+i[t[e+1]]+i[t[e+2]]+i[t[e+3]]+"-"+i[t[e+4]]+i[t[e+5]]+"-"+i[t[e+6]]+i[t[e+7]]+"-"+i[t[e+8]]+i[t[e+9]]+"-"+i[t[e+10]]+i[t[e+11]]+i[t[e+12]]+i[t[e+13]]+i[t[e+14]]+i[t[e+15]]}var k=typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto),l={randomUUID:k};function S(t,e,n){if(l.randomUUID&&!e&&!t)return l.randomUUID();t=t||{};let o=t.random||(t.rng||y)();if(o[6]=o[6]&15|64,o[8]=o[8]&63|128,e){n=n||0;for(let r=0;r<16;++r)e[n+r]=o[r];return e}return m(o)}var d=S;var v="schematicId";var u=class{apiKey;conn=null;context={};eventQueue;storage;useWebSocket=!1;values={};flagListener;constructor(e,n){this.apiKey=e,this.eventQueue=[],this.useWebSocket=n?.useWebSocket??!1,this.flagListener=n?.flagListener,n?.storage?this.storage=n.storage:typeof localStorage<"u"&&(this.storage=localStorage),typeof window<"u"&&window.addEventListener("beforeunload",()=>{this.flushEventQueue()})}checkFlag=async e=>{let{fallback:n=!1,key:o}=e,r=e.context||this.context;if(this.useWebSocket){let s=this.values[h(r)]??{};return typeof s[o]>"u"?n:s[o]}let a=`https://api.schematichq.com/flags/${o}/check`;return fetch(a,{method:"POST",headers:{"X-Schematic-Api-Key":this.apiKey,"Content-Type":"application/json;charset=UTF-8"},body:JSON.stringify(r)}).then(s=>{if(!s.ok)throw new Error("Network response was not ok");return s.json()}).then(s=>s.data.value).catch(s=>(console.error("There was a problem with the fetch operation:",s),n))};checkFlags=async e=>{e=e||this.context;let n="https://api.schematichq.com/flags/check",o=JSON.stringify(e);return fetch(n,{method:"POST",headers:{"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:o}).then(r=>{if(!r.ok)throw new Error("Network response was not ok");return r.json()}).then(r=>(r?.data?.flags??[]).reduce((a,s)=>(a[s.flag]=s.value,a),{})).catch(r=>(console.error("There was a problem with the fetch operation:",r),!1))};cleanup=()=>{this.conn&&this.conn.close()};identify=e=>{this.handleEvent("identify",e)};setContext=e=>this.useWebSocket?new Promise(n=>{this.wsConnect().then(()=>{this.wsSendMessage(e),n()})}):(this.context=e,Promise.resolve());track=e=>{this.handleEvent("track",e)};flushEventQueue=()=>{for(;this.eventQueue.length>0;){let e=this.eventQueue.shift();e&&this.sendEvent(e)}};getAnonymousId=()=>{if(!this.storage)return d();let e=this.storage.getItem(v);if(typeof e<"u")return e;let n=d();return this.storage.setItem(v,n),n};handleEvent=(e,n)=>{let o={api_key:this.apiKey,body:n,sent_at:new Date().toISOString(),tracker_event_id:d(),tracker_user_id:this.getAnonymousId(),type:e};typeof document<"u"&&document.hidden?this.storeEvent(o):this.sendEvent(o)};sendEvent=e=>{let n="https://c.schematichq.com/e",o=JSON.stringify(e);fetch(n,{method:"POST",headers:{"Content-Type":"application/json;charset=UTF-8"},body:o}).then(r=>{if(!r.ok)throw new Error(`Network response was not ok: ${r.statusText}`)}).catch(r=>{console.error("There was a problem with the fetch operation:",r)})};storeEvent=e=>{this.eventQueue.push(e)};wsConnect=()=>new Promise(e=>{this.conn&&e();let n="wss://api.schematichq.com/flags/bootstrap",o=new WebSocket(n);this.conn=o,o.onopen=()=>{e()},o.onclose=()=>{this.conn=null}});wsSendMessage=e=>new Promise((n,o)=>{if(h(e)==h(this.context)&&n(),this.context=e,!this.conn){o("Not connected");return}if(this.conn.readyState===WebSocket.OPEN){let r=!1;this.conn.onmessage=a=>{let s=JSON.parse(a.data);this.values[h(e)]=(s.flags??[]).reduce((c,g)=>(c[g.flag]=g.value,c),{}),this.flagListener&&this.flagListener(this.values[h(e)]),r||(r=!0,n())},this.conn.onerror=a=>{console.error("Schematic websocket error: ",a)},this.conn.send(JSON.stringify({apiKey:this.apiKey,data:e}))}else this.conn.readyState===WebSocket.CONNECTING?this.conn.onopen=()=>{this.wsSendMessage(e)}:o("Not connected")})};function h(t){let e=Object.keys(t).reduce((n,o)=>{let a=Object.keys(t[o]||{}).sort().reduce((s,c)=>(s[c]=t[o][c],s),{});return n[o]=a,n},{});return JSON.stringify(e)}window.Schematic=u;})();
|
|
2
2
|
/* @preserve */
|
package/dist/schematic.cjs.js
CHANGED
|
@@ -76,13 +76,20 @@ var v4_default = v4;
|
|
|
76
76
|
var anonymousIdKey = "schematicId";
|
|
77
77
|
var Schematic = class {
|
|
78
78
|
apiKey;
|
|
79
|
+
conn = null;
|
|
80
|
+
context = {};
|
|
79
81
|
eventQueue;
|
|
80
82
|
storage;
|
|
81
|
-
|
|
83
|
+
useWebSocket = false;
|
|
84
|
+
values = {};
|
|
85
|
+
flagListener;
|
|
86
|
+
constructor(apiKey, options) {
|
|
82
87
|
this.apiKey = apiKey;
|
|
83
88
|
this.eventQueue = [];
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
this.useWebSocket = options?.useWebSocket ?? false;
|
|
90
|
+
this.flagListener = options?.flagListener;
|
|
91
|
+
if (options?.storage) {
|
|
92
|
+
this.storage = options.storage;
|
|
86
93
|
} else if (typeof localStorage !== "undefined") {
|
|
87
94
|
this.storage = localStorage;
|
|
88
95
|
}
|
|
@@ -92,37 +99,113 @@ var Schematic = class {
|
|
|
92
99
|
});
|
|
93
100
|
}
|
|
94
101
|
}
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
102
|
+
checkFlag = async (options) => {
|
|
103
|
+
const { fallback = false, key } = options;
|
|
104
|
+
const context = options.context || this.context;
|
|
105
|
+
if (this.useWebSocket) {
|
|
106
|
+
const contextVals = this.values[contextString(context)] ?? {};
|
|
107
|
+
return typeof contextVals[key] === "undefined" ? fallback : contextVals[key];
|
|
108
|
+
}
|
|
109
|
+
const requestUrl = `https://api.schematichq.com/flags/${key}/check`;
|
|
110
|
+
return fetch(requestUrl, {
|
|
99
111
|
method: "POST",
|
|
100
112
|
headers: {
|
|
113
|
+
"X-Schematic-Api-Key": this.apiKey,
|
|
101
114
|
"Content-Type": "application/json;charset=UTF-8"
|
|
102
115
|
},
|
|
103
|
-
body:
|
|
116
|
+
body: JSON.stringify(context)
|
|
104
117
|
}).then((response) => {
|
|
105
118
|
if (!response.ok) {
|
|
106
|
-
throw new Error(
|
|
107
|
-
`Network response was not ok: ${response.statusText}`
|
|
108
|
-
);
|
|
119
|
+
throw new Error("Network response was not ok");
|
|
109
120
|
}
|
|
121
|
+
return response.json();
|
|
122
|
+
}).then((data) => {
|
|
123
|
+
return data.data.value;
|
|
110
124
|
}).catch((error) => {
|
|
111
125
|
console.error("There was a problem with the fetch operation:", error);
|
|
126
|
+
return fallback;
|
|
112
127
|
});
|
|
113
|
-
}
|
|
114
|
-
|
|
128
|
+
};
|
|
129
|
+
// Make a REST API call to fetch all flag values for a given context
|
|
130
|
+
checkFlags = async (context) => {
|
|
131
|
+
context = context || this.context;
|
|
132
|
+
const requestUrl = "https://api.schematichq.com/flags/check";
|
|
133
|
+
const requestBody = JSON.stringify(context);
|
|
134
|
+
return fetch(requestUrl, {
|
|
135
|
+
method: "POST",
|
|
136
|
+
headers: {
|
|
137
|
+
"Content-Type": "application/json;charset=UTF-8",
|
|
138
|
+
"X-Schematic-Api-Key": this.apiKey
|
|
139
|
+
},
|
|
140
|
+
body: requestBody
|
|
141
|
+
}).then((response) => {
|
|
142
|
+
if (!response.ok) {
|
|
143
|
+
throw new Error("Network response was not ok");
|
|
144
|
+
}
|
|
145
|
+
return response.json();
|
|
146
|
+
}).then((data) => {
|
|
147
|
+
return (data?.data?.flags ?? []).reduce(
|
|
148
|
+
(accum, flag) => {
|
|
149
|
+
accum[flag.flag] = flag.value;
|
|
150
|
+
return accum;
|
|
151
|
+
},
|
|
152
|
+
{}
|
|
153
|
+
);
|
|
154
|
+
}).catch((error) => {
|
|
155
|
+
console.error("There was a problem with the fetch operation:", error);
|
|
156
|
+
return false;
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
cleanup = () => {
|
|
160
|
+
if (this.conn) {
|
|
161
|
+
this.conn.close();
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
// Send an identify event
|
|
165
|
+
identify = (body) => {
|
|
166
|
+
this.handleEvent("identify", body);
|
|
167
|
+
};
|
|
168
|
+
// Set the flag evaluation context; if the context has changed,
|
|
169
|
+
// this will open a websocket connection (if not already open)
|
|
170
|
+
// and submit this context. The promise will resolve when the
|
|
171
|
+
// websocket sends back an initial set of flag values.
|
|
172
|
+
setContext = (context) => {
|
|
173
|
+
if (!this.useWebSocket) {
|
|
174
|
+
this.context = context;
|
|
175
|
+
return Promise.resolve();
|
|
176
|
+
}
|
|
177
|
+
return new Promise((resolve) => {
|
|
178
|
+
this.wsConnect().then(() => {
|
|
179
|
+
this.wsSendMessage(context);
|
|
180
|
+
resolve();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
// Send track event
|
|
185
|
+
track = (body) => {
|
|
186
|
+
this.handleEvent("track", body);
|
|
187
|
+
};
|
|
188
|
+
flushEventQueue = () => {
|
|
115
189
|
while (this.eventQueue.length > 0) {
|
|
116
190
|
const event = this.eventQueue.shift();
|
|
117
191
|
if (event) {
|
|
118
192
|
this.sendEvent(event);
|
|
119
193
|
}
|
|
120
194
|
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
this.
|
|
124
|
-
|
|
125
|
-
|
|
195
|
+
};
|
|
196
|
+
getAnonymousId = () => {
|
|
197
|
+
if (!this.storage) {
|
|
198
|
+
return v4_default();
|
|
199
|
+
}
|
|
200
|
+
const storedAnonymousId = this.storage.getItem(anonymousIdKey);
|
|
201
|
+
if (typeof storedAnonymousId !== "undefined") {
|
|
202
|
+
return storedAnonymousId;
|
|
203
|
+
}
|
|
204
|
+
const generatedAnonymousId = v4_default();
|
|
205
|
+
this.storage.setItem(anonymousIdKey, generatedAnonymousId);
|
|
206
|
+
return generatedAnonymousId;
|
|
207
|
+
};
|
|
208
|
+
handleEvent = (eventType, eventBody) => {
|
|
126
209
|
const event = {
|
|
127
210
|
api_key: this.apiKey,
|
|
128
211
|
body: eventBody,
|
|
@@ -136,71 +219,106 @@ var Schematic = class {
|
|
|
136
219
|
} else {
|
|
137
220
|
this.sendEvent(event);
|
|
138
221
|
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const storedAnonymousId = this.storage.getItem(anonymousIdKey);
|
|
145
|
-
if (typeof storedAnonymousId !== "undefined") {
|
|
146
|
-
return storedAnonymousId;
|
|
147
|
-
}
|
|
148
|
-
const generatedAnonymousId = v4_default();
|
|
149
|
-
this.storage.setItem(anonymousIdKey, generatedAnonymousId);
|
|
150
|
-
return generatedAnonymousId;
|
|
151
|
-
}
|
|
152
|
-
async checkFlag(key, context) {
|
|
153
|
-
const requestUrl = `https://api.schematichq.com/flags/${key}/check`;
|
|
154
|
-
const requestBody = JSON.stringify(context);
|
|
155
|
-
return fetch(requestUrl, {
|
|
222
|
+
};
|
|
223
|
+
sendEvent = (event) => {
|
|
224
|
+
const captureUrl = "https://c.schematichq.com/e";
|
|
225
|
+
const payload = JSON.stringify(event);
|
|
226
|
+
fetch(captureUrl, {
|
|
156
227
|
method: "POST",
|
|
157
228
|
headers: {
|
|
158
|
-
"X-Schematic-Api-Key": this.apiKey,
|
|
159
229
|
"Content-Type": "application/json;charset=UTF-8"
|
|
160
230
|
},
|
|
161
|
-
body:
|
|
231
|
+
body: payload
|
|
162
232
|
}).then((response) => {
|
|
163
233
|
if (!response.ok) {
|
|
164
|
-
throw new Error(
|
|
234
|
+
throw new Error(
|
|
235
|
+
`Network response was not ok: ${response.statusText}`
|
|
236
|
+
);
|
|
165
237
|
}
|
|
166
|
-
return response.json();
|
|
167
|
-
}).then((data) => {
|
|
168
|
-
return data.data.value;
|
|
169
238
|
}).catch((error) => {
|
|
170
239
|
console.error("There was a problem with the fetch operation:", error);
|
|
171
|
-
return false;
|
|
172
240
|
});
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
"X-Schematic-Api-Key": this.apiKey
|
|
182
|
-
},
|
|
183
|
-
body: requestBody
|
|
184
|
-
}).then((response) => {
|
|
185
|
-
if (!response.ok) {
|
|
186
|
-
throw new Error("Network response was not ok");
|
|
241
|
+
};
|
|
242
|
+
storeEvent = (event) => {
|
|
243
|
+
this.eventQueue.push(event);
|
|
244
|
+
};
|
|
245
|
+
wsConnect = () => {
|
|
246
|
+
return new Promise((resolve) => {
|
|
247
|
+
if (this.conn) {
|
|
248
|
+
resolve();
|
|
187
249
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
250
|
+
const wsUrl = "wss://api.schematichq.com/flags/bootstrap";
|
|
251
|
+
const webSocket = new WebSocket(wsUrl);
|
|
252
|
+
this.conn = webSocket;
|
|
253
|
+
webSocket.onopen = () => {
|
|
254
|
+
resolve();
|
|
255
|
+
};
|
|
256
|
+
webSocket.onclose = () => {
|
|
257
|
+
this.conn = null;
|
|
258
|
+
};
|
|
197
259
|
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
260
|
+
};
|
|
261
|
+
// Sends a message with a new context over the websocket connection
|
|
262
|
+
wsSendMessage = (context) => {
|
|
263
|
+
return new Promise((resolve, reject) => {
|
|
264
|
+
if (contextString(context) == contextString(this.context)) {
|
|
265
|
+
resolve();
|
|
266
|
+
}
|
|
267
|
+
this.context = context;
|
|
268
|
+
if (!this.conn) {
|
|
269
|
+
reject("Not connected");
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (this.conn.readyState === WebSocket.OPEN) {
|
|
273
|
+
let resolved = false;
|
|
274
|
+
this.conn.onmessage = (event) => {
|
|
275
|
+
const message = JSON.parse(event.data);
|
|
276
|
+
this.values[contextString(context)] = (message.flags ?? []).reduce(
|
|
277
|
+
(accum, flag) => {
|
|
278
|
+
accum[flag.flag] = flag.value;
|
|
279
|
+
return accum;
|
|
280
|
+
},
|
|
281
|
+
{}
|
|
282
|
+
);
|
|
283
|
+
if (this.flagListener) {
|
|
284
|
+
this.flagListener(this.values[contextString(context)]);
|
|
285
|
+
}
|
|
286
|
+
if (!resolved) {
|
|
287
|
+
resolved = true;
|
|
288
|
+
resolve();
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
this.conn.onerror = (error) => {
|
|
292
|
+
console.error("Schematic websocket error: ", error);
|
|
293
|
+
};
|
|
294
|
+
this.conn.send(
|
|
295
|
+
JSON.stringify({
|
|
296
|
+
apiKey: this.apiKey,
|
|
297
|
+
data: context
|
|
298
|
+
})
|
|
299
|
+
);
|
|
300
|
+
} else if (this.conn.readyState === WebSocket.CONNECTING) {
|
|
301
|
+
this.conn.onopen = () => {
|
|
302
|
+
this.wsSendMessage(context);
|
|
303
|
+
};
|
|
304
|
+
} else {
|
|
305
|
+
reject("Not connected");
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
};
|
|
205
309
|
};
|
|
310
|
+
function contextString(context) {
|
|
311
|
+
const sortedContext = Object.keys(context).reduce((acc, key) => {
|
|
312
|
+
const sortedKeys = Object.keys(
|
|
313
|
+
context[key] || {}
|
|
314
|
+
).sort();
|
|
315
|
+
const sortedObj = sortedKeys.reduce((obj, sortedKey) => {
|
|
316
|
+
obj[sortedKey] = context[key][sortedKey];
|
|
317
|
+
return obj;
|
|
318
|
+
}, {});
|
|
319
|
+
acc[key] = sortedObj;
|
|
320
|
+
return acc;
|
|
321
|
+
}, {});
|
|
322
|
+
return JSON.stringify(sortedContext);
|
|
323
|
+
}
|
|
206
324
|
/* @preserve */
|
package/dist/schematic.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export declare type CheckOptions = {
|
|
2
|
+
context?: SchematicContext;
|
|
3
|
+
fallback?: boolean;
|
|
4
|
+
key: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
1
7
|
declare type Event_2 = {
|
|
2
8
|
api_key: string;
|
|
3
9
|
body: EventBody;
|
|
@@ -23,20 +29,13 @@ export declare type EventBodyIdentify = {
|
|
|
23
29
|
traits: Traits;
|
|
24
30
|
};
|
|
25
31
|
|
|
26
|
-
export declare type EventBodyTrack = {
|
|
27
|
-
company?: Keys;
|
|
32
|
+
export declare type EventBodyTrack = SchematicContext & {
|
|
28
33
|
event: string;
|
|
29
34
|
traits: Traits;
|
|
30
|
-
user?: Keys;
|
|
31
35
|
};
|
|
32
36
|
|
|
33
37
|
export declare type EventType = "identify" | "track";
|
|
34
38
|
|
|
35
|
-
export declare type FlagCheckContext = {
|
|
36
|
-
company?: Keys;
|
|
37
|
-
user?: Keys;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
39
|
export declare type FlagCheckResponseBody = {
|
|
41
40
|
company_id?: string;
|
|
42
41
|
error?: string;
|
|
@@ -54,20 +53,40 @@ export declare type Keys = Record<string, string>;
|
|
|
54
53
|
|
|
55
54
|
export declare class Schematic {
|
|
56
55
|
private apiKey;
|
|
56
|
+
private conn;
|
|
57
|
+
private context;
|
|
57
58
|
private eventQueue;
|
|
58
59
|
private storage;
|
|
59
|
-
|
|
60
|
-
private
|
|
60
|
+
private useWebSocket;
|
|
61
|
+
private values;
|
|
62
|
+
private flagListener?;
|
|
63
|
+
constructor(apiKey: string, options?: SchematicOptions);
|
|
64
|
+
checkFlag: (options: CheckOptions) => Promise<boolean>;
|
|
65
|
+
checkFlags: (context?: SchematicContext) => Promise<Record<string, boolean>>;
|
|
66
|
+
cleanup: () => void;
|
|
67
|
+
identify: (body: EventBodyIdentify) => void;
|
|
68
|
+
setContext: (context: SchematicContext) => Promise<void>;
|
|
69
|
+
track: (body: EventBodyTrack) => void;
|
|
61
70
|
private flushEventQueue;
|
|
62
|
-
private storeEvent;
|
|
63
|
-
private handleEvent;
|
|
64
71
|
private getAnonymousId;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
private handleEvent;
|
|
73
|
+
private sendEvent;
|
|
74
|
+
private storeEvent;
|
|
75
|
+
private wsConnect;
|
|
76
|
+
private wsSendMessage;
|
|
69
77
|
}
|
|
70
78
|
|
|
79
|
+
export declare type SchematicContext = {
|
|
80
|
+
company?: Keys;
|
|
81
|
+
user?: Keys;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
declare type SchematicOptions = {
|
|
85
|
+
flagListener?: (values: Record<string, boolean>) => void;
|
|
86
|
+
storage?: StoragePersister;
|
|
87
|
+
useWebSocket?: boolean;
|
|
88
|
+
};
|
|
89
|
+
|
|
71
90
|
declare type StoragePersister = {
|
|
72
91
|
setItem(key: string, value: any): void;
|
|
73
92
|
getItem(key: string): any;
|
package/dist/schematic.esm.js
CHANGED
|
@@ -50,13 +50,20 @@ var v4_default = v4;
|
|
|
50
50
|
var anonymousIdKey = "schematicId";
|
|
51
51
|
var Schematic = class {
|
|
52
52
|
apiKey;
|
|
53
|
+
conn = null;
|
|
54
|
+
context = {};
|
|
53
55
|
eventQueue;
|
|
54
56
|
storage;
|
|
55
|
-
|
|
57
|
+
useWebSocket = false;
|
|
58
|
+
values = {};
|
|
59
|
+
flagListener;
|
|
60
|
+
constructor(apiKey, options) {
|
|
56
61
|
this.apiKey = apiKey;
|
|
57
62
|
this.eventQueue = [];
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
this.useWebSocket = options?.useWebSocket ?? false;
|
|
64
|
+
this.flagListener = options?.flagListener;
|
|
65
|
+
if (options?.storage) {
|
|
66
|
+
this.storage = options.storage;
|
|
60
67
|
} else if (typeof localStorage !== "undefined") {
|
|
61
68
|
this.storage = localStorage;
|
|
62
69
|
}
|
|
@@ -66,37 +73,113 @@ var Schematic = class {
|
|
|
66
73
|
});
|
|
67
74
|
}
|
|
68
75
|
}
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
76
|
+
checkFlag = async (options) => {
|
|
77
|
+
const { fallback = false, key } = options;
|
|
78
|
+
const context = options.context || this.context;
|
|
79
|
+
if (this.useWebSocket) {
|
|
80
|
+
const contextVals = this.values[contextString(context)] ?? {};
|
|
81
|
+
return typeof contextVals[key] === "undefined" ? fallback : contextVals[key];
|
|
82
|
+
}
|
|
83
|
+
const requestUrl = `https://api.schematichq.com/flags/${key}/check`;
|
|
84
|
+
return fetch(requestUrl, {
|
|
73
85
|
method: "POST",
|
|
74
86
|
headers: {
|
|
87
|
+
"X-Schematic-Api-Key": this.apiKey,
|
|
75
88
|
"Content-Type": "application/json;charset=UTF-8"
|
|
76
89
|
},
|
|
77
|
-
body:
|
|
90
|
+
body: JSON.stringify(context)
|
|
78
91
|
}).then((response) => {
|
|
79
92
|
if (!response.ok) {
|
|
80
|
-
throw new Error(
|
|
81
|
-
`Network response was not ok: ${response.statusText}`
|
|
82
|
-
);
|
|
93
|
+
throw new Error("Network response was not ok");
|
|
83
94
|
}
|
|
95
|
+
return response.json();
|
|
96
|
+
}).then((data) => {
|
|
97
|
+
return data.data.value;
|
|
84
98
|
}).catch((error) => {
|
|
85
99
|
console.error("There was a problem with the fetch operation:", error);
|
|
100
|
+
return fallback;
|
|
86
101
|
});
|
|
87
|
-
}
|
|
88
|
-
|
|
102
|
+
};
|
|
103
|
+
// Make a REST API call to fetch all flag values for a given context
|
|
104
|
+
checkFlags = async (context) => {
|
|
105
|
+
context = context || this.context;
|
|
106
|
+
const requestUrl = "https://api.schematichq.com/flags/check";
|
|
107
|
+
const requestBody = JSON.stringify(context);
|
|
108
|
+
return fetch(requestUrl, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers: {
|
|
111
|
+
"Content-Type": "application/json;charset=UTF-8",
|
|
112
|
+
"X-Schematic-Api-Key": this.apiKey
|
|
113
|
+
},
|
|
114
|
+
body: requestBody
|
|
115
|
+
}).then((response) => {
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
throw new Error("Network response was not ok");
|
|
118
|
+
}
|
|
119
|
+
return response.json();
|
|
120
|
+
}).then((data) => {
|
|
121
|
+
return (data?.data?.flags ?? []).reduce(
|
|
122
|
+
(accum, flag) => {
|
|
123
|
+
accum[flag.flag] = flag.value;
|
|
124
|
+
return accum;
|
|
125
|
+
},
|
|
126
|
+
{}
|
|
127
|
+
);
|
|
128
|
+
}).catch((error) => {
|
|
129
|
+
console.error("There was a problem with the fetch operation:", error);
|
|
130
|
+
return false;
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
cleanup = () => {
|
|
134
|
+
if (this.conn) {
|
|
135
|
+
this.conn.close();
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
// Send an identify event
|
|
139
|
+
identify = (body) => {
|
|
140
|
+
this.handleEvent("identify", body);
|
|
141
|
+
};
|
|
142
|
+
// Set the flag evaluation context; if the context has changed,
|
|
143
|
+
// this will open a websocket connection (if not already open)
|
|
144
|
+
// and submit this context. The promise will resolve when the
|
|
145
|
+
// websocket sends back an initial set of flag values.
|
|
146
|
+
setContext = (context) => {
|
|
147
|
+
if (!this.useWebSocket) {
|
|
148
|
+
this.context = context;
|
|
149
|
+
return Promise.resolve();
|
|
150
|
+
}
|
|
151
|
+
return new Promise((resolve) => {
|
|
152
|
+
this.wsConnect().then(() => {
|
|
153
|
+
this.wsSendMessage(context);
|
|
154
|
+
resolve();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
// Send track event
|
|
159
|
+
track = (body) => {
|
|
160
|
+
this.handleEvent("track", body);
|
|
161
|
+
};
|
|
162
|
+
flushEventQueue = () => {
|
|
89
163
|
while (this.eventQueue.length > 0) {
|
|
90
164
|
const event = this.eventQueue.shift();
|
|
91
165
|
if (event) {
|
|
92
166
|
this.sendEvent(event);
|
|
93
167
|
}
|
|
94
168
|
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
this.
|
|
98
|
-
|
|
99
|
-
|
|
169
|
+
};
|
|
170
|
+
getAnonymousId = () => {
|
|
171
|
+
if (!this.storage) {
|
|
172
|
+
return v4_default();
|
|
173
|
+
}
|
|
174
|
+
const storedAnonymousId = this.storage.getItem(anonymousIdKey);
|
|
175
|
+
if (typeof storedAnonymousId !== "undefined") {
|
|
176
|
+
return storedAnonymousId;
|
|
177
|
+
}
|
|
178
|
+
const generatedAnonymousId = v4_default();
|
|
179
|
+
this.storage.setItem(anonymousIdKey, generatedAnonymousId);
|
|
180
|
+
return generatedAnonymousId;
|
|
181
|
+
};
|
|
182
|
+
handleEvent = (eventType, eventBody) => {
|
|
100
183
|
const event = {
|
|
101
184
|
api_key: this.apiKey,
|
|
102
185
|
body: eventBody,
|
|
@@ -110,73 +193,108 @@ var Schematic = class {
|
|
|
110
193
|
} else {
|
|
111
194
|
this.sendEvent(event);
|
|
112
195
|
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const storedAnonymousId = this.storage.getItem(anonymousIdKey);
|
|
119
|
-
if (typeof storedAnonymousId !== "undefined") {
|
|
120
|
-
return storedAnonymousId;
|
|
121
|
-
}
|
|
122
|
-
const generatedAnonymousId = v4_default();
|
|
123
|
-
this.storage.setItem(anonymousIdKey, generatedAnonymousId);
|
|
124
|
-
return generatedAnonymousId;
|
|
125
|
-
}
|
|
126
|
-
async checkFlag(key, context) {
|
|
127
|
-
const requestUrl = `https://api.schematichq.com/flags/${key}/check`;
|
|
128
|
-
const requestBody = JSON.stringify(context);
|
|
129
|
-
return fetch(requestUrl, {
|
|
196
|
+
};
|
|
197
|
+
sendEvent = (event) => {
|
|
198
|
+
const captureUrl = "https://c.schematichq.com/e";
|
|
199
|
+
const payload = JSON.stringify(event);
|
|
200
|
+
fetch(captureUrl, {
|
|
130
201
|
method: "POST",
|
|
131
202
|
headers: {
|
|
132
|
-
"X-Schematic-Api-Key": this.apiKey,
|
|
133
203
|
"Content-Type": "application/json;charset=UTF-8"
|
|
134
204
|
},
|
|
135
|
-
body:
|
|
205
|
+
body: payload
|
|
136
206
|
}).then((response) => {
|
|
137
207
|
if (!response.ok) {
|
|
138
|
-
throw new Error(
|
|
208
|
+
throw new Error(
|
|
209
|
+
`Network response was not ok: ${response.statusText}`
|
|
210
|
+
);
|
|
139
211
|
}
|
|
140
|
-
return response.json();
|
|
141
|
-
}).then((data) => {
|
|
142
|
-
return data.data.value;
|
|
143
212
|
}).catch((error) => {
|
|
144
213
|
console.error("There was a problem with the fetch operation:", error);
|
|
145
|
-
return false;
|
|
146
214
|
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
"X-Schematic-Api-Key": this.apiKey
|
|
156
|
-
},
|
|
157
|
-
body: requestBody
|
|
158
|
-
}).then((response) => {
|
|
159
|
-
if (!response.ok) {
|
|
160
|
-
throw new Error("Network response was not ok");
|
|
215
|
+
};
|
|
216
|
+
storeEvent = (event) => {
|
|
217
|
+
this.eventQueue.push(event);
|
|
218
|
+
};
|
|
219
|
+
wsConnect = () => {
|
|
220
|
+
return new Promise((resolve) => {
|
|
221
|
+
if (this.conn) {
|
|
222
|
+
resolve();
|
|
161
223
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
224
|
+
const wsUrl = "wss://api.schematichq.com/flags/bootstrap";
|
|
225
|
+
const webSocket = new WebSocket(wsUrl);
|
|
226
|
+
this.conn = webSocket;
|
|
227
|
+
webSocket.onopen = () => {
|
|
228
|
+
resolve();
|
|
229
|
+
};
|
|
230
|
+
webSocket.onclose = () => {
|
|
231
|
+
this.conn = null;
|
|
232
|
+
};
|
|
171
233
|
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
234
|
+
};
|
|
235
|
+
// Sends a message with a new context over the websocket connection
|
|
236
|
+
wsSendMessage = (context) => {
|
|
237
|
+
return new Promise((resolve, reject) => {
|
|
238
|
+
if (contextString(context) == contextString(this.context)) {
|
|
239
|
+
resolve();
|
|
240
|
+
}
|
|
241
|
+
this.context = context;
|
|
242
|
+
if (!this.conn) {
|
|
243
|
+
reject("Not connected");
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (this.conn.readyState === WebSocket.OPEN) {
|
|
247
|
+
let resolved = false;
|
|
248
|
+
this.conn.onmessage = (event) => {
|
|
249
|
+
const message = JSON.parse(event.data);
|
|
250
|
+
this.values[contextString(context)] = (message.flags ?? []).reduce(
|
|
251
|
+
(accum, flag) => {
|
|
252
|
+
accum[flag.flag] = flag.value;
|
|
253
|
+
return accum;
|
|
254
|
+
},
|
|
255
|
+
{}
|
|
256
|
+
);
|
|
257
|
+
if (this.flagListener) {
|
|
258
|
+
this.flagListener(this.values[contextString(context)]);
|
|
259
|
+
}
|
|
260
|
+
if (!resolved) {
|
|
261
|
+
resolved = true;
|
|
262
|
+
resolve();
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
this.conn.onerror = (error) => {
|
|
266
|
+
console.error("Schematic websocket error: ", error);
|
|
267
|
+
};
|
|
268
|
+
this.conn.send(
|
|
269
|
+
JSON.stringify({
|
|
270
|
+
apiKey: this.apiKey,
|
|
271
|
+
data: context
|
|
272
|
+
})
|
|
273
|
+
);
|
|
274
|
+
} else if (this.conn.readyState === WebSocket.CONNECTING) {
|
|
275
|
+
this.conn.onopen = () => {
|
|
276
|
+
this.wsSendMessage(context);
|
|
277
|
+
};
|
|
278
|
+
} else {
|
|
279
|
+
reject("Not connected");
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
};
|
|
179
283
|
};
|
|
284
|
+
function contextString(context) {
|
|
285
|
+
const sortedContext = Object.keys(context).reduce((acc, key) => {
|
|
286
|
+
const sortedKeys = Object.keys(
|
|
287
|
+
context[key] || {}
|
|
288
|
+
).sort();
|
|
289
|
+
const sortedObj = sortedKeys.reduce((obj, sortedKey) => {
|
|
290
|
+
obj[sortedKey] = context[key][sortedKey];
|
|
291
|
+
return obj;
|
|
292
|
+
}, {});
|
|
293
|
+
acc[key] = sortedObj;
|
|
294
|
+
return acc;
|
|
295
|
+
}, {});
|
|
296
|
+
return JSON.stringify(sortedContext);
|
|
297
|
+
}
|
|
180
298
|
export {
|
|
181
299
|
Schematic
|
|
182
300
|
};
|
package/package.json
CHANGED