@hivedev/hivesdk 1.0.33 → 1.0.35
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 +739 -1
- package/hive-server.cjs +194 -44
- package/hive-server.js +192 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1 +1,739 @@
|
|
|
1
|
-
|
|
1
|
+
# @hivedev/hivesdk
|
|
2
|
+
|
|
3
|
+
Software development kit for microservices that integrate with the **Hive** system — a hub-and-spoke platform for educational institutions.
|
|
4
|
+
|
|
5
|
+
Every service in the Hive ecosystem (NoteHive, and any future service) is built around this SDK. It handles authentication, configuration management, service registration, database connectivity, push notifications, and client-side session management — so each service only has to implement its own domain logic.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Concepts](#concepts)
|
|
12
|
+
- [Installation](#installation)
|
|
13
|
+
- [Server SDK](#server-sdk)
|
|
14
|
+
- [1. Initialising the Server](#1-initialising-the-server)
|
|
15
|
+
- [2. Registering the Service](#2-registering-the-service)
|
|
16
|
+
- [3. Mounting the Middleware](#3-mounting-the-middleware)
|
|
17
|
+
- [4. Loading Configuration](#4-loading-configuration)
|
|
18
|
+
- [5. Connecting to the Database](#5-connecting-to-the-database)
|
|
19
|
+
- [6. Guarding Routes](#6-guarding-routes)
|
|
20
|
+
- [7. Sending Mail](#7-sending-mail)
|
|
21
|
+
- [8. Utility Functions](#8-utility-functions)
|
|
22
|
+
- [Full Server Bootstrap Example](#full-server-bootstrap-example)
|
|
23
|
+
- [Client SDK](#client-sdk)
|
|
24
|
+
- [1. Loading the Client Bundle](#1-loading-the-client-bundle)
|
|
25
|
+
- [2. Initialising the Client](#2-initialising-the-client)
|
|
26
|
+
- [3. Login](#3-login)
|
|
27
|
+
- [4. Checking Login State](#4-checking-login-state)
|
|
28
|
+
- [Full Client Bootstrap Example](#full-client-bootstrap-example)
|
|
29
|
+
- [Enumerations](#enumerations)
|
|
30
|
+
- [Environment Variables Reference](#environment-variables-reference)
|
|
31
|
+
- [How HivePortal Fits In](#how-hiveportal-fits-in)
|
|
32
|
+
- [Known Limitations](#known-limitations)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Concepts
|
|
37
|
+
|
|
38
|
+
**HivePortal** is the central hub. It owns all user accounts, sessions, permissions, and service registry. Every microservice is a spoke — it delegates all authentication and configuration to HivePortal via this SDK.
|
|
39
|
+
|
|
40
|
+
**Service registration** is a two-step process:
|
|
41
|
+
1. The service announces itself to HivePortal with its name and local/remote URLs.
|
|
42
|
+
2. HivePortal administrators approve it. Once approved, HivePortal sends back an encrypted configuration payload containing database credentials, Firebase credentials, permissions, and SMTP credentials.
|
|
43
|
+
|
|
44
|
+
**Configuration** is stored encrypted on disk (AES-256-CBC, key derived via scrypt from `SERVER_PASSWORD`). It is decrypted at startup and cached in `process.env`. This means credentials never sit on disk in plaintext.
|
|
45
|
+
|
|
46
|
+
**Authentication** is always proxied. A service never validates a session token itself — it forwards the check to HivePortal and acts on the response.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install @hivedev/hivesdk
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The SDK ships three pre-built files:
|
|
57
|
+
- `hive-server.js` — ESM bundle for Node.js backends
|
|
58
|
+
- `hive-server.cjs` — CommonJS bundle for Node.js backends
|
|
59
|
+
- `hive-client.js` — ESM bundle for browser frontends
|
|
60
|
+
|
|
61
|
+
Import paths:
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
// Server-side (ESM)
|
|
65
|
+
import { initServer, registerService } from "@hivedev/hivesdk/server";
|
|
66
|
+
|
|
67
|
+
// Client-side (browser, via a script tag or bundler)
|
|
68
|
+
import { initClient, login } from "@hivedev/hivesdk/client";
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Server SDK
|
|
74
|
+
|
|
75
|
+
### 1. Initialising the Server
|
|
76
|
+
|
|
77
|
+
Call `initServer` once at the very start of your service, before anything else. It sets the service's identity and resolves the server password.
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
import { initServer } from "@hivedev/hivesdk/server";
|
|
81
|
+
|
|
82
|
+
await initServer({
|
|
83
|
+
serviceName: "NoteHive", // Must match the name registered in HivePortal
|
|
84
|
+
servicePort: 49161, // The port this service listens on
|
|
85
|
+
remoteUrl: "", // Public-facing URL if accessible from outside LAN, empty string if LAN-only
|
|
86
|
+
serverPassword: "" // Optional — see password resolution below
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Password resolution order:**
|
|
91
|
+
|
|
92
|
+
1. The `serverPassword` argument, if provided.
|
|
93
|
+
2. The `SERVER_PASSWORD` environment variable, if set.
|
|
94
|
+
3. An interactive terminal prompt, if neither of the above is available — it will ask the user to create a password on first run, or enter their existing password on subsequent runs.
|
|
95
|
+
|
|
96
|
+
The password is used as the encryption key for all `.dat` configuration files. Losing it means losing access to the stored configuration.
|
|
97
|
+
|
|
98
|
+
> **Important:** `initServer` must only be called once. Calling it a second time is a no-op with a console warning. In a clustered setup, call it only from the primary process.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
### 2. Registering the Service
|
|
103
|
+
|
|
104
|
+
After `initServer`, call `registerService` to announce this service to HivePortal and receive configuration.
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
import { registerService } from "@hivedev/hivesdk/server";
|
|
108
|
+
|
|
109
|
+
await registerService();
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
What this does internally:
|
|
113
|
+
|
|
114
|
+
1. Sets the service's local URL (`http://<hostIp>:<servicePort>`) and remote URL in the internal registry.
|
|
115
|
+
2. POSTs to HivePortal's `/RegisterService` endpoint.
|
|
116
|
+
3. If HivePortal accepts the registration request, begins polling HivePortal's `/Configuration` endpoint every 2 seconds.
|
|
117
|
+
4. Once a Hive administrator approves the service in HivePortal, the configuration payload is received, decrypted, and saved to disk via `saveConfiguration`.
|
|
118
|
+
|
|
119
|
+
**On first run**, the terminal will show:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
Registering service...
|
|
123
|
+
Service registration hasn't been approved yet. Please contact administrators!
|
|
124
|
+
Service registration hasn't been approved yet. Please contact administrators!
|
|
125
|
+
...
|
|
126
|
+
Configuration received.
|
|
127
|
+
Saving configuration...
|
|
128
|
+
Service registered.
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Once registered and approved, subsequent startups will load configuration from disk directly via `loadConfiguration` and will not need to go through this polling process again, unless credentials change.
|
|
132
|
+
|
|
133
|
+
> In a clustered setup (e.g. Node.js `cluster` module), call `registerService` only from the primary process, after a short delay to allow workers to start up first.
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
import cluster from "cluster";
|
|
137
|
+
import os from "os";
|
|
138
|
+
|
|
139
|
+
if (cluster.isPrimary)
|
|
140
|
+
{
|
|
141
|
+
await initServer({ serviceName: "NoteHive", servicePort: 49161, remoteUrl: "" });
|
|
142
|
+
|
|
143
|
+
setTimeout(async () =>
|
|
144
|
+
{
|
|
145
|
+
await registerService();
|
|
146
|
+
}, 3000);
|
|
147
|
+
|
|
148
|
+
for (let i = 0; i < os.cpus().length; i++)
|
|
149
|
+
{
|
|
150
|
+
cluster.fork({ ...process.env });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else
|
|
154
|
+
{
|
|
155
|
+
// Start your HTTP server in workers
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
### 3. Mounting the Middleware
|
|
162
|
+
|
|
163
|
+
`handleHiveRequests` is an Express-compatible middleware that intercepts all Hive-related routes. Mount it before your own route handlers.
|
|
164
|
+
|
|
165
|
+
```js
|
|
166
|
+
import express from "express";
|
|
167
|
+
import { handleHiveRequests } from "@hivedev/hivesdk/server";
|
|
168
|
+
|
|
169
|
+
const app = express();
|
|
170
|
+
|
|
171
|
+
app.use(express.json());
|
|
172
|
+
app.use(cookieParser());
|
|
173
|
+
app.use(handleHiveRequests); // Must come before your own routes
|
|
174
|
+
|
|
175
|
+
// Your own routes go here
|
|
176
|
+
app.get("/my-route", (request, response) => { ... });
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Routes handled automatically by this middleware:**
|
|
180
|
+
|
|
181
|
+
| Method | Route | What it does |
|
|
182
|
+
|--------|-------|--------------|
|
|
183
|
+
| `POST` | `/Login` | Proxies login credentials to HivePortal, sets session cookie and/or `x-session-token` header |
|
|
184
|
+
| `POST` | `/Logout` | Proxies logout to HivePortal, clears session |
|
|
185
|
+
| `GET` | `/ServiceUrls` | Returns local or remote URL for a named service |
|
|
186
|
+
| `POST` | `/IsLoggedIn` | Checks if the current session is valid with HivePortal |
|
|
187
|
+
| `POST` | `/IsLoggedInWithPermission` | Checks session validity and a specific permission |
|
|
188
|
+
| `POST` | `/SaveUserFilter` | Saves a user-defined data filter (requires `FILTER_OPERATIONS` permission) |
|
|
189
|
+
| `POST` | `/PushNotificationToken` | Registers a push notification token for the current device |
|
|
190
|
+
| `GET` | `/hive-client.js` | Serves the built client-side SDK bundle |
|
|
191
|
+
|
|
192
|
+
You do not need to implement any of these routes yourself. All of them are handled and proxied to HivePortal automatically.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
### 4. Loading Configuration
|
|
197
|
+
|
|
198
|
+
On startup (after `initServer`), call `loadConfiguration` to decrypt and load credentials from disk into `process.env`.
|
|
199
|
+
|
|
200
|
+
```js
|
|
201
|
+
import { loadConfiguration } from "@hivedev/hivesdk/server";
|
|
202
|
+
|
|
203
|
+
await loadConfiguration();
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
This populates the following environment variables (only those with saved `.dat` files are loaded):
|
|
207
|
+
|
|
208
|
+
| Variable | Contents |
|
|
209
|
+
|----------|----------|
|
|
210
|
+
| `FIREBASE_ADMIN_CREDENTIALS` | JSON string of Firebase Admin SDK credentials |
|
|
211
|
+
| `DATABASE_CREDENTIALS` | JSON string of database credentials, keyed by service name |
|
|
212
|
+
| `PERMISSIONS` | JSON string of permission definitions |
|
|
213
|
+
| `SMTP_CREDENTIALS` | JSON string of SMTP credentials |
|
|
214
|
+
| `SMTP_PASSWORD` | Extracted SMTP password, ready for use in `nodemailer` |
|
|
215
|
+
|
|
216
|
+
It also calls `DatabaseConnector.setCredentials` internally using the credentials stored under your service's name, so the database is ready to connect after this call.
|
|
217
|
+
|
|
218
|
+
If a `.dat` file does not exist yet (e.g. first run before registration), that credential is simply skipped.
|
|
219
|
+
|
|
220
|
+
> You should call `loadConfiguration` before `registerService`. If no configuration files exist yet, it will silently skip them. Once `registerService` completes, the files will be written to disk, and `loadConfiguration` will populate everything on the next startup.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### 5. Connecting to the Database
|
|
225
|
+
|
|
226
|
+
The SDK includes a MongoDB connector. After `loadConfiguration`, call `DatabaseConnector.connect()` to establish the connection.
|
|
227
|
+
|
|
228
|
+
```js
|
|
229
|
+
import DatabaseConnector from "@hivedev/hivesdk/server";
|
|
230
|
+
|
|
231
|
+
const connected = await DatabaseConnector.connect();
|
|
232
|
+
|
|
233
|
+
if (!connected)
|
|
234
|
+
{
|
|
235
|
+
console.error("Failed to connect to database.");
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// DatabaseConnector.databaseObject is now a live Mongo db instance
|
|
240
|
+
const collection = DatabaseConnector.databaseObject.collection("my_collection");
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`DatabaseConnector` is not exported from `server.js` directly — you access it from its own path within the SDK. If you do not call `loadConfiguration` first, `DatabaseConnector.connect()` will attempt to call it automatically.
|
|
244
|
+
|
|
245
|
+
**Disconnecting:**
|
|
246
|
+
|
|
247
|
+
```js
|
|
248
|
+
await DatabaseConnector.disconnect();
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
### 6. Guarding Routes
|
|
254
|
+
|
|
255
|
+
Use `isLoggedIn` and `isLoggedInWithPermission` to protect your own route handlers. Both functions accept the request and response objects and a boolean `bSendResponse` flag.
|
|
256
|
+
|
|
257
|
+
When `bSendResponse` is `false`, the functions return a boolean and leave the response untouched — use this when you need to guard logic inside a handler.
|
|
258
|
+
|
|
259
|
+
When `bSendResponse` is `true`, the functions write the result directly to the response — this is used internally by `handleHiveRequests` for the `/IsLoggedIn` and `/IsLoggedInWithPermission` endpoints.
|
|
260
|
+
|
|
261
|
+
**Guarding a route with login check:**
|
|
262
|
+
|
|
263
|
+
```js
|
|
264
|
+
import { isLoggedIn } from "@hivedev/hivesdk/server";
|
|
265
|
+
|
|
266
|
+
app.get("/my-protected-route", async (request, response) =>
|
|
267
|
+
{
|
|
268
|
+
const loggedIn = await isLoggedIn(request, response, false);
|
|
269
|
+
|
|
270
|
+
if (!loggedIn)
|
|
271
|
+
{
|
|
272
|
+
response.statusCode = 401;
|
|
273
|
+
response.end("Unauthorized");
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
response.end("Here is your protected data.");
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Guarding a route with a permission check:**
|
|
282
|
+
|
|
283
|
+
The permission name must be set on `request.body.permissionName` before calling `isLoggedInWithPermission`.
|
|
284
|
+
|
|
285
|
+
```js
|
|
286
|
+
import { isLoggedInWithPermission } from "@hivedev/hivesdk/server";
|
|
287
|
+
|
|
288
|
+
app.post("/admin-action", async (request, response) =>
|
|
289
|
+
{
|
|
290
|
+
request.body.permissionName = "ADMIN_OPERATIONS";
|
|
291
|
+
|
|
292
|
+
const permitted = await isLoggedInWithPermission(request, response, false);
|
|
293
|
+
|
|
294
|
+
if (!permitted)
|
|
295
|
+
{
|
|
296
|
+
response.statusCode = 403;
|
|
297
|
+
response.end("Forbidden");
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
response.end("Admin action performed.");
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Both functions handle **session refresh transparently** — if HivePortal signals that the session token should be refreshed, a new cookie and/or `x-session-token` header is set automatically before your handler continues.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
### 7. Sending Mail
|
|
310
|
+
|
|
311
|
+
```js
|
|
312
|
+
import { sendMail } from "@hivedev/hivesdk/server";
|
|
313
|
+
|
|
314
|
+
await sendMail(
|
|
315
|
+
"sender@yourdomain.com",
|
|
316
|
+
"recipient@example.com",
|
|
317
|
+
"Subject line here",
|
|
318
|
+
"<p>HTML body here</p>"
|
|
319
|
+
);
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Uses Gmail SMTP via nodemailer. The SMTP password is read from `process.env.SMTP_PASSWORD`, which is populated automatically by `loadConfiguration`. The sender Gmail address is currently hardcoded in `SendMail.js` — change it there if needed.
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
### 8. Utility Functions
|
|
327
|
+
|
|
328
|
+
All utilities are exported from `@hivedev/hivesdk/server`.
|
|
329
|
+
|
|
330
|
+
**`getAppDataDirectory()`**
|
|
331
|
+
|
|
332
|
+
Returns the platform-appropriate data directory for your service, creating it if it doesn't exist. On Windows this is `%APPDATA%\<ServiceName>`, on macOS `~/Library/Application Support/<ServiceName>`, on Linux `~/.config/<ServiceName>`.
|
|
333
|
+
|
|
334
|
+
```js
|
|
335
|
+
import { getAppDataDirectory } from "@hivedev/hivesdk/server";
|
|
336
|
+
|
|
337
|
+
const dataDir = getAppDataDirectory();
|
|
338
|
+
// e.g. "/home/user/.config/NoteHive"
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**`getHostIp()`**
|
|
342
|
+
|
|
343
|
+
Returns the first non-loopback IPv4 address of the host machine. Used internally by `registerService` to build the local service URL.
|
|
344
|
+
|
|
345
|
+
```js
|
|
346
|
+
import { getHostIp } from "@hivedev/hivesdk/server";
|
|
347
|
+
|
|
348
|
+
const ip = getHostIp(); // e.g. "192.168.1.42"
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**`findService(serviceName)`**
|
|
352
|
+
|
|
353
|
+
Discovers another Hive service on the LAN via UDP broadcast to port 49153. Returns an object with `{ name, urls: { local, remote } }` or `null` if not found.
|
|
354
|
+
|
|
355
|
+
```js
|
|
356
|
+
import { findService } from "@hivedev/hivesdk/server";
|
|
357
|
+
|
|
358
|
+
const service = await findService("NoteHive");
|
|
359
|
+
|
|
360
|
+
if (service)
|
|
361
|
+
{
|
|
362
|
+
console.log(service.urls.local); // "http://192.168.1.42:49161"
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Requires `HIVE_PORTAL_LAN_IP` environment variable to be set. If not set, returns a default localhost URL.
|
|
367
|
+
|
|
368
|
+
**`extractConfigurationForService()`**
|
|
369
|
+
|
|
370
|
+
Parses `process.env` and returns the configuration as structured objects, extracting the correct database credentials for the current service name.
|
|
371
|
+
|
|
372
|
+
```js
|
|
373
|
+
import { extractConfigurationForService } from "@hivedev/hivesdk/server";
|
|
374
|
+
|
|
375
|
+
const { firebaseAdminCredentials, databaseCredentials, permissions, smtpCredentials } = extractConfigurationForService();
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**`encryptAndStoreFile(data, fullPath, password)`**
|
|
379
|
+
|
|
380
|
+
Encrypts a string or Buffer with AES-256-CBC and writes it to disk.
|
|
381
|
+
|
|
382
|
+
```js
|
|
383
|
+
import { encryptAndStoreFile } from "@hivedev/hivesdk/server";
|
|
384
|
+
|
|
385
|
+
await encryptAndStoreFile("my secret data", "/path/to/file.dat", process.env.SERVER_PASSWORD);
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**`decryptFileWithPassword(fullPath, password)`**
|
|
389
|
+
|
|
390
|
+
Reads and decrypts an encrypted `.dat` file. Returns the decrypted string, or `null` on failure.
|
|
391
|
+
|
|
392
|
+
```js
|
|
393
|
+
import { decryptFileWithPassword } from "@hivedev/hivesdk/server";
|
|
394
|
+
|
|
395
|
+
const contents = await decryptFileWithPassword("/path/to/file.dat", process.env.SERVER_PASSWORD);
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
**`saveConfiguration(configurationObject)` / `loadConfiguration()`**
|
|
399
|
+
|
|
400
|
+
Normally called automatically by `registerService` and during startup respectively. Available for manual use if needed.
|
|
401
|
+
|
|
402
|
+
```js
|
|
403
|
+
import { saveConfiguration, loadConfiguration } from "@hivedev/hivesdk/server";
|
|
404
|
+
|
|
405
|
+
// Save a configuration object (any subset of keys is valid — only present keys are written)
|
|
406
|
+
await saveConfiguration({
|
|
407
|
+
databaseCredentials: { NoteHive: { host: "127.0.0.1", username: "admin", password: "pass", name: "notehive" } },
|
|
408
|
+
smtpCredentials: { password: "smtp-password" }
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// Load configuration from disk into process.env
|
|
412
|
+
await loadConfiguration();
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
### Full Server Bootstrap Example
|
|
418
|
+
|
|
419
|
+
This is the recommended startup sequence for any Hive microservice.
|
|
420
|
+
|
|
421
|
+
```js
|
|
422
|
+
import express from "express";
|
|
423
|
+
import cookieParser from "cookie-parser";
|
|
424
|
+
import cluster from "cluster";
|
|
425
|
+
import os from "os";
|
|
426
|
+
import { handleHiveRequests, initServer, registerService, loadConfiguration } from "@hivedev/hivesdk/server";
|
|
427
|
+
import DatabaseConnector from "./node_modules/@hivedev/hivesdk/DatabaseConnector.js";
|
|
428
|
+
|
|
429
|
+
const SERVICE_NAME = "NoteHive";
|
|
430
|
+
const SERVICE_PORT = 49161;
|
|
431
|
+
|
|
432
|
+
const app = express();
|
|
433
|
+
|
|
434
|
+
app.use(express.json());
|
|
435
|
+
app.use(cookieParser());
|
|
436
|
+
app.use(handleHiveRequests);
|
|
437
|
+
|
|
438
|
+
// Your own routes
|
|
439
|
+
app.get("/", (request, response) => response.redirect("/Client/Pages/HomePage.html"));
|
|
440
|
+
|
|
441
|
+
if (cluster.isPrimary)
|
|
442
|
+
{
|
|
443
|
+
await initServer({
|
|
444
|
+
serviceName: SERVICE_NAME,
|
|
445
|
+
servicePort: SERVICE_PORT,
|
|
446
|
+
remoteUrl: process.env.REMOTE_URL || ""
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
await loadConfiguration();
|
|
450
|
+
|
|
451
|
+
// Delay slightly so workers are ready before registration begins
|
|
452
|
+
setTimeout(async () =>
|
|
453
|
+
{
|
|
454
|
+
await registerService();
|
|
455
|
+
}, 3000);
|
|
456
|
+
|
|
457
|
+
for (let i = 0; i < os.cpus().length; i++)
|
|
458
|
+
{
|
|
459
|
+
cluster.fork({ ...process.env });
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
cluster.on("exit", (worker) =>
|
|
463
|
+
{
|
|
464
|
+
console.log(`Worker ${worker.process.pid} died. Restarting...`);
|
|
465
|
+
cluster.fork();
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
else
|
|
469
|
+
{
|
|
470
|
+
await DatabaseConnector.connect();
|
|
471
|
+
|
|
472
|
+
app.listen(SERVICE_PORT, () =>
|
|
473
|
+
{
|
|
474
|
+
console.log(`Worker ${process.pid} listening on port ${SERVICE_PORT}`);
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## Client SDK
|
|
482
|
+
|
|
483
|
+
The client SDK is a browser bundle. It is automatically served at `/hive-client.js` by the `handleHiveRequests` middleware — you do not need to copy or host the file yourself.
|
|
484
|
+
|
|
485
|
+
### 1. Loading the Client Bundle
|
|
486
|
+
|
|
487
|
+
Include the script in your HTML pages:
|
|
488
|
+
|
|
489
|
+
```html
|
|
490
|
+
<script type="module">
|
|
491
|
+
import { initClient, login } from "/hive-client.js";
|
|
492
|
+
</script>
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
Or via a module script tag:
|
|
496
|
+
|
|
497
|
+
```html
|
|
498
|
+
<script type="module" src="/hive-client.js"></script>
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
### 2. Initialising the Client
|
|
504
|
+
|
|
505
|
+
Call `initClient` once on page load. It sets up app detection, cookie signalling, and the periodic login state checker.
|
|
506
|
+
|
|
507
|
+
```js
|
|
508
|
+
import { initClient } from "/hive-client.js";
|
|
509
|
+
|
|
510
|
+
await initClient({});
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
`initClient` accepts one optional parameter:
|
|
514
|
+
|
|
515
|
+
| Parameter | Type | Default | Description |
|
|
516
|
+
|-----------|------|---------|-------------|
|
|
517
|
+
| `loginTokenStorageMethod` | `sessionStorageMethod` enum | `HTTP_ONLY_COOKIE` | How session tokens are stored on the client |
|
|
518
|
+
|
|
519
|
+
In practice, the SDK currently implements the `HTTP_ONLY_COOKIE` path fully. The token is stored in an `httpOnly` cookie set by the server. On native app clients (WebView), it is also sent via the `x-session-token` header since cookies may not be reliable across WebView boundaries.
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
### 3. Login
|
|
524
|
+
|
|
525
|
+
Call `login()` to open the HivePortal login popup. The SDK handles the entire flow — opening the window, receiving credentials via `postMessage`, posting to `/Login`, and redirecting on success.
|
|
526
|
+
|
|
527
|
+
```js
|
|
528
|
+
import { login } from "/hive-client.js";
|
|
529
|
+
|
|
530
|
+
document.getElementById("login-button").addEventListener("click", async () =>
|
|
531
|
+
{
|
|
532
|
+
await login();
|
|
533
|
+
});
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
When running inside the native mobile app (detected automatically via `isInApp()`), the login popup is rendered as an inline full-screen iframe overlay instead of a real popup window, since WebViews block `window.open`.
|
|
537
|
+
|
|
538
|
+
On success, the page redirects to `/`. On failure, the login page itself displays the error.
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
### 4. Checking Login State
|
|
543
|
+
|
|
544
|
+
`initClient` automatically starts a periodic login check (every 30 seconds by default, clamped between 10 and 60 seconds). Each check fires a `login-state-changed` custom event on the `window` object.
|
|
545
|
+
|
|
546
|
+
Listen for this event to reactively show or hide UI based on login state:
|
|
547
|
+
|
|
548
|
+
```js
|
|
549
|
+
window.addEventListener("login-state-changed", (event) =>
|
|
550
|
+
{
|
|
551
|
+
const isLoggedIn = event.detail;
|
|
552
|
+
|
|
553
|
+
if (isLoggedIn)
|
|
554
|
+
{
|
|
555
|
+
document.getElementById("dashboard").style.display = "block";
|
|
556
|
+
document.getElementById("login-prompt").style.display = "none";
|
|
557
|
+
}
|
|
558
|
+
else
|
|
559
|
+
{
|
|
560
|
+
document.getElementById("dashboard").style.display = "none";
|
|
561
|
+
document.getElementById("login-prompt").style.display = "block";
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
The current login state is also available synchronously at any time via `window.IS_LOGGED_IN`.
|
|
567
|
+
|
|
568
|
+
```js
|
|
569
|
+
if (window.IS_LOGGED_IN)
|
|
570
|
+
{
|
|
571
|
+
// Safe to load user-specific content
|
|
572
|
+
}
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
### Full Client Bootstrap Example
|
|
578
|
+
|
|
579
|
+
A typical HTML page in a Hive service would look like this:
|
|
580
|
+
|
|
581
|
+
```html
|
|
582
|
+
<!DOCTYPE html>
|
|
583
|
+
<html lang="en">
|
|
584
|
+
<head>
|
|
585
|
+
<meta charset="UTF-8">
|
|
586
|
+
<title>NoteHive</title>
|
|
587
|
+
</head>
|
|
588
|
+
<body>
|
|
589
|
+
|
|
590
|
+
<div id="login-prompt">
|
|
591
|
+
<button id="login-button">Sign In</button>
|
|
592
|
+
</div>
|
|
593
|
+
|
|
594
|
+
<div id="dashboard" style="display: none;">
|
|
595
|
+
<h1>Welcome to NoteHive</h1>
|
|
596
|
+
</div>
|
|
597
|
+
|
|
598
|
+
<script type="module">
|
|
599
|
+
import { initClient, login } from "/hive-client.js";
|
|
600
|
+
|
|
601
|
+
// Initialise the SDK — sets up app detection, cookies, and login polling
|
|
602
|
+
await initClient({});
|
|
603
|
+
|
|
604
|
+
// React to login state changes
|
|
605
|
+
window.addEventListener("login-state-changed", (event) =>
|
|
606
|
+
{
|
|
607
|
+
const isLoggedIn = event.detail;
|
|
608
|
+
document.getElementById("login-prompt").style.display = isLoggedIn ? "none" : "block";
|
|
609
|
+
document.getElementById("dashboard").style.display = isLoggedIn ? "block" : "none";
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// Wire up the login button
|
|
613
|
+
document.getElementById("login-button").addEventListener("click", async () =>
|
|
614
|
+
{
|
|
615
|
+
await login();
|
|
616
|
+
});
|
|
617
|
+
</script>
|
|
618
|
+
|
|
619
|
+
</body>
|
|
620
|
+
</html>
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
## Enumerations
|
|
626
|
+
|
|
627
|
+
The SDK exports several enumerations from the client bundle. These are shared constants used throughout the Hive ecosystem.
|
|
628
|
+
|
|
629
|
+
**`userPrivileges`** — Numeric privilege levels assigned to users.
|
|
630
|
+
|
|
631
|
+
| Key | Value |
|
|
632
|
+
|-----|-------|
|
|
633
|
+
| `STUDENT` | 0 |
|
|
634
|
+
| `CLASS_REPRESENTATIVE` | 10 |
|
|
635
|
+
| `STUDENT_COORDINATOR` | 20 |
|
|
636
|
+
| `ASSISTANT` | 30 |
|
|
637
|
+
| `TEACHER` | 40 |
|
|
638
|
+
| `COURSE_COORDINATOR` | 50 |
|
|
639
|
+
| `HEAD_OF_DEPARTMENT` | 70 |
|
|
640
|
+
| `MANAGEMENT` | 80 |
|
|
641
|
+
| `SUPER_USER` | 100 |
|
|
642
|
+
|
|
643
|
+
**`sessionStorageMethod`** — How session tokens are stored on the client.
|
|
644
|
+
|
|
645
|
+
| Key | Value |
|
|
646
|
+
|-----|-------|
|
|
647
|
+
| `HTTP_ONLY_COOKIE` | 0 |
|
|
648
|
+
| `LOCAL_STORAGE` | 1 |
|
|
649
|
+
| `SESSION_STORAGE` | 2 |
|
|
650
|
+
|
|
651
|
+
**`clientConnectionTypeFlags`** — Bitmask flags describing how a client is connected.
|
|
652
|
+
|
|
653
|
+
| Key | Value |
|
|
654
|
+
|-----|-------|
|
|
655
|
+
| `LAN` | 1 |
|
|
656
|
+
| `REMOTE` | 2 |
|
|
657
|
+
| `SAME_SYSTEM` | 4 |
|
|
658
|
+
| `UNKNOWN` | 8 |
|
|
659
|
+
|
|
660
|
+
**`appWebViewInteractions`** — Message types exchanged between the web page and the native app WebView layer.
|
|
661
|
+
|
|
662
|
+
| Key | Value |
|
|
663
|
+
|-----|-------|
|
|
664
|
+
| `REQUEST_NOTIFICATION_TOKEN` | 0 |
|
|
665
|
+
| `BACK_PRESSED` | 1 |
|
|
666
|
+
|
|
667
|
+
**`approvalTypes`** — Types of approval requests sent to HivePortal administrators.
|
|
668
|
+
|
|
669
|
+
| Key | Value |
|
|
670
|
+
|-----|-------|
|
|
671
|
+
| `USER_REGISTERATION` | 0 |
|
|
672
|
+
| `SERVICE_REGISTERATION` | 1 |
|
|
673
|
+
|
|
674
|
+
**`filterTypes`**, **`filterOperators`**, **`userDataFetchFilterComparision`**, **`userFieldTypes`**, **`userFieldFormats`**, **`fileTreeNodeTypes`** — Domain-specific enumerations used for data querying, user field definitions, and file tree structures. Refer to their respective source files for values.
|
|
675
|
+
|
|
676
|
+
---
|
|
677
|
+
|
|
678
|
+
## Environment Variables Reference
|
|
679
|
+
|
|
680
|
+
These variables are either expected to be set externally or are populated by the SDK itself.
|
|
681
|
+
|
|
682
|
+
| Variable | Set by | Description |
|
|
683
|
+
|----------|--------|-------------|
|
|
684
|
+
| `SERVER_PASSWORD` | External / `setupPassword()` | Master password for encrypting/decrypting config files |
|
|
685
|
+
| `SERVICE_NAME` | `initServer` | Name of this service as registered with HivePortal |
|
|
686
|
+
| `SERVICE_PORT` | `initServer` | Port this service listens on |
|
|
687
|
+
| `REMOTE_URL` | `initServer` | Public-facing URL of this service (empty if LAN-only) |
|
|
688
|
+
| `HIVE_PORTAL_LAN_IP` | External | LAN IP address of HivePortal, used by `findService` for UDP discovery |
|
|
689
|
+
| `FIREBASE_ADMIN_CREDENTIALS` | `loadConfiguration` | JSON string of Firebase Admin credentials |
|
|
690
|
+
| `DATABASE_CREDENTIALS` | `loadConfiguration` | JSON string of database credentials keyed by service name |
|
|
691
|
+
| `PERMISSIONS` | `loadConfiguration` | JSON string of permission definitions |
|
|
692
|
+
| `SMTP_CREDENTIALS` | `loadConfiguration` | JSON string of SMTP credentials |
|
|
693
|
+
| `SMTP_PASSWORD` | `loadConfiguration` | Extracted SMTP password, ready for nodemailer |
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
## How HivePortal Fits In
|
|
698
|
+
|
|
699
|
+
```
|
|
700
|
+
┌─────────────────────┐
|
|
701
|
+
│ HivePortal │
|
|
702
|
+
│ (hub — port 49152) │
|
|
703
|
+
│ │
|
|
704
|
+
│ - User accounts │
|
|
705
|
+
│ - Sessions │
|
|
706
|
+
│ - Permissions │
|
|
707
|
+
│ - Service registry │
|
|
708
|
+
│ - Configuration │
|
|
709
|
+
└────────┬─────────────┘
|
|
710
|
+
│
|
|
711
|
+
┌────────────────────┼────────────────────┐
|
|
712
|
+
│ │ │
|
|
713
|
+
┌──────────▼──────────┐ │ ┌──────────▼──────────┐
|
|
714
|
+
│ NoteHive │ │ │ FutureService │
|
|
715
|
+
│ (port 49161) │ ... │ │ (port 49162) │
|
|
716
|
+
│ │ │ │ │
|
|
717
|
+
│ uses @hivedev/ │ │ │ uses @hivedev/ │
|
|
718
|
+
│ hivesdk │ │ │ hivesdk │
|
|
719
|
+
└──────────────────────┘ │ └─────────────────────┘
|
|
720
|
+
│
|
|
721
|
+
Each service:
|
|
722
|
+
1. Calls initServer()
|
|
723
|
+
2. Calls loadConfiguration()
|
|
724
|
+
3. Calls registerService()
|
|
725
|
+
4. Mounts handleHiveRequests
|
|
726
|
+
5. Implements own domain logic
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
Every authentication check a service performs is a proxied call to HivePortal. HivePortal is the single source of truth for all session and permission data. Services only store their own domain data (notes, files, etc.) — nothing related to users or sessions.
|
|
730
|
+
|
|
731
|
+
---
|
|
732
|
+
|
|
733
|
+
## Known Limitations
|
|
734
|
+
|
|
735
|
+
- **`ScheduleServiceUrlUpdate`** — This function is currently a stub and does nothing. It is intended for future use to periodically re-broadcast service URLs to HivePortal (e.g. if the host IP changes).
|
|
736
|
+
- **`loginTokenStorageMethod`** — The `LOCAL_STORAGE` and `SESSION_STORAGE` options in the `sessionStorageMethod` enum are defined but not yet implemented in the client SDK. Only `HTTP_ONLY_COOKIE` (with the `x-session-token` header fallback for app clients) is fully functional.
|
|
737
|
+
- **SMTP sender address** — The sender Gmail address in `SendMail.js` is hardcoded. If your service needs to send mail from a different address, edit `SendMail.js` in the SDK directly.
|
|
738
|
+
- **MongoDB only** — `DatabaseConnector` is built specifically for MongoDB. There is no support for other databases.
|
|
739
|
+
- **Port 27017 only** — The MongoDB port is hardcoded to 27017 in `DatabaseConnector`. Host is configurable via credentials but the port is not.
|