@schematichq/schematic-js 0.0.13 → 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 -72
- package/dist/schematic.d.ts +33 -16
- package/dist/schematic.esm.js +185 -72
- 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,71 +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
|
-
const requestUrl = `https://api.schematichq.com/flags/${key}/check`;
|
|
154
|
-
const requestBody = JSON.stringify(context);
|
|
155
|
-
return fetch(requestUrl, {
|
|
220
|
+
};
|
|
221
|
+
sendEvent = (event) => {
|
|
222
|
+
const captureUrl = "https://c.schematichq.com/e";
|
|
223
|
+
const payload = JSON.stringify(event);
|
|
224
|
+
fetch(captureUrl, {
|
|
156
225
|
method: "POST",
|
|
157
226
|
headers: {
|
|
158
|
-
"X-Schematic-Api-Key": this.apiKey,
|
|
159
227
|
"Content-Type": "application/json;charset=UTF-8"
|
|
160
228
|
},
|
|
161
|
-
body:
|
|
229
|
+
body: payload
|
|
162
230
|
}).then((response) => {
|
|
163
231
|
if (!response.ok) {
|
|
164
|
-
throw new Error(
|
|
232
|
+
throw new Error(
|
|
233
|
+
`Network response was not ok: ${response.statusText}`
|
|
234
|
+
);
|
|
165
235
|
}
|
|
166
|
-
return response.json();
|
|
167
|
-
}).then((data) => {
|
|
168
|
-
return data.data.value;
|
|
169
236
|
}).catch((error) => {
|
|
170
237
|
console.error("There was a problem with the fetch operation:", error);
|
|
171
|
-
return false;
|
|
172
238
|
});
|
|
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");
|
|
239
|
+
};
|
|
240
|
+
storeEvent = (event) => {
|
|
241
|
+
this.eventQueue.push(event);
|
|
242
|
+
};
|
|
243
|
+
wsConnect = () => {
|
|
244
|
+
return new Promise((resolve) => {
|
|
245
|
+
if (this.conn) {
|
|
246
|
+
resolve();
|
|
187
247
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
+
};
|
|
197
257
|
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
+
};
|
|
205
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
|
+
}
|
|
206
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,73 +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
|
-
const requestUrl = `https://api.schematichq.com/flags/${key}/check`;
|
|
128
|
-
const requestBody = JSON.stringify(context);
|
|
129
|
-
return fetch(requestUrl, {
|
|
194
|
+
};
|
|
195
|
+
sendEvent = (event) => {
|
|
196
|
+
const captureUrl = "https://c.schematichq.com/e";
|
|
197
|
+
const payload = JSON.stringify(event);
|
|
198
|
+
fetch(captureUrl, {
|
|
130
199
|
method: "POST",
|
|
131
200
|
headers: {
|
|
132
|
-
"X-Schematic-Api-Key": this.apiKey,
|
|
133
201
|
"Content-Type": "application/json;charset=UTF-8"
|
|
134
202
|
},
|
|
135
|
-
body:
|
|
203
|
+
body: payload
|
|
136
204
|
}).then((response) => {
|
|
137
205
|
if (!response.ok) {
|
|
138
|
-
throw new Error(
|
|
206
|
+
throw new Error(
|
|
207
|
+
`Network response was not ok: ${response.statusText}`
|
|
208
|
+
);
|
|
139
209
|
}
|
|
140
|
-
return response.json();
|
|
141
|
-
}).then((data) => {
|
|
142
|
-
return data.data.value;
|
|
143
210
|
}).catch((error) => {
|
|
144
211
|
console.error("There was a problem with the fetch operation:", error);
|
|
145
|
-
return false;
|
|
146
212
|
});
|
|
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");
|
|
213
|
+
};
|
|
214
|
+
storeEvent = (event) => {
|
|
215
|
+
this.eventQueue.push(event);
|
|
216
|
+
};
|
|
217
|
+
wsConnect = () => {
|
|
218
|
+
return new Promise((resolve) => {
|
|
219
|
+
if (this.conn) {
|
|
220
|
+
resolve();
|
|
161
221
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
+
};
|
|
171
231
|
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
+
};
|
|
179
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
|
+
}
|
|
180
293
|
export {
|
|
181
294
|
Schematic
|
|
182
295
|
};
|
package/package.json
CHANGED