PageSourceSearch

https://kagoafrica.com/js/NotificationService.js?version=3.1.0.4

js kagoafrica.com collected 2026-10-02 00:52:14 UTC 5,548 bytes, 205 lines download raw bytes

1class NotificationService {
2  projectId;
3  projectKey;
4  userId;
5  channelId;
6  socket;
7  baseUrl = "https://notification-service.softfusion.dev";
8  baseSocketUrl = "https://notification-service.softfusion.dev";
9  allowedConfigVars = ["projectId", "projectKey", "userId", "channelId", "userAuth"];
10  connected = false;
11  queuedActions = [];
12  subscribedChannels = [];
13  resubscribe = false;
14  userAuth;
15
16  constructor(configObj) {
17    Object.keys(configObj).forEach(key => {
18      if (this.allowedConfigVars.includes(key)) {
19        this[key] = configObj[key];
20      } else {
21        console.warn(`${key} is not a valid config var`);
22      }
23    });
24
25    if (!("channelId" in configObj)) {
26      this.channelId = configObj.userId;
27    }
28  }
29
30  async connect() {
31    // Fetch project settings
32    const projectSettingsRes = await fetch(`${this.baseUrl}/project/settings/${this.projectId}`);
33    const projectSettings = await projectSettingsRes.json();
34
35    if (!projectSettings) {
36      throw new Error("Failed to fetch project meta");
37    }
38
39    // Run developer's auth endpoint
40    const runtimeParams = this.userAuth?.params || {};
41    const userAuthParamString = new URLSearchParams({
42      ...projectSettings.userAuthConfig.params,
43      ...runtimeParams,
44      userId: this.userId
45    }).toString();
46
47    const userAuthRes = await fetch(`${projectSettings.userAuthConfig.url}${this.userAuth.endpoint}/?${userAuthParamString}`, {
48      credentials: this.userAuth.credentials,
49      headers: this.userAuth.headers ? this.userAuth.headers : {},
50    });
51    const userAuth = await userAuthRes.json();
52
53    if (userAuth.status !== "success") {
54      throw new Error("User verification failed. userId has failed verification");
55    }
56
57    // Init socket
58    this.socket = io(this.baseSocketUrl, {
59      auth: {
60        token: this.projectKey,
61        userAuthToken: userAuth.token
62      },
63      query: {
64        userId: this.userId,
65        channelId: this.channelId,
66        // channelId: `${this.projectId}-${this.channelId}`,
67      }
68    });
69
70    this.socket.on("connect", () => {
71      this.connected = true;
72
73      // If reconnecting, requeue subscriptions
74      if (this.resubscribe && this.subscribedChannels.length > 0) {
75        this.queueAction("subscribe", this.subscribedChannels);
76      }
77
78      this.replayQueuedActions();
79    });
80
81    this.socket.on("connect_error", (err) => {
82      console.error("Connection failed:", err);
83      this.resubscribe = true;
84    });
85
86    this.socket.on("error", (err) => {
87      console.error("Error:", err);
88      this.resubscribe = true;
89    });
90
91    this.socket.on("subscribe_error", (err) => {
92      console.log("Error: ", err);
93    })
94
95    // Promise interface
96    return new Promise((resolve, reject) => {
97      this.socket.once("connect", resolve);
98      this.socket.once("connect_error", reject);
99    });
100  }
101
102  // ---------- Queue System ----------
103  queueAction(type, payload) {
104    const exists = this.queuedActions.find(
105      a => a.type === type && JSON.stringify(a.payload) === JSON.stringify(payload)
106    );
107    if (!exists) {
108      this.queuedActions.push({ type, payload });
109    }
110  }
111
112  replayQueuedActions() {
113    this.queuedActions.forEach(action => {
114      switch (action.type) {
115        case "subscribe":
116          this._subscribeNow(action.payload);
117          break;
118        case "sendDirect":
119          this._sendDirectNow(action.payload);
120          break;
121        case "sendGroup":
122          this._sendGroupNow(action.payload);
123          break;
124        case "addListener":
125          this._addListenerNow(action.payload);
126          break;
127        default:
128          console.warn("Unknown action type:", action.type);
129      }
130    });
131
132    this.queuedActions = [];
133  }
134
135  // ---------- Public API ----------
136  subscribe(channels) {
137    if (!Array.isArray(channels)) channels = [channels];
138
139    if (!this.connected) {
140      this.queueAction("subscribe", channels);
141    } else {
142      this._subscribeNow(channels);
143    }
144
145    // Track subscriptions
146    this.subscribedChannels.push(...channels);
147    this.subscribedChannels = [...new Set(this.subscribedChannels)];
148  }
149
150  addNotificationListener(listenerCallback) {
151    const payload = { callback: listenerCallback };
152
153    if (!this.connected) {
154      this.queueAction("addListener", payload);
155    } else {
156      this._addListenerNow(payload);
157    }
158  }
159
160  sendDirectNotification(channel, message) {
161    const payload = { channel, message };
162
163    if (!this.connected) {
164      this.queueAction("sendDirect", payload);
165    } else {
166      this._sendDirectNow(payload);
167    }
168  }
169
170  sendGroupNotification(message) {
171    const payload = { message };
172
173    if (!this.connected) {
174      this.queueAction("sendGroup", payload);
175    } else {
176      this._sendGroupNow(payload);
177    }
178  }
179
180  fetchNotifications(channelId) {
181    return fetch(`${this.baseUrl}/notifications/${this.projectId}?channelId=${channelId}`);
182  }
183
184  // ---------- Private Immediate Methods ----------
185  _subscribeNow(channels) {
186    channels.forEach(channel => {
187      this.socket.emit("join-channel", { channelId: channel });
188    });
189  }
190
191  _addListenerNow(payload) {
192    this.socket.on("receive-message", (data) => payload.callback(data));
193  }
194
195  _sendDirectNow(payload) {
196    this.socket.emit("send-direct-message", {
197      message: payload.message,
198      recepientChannelId: payload.channel
199    });
200  }
201
202  _sendGroupNow(payload) {
203    this.socket.emit("send-group-message", { message: payload.message, channelId: this.channelId, senderUserId: this.userId });
204  }
205}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.