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