@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.
@@ -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 m=typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto),c={randomUUID:m};function g(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=g;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){if(!r.company)return Promise.resolve(!1);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){if(!e.company)return Promise.resolve({});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 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 */
@@ -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
- constructor(apiKey, storage) {
83
+ useWebSocket = false;
84
+ values = {};
85
+ constructor(apiKey, options) {
82
86
  this.apiKey = apiKey;
83
87
  this.eventQueue = [];
84
- if (storage) {
85
- this.storage = storage;
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
- sendEvent(event) {
96
- const captureUrl = "https://c.schematichq.com/e";
97
- const payload = JSON.stringify(event);
98
- fetch(captureUrl, {
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: payload
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
- flushEventQueue() {
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
- storeEvent(event) {
123
- this.eventQueue.push(event);
124
- }
125
- handleEvent(eventType, eventBody) {
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
- 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
- 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: requestBody
229
+ body: payload
165
230
  }).then((response) => {
166
231
  if (!response.ok) {
167
- throw new Error("Network response was not ok");
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
- async checkFlags(context) {
178
- if (!context.company) {
179
- return Promise.resolve({});
180
- }
181
- const requestUrl = "https://api.schematichq.com/flags/check";
182
- const requestBody = JSON.stringify(context);
183
- return fetch(requestUrl, {
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
- return response.json();
195
- }).then((data) => {
196
- return data.data.flags.reduce((accum, flag) => {
197
- accum[flag.flag] = flag.value;
198
- return accum;
199
- }, {});
200
- }).catch((error) => {
201
- console.error("There was a problem with the fetch operation:", error);
202
- return false;
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
- identify(body) {
206
- this.handleEvent("identify", body);
207
- }
208
- track(body) {
209
- this.handleEvent("track", body);
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 */
@@ -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
- constructor(apiKey: string, storage?: StoragePersister);
60
- private sendEvent;
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
- 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;
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;
@@ -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
- constructor(apiKey, storage) {
57
+ useWebSocket = false;
58
+ values = {};
59
+ constructor(apiKey, options) {
56
60
  this.apiKey = apiKey;
57
61
  this.eventQueue = [];
58
- if (storage) {
59
- this.storage = storage;
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
- sendEvent(event) {
70
- const captureUrl = "https://c.schematichq.com/e";
71
- const payload = JSON.stringify(event);
72
- fetch(captureUrl, {
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: payload
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
- flushEventQueue() {
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
- storeEvent(event) {
97
- this.eventQueue.push(event);
98
- }
99
- handleEvent(eventType, eventBody) {
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
- 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
- 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: requestBody
203
+ body: payload
139
204
  }).then((response) => {
140
205
  if (!response.ok) {
141
- throw new Error("Network response was not ok");
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
- async checkFlags(context) {
152
- if (!context.company) {
153
- return Promise.resolve({});
154
- }
155
- const requestUrl = "https://api.schematichq.com/flags/check";
156
- const requestBody = JSON.stringify(context);
157
- return fetch(requestUrl, {
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
- return response.json();
169
- }).then((data) => {
170
- return data.data.flags.reduce((accum, flag) => {
171
- accum[flag.flag] = flag.value;
172
- return accum;
173
- }, {});
174
- }).catch((error) => {
175
- console.error("There was a problem with the fetch operation:", error);
176
- return false;
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
- identify(body) {
180
- this.handleEvent("identify", body);
181
- }
182
- track(body) {
183
- this.handleEvent("track", body);
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
@@ -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.12"
47
+ "version": "0.1.0"
48
48
  }