arifa-client 1.0.13 → 1.1.14

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.
@@ -4,27 +4,28 @@ class ArifaClient {
4
4
  this.isConnected = false;
5
5
  this.reconnectAttempts = 0;
6
6
  this.reconnectTimer = null;
7
+ this.reconnectScheduled = false;
7
8
  this.internetOnline = true;
8
9
  this.internetTimer = null;
9
10
  this.recipient = null;
10
- this.listeners = [];
11
- this.connectionListeners = [];
11
+ this.listeners = new Set();
12
+ this.connectionListeners = new Set();
12
13
  this.HEALTH_URL = "https://notifications.arifa.dev/health";
13
14
  this.MAX_BACKOFF = 60000;
14
15
  this.apiKey = apiKey;
15
16
  this.client = client;
16
17
  this.wsUrl = wsUrl || "wss://notifications.arifa.dev/ws";
17
18
  this.apiEndpoint = apiEndpoint || "https://notifications.arifa.dev/notify";
18
- this.startInternetMonitor();
19
+ if (typeof window !== "undefined") {
20
+ this.startInternetMonitor();
21
+ }
19
22
  }
20
23
  safeParse(input) {
21
24
  try {
22
25
  if (typeof input === "object")
23
26
  return input;
24
- let parsed = JSON.parse(input);
25
- if (typeof parsed === "string")
26
- parsed = JSON.parse(parsed);
27
- return parsed;
27
+ const parsed = JSON.parse(String(input));
28
+ return typeof parsed === "string" ? JSON.parse(parsed) : parsed;
28
29
  }
29
30
  catch (_a) {
30
31
  return null;
@@ -36,7 +37,7 @@ class ArifaClient {
36
37
  const controller = new AbortController();
37
38
  const timeout = setTimeout(() => controller.abort(), 3000);
38
39
  await fetch(this.HEALTH_URL, {
39
- method: "GET",
40
+ method: "HEAD",
40
41
  cache: "no-store",
41
42
  signal: controller.signal,
42
43
  });
@@ -76,43 +77,58 @@ class ArifaClient {
76
77
  }
77
78
  return {
78
79
  listen: (callback) => {
79
- this.listeners.push(callback);
80
+ this.listeners.add(callback);
81
+ return () => this.listeners.delete(callback);
80
82
  },
81
- unsubscribe: () => {
82
- this.listeners = [];
83
+ unsubscribe: (callback) => {
84
+ if (callback)
85
+ this.listeners.delete(callback);
86
+ else
87
+ this.listeners.clear();
83
88
  },
84
89
  };
85
90
  }
86
91
  onConnectionChange(callback) {
87
- this.connectionListeners.push(callback);
92
+ this.connectionListeners.add(callback);
93
+ return () => this.connectionListeners.delete(callback);
88
94
  }
89
95
  emitConnection(state) {
90
96
  this.connectionListeners.forEach((cb) => cb(state));
91
97
  }
92
98
  /* ---------------- WEBSOCKET ---------------- */
93
99
  connect() {
94
- if (!this.recipient)
95
- return;
96
- if (!this.internetOnline)
100
+ if (!this.recipient || !this.internetOnline)
97
101
  return;
98
- if (this.ws && this.ws.readyState === WebSocket.OPEN)
102
+ if (this.ws &&
103
+ (this.ws.readyState === WebSocket.OPEN ||
104
+ this.ws.readyState === WebSocket.CONNECTING)) {
99
105
  return;
100
- this.ws = new WebSocket(`${this.wsUrl}/connect?api_key=${this.apiKey}&recipient=${this.recipient}&client=${this.client}`);
106
+ }
107
+ this.ws = new WebSocket(`${this.wsUrl}/connect?api_key=${encodeURIComponent(this.apiKey)}&recipient=${encodeURIComponent(this.recipient)}&client=${this.client}`);
101
108
  this.ws.onopen = () => {
102
109
  this.isConnected = true;
103
110
  this.reconnectAttempts = 0;
111
+ this.reconnectScheduled = false;
112
+ if (this.reconnectTimer) {
113
+ clearTimeout(this.reconnectTimer);
114
+ this.reconnectTimer = null;
115
+ }
104
116
  this.emitConnection("connected");
105
117
  };
106
118
  this.ws.onmessage = (event) => {
119
+ var _a, _b;
107
120
  const parsed = this.safeParse(event.data);
108
121
  if (!parsed)
109
122
  return;
110
- // IMMEDIATE ACK
111
- this.ws.send(JSON.stringify({
112
- kind: "ack",
113
- event_id: parsed.ack.event_id,
114
- }));
115
- this.listeners.forEach((fn) => fn(parsed.event));
123
+ if ((_a = parsed === null || parsed === void 0 ? void 0 : parsed.ack) === null || _a === void 0 ? void 0 : _a.event_id) {
124
+ (_b = this.ws) === null || _b === void 0 ? void 0 : _b.send(JSON.stringify({
125
+ kind: "ack",
126
+ event_id: parsed.ack.event_id,
127
+ }));
128
+ }
129
+ if (parsed.event) {
130
+ this.listeners.forEach((fn) => fn(parsed.event));
131
+ }
116
132
  };
117
133
  this.ws.onclose = () => {
118
134
  this.isConnected = false;
@@ -125,34 +141,44 @@ class ArifaClient {
125
141
  };
126
142
  }
127
143
  scheduleReconnect() {
128
- if (!this.internetOnline)
144
+ if (!this.internetOnline || this.reconnectScheduled)
129
145
  return;
146
+ this.reconnectScheduled = true;
130
147
  const base = Math.min(this.MAX_BACKOFF, 1000 * 2 ** this.reconnectAttempts);
131
148
  const jitter = Math.random() * 1000;
132
- const timeout = base + jitter;
133
149
  this.reconnectAttempts++;
134
150
  this.reconnectTimer = window.setTimeout(() => {
151
+ this.reconnectScheduled = false;
135
152
  this.connect();
136
- }, timeout);
153
+ }, base + jitter);
137
154
  }
138
155
  /* ---------------- HTTP NOTIFY ---------------- */
139
156
  async notify({ recipient, payload, client, origin, }) {
140
- const res = await fetch(this.apiEndpoint, {
141
- method: "POST",
142
- headers: {
143
- "Content-Type": "application/json",
144
- Origin: origin || window.location.origin,
145
- },
146
- body: JSON.stringify({
147
- recipient,
148
- payload,
149
- api_key: this.apiKey,
150
- client: client || this.client,
151
- }),
152
- });
157
+ const controller = new AbortController();
158
+ const timeout = setTimeout(() => controller.abort(), 8000);
159
+ let res;
160
+ try {
161
+ res = await fetch(this.apiEndpoint, {
162
+ method: "POST",
163
+ headers: {
164
+ "Content-Type": "application/json",
165
+ },
166
+ signal: controller.signal,
167
+ body: JSON.stringify({
168
+ recipient,
169
+ payload,
170
+ api_key: this.apiKey,
171
+ client: client || this.client,
172
+ origin,
173
+ }),
174
+ });
175
+ }
176
+ finally {
177
+ clearTimeout(timeout);
178
+ }
153
179
  const json = await res.json();
154
180
  if (!res.ok) {
155
- throw new Error(json.message || "Notification failed");
181
+ throw new Error((json === null || json === void 0 ? void 0 : json.message) || "Notification failed");
156
182
  }
157
183
  return json;
158
184
  }
@@ -7,27 +7,28 @@ var ArifaClient = (function () {
7
7
  this.isConnected = false;
8
8
  this.reconnectAttempts = 0;
9
9
  this.reconnectTimer = null;
10
+ this.reconnectScheduled = false;
10
11
  this.internetOnline = true;
11
12
  this.internetTimer = null;
12
13
  this.recipient = null;
13
- this.listeners = [];
14
- this.connectionListeners = [];
14
+ this.listeners = new Set();
15
+ this.connectionListeners = new Set();
15
16
  this.HEALTH_URL = "https://notifications.arifa.dev/health";
16
17
  this.MAX_BACKOFF = 60000;
17
18
  this.apiKey = apiKey;
18
19
  this.client = client;
19
20
  this.wsUrl = wsUrl || "wss://notifications.arifa.dev/ws";
20
21
  this.apiEndpoint = apiEndpoint || "https://notifications.arifa.dev/notify";
21
- this.startInternetMonitor();
22
+ if (typeof window !== "undefined") {
23
+ this.startInternetMonitor();
24
+ }
22
25
  }
23
26
  safeParse(input) {
24
27
  try {
25
28
  if (typeof input === "object")
26
29
  return input;
27
- let parsed = JSON.parse(input);
28
- if (typeof parsed === "string")
29
- parsed = JSON.parse(parsed);
30
- return parsed;
30
+ const parsed = JSON.parse(String(input));
31
+ return typeof parsed === "string" ? JSON.parse(parsed) : parsed;
31
32
  }
32
33
  catch (_a) {
33
34
  return null;
@@ -39,7 +40,7 @@ var ArifaClient = (function () {
39
40
  const controller = new AbortController();
40
41
  const timeout = setTimeout(() => controller.abort(), 3000);
41
42
  await fetch(this.HEALTH_URL, {
42
- method: "GET",
43
+ method: "HEAD",
43
44
  cache: "no-store",
44
45
  signal: controller.signal,
45
46
  });
@@ -79,43 +80,58 @@ var ArifaClient = (function () {
79
80
  }
80
81
  return {
81
82
  listen: (callback) => {
82
- this.listeners.push(callback);
83
+ this.listeners.add(callback);
84
+ return () => this.listeners.delete(callback);
83
85
  },
84
- unsubscribe: () => {
85
- this.listeners = [];
86
+ unsubscribe: (callback) => {
87
+ if (callback)
88
+ this.listeners.delete(callback);
89
+ else
90
+ this.listeners.clear();
86
91
  },
87
92
  };
88
93
  }
89
94
  onConnectionChange(callback) {
90
- this.connectionListeners.push(callback);
95
+ this.connectionListeners.add(callback);
96
+ return () => this.connectionListeners.delete(callback);
91
97
  }
92
98
  emitConnection(state) {
93
99
  this.connectionListeners.forEach((cb) => cb(state));
94
100
  }
95
101
  /* ---------------- WEBSOCKET ---------------- */
96
102
  connect() {
97
- if (!this.recipient)
98
- return;
99
- if (!this.internetOnline)
103
+ if (!this.recipient || !this.internetOnline)
100
104
  return;
101
- if (this.ws && this.ws.readyState === WebSocket.OPEN)
105
+ if (this.ws &&
106
+ (this.ws.readyState === WebSocket.OPEN ||
107
+ this.ws.readyState === WebSocket.CONNECTING)) {
102
108
  return;
103
- this.ws = new WebSocket(`${this.wsUrl}/connect?api_key=${this.apiKey}&recipient=${this.recipient}&client=${this.client}`);
109
+ }
110
+ this.ws = new WebSocket(`${this.wsUrl}/connect?api_key=${encodeURIComponent(this.apiKey)}&recipient=${encodeURIComponent(this.recipient)}&client=${this.client}`);
104
111
  this.ws.onopen = () => {
105
112
  this.isConnected = true;
106
113
  this.reconnectAttempts = 0;
114
+ this.reconnectScheduled = false;
115
+ if (this.reconnectTimer) {
116
+ clearTimeout(this.reconnectTimer);
117
+ this.reconnectTimer = null;
118
+ }
107
119
  this.emitConnection("connected");
108
120
  };
109
121
  this.ws.onmessage = (event) => {
122
+ var _a, _b;
110
123
  const parsed = this.safeParse(event.data);
111
124
  if (!parsed)
112
125
  return;
113
- // IMMEDIATE ACK
114
- this.ws.send(JSON.stringify({
115
- kind: "ack",
116
- event_id: parsed.ack.event_id,
117
- }));
118
- this.listeners.forEach((fn) => fn(parsed.event));
126
+ if ((_a = parsed === null || parsed === void 0 ? void 0 : parsed.ack) === null || _a === void 0 ? void 0 : _a.event_id) {
127
+ (_b = this.ws) === null || _b === void 0 ? void 0 : _b.send(JSON.stringify({
128
+ kind: "ack",
129
+ event_id: parsed.ack.event_id,
130
+ }));
131
+ }
132
+ if (parsed.event) {
133
+ this.listeners.forEach((fn) => fn(parsed.event));
134
+ }
119
135
  };
120
136
  this.ws.onclose = () => {
121
137
  this.isConnected = false;
@@ -128,34 +144,44 @@ var ArifaClient = (function () {
128
144
  };
129
145
  }
130
146
  scheduleReconnect() {
131
- if (!this.internetOnline)
147
+ if (!this.internetOnline || this.reconnectScheduled)
132
148
  return;
149
+ this.reconnectScheduled = true;
133
150
  const base = Math.min(this.MAX_BACKOFF, 1000 * 2 ** this.reconnectAttempts);
134
151
  const jitter = Math.random() * 1000;
135
- const timeout = base + jitter;
136
152
  this.reconnectAttempts++;
137
153
  this.reconnectTimer = window.setTimeout(() => {
154
+ this.reconnectScheduled = false;
138
155
  this.connect();
139
- }, timeout);
156
+ }, base + jitter);
140
157
  }
141
158
  /* ---------------- HTTP NOTIFY ---------------- */
142
159
  async notify({ recipient, payload, client, origin, }) {
143
- const res = await fetch(this.apiEndpoint, {
144
- method: "POST",
145
- headers: {
146
- "Content-Type": "application/json",
147
- Origin: origin || window.location.origin,
148
- },
149
- body: JSON.stringify({
150
- recipient,
151
- payload,
152
- api_key: this.apiKey,
153
- client: client || this.client,
154
- }),
155
- });
160
+ const controller = new AbortController();
161
+ const timeout = setTimeout(() => controller.abort(), 8000);
162
+ let res;
163
+ try {
164
+ res = await fetch(this.apiEndpoint, {
165
+ method: "POST",
166
+ headers: {
167
+ "Content-Type": "application/json",
168
+ },
169
+ signal: controller.signal,
170
+ body: JSON.stringify({
171
+ recipient,
172
+ payload,
173
+ api_key: this.apiKey,
174
+ client: client || this.client,
175
+ origin,
176
+ }),
177
+ });
178
+ }
179
+ finally {
180
+ clearTimeout(timeout);
181
+ }
156
182
  const json = await res.json();
157
183
  if (!res.ok) {
158
- throw new Error(json.message || "Notification failed");
184
+ throw new Error((json === null || json === void 0 ? void 0 : json.message) || "Notification failed");
159
185
  }
160
186
  return json;
161
187
  }
@@ -1,4 +1,4 @@
1
- type ArifaEvent = any;
1
+ type ArifaEvent = Record<string, unknown>;
2
2
  type ConnectionState = "connected" | "disconnected";
3
3
  interface ArifaClientOptions {
4
4
  apiKey: string;
@@ -8,7 +8,7 @@ interface ArifaClientOptions {
8
8
  }
9
9
  interface NotifyPayload {
10
10
  recipient: string;
11
- payload: Record<string, any>;
11
+ payload: Record<string, unknown>;
12
12
  client?: "web" | "mobile";
13
13
  origin?: string;
14
14
  }
@@ -26,6 +26,7 @@ export declare class ArifaClient {
26
26
  private isConnected;
27
27
  private reconnectAttempts;
28
28
  private reconnectTimer;
29
+ private reconnectScheduled;
29
30
  private internetOnline;
30
31
  private internetTimer;
31
32
  private recipient;
@@ -38,10 +39,10 @@ export declare class ArifaClient {
38
39
  private checkInternet;
39
40
  private startInternetMonitor;
40
41
  subscribe(recipient: string): {
41
- listen: (callback: (event: ArifaEvent) => void) => void;
42
- unsubscribe: () => void;
42
+ listen: (callback: (event: ArifaEvent) => void) => () => boolean;
43
+ unsubscribe: (callback?: (event: ArifaEvent) => void) => void;
43
44
  };
44
- onConnectionChange(callback: ConnectionCallback): void;
45
+ onConnectionChange(callback: ConnectionCallback): () => boolean;
45
46
  private emitConnection;
46
47
  private connect;
47
48
  private scheduleReconnect;
@@ -1 +1 @@
1
- {"version":3,"file":"ArifaClient.d.ts","sourceRoot":"","sources":["../../src/ArifaClient.ts"],"names":[],"mappings":"AAAA,KAAK,UAAU,GAAG,GAAG,CAAC;AACtB,KAAK,eAAe,GAAG,WAAW,GAAG,cAAc,CAAC;AAEpD,UAAU,kBAAkB;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,KAAK,GAAG,QAAQ,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,aAAa;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,cAAc;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,KAAK,kBAAkB,GAAG,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;AAE3D,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,WAAW,CAAS;IAE5B,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,WAAW,CAAS;IAE5B,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,cAAc,CAAuB;IAE7C,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,aAAa,CAAuB;IAE5C,OAAO,CAAC,SAAS,CAAuB;IAExC,OAAO,CAAC,SAAS,CAA0C;IAC3D,OAAO,CAAC,mBAAmB,CAA4B;IAEvD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA4C;IACvE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAU;gBAE1B,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,kBAAkB;IAStE,OAAO,CAAC,SAAS;YAaH,aAAa;IAkB3B,OAAO,CAAC,oBAAoB;IA2B5B,SAAS,CAAC,SAAS,EAAE,MAAM;2BAOJ,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI;;;IASlD,kBAAkB,CAAC,QAAQ,EAAE,kBAAkB;IAI/C,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,OAAO;IAyCf,OAAO,CAAC,iBAAiB;IAiBnB,MAAM,CAAC,EACX,SAAS,EACT,OAAO,EACP,MAAM,EACN,MAAM,GACP,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;IA0B1C,UAAU;IAUV,SAAS;CAGV"}
1
+ {"version":3,"file":"ArifaClient.d.ts","sourceRoot":"","sources":["../../src/ArifaClient.ts"],"names":[],"mappings":"AAAA,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC1C,KAAK,eAAe,GAAG,WAAW,GAAG,cAAc,CAAC;AAEpD,UAAU,kBAAkB;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,KAAK,GAAG,QAAQ,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,aAAa;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,cAAc;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,KAAK,kBAAkB,GAAG,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;AAE3D,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,WAAW,CAAS;IAE5B,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,WAAW,CAAS;IAE5B,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,kBAAkB,CAAS;IAEnC,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,aAAa,CAAuB;IAE5C,OAAO,CAAC,SAAS,CAAuB;IAExC,OAAO,CAAC,SAAS,CAA0C;IAC3D,OAAO,CAAC,mBAAmB,CAAiC;IAE5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA4C;IACvE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAU;gBAE1B,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,kBAAkB;IAWtE,OAAO,CAAC,SAAS;YAYH,aAAa;IAkB3B,OAAO,CAAC,oBAAoB;IA2B5B,SAAS,CAAC,SAAS,EAAE,MAAM;2BAOJ,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI;iCAIrB,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI;;IAOxD,kBAAkB,CAAC,QAAQ,EAAE,kBAAkB;IAK/C,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,OAAO;IA2Df,OAAO,CAAC,iBAAiB;IAkBnB,MAAM,CAAC,EACX,SAAS,EACT,OAAO,EACP,MAAM,EACN,MAAM,GACP,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;IAoC1C,UAAU;IAUV,SAAS;CAGV"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arifa-client",
3
- "version": "1.0.13",
3
+ "version": "1.1.14",
4
4
  "description": "JavaScript/TypeScript client SDK for Arifa Realtime Notification Service",
5
5
  "main": "dist/arifa-client.iife.js",
6
6
  "module": "dist/arifa-client.esm.js",