arifa-client 1.0.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/README.md +11 -0
- package/dist/ArifaClient.d.ts +45 -0
- package/dist/ArifaClient.js +118 -0
- package/package.json +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# ArifaClient SDK
|
|
2
|
+
|
|
3
|
+
JavaScript/TypeScript client SDK for **Arifa Realtime Notification Service**.
|
|
4
|
+
Allows you to connect via WebSocket, listen to events, and send notifications using HTTP.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install arifa-client
|
|
11
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
type ArifaEvent = any;
|
|
2
|
+
type ConnectionState = "connected" | "disconnected";
|
|
3
|
+
interface ArifaClientOptions {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
client: "web" | "mobile";
|
|
6
|
+
wsUrl?: string;
|
|
7
|
+
apiEndpoint?: string;
|
|
8
|
+
}
|
|
9
|
+
interface NotifyPayload {
|
|
10
|
+
recipient: string;
|
|
11
|
+
payload: Record<string, any>;
|
|
12
|
+
client?: "web" | "mobile";
|
|
13
|
+
}
|
|
14
|
+
type ConnectionCallback = (state: ConnectionState) => void;
|
|
15
|
+
export declare class ArifaClient {
|
|
16
|
+
private apiKey;
|
|
17
|
+
private client;
|
|
18
|
+
private wsUrl;
|
|
19
|
+
private apiEndpoint;
|
|
20
|
+
private ws;
|
|
21
|
+
private isConnected;
|
|
22
|
+
private reconnectAttempts;
|
|
23
|
+
private reconnectTimer;
|
|
24
|
+
private subscriptions;
|
|
25
|
+
private connectionListeners;
|
|
26
|
+
constructor({ apiKey, client, wsUrl, apiEndpoint }: ArifaClientOptions);
|
|
27
|
+
private safeParse;
|
|
28
|
+
/** Subscribe to a recipient */
|
|
29
|
+
subscribe(recipient: string): {
|
|
30
|
+
listen: (callback: (event: ArifaEvent) => void) => void;
|
|
31
|
+
unsubscribe: () => void;
|
|
32
|
+
};
|
|
33
|
+
/** Listen to connection state changes */
|
|
34
|
+
onConnectionChange(callback: ConnectionCallback): void;
|
|
35
|
+
private emitConnection;
|
|
36
|
+
/** Connect WebSocket */
|
|
37
|
+
private connect;
|
|
38
|
+
/** Send notification */
|
|
39
|
+
notify({ recipient, payload, client }: NotifyPayload): Promise<any>;
|
|
40
|
+
/** Disconnect WS */
|
|
41
|
+
disconnect(): void;
|
|
42
|
+
/** Return connection state */
|
|
43
|
+
connected(): boolean;
|
|
44
|
+
}
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
export class ArifaClient {
|
|
2
|
+
apiKey;
|
|
3
|
+
client;
|
|
4
|
+
wsUrl;
|
|
5
|
+
apiEndpoint;
|
|
6
|
+
ws = null;
|
|
7
|
+
isConnected = false;
|
|
8
|
+
reconnectAttempts = 0;
|
|
9
|
+
reconnectTimer = null;
|
|
10
|
+
subscriptions = {};
|
|
11
|
+
connectionListeners = [];
|
|
12
|
+
constructor({ apiKey, client, wsUrl, apiEndpoint }) {
|
|
13
|
+
this.apiKey = apiKey;
|
|
14
|
+
this.client = client;
|
|
15
|
+
this.wsUrl = wsUrl || "wss://notifications.arifa.dev/ws";
|
|
16
|
+
this.apiEndpoint = apiEndpoint || "https://notifications.arifa.dev/notify";
|
|
17
|
+
}
|
|
18
|
+
safeParse(input) {
|
|
19
|
+
try {
|
|
20
|
+
if (typeof input === "object")
|
|
21
|
+
return input;
|
|
22
|
+
let parsed = JSON.parse(input);
|
|
23
|
+
if (typeof parsed === "string")
|
|
24
|
+
parsed = JSON.parse(parsed);
|
|
25
|
+
return parsed;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Subscribe to a recipient */
|
|
32
|
+
subscribe(recipient) {
|
|
33
|
+
if (!this.subscriptions[recipient]) {
|
|
34
|
+
this.subscriptions[recipient] = { recipient, listeners: [] };
|
|
35
|
+
}
|
|
36
|
+
this.connect(recipient);
|
|
37
|
+
return {
|
|
38
|
+
listen: (callback) => {
|
|
39
|
+
this.subscriptions[recipient].listeners.push(callback);
|
|
40
|
+
},
|
|
41
|
+
unsubscribe: () => {
|
|
42
|
+
delete this.subscriptions[recipient];
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** Listen to connection state changes */
|
|
47
|
+
onConnectionChange(callback) {
|
|
48
|
+
this.connectionListeners.push(callback);
|
|
49
|
+
}
|
|
50
|
+
emitConnection(state) {
|
|
51
|
+
this.connectionListeners.forEach((cb) => cb(state));
|
|
52
|
+
}
|
|
53
|
+
/** Connect WebSocket */
|
|
54
|
+
connect(recipient) {
|
|
55
|
+
if (this.ws && this.ws.readyState !== WebSocket.CLOSED)
|
|
56
|
+
return;
|
|
57
|
+
this.ws = new WebSocket(`${this.wsUrl}/connect?api_key=${this.apiKey}&recipient=${recipient}&client=${this.client}`);
|
|
58
|
+
this.ws.onopen = () => {
|
|
59
|
+
this.isConnected = true;
|
|
60
|
+
this.reconnectAttempts = 0;
|
|
61
|
+
this.emitConnection("connected");
|
|
62
|
+
console.log("ArifaClient connected");
|
|
63
|
+
};
|
|
64
|
+
this.ws.onmessage = (event) => {
|
|
65
|
+
const parsed = this.safeParse(event.data);
|
|
66
|
+
if (!parsed || !parsed.recipient)
|
|
67
|
+
return;
|
|
68
|
+
const sub = this.subscriptions[parsed.recipient];
|
|
69
|
+
if (sub)
|
|
70
|
+
sub.listeners.forEach((fn) => fn(parsed));
|
|
71
|
+
};
|
|
72
|
+
this.ws.onclose = (event) => {
|
|
73
|
+
this.isConnected = false;
|
|
74
|
+
this.emitConnection("disconnected");
|
|
75
|
+
if ([4001, 4003].includes(event.code) || !navigator.onLine)
|
|
76
|
+
return;
|
|
77
|
+
const timeout = Math.min(30000, 2000 * 2 ** this.reconnectAttempts);
|
|
78
|
+
this.reconnectAttempts++;
|
|
79
|
+
this.reconnectTimer = window.setTimeout(() => this.connect(recipient), timeout);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** Send notification */
|
|
83
|
+
async notify({ recipient, payload, client }) {
|
|
84
|
+
if (!recipient || !payload)
|
|
85
|
+
throw new Error("recipient and payload are required");
|
|
86
|
+
const body = {
|
|
87
|
+
recipient,
|
|
88
|
+
payload,
|
|
89
|
+
api_key: this.apiKey,
|
|
90
|
+
client: client || this.client,
|
|
91
|
+
};
|
|
92
|
+
const res = await fetch(this.apiEndpoint, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: {
|
|
95
|
+
"Content-Type": "application/json",
|
|
96
|
+
Origin: window.location.origin,
|
|
97
|
+
},
|
|
98
|
+
body: JSON.stringify(body),
|
|
99
|
+
});
|
|
100
|
+
if (!res.ok) {
|
|
101
|
+
const text = await res.text();
|
|
102
|
+
throw new Error(`Failed to send notification: ${res.status} ${res.statusText} ${text}`);
|
|
103
|
+
}
|
|
104
|
+
return res.json();
|
|
105
|
+
}
|
|
106
|
+
/** Disconnect WS */
|
|
107
|
+
disconnect() {
|
|
108
|
+
if (this.reconnectTimer)
|
|
109
|
+
clearTimeout(this.reconnectTimer);
|
|
110
|
+
this.ws?.close();
|
|
111
|
+
this.isConnected = false;
|
|
112
|
+
this.emitConnection("disconnected");
|
|
113
|
+
}
|
|
114
|
+
/** Return connection state */
|
|
115
|
+
connected() {
|
|
116
|
+
return this.isConnected;
|
|
117
|
+
}
|
|
118
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "arifa-client",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "JavaScript/TypeScript client SDK for Arifa Realtime Notification Service",
|
|
5
|
+
"main": "dist/ArifaClient.js",
|
|
6
|
+
"module": "dist/ArifaClient.js",
|
|
7
|
+
"types": "dist/ArifaClient.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"arifa",
|
|
17
|
+
"realtime",
|
|
18
|
+
"notifications",
|
|
19
|
+
"websocket",
|
|
20
|
+
"sdk"
|
|
21
|
+
],
|
|
22
|
+
"author": "Peter Nyando",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"typescript": "^5.9.3"
|
|
27
|
+
}
|
|
28
|
+
}
|