@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.
@@ -1,2 +1,2 @@
1
- "use strict";(()=>{var d,v=new Uint8Array(16);function y(){if(!d&&(d=typeof crypto<"u"&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto),!d))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return d(v)}var o=[];for(let t=0;t<256;++t)o.push((t+256).toString(16).slice(1));function h(t,e=0){return o[t[e+0]]+o[t[e+1]]+o[t[e+2]]+o[t[e+3]]+"-"+o[t[e+4]]+o[t[e+5]]+"-"+o[t[e+6]]+o[t[e+7]]+"-"+o[t[e+8]]+o[t[e+9]]+"-"+o[t[e+10]]+o[t[e+11]]+o[t[e+12]]+o[t[e+13]]+o[t[e+14]]+o[t[e+15]]}var g=typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto),c={randomUUID:g};function m(t,e,r){if(c.randomUUID&&!e&&!t)return c.randomUUID();t=t||{};let i=t.random||(t.rng||y)();if(i[6]=i[6]&15|64,i[8]=i[8]&63|128,e){r=r||0;for(let n=0;n<16;++n)e[r+n]=i[n];return e}return h(i)}var a=m;var l="schematicId";var p=class{apiKey;eventQueue;storage;constructor(e,r){this.apiKey=e,this.eventQueue=[],r?this.storage=r:typeof localStorage<"u"&&(this.storage=localStorage),typeof window<"u"&&window.addEventListener("beforeunload",()=>{this.flushEventQueue()})}sendEvent(e){let r="https://c.schematichq.com/e",i=JSON.stringify(e);fetch(r,{method:"POST",headers:{"Content-Type":"application/json;charset=UTF-8"},body:i}).then(n=>{if(!n.ok)throw new Error(`Network response was not ok: ${n.statusText}`)}).catch(n=>{console.error("There was a problem with the fetch operation:",n)})}flushEventQueue(){for(;this.eventQueue.length>0;){let e=this.eventQueue.shift();e&&this.sendEvent(e)}}storeEvent(e){this.eventQueue.push(e)}handleEvent(e,r){let i={api_key:this.apiKey,body:r,sent_at:new Date().toISOString(),tracker_event_id:a(),tracker_user_id:this.getAnonymousId(),type:e};typeof document<"u"&&document.hidden?this.storeEvent(i):this.sendEvent(i)}getAnonymousId(){if(!this.storage)return a();let e=this.storage.getItem(l);if(typeof e<"u")return e;let r=a();return this.storage.setItem(l,r),r}async checkFlag(e,r){let i=`https://api.schematichq.com/flags/${e}/check`,n=JSON.stringify(r);return fetch(i,{method:"POST",headers:{"X-Schematic-Api-Key":this.apiKey,"Content-Type":"application/json;charset=UTF-8"},body:n}).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),!1))}async checkFlags(e){let r="https://api.schematichq.com/flags/check",i=JSON.stringify(e);return fetch(r,{method:"POST",headers:{"Content-Type":"application/json;charset=UTF-8","X-Schematic-Api-Key":this.apiKey},body:i}).then(n=>{if(!n.ok)throw new Error("Network response was not ok");return n.json()}).then(n=>n.data.flags.reduce((s,u)=>(s[u.flag]=u.value,s),{})).catch(n=>(console.error("There was a problem with the fetch operation:",n),!1))}identify(e){this.handleEvent("identify",e)}track(e){this.handleEvent("track",e)}};window.Schematic=p;})();
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 */
@@ -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
- constructor(apiKey, storage) {
83
+ useWebSocket = false;
84
+ values = {};
85
+ flagListener;
86
+ constructor(apiKey, options) {
82
87
  this.apiKey = apiKey;
83
88
  this.eventQueue = [];
84
- if (storage) {
85
- this.storage = storage;
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
- sendEvent(event) {
96
- const captureUrl = "https://c.schematichq.com/e";
97
- const payload = JSON.stringify(event);
98
- fetch(captureUrl, {
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: payload
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
- flushEventQueue() {
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
- storeEvent(event) {
123
- this.eventQueue.push(event);
124
- }
125
- handleEvent(eventType, eventBody) {
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
- getAnonymousId() {
141
- if (!this.storage) {
142
- return v4_default();
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: requestBody
231
+ body: payload
162
232
  }).then((response) => {
163
233
  if (!response.ok) {
164
- throw new Error("Network response was not ok");
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
- async checkFlags(context) {
175
- const requestUrl = "https://api.schematichq.com/flags/check";
176
- const requestBody = JSON.stringify(context);
177
- return fetch(requestUrl, {
178
- method: "POST",
179
- headers: {
180
- "Content-Type": "application/json;charset=UTF-8",
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
- return response.json();
189
- }).then((data) => {
190
- return data.data.flags.reduce((accum, flag) => {
191
- accum[flag.flag] = flag.value;
192
- return accum;
193
- }, {});
194
- }).catch((error) => {
195
- console.error("There was a problem with the fetch operation:", error);
196
- return false;
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
- identify(body) {
200
- this.handleEvent("identify", body);
201
- }
202
- track(body) {
203
- this.handleEvent("track", body);
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 */
@@ -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
- constructor(apiKey: string, storage?: StoragePersister);
60
- private sendEvent;
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
- checkFlag(key: string, context: FlagCheckContext): Promise<boolean>;
66
- checkFlags(context: FlagCheckContext): Promise<Record<string, boolean>>;
67
- identify(body: EventBodyIdentify): void;
68
- track(body: EventBodyTrack): void;
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;
@@ -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
- constructor(apiKey, storage) {
57
+ useWebSocket = false;
58
+ values = {};
59
+ flagListener;
60
+ constructor(apiKey, options) {
56
61
  this.apiKey = apiKey;
57
62
  this.eventQueue = [];
58
- if (storage) {
59
- this.storage = storage;
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
- sendEvent(event) {
70
- const captureUrl = "https://c.schematichq.com/e";
71
- const payload = JSON.stringify(event);
72
- fetch(captureUrl, {
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: payload
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
- flushEventQueue() {
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
- storeEvent(event) {
97
- this.eventQueue.push(event);
98
- }
99
- handleEvent(eventType, eventBody) {
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
- getAnonymousId() {
115
- if (!this.storage) {
116
- return v4_default();
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: requestBody
205
+ body: payload
136
206
  }).then((response) => {
137
207
  if (!response.ok) {
138
- throw new Error("Network response was not ok");
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
- async checkFlags(context) {
149
- const requestUrl = "https://api.schematichq.com/flags/check";
150
- const requestBody = JSON.stringify(context);
151
- return fetch(requestUrl, {
152
- method: "POST",
153
- headers: {
154
- "Content-Type": "application/json;charset=UTF-8",
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
- return response.json();
163
- }).then((data) => {
164
- return data.data.flags.reduce((accum, flag) => {
165
- accum[flag.flag] = flag.value;
166
- return accum;
167
- }, {});
168
- }).catch((error) => {
169
- console.error("There was a problem with the fetch operation:", error);
170
- return false;
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
- identify(body) {
174
- this.handleEvent("identify", body);
175
- }
176
- track(body) {
177
- this.handleEvent("track", body);
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
@@ -44,5 +44,5 @@
44
44
  "test": "jest --config jest.config.js"
45
45
  },
46
46
  "types": "dist/schematic.d.ts",
47
- "version": "0.0.13"
47
+ "version": "0.1.1"
48
48
  }