@hivedev/hivesdk 1.0.34 → 1.0.36
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 +987 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1 +1,987 @@
|
|
|
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 the 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. A HivePortal administrator approves 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`. 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` — CJS bundle for Node.js backends
|
|
59
|
+
- `hive-client.js` — ESM bundle for browser frontends
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Server SDK
|
|
64
|
+
|
|
65
|
+
### 1. Initialising the Server
|
|
66
|
+
|
|
67
|
+
Call `initServer` once at the very start of your service, before anything else. It sets the service's identity and resolves the server password.
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
// ESM
|
|
71
|
+
import { initServer } from "@hivedev/hivesdk/server";
|
|
72
|
+
|
|
73
|
+
await initServer({
|
|
74
|
+
serviceName: "NoteHive", // Must match the name registered in HivePortal
|
|
75
|
+
servicePort: 49161, // The port this service listens on
|
|
76
|
+
remoteUrl: "", // Public-facing URL if accessible from outside LAN, empty string if LAN-only
|
|
77
|
+
serverPassword: "" // Optional — see password resolution below
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
// CJS
|
|
83
|
+
const { initServer } = require("@hivedev/hivesdk/server");
|
|
84
|
+
|
|
85
|
+
await initServer({
|
|
86
|
+
serviceName: "NoteHive",
|
|
87
|
+
servicePort: 49161,
|
|
88
|
+
remoteUrl: "",
|
|
89
|
+
serverPassword: ""
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Password resolution order:**
|
|
94
|
+
|
|
95
|
+
1. The `serverPassword` argument, if provided.
|
|
96
|
+
2. The `SERVER_PASSWORD` environment variable, if set.
|
|
97
|
+
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.
|
|
98
|
+
|
|
99
|
+
The password is used as the encryption key for all `.dat` configuration files. Losing it means losing access to the stored configuration.
|
|
100
|
+
|
|
101
|
+
> **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.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### 2. Registering the Service
|
|
106
|
+
|
|
107
|
+
After `initServer`, call `registerService` to announce this service to HivePortal and receive configuration.
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
// ESM
|
|
111
|
+
import { registerService } from "@hivedev/hivesdk/server";
|
|
112
|
+
|
|
113
|
+
await registerService();
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
// CJS
|
|
118
|
+
const { registerService } = require("@hivedev/hivesdk/server");
|
|
119
|
+
|
|
120
|
+
await registerService();
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
What this does internally:
|
|
124
|
+
|
|
125
|
+
1. Sets the service's local URL (`http://<hostIp>:<servicePort>`) and remote URL in the internal registry.
|
|
126
|
+
2. POSTs to HivePortal's `/RegisterService` endpoint.
|
|
127
|
+
3. If HivePortal accepts the request, begins polling HivePortal's `/Configuration` endpoint every 2 seconds.
|
|
128
|
+
4. Once an administrator approves the service in HivePortal, the configuration payload is received, decrypted, and saved to disk via `saveConfiguration`.
|
|
129
|
+
|
|
130
|
+
**On first run**, the terminal will show:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
Registering service...
|
|
134
|
+
Service registration hasn't been approved yet. Please contact administrators!
|
|
135
|
+
Service registration hasn't been approved yet. Please contact administrators!
|
|
136
|
+
...
|
|
137
|
+
Configuration received.
|
|
138
|
+
Saving configuration...
|
|
139
|
+
Service registered.
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Once registered and approved, subsequent startups load configuration from disk directly via `loadConfiguration` and do not need to poll again, unless credentials change in HivePortal.
|
|
143
|
+
|
|
144
|
+
> In a clustered setup, call `registerService` only from the primary process, after a short delay to allow workers to start up first.
|
|
145
|
+
|
|
146
|
+
```js
|
|
147
|
+
// ESM
|
|
148
|
+
import cluster from "cluster";
|
|
149
|
+
import os from "os";
|
|
150
|
+
import { initServer, registerService } from "@hivedev/hivesdk/server";
|
|
151
|
+
|
|
152
|
+
if (cluster.isPrimary)
|
|
153
|
+
{
|
|
154
|
+
await initServer({ serviceName: "NoteHive", servicePort: 49161, remoteUrl: "" });
|
|
155
|
+
|
|
156
|
+
setTimeout(async () =>
|
|
157
|
+
{
|
|
158
|
+
await registerService();
|
|
159
|
+
}, 3000);
|
|
160
|
+
|
|
161
|
+
for (let i = 0; i < os.cpus().length; i++)
|
|
162
|
+
{
|
|
163
|
+
cluster.fork({ ...process.env });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else
|
|
167
|
+
{
|
|
168
|
+
// Start your HTTP server in workers
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
// CJS
|
|
174
|
+
const cluster = require("cluster");
|
|
175
|
+
const os = require("os");
|
|
176
|
+
const { initServer, registerService } = require("@hivedev/hivesdk/server");
|
|
177
|
+
|
|
178
|
+
if (cluster.isPrimary)
|
|
179
|
+
{
|
|
180
|
+
await initServer({ serviceName: "NoteHive", servicePort: 49161, remoteUrl: "" });
|
|
181
|
+
|
|
182
|
+
setTimeout(async () =>
|
|
183
|
+
{
|
|
184
|
+
await registerService();
|
|
185
|
+
}, 3000);
|
|
186
|
+
|
|
187
|
+
for (let i = 0; i < os.cpus().length; i++)
|
|
188
|
+
{
|
|
189
|
+
cluster.fork({ ...process.env });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else
|
|
193
|
+
{
|
|
194
|
+
// Start your HTTP server in workers
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
### 3. Mounting the Middleware
|
|
201
|
+
|
|
202
|
+
`handleHiveRequests` is an Express-compatible middleware that intercepts all Hive-related routes. Mount it before your own route handlers.
|
|
203
|
+
|
|
204
|
+
```js
|
|
205
|
+
// ESM
|
|
206
|
+
import express from "express";
|
|
207
|
+
import cookieParser from "cookie-parser";
|
|
208
|
+
import { handleHiveRequests } from "@hivedev/hivesdk/server";
|
|
209
|
+
|
|
210
|
+
const app = express();
|
|
211
|
+
|
|
212
|
+
app.use(express.json());
|
|
213
|
+
app.use(cookieParser());
|
|
214
|
+
app.use(handleHiveRequests); // Must come before your own routes
|
|
215
|
+
|
|
216
|
+
app.get("/my-route", (request, response) => { ... });
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
// CJS
|
|
221
|
+
const express = require("express");
|
|
222
|
+
const cookieParser = require("cookie-parser");
|
|
223
|
+
const { handleHiveRequests } = require("@hivedev/hivesdk/server");
|
|
224
|
+
|
|
225
|
+
const app = express();
|
|
226
|
+
|
|
227
|
+
app.use(express.json());
|
|
228
|
+
app.use(cookieParser());
|
|
229
|
+
app.use(handleHiveRequests);
|
|
230
|
+
|
|
231
|
+
app.get("/my-route", (request, response) => { ... });
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Routes handled automatically by this middleware:**
|
|
235
|
+
|
|
236
|
+
| Method | Route | What it does |
|
|
237
|
+
|--------|-------|--------------|
|
|
238
|
+
| `POST` | `/Login` | Proxies login credentials to HivePortal, sets session cookie and/or `x-session-token` header |
|
|
239
|
+
| `POST` | `/Logout` | Proxies logout to HivePortal, clears session |
|
|
240
|
+
| `GET` | `/ServiceUrls` | Returns local or remote URL for a named service |
|
|
241
|
+
| `POST` | `/IsLoggedIn` | Checks if the current session is valid with HivePortal |
|
|
242
|
+
| `POST` | `/IsLoggedInWithPermission` | Checks session validity and a specific named permission |
|
|
243
|
+
| `POST` | `/SaveUserFilter` | Saves a user-defined data filter (requires `FILTER_OPERATIONS` permission) |
|
|
244
|
+
| `POST` | `/PushNotificationToken` | Registers a push notification token for the current device |
|
|
245
|
+
| `GET` | `/hive-client.js` | Serves the built client-side SDK bundle (see [Client SDK](#client-sdk)) |
|
|
246
|
+
|
|
247
|
+
You do not need to implement any of these routes yourself.
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### 4. Loading Configuration
|
|
252
|
+
|
|
253
|
+
On startup (after `initServer`), call `loadConfiguration` to decrypt and load credentials from disk into `process.env`.
|
|
254
|
+
|
|
255
|
+
```js
|
|
256
|
+
// ESM
|
|
257
|
+
import { loadConfiguration } from "@hivedev/hivesdk/server";
|
|
258
|
+
|
|
259
|
+
await loadConfiguration();
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
```js
|
|
263
|
+
// CJS
|
|
264
|
+
const { loadConfiguration } = require("@hivedev/hivesdk/server");
|
|
265
|
+
|
|
266
|
+
await loadConfiguration();
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
This populates the following environment variables (only those with saved `.dat` files on disk are loaded):
|
|
270
|
+
|
|
271
|
+
| Variable | Contents |
|
|
272
|
+
|----------|----------|
|
|
273
|
+
| `FIREBASE_ADMIN_CREDENTIALS` | JSON string of Firebase Admin SDK credentials |
|
|
274
|
+
| `DATABASE_CREDENTIALS` | JSON string of database credentials, keyed by service name |
|
|
275
|
+
| `PERMISSIONS` | JSON string of permission definitions |
|
|
276
|
+
| `SMTP_CREDENTIALS` | JSON string of SMTP credentials |
|
|
277
|
+
| `SMTP_PASSWORD` | Extracted SMTP password, ready for use in nodemailer |
|
|
278
|
+
|
|
279
|
+
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.
|
|
280
|
+
|
|
281
|
+
If a `.dat` file does not exist yet (e.g. first run before registration is approved), that credential is silently skipped.
|
|
282
|
+
|
|
283
|
+
> Call `loadConfiguration` before `registerService`. On first run the files won't exist yet and it will skip gracefully. Once `registerService` completes and the administrator approves it, files are written to disk and will be fully loaded on the next startup.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
### 5. Connecting to the Database
|
|
288
|
+
|
|
289
|
+
After `loadConfiguration`, use `DatabaseConnector` to establish a MongoDB connection.
|
|
290
|
+
|
|
291
|
+
```js
|
|
292
|
+
// ESM — import directly from the SDK's built file
|
|
293
|
+
import DatabaseConnector from "./node_modules/@hivedev/hivesdk/DatabaseConnector.js";
|
|
294
|
+
|
|
295
|
+
const connected = await DatabaseConnector.connect();
|
|
296
|
+
|
|
297
|
+
if (!connected)
|
|
298
|
+
{
|
|
299
|
+
console.error("Failed to connect to database.");
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// DatabaseConnector.databaseObject is now a live MongoDB db instance
|
|
304
|
+
const collection = DatabaseConnector.databaseObject.collection("my_collection");
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
```js
|
|
308
|
+
// CJS
|
|
309
|
+
const DatabaseConnector = require("./node_modules/@hivedev/hivesdk/DatabaseConnector.js");
|
|
310
|
+
|
|
311
|
+
const connected = await DatabaseConnector.connect();
|
|
312
|
+
|
|
313
|
+
if (!connected)
|
|
314
|
+
{
|
|
315
|
+
console.error("Failed to connect to database.");
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const collection = DatabaseConnector.databaseObject.collection("my_collection");
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
If `loadConfiguration` was not called first, `DatabaseConnector.connect()` will attempt to call it automatically.
|
|
323
|
+
|
|
324
|
+
**Disconnecting:**
|
|
325
|
+
|
|
326
|
+
```js
|
|
327
|
+
await DatabaseConnector.disconnect();
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
### 6. Guarding Routes
|
|
333
|
+
|
|
334
|
+
Use `isLoggedIn` and `isLoggedInWithPermission` to protect your own route handlers. Both accept the request and response objects and a `bSendResponse` boolean.
|
|
335
|
+
|
|
336
|
+
When `bSendResponse` is `false`, the function returns a boolean and leaves the response untouched — use this to guard logic inside your own handlers.
|
|
337
|
+
|
|
338
|
+
When `bSendResponse` is `true`, the function writes the result directly to the response — used internally by `handleHiveRequests` for the `/IsLoggedIn` and `/IsLoggedInWithPermission` endpoints.
|
|
339
|
+
|
|
340
|
+
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.
|
|
341
|
+
|
|
342
|
+
**Guarding a route with a login check:**
|
|
343
|
+
|
|
344
|
+
```js
|
|
345
|
+
// ESM
|
|
346
|
+
import { isLoggedIn } from "@hivedev/hivesdk/server";
|
|
347
|
+
|
|
348
|
+
app.get("/my-protected-route", async (request, response) =>
|
|
349
|
+
{
|
|
350
|
+
const loggedIn = await isLoggedIn(request, response, false);
|
|
351
|
+
|
|
352
|
+
if (!loggedIn)
|
|
353
|
+
{
|
|
354
|
+
response.statusCode = 401;
|
|
355
|
+
response.end("Unauthorized");
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
response.end("Here is your protected data.");
|
|
360
|
+
});
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
```js
|
|
364
|
+
// CJS
|
|
365
|
+
const { isLoggedIn } = require("@hivedev/hivesdk/server");
|
|
366
|
+
|
|
367
|
+
app.get("/my-protected-route", async (request, response) =>
|
|
368
|
+
{
|
|
369
|
+
const loggedIn = await isLoggedIn(request, response, false);
|
|
370
|
+
|
|
371
|
+
if (!loggedIn)
|
|
372
|
+
{
|
|
373
|
+
response.statusCode = 401;
|
|
374
|
+
response.end("Unauthorized");
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
response.end("Here is your protected data.");
|
|
379
|
+
});
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
**Guarding a route with a permission check:**
|
|
383
|
+
|
|
384
|
+
The permission name must be set on `request.body.permissionName` before calling `isLoggedInWithPermission`.
|
|
385
|
+
|
|
386
|
+
```js
|
|
387
|
+
// ESM
|
|
388
|
+
import { isLoggedInWithPermission } from "@hivedev/hivesdk/server";
|
|
389
|
+
|
|
390
|
+
app.post("/admin-action", async (request, response) =>
|
|
391
|
+
{
|
|
392
|
+
request.body.permissionName = "ADMIN_OPERATIONS";
|
|
393
|
+
|
|
394
|
+
const permitted = await isLoggedInWithPermission(request, response, false);
|
|
395
|
+
|
|
396
|
+
if (!permitted)
|
|
397
|
+
{
|
|
398
|
+
response.statusCode = 403;
|
|
399
|
+
response.end("Forbidden");
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
response.end("Admin action performed.");
|
|
404
|
+
});
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
```js
|
|
408
|
+
// CJS
|
|
409
|
+
const { isLoggedInWithPermission } = require("@hivedev/hivesdk/server");
|
|
410
|
+
|
|
411
|
+
app.post("/admin-action", async (request, response) =>
|
|
412
|
+
{
|
|
413
|
+
request.body.permissionName = "ADMIN_OPERATIONS";
|
|
414
|
+
|
|
415
|
+
const permitted = await isLoggedInWithPermission(request, response, false);
|
|
416
|
+
|
|
417
|
+
if (!permitted)
|
|
418
|
+
{
|
|
419
|
+
response.statusCode = 403;
|
|
420
|
+
response.end("Forbidden");
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
response.end("Admin action performed.");
|
|
425
|
+
});
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
### 7. Sending Mail
|
|
431
|
+
|
|
432
|
+
```js
|
|
433
|
+
// ESM
|
|
434
|
+
import { sendMail } from "@hivedev/hivesdk/server";
|
|
435
|
+
|
|
436
|
+
await sendMail(
|
|
437
|
+
"sender@yourdomain.com",
|
|
438
|
+
"recipient@example.com",
|
|
439
|
+
"Subject line here",
|
|
440
|
+
"<p>HTML body here</p>"
|
|
441
|
+
);
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
```js
|
|
445
|
+
// CJS
|
|
446
|
+
const { sendMail } = require("@hivedev/hivesdk/server");
|
|
447
|
+
|
|
448
|
+
await sendMail(
|
|
449
|
+
"sender@yourdomain.com",
|
|
450
|
+
"recipient@example.com",
|
|
451
|
+
"Subject line here",
|
|
452
|
+
"<p>HTML body here</p>"
|
|
453
|
+
);
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
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` — edit it there if needed.
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
### 8. Utility Functions
|
|
461
|
+
|
|
462
|
+
All utilities are exported from `@hivedev/hivesdk/server`.
|
|
463
|
+
|
|
464
|
+
**`getAppDataDirectory()`**
|
|
465
|
+
|
|
466
|
+
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>`.
|
|
467
|
+
|
|
468
|
+
```js
|
|
469
|
+
// ESM
|
|
470
|
+
import { getAppDataDirectory } from "@hivedev/hivesdk/server";
|
|
471
|
+
const dataDir = getAppDataDirectory();
|
|
472
|
+
// e.g. "/home/user/.config/NoteHive"
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
```js
|
|
476
|
+
// CJS
|
|
477
|
+
const { getAppDataDirectory } = require("@hivedev/hivesdk/server");
|
|
478
|
+
const dataDir = getAppDataDirectory();
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
**`getHostIp()`**
|
|
482
|
+
|
|
483
|
+
Returns the first non-loopback IPv4 address of the host machine.
|
|
484
|
+
|
|
485
|
+
```js
|
|
486
|
+
// ESM
|
|
487
|
+
import { getHostIp } from "@hivedev/hivesdk/server";
|
|
488
|
+
const ip = getHostIp(); // e.g. "192.168.1.42"
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
```js
|
|
492
|
+
// CJS
|
|
493
|
+
const { getHostIp } = require("@hivedev/hivesdk/server");
|
|
494
|
+
const ip = getHostIp();
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
**`findService(serviceName)`**
|
|
498
|
+
|
|
499
|
+
Discovers another Hive service on the LAN via UDP to port 49153. Returns `{ name, urls: { local, remote } }` or `null` if not found within 3 seconds. Requires `HIVE_PORTAL_LAN_IP` environment variable to be set.
|
|
500
|
+
|
|
501
|
+
```js
|
|
502
|
+
// ESM
|
|
503
|
+
import { findService } from "@hivedev/hivesdk/server";
|
|
504
|
+
|
|
505
|
+
const service = await findService("NoteHive");
|
|
506
|
+
|
|
507
|
+
if (service)
|
|
508
|
+
{
|
|
509
|
+
console.log(service.urls.local); // "http://192.168.1.42:49161"
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
```js
|
|
514
|
+
// CJS
|
|
515
|
+
const { findService } = require("@hivedev/hivesdk/server");
|
|
516
|
+
|
|
517
|
+
const service = await findService("NoteHive");
|
|
518
|
+
|
|
519
|
+
if (service)
|
|
520
|
+
{
|
|
521
|
+
console.log(service.urls.local);
|
|
522
|
+
}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
**`extractConfigurationForService()`**
|
|
526
|
+
|
|
527
|
+
Parses `process.env` and returns the configuration as structured objects, extracting the correct database credentials for the current service name.
|
|
528
|
+
|
|
529
|
+
```js
|
|
530
|
+
// ESM
|
|
531
|
+
import { extractConfigurationForService } from "@hivedev/hivesdk/server";
|
|
532
|
+
|
|
533
|
+
const { firebaseAdminCredentials, databaseCredentials, permissions, smtpCredentials } = extractConfigurationForService();
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
```js
|
|
537
|
+
// CJS
|
|
538
|
+
const { extractConfigurationForService } = require("@hivedev/hivesdk/server");
|
|
539
|
+
|
|
540
|
+
const { firebaseAdminCredentials, databaseCredentials, permissions, smtpCredentials } = extractConfigurationForService();
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
**`encryptAndStoreFile(data, fullPath, password)`**
|
|
544
|
+
|
|
545
|
+
Encrypts a string or Buffer with AES-256-CBC and writes it to disk. Creates the directory if it doesn't exist.
|
|
546
|
+
|
|
547
|
+
```js
|
|
548
|
+
// ESM
|
|
549
|
+
import { encryptAndStoreFile } from "@hivedev/hivesdk/server";
|
|
550
|
+
|
|
551
|
+
await encryptAndStoreFile("my secret data", "/path/to/file.dat", process.env.SERVER_PASSWORD);
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
```js
|
|
555
|
+
// CJS
|
|
556
|
+
const { encryptAndStoreFile } = require("@hivedev/hivesdk/server");
|
|
557
|
+
|
|
558
|
+
await encryptAndStoreFile("my secret data", "/path/to/file.dat", process.env.SERVER_PASSWORD);
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
**`decryptFileWithPassword(fullPath, password)`**
|
|
562
|
+
|
|
563
|
+
Reads and decrypts an encrypted `.dat` file. Returns the decrypted string, or `null` on failure.
|
|
564
|
+
|
|
565
|
+
```js
|
|
566
|
+
// ESM
|
|
567
|
+
import { decryptFileWithPassword } from "@hivedev/hivesdk/server";
|
|
568
|
+
|
|
569
|
+
const contents = await decryptFileWithPassword("/path/to/file.dat", process.env.SERVER_PASSWORD);
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
```js
|
|
573
|
+
// CJS
|
|
574
|
+
const { decryptFileWithPassword } = require("@hivedev/hivesdk/server");
|
|
575
|
+
|
|
576
|
+
const contents = await decryptFileWithPassword("/path/to/file.dat", process.env.SERVER_PASSWORD);
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
**`saveConfiguration(configurationObject)` / `loadConfiguration()`**
|
|
580
|
+
|
|
581
|
+
Normally called automatically by `registerService` and during startup respectively. Available for manual use if needed. `saveConfiguration` only writes keys that are present in the object — missing keys do not overwrite existing files.
|
|
582
|
+
|
|
583
|
+
```js
|
|
584
|
+
// ESM
|
|
585
|
+
import { saveConfiguration, loadConfiguration } from "@hivedev/hivesdk/server";
|
|
586
|
+
|
|
587
|
+
await saveConfiguration({
|
|
588
|
+
databaseCredentials: { NoteHive: { host: "127.0.0.1", username: "admin", password: "pass", name: "notehive" } },
|
|
589
|
+
smtpCredentials: { password: "smtp-password" }
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
await loadConfiguration();
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
```js
|
|
596
|
+
// CJS
|
|
597
|
+
const { saveConfiguration, loadConfiguration } = require("@hivedev/hivesdk/server");
|
|
598
|
+
|
|
599
|
+
await saveConfiguration({
|
|
600
|
+
databaseCredentials: { NoteHive: { host: "127.0.0.1", username: "admin", password: "pass", name: "notehive" } },
|
|
601
|
+
smtpCredentials: { password: "smtp-password" }
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
await loadConfiguration();
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
### Full Server Bootstrap Example
|
|
610
|
+
|
|
611
|
+
This is the recommended startup sequence for any Hive microservice.
|
|
612
|
+
|
|
613
|
+
```js
|
|
614
|
+
// ESM
|
|
615
|
+
import express from "express";
|
|
616
|
+
import cookieParser from "cookie-parser";
|
|
617
|
+
import cluster from "cluster";
|
|
618
|
+
import os from "os";
|
|
619
|
+
import { handleHiveRequests, initServer, registerService, loadConfiguration } from "@hivedev/hivesdk/server";
|
|
620
|
+
import DatabaseConnector from "./node_modules/@hivedev/hivesdk/DatabaseConnector.js";
|
|
621
|
+
|
|
622
|
+
const SERVICE_NAME = "NoteHive";
|
|
623
|
+
const SERVICE_PORT = 49161;
|
|
624
|
+
|
|
625
|
+
const app = express();
|
|
626
|
+
|
|
627
|
+
app.use(express.json());
|
|
628
|
+
app.use(cookieParser());
|
|
629
|
+
app.use(handleHiveRequests);
|
|
630
|
+
|
|
631
|
+
app.get("/", (request, response) => response.redirect("/Client/Pages/HomePage.html"));
|
|
632
|
+
|
|
633
|
+
if (cluster.isPrimary)
|
|
634
|
+
{
|
|
635
|
+
await initServer({
|
|
636
|
+
serviceName: SERVICE_NAME,
|
|
637
|
+
servicePort: SERVICE_PORT,
|
|
638
|
+
remoteUrl: process.env.REMOTE_URL || ""
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
await loadConfiguration();
|
|
642
|
+
|
|
643
|
+
setTimeout(async () =>
|
|
644
|
+
{
|
|
645
|
+
await registerService();
|
|
646
|
+
}, 3000);
|
|
647
|
+
|
|
648
|
+
for (let i = 0; i < os.cpus().length; i++)
|
|
649
|
+
{
|
|
650
|
+
cluster.fork({ ...process.env });
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
cluster.on("exit", (worker) =>
|
|
654
|
+
{
|
|
655
|
+
console.log(`Worker ${worker.process.pid} died. Restarting...`);
|
|
656
|
+
cluster.fork();
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
else
|
|
660
|
+
{
|
|
661
|
+
await DatabaseConnector.connect();
|
|
662
|
+
|
|
663
|
+
app.listen(SERVICE_PORT, () =>
|
|
664
|
+
{
|
|
665
|
+
console.log(`Worker ${process.pid} listening on port ${SERVICE_PORT}`);
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
```js
|
|
671
|
+
// CJS
|
|
672
|
+
const express = require("express");
|
|
673
|
+
const cookieParser = require("cookie-parser");
|
|
674
|
+
const cluster = require("cluster");
|
|
675
|
+
const os = require("os");
|
|
676
|
+
const { handleHiveRequests, initServer, registerService, loadConfiguration } = require("@hivedev/hivesdk/server");
|
|
677
|
+
const DatabaseConnector = require("./node_modules/@hivedev/hivesdk/DatabaseConnector.js");
|
|
678
|
+
|
|
679
|
+
const SERVICE_NAME = "NoteHive";
|
|
680
|
+
const SERVICE_PORT = 49161;
|
|
681
|
+
|
|
682
|
+
const app = express();
|
|
683
|
+
|
|
684
|
+
app.use(express.json());
|
|
685
|
+
app.use(cookieParser());
|
|
686
|
+
app.use(handleHiveRequests);
|
|
687
|
+
|
|
688
|
+
app.get("/", (request, response) => response.redirect("/Client/Pages/HomePage.html"));
|
|
689
|
+
|
|
690
|
+
(async () =>
|
|
691
|
+
{
|
|
692
|
+
if (cluster.isPrimary)
|
|
693
|
+
{
|
|
694
|
+
await initServer({
|
|
695
|
+
serviceName: SERVICE_NAME,
|
|
696
|
+
servicePort: SERVICE_PORT,
|
|
697
|
+
remoteUrl: process.env.REMOTE_URL || ""
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
await loadConfiguration();
|
|
701
|
+
|
|
702
|
+
setTimeout(async () =>
|
|
703
|
+
{
|
|
704
|
+
await registerService();
|
|
705
|
+
}, 3000);
|
|
706
|
+
|
|
707
|
+
for (let i = 0; i < os.cpus().length; i++)
|
|
708
|
+
{
|
|
709
|
+
cluster.fork({ ...process.env });
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
cluster.on("exit", (worker) =>
|
|
713
|
+
{
|
|
714
|
+
console.log(`Worker ${worker.process.pid} died. Restarting...`);
|
|
715
|
+
cluster.fork();
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
else
|
|
719
|
+
{
|
|
720
|
+
await DatabaseConnector.connect();
|
|
721
|
+
|
|
722
|
+
app.listen(SERVICE_PORT, () =>
|
|
723
|
+
{
|
|
724
|
+
console.log(`Worker ${process.pid} listening on port ${SERVICE_PORT}`);
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
})();
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
---
|
|
731
|
+
|
|
732
|
+
## Client SDK
|
|
733
|
+
|
|
734
|
+
The client SDK is a browser-only ESM bundle (`hive-client.js`). There is no CJS variant — it runs exclusively in the browser.
|
|
735
|
+
|
|
736
|
+
### 1. Loading the Client Bundle
|
|
737
|
+
|
|
738
|
+
The `handleHiveRequests` middleware automatically serves the client bundle at `/hive-client.js` by reading it from `node_modules/@hivedev/hivesdk/hive-client.js`. **This works as long as your service's Node.js process is started from the root of the package** (i.e. the directory that contains `node_modules`), which is the recommended setup.
|
|
739
|
+
|
|
740
|
+
If your service is started from a different working directory, the automatic route will fail to locate the file. In that case, serve it yourself:
|
|
741
|
+
|
|
742
|
+
```js
|
|
743
|
+
import path from "path";
|
|
744
|
+
import { createReadStream } from "fs";
|
|
745
|
+
|
|
746
|
+
app.get("/hive-client.js", (request, response) =>
|
|
747
|
+
{
|
|
748
|
+
const filePath = path.join("/absolute/path/to/node_modules/@hivedev/hivesdk/hive-client.js");
|
|
749
|
+
response.setHeader("Content-Type", "application/javascript");
|
|
750
|
+
createReadStream(filePath).pipe(response);
|
|
751
|
+
});
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
Include it in your HTML pages as a module script:
|
|
755
|
+
|
|
756
|
+
```html
|
|
757
|
+
<script type="module" src="/hive-client.js"></script>
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
Or import from it in another module script on the page:
|
|
761
|
+
|
|
762
|
+
```html
|
|
763
|
+
<script type="module">
|
|
764
|
+
import { login } from "/hive-client.js";
|
|
765
|
+
</script>
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
---
|
|
769
|
+
|
|
770
|
+
### 2. Initialising the Client
|
|
771
|
+
|
|
772
|
+
Call `initClient` once on page load. It sets up in-app WebView detection, signals cookie support to the server, and starts the periodic login state checker.
|
|
773
|
+
|
|
774
|
+
```js
|
|
775
|
+
import { initClient } from "/hive-client.js";
|
|
776
|
+
|
|
777
|
+
await initClient({});
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
`initClient` accepts one optional parameter:
|
|
781
|
+
|
|
782
|
+
| Parameter | Type | Default | Description |
|
|
783
|
+
|-----------|------|---------|-------------|
|
|
784
|
+
| `loginTokenStorageMethod` | `sessionStorageMethod` enum | `HTTP_ONLY_COOKIE` | How session tokens are stored on the client |
|
|
785
|
+
|
|
786
|
+
---
|
|
787
|
+
|
|
788
|
+
### 3. Login
|
|
789
|
+
|
|
790
|
+
Call `login()` to open the HivePortal login page. The SDK handles the entire flow — the popup opens, the user enters their credentials, and on success the page reloads at `/`.
|
|
791
|
+
|
|
792
|
+
```js
|
|
793
|
+
import { login } from "/hive-client.js";
|
|
794
|
+
|
|
795
|
+
login();
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
You can wire it to any element:
|
|
799
|
+
|
|
800
|
+
```js
|
|
801
|
+
import { login } from "/hive-client.js";
|
|
802
|
+
|
|
803
|
+
document.getElementById("login-button").addEventListener("click", login);
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
When running inside the native mobile app (detected automatically), the login popup is rendered as a full-screen iframe overlay instead of a real popup window, since WebViews block `window.open`.
|
|
807
|
+
|
|
808
|
+
---
|
|
809
|
+
|
|
810
|
+
### 4. Checking Login State
|
|
811
|
+
|
|
812
|
+
`initClient` automatically starts a periodic login check (every 30 seconds, clamped between 10 and 60 seconds). Each check fires a `login-state-changed` custom event on `window`.
|
|
813
|
+
|
|
814
|
+
```js
|
|
815
|
+
window.addEventListener("login-state-changed", (event) =>
|
|
816
|
+
{
|
|
817
|
+
const isLoggedIn = event.detail;
|
|
818
|
+
|
|
819
|
+
document.getElementById("login-prompt").style.display = isLoggedIn ? "none" : "block";
|
|
820
|
+
document.getElementById("dashboard").style.display = isLoggedIn ? "block" : "none";
|
|
821
|
+
});
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
The current login state is also available synchronously at any time:
|
|
825
|
+
|
|
826
|
+
```js
|
|
827
|
+
if (window.IS_LOGGED_IN)
|
|
828
|
+
{
|
|
829
|
+
// Safe to load user-specific content
|
|
830
|
+
}
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
---
|
|
834
|
+
|
|
835
|
+
### Full Client Bootstrap Example
|
|
836
|
+
|
|
837
|
+
```html
|
|
838
|
+
<!DOCTYPE html>
|
|
839
|
+
<html lang="en">
|
|
840
|
+
<head>
|
|
841
|
+
<meta charset="UTF-8">
|
|
842
|
+
<title>NoteHive</title>
|
|
843
|
+
</head>
|
|
844
|
+
<body>
|
|
845
|
+
|
|
846
|
+
<div id="login-prompt">
|
|
847
|
+
<button id="login-button">Sign In</button>
|
|
848
|
+
</div>
|
|
849
|
+
|
|
850
|
+
<div id="dashboard" style="display: none;">
|
|
851
|
+
<h1>Welcome to NoteHive</h1>
|
|
852
|
+
</div>
|
|
853
|
+
|
|
854
|
+
<script type="module">
|
|
855
|
+
import { initClient, login } from "/hive-client.js";
|
|
856
|
+
|
|
857
|
+
await initClient({});
|
|
858
|
+
|
|
859
|
+
window.addEventListener("login-state-changed", (event) =>
|
|
860
|
+
{
|
|
861
|
+
const isLoggedIn = event.detail;
|
|
862
|
+
document.getElementById("login-prompt").style.display = isLoggedIn ? "none" : "block";
|
|
863
|
+
document.getElementById("dashboard").style.display = isLoggedIn ? "block" : "none";
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
document.getElementById("login-button").addEventListener("click", login);
|
|
867
|
+
</script>
|
|
868
|
+
|
|
869
|
+
</body>
|
|
870
|
+
</html>
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
---
|
|
874
|
+
|
|
875
|
+
## Enumerations
|
|
876
|
+
|
|
877
|
+
The SDK exports several enumerations used throughout the Hive ecosystem.
|
|
878
|
+
|
|
879
|
+
**`userPrivileges`** — Numeric privilege levels assigned to users.
|
|
880
|
+
|
|
881
|
+
| Key | Value |
|
|
882
|
+
|-----|-------|
|
|
883
|
+
| `STUDENT` | 0 |
|
|
884
|
+
| `CLASS_REPRESENTATIVE` | 10 |
|
|
885
|
+
| `STUDENT_COORDINATOR` | 20 |
|
|
886
|
+
| `ASSISTANT` | 30 |
|
|
887
|
+
| `TEACHER` | 40 |
|
|
888
|
+
| `COURSE_COORDINATOR` | 50 |
|
|
889
|
+
| `HEAD_OF_DEPARTMENT` | 70 |
|
|
890
|
+
| `MANAGEMENT` | 80 |
|
|
891
|
+
| `SUPER_USER` | 100 |
|
|
892
|
+
|
|
893
|
+
**`sessionStorageMethod`** — How session tokens are stored on the client.
|
|
894
|
+
|
|
895
|
+
| Key | Value |
|
|
896
|
+
|-----|-------|
|
|
897
|
+
| `HTTP_ONLY_COOKIE` | 0 |
|
|
898
|
+
| `LOCAL_STORAGE` | 1 |
|
|
899
|
+
| `SESSION_STORAGE` | 2 |
|
|
900
|
+
|
|
901
|
+
**`clientConnectionTypeFlags`** — Bitmask flags describing how a client is connected.
|
|
902
|
+
|
|
903
|
+
| Key | Value |
|
|
904
|
+
|-----|-------|
|
|
905
|
+
| `LAN` | 1 |
|
|
906
|
+
| `REMOTE` | 2 |
|
|
907
|
+
| `SAME_SYSTEM` | 4 |
|
|
908
|
+
| `UNKNOWN` | 8 |
|
|
909
|
+
|
|
910
|
+
**`appWebViewInteractions`** — Message types exchanged between the web page and the native app WebView layer.
|
|
911
|
+
|
|
912
|
+
| Key | Value |
|
|
913
|
+
|-----|-------|
|
|
914
|
+
| `REQUEST_NOTIFICATION_TOKEN` | 0 |
|
|
915
|
+
| `BACK_PRESSED` | 1 |
|
|
916
|
+
|
|
917
|
+
**`approvalTypes`** — Types of approval requests sent to HivePortal administrators.
|
|
918
|
+
|
|
919
|
+
| Key | Value |
|
|
920
|
+
|-----|-------|
|
|
921
|
+
| `USER_REGISTERATION` | 0 |
|
|
922
|
+
| `SERVICE_REGISTERATION` | 1 |
|
|
923
|
+
|
|
924
|
+
**`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.
|
|
925
|
+
|
|
926
|
+
---
|
|
927
|
+
|
|
928
|
+
## Environment Variables Reference
|
|
929
|
+
|
|
930
|
+
| Variable | Set by | Description |
|
|
931
|
+
|----------|--------|-------------|
|
|
932
|
+
| `SERVER_PASSWORD` | External / `setupPassword()` | Master password for encrypting/decrypting config files |
|
|
933
|
+
| `SERVICE_NAME` | `initServer` | Name of this service as registered with HivePortal |
|
|
934
|
+
| `SERVICE_PORT` | `initServer` | Port this service listens on |
|
|
935
|
+
| `REMOTE_URL` | `initServer` | Public-facing URL of this service (empty if LAN-only) |
|
|
936
|
+
| `HIVE_PORTAL_LAN_IP` | External | LAN IP of HivePortal, used by `findService` for UDP discovery |
|
|
937
|
+
| `FIREBASE_ADMIN_CREDENTIALS` | `loadConfiguration` | JSON string of Firebase Admin credentials |
|
|
938
|
+
| `DATABASE_CREDENTIALS` | `loadConfiguration` | JSON string of DB credentials keyed by service name |
|
|
939
|
+
| `PERMISSIONS` | `loadConfiguration` | JSON string of permission definitions |
|
|
940
|
+
| `SMTP_CREDENTIALS` | `loadConfiguration` | JSON string of SMTP credentials |
|
|
941
|
+
| `SMTP_PASSWORD` | `loadConfiguration` | Extracted SMTP password, ready for nodemailer |
|
|
942
|
+
|
|
943
|
+
---
|
|
944
|
+
|
|
945
|
+
## How HivePortal Fits In
|
|
946
|
+
|
|
947
|
+
```
|
|
948
|
+
┌─────────────────────┐
|
|
949
|
+
│ HivePortal │
|
|
950
|
+
│ (hub — port 49152) │
|
|
951
|
+
│ │
|
|
952
|
+
│ - User accounts │
|
|
953
|
+
│ - Sessions │
|
|
954
|
+
│ - Permissions │
|
|
955
|
+
│ - Service registry │
|
|
956
|
+
│ - Configuration │
|
|
957
|
+
└────────┬─────────────┘
|
|
958
|
+
│
|
|
959
|
+
┌────────────────────┼────────────────────┐
|
|
960
|
+
│ │ │
|
|
961
|
+
┌──────────▼──────────┐ │ ┌──────────▼──────────┐
|
|
962
|
+
│ NoteHive │ ... │ FutureService │
|
|
963
|
+
│ (port 49161) │ │ (port 49162) │
|
|
964
|
+
│ │ │ │
|
|
965
|
+
│ uses @hivedev/ │ │ uses @hivedev/ │
|
|
966
|
+
│ hivesdk │ │ hivesdk │
|
|
967
|
+
└──────────────────────┘ └─────────────────────┘
|
|
968
|
+
|
|
969
|
+
Each service:
|
|
970
|
+
1. initServer() — establish identity and password
|
|
971
|
+
2. loadConfiguration() — decrypt credentials from disk
|
|
972
|
+
3. registerService() — announce to HivePortal, receive config
|
|
973
|
+
4. handleHiveRequests — mount middleware
|
|
974
|
+
5. Own domain logic — notes, files, etc.
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
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 and serve their own domain data.
|
|
978
|
+
|
|
979
|
+
---
|
|
980
|
+
|
|
981
|
+
## Known Limitations
|
|
982
|
+
|
|
983
|
+
- **`ScheduleServiceUrlUpdate`** — This function is currently a stub and does nothing. It is intended for future use to periodically re-broadcast service URLs to HivePortal if the host IP changes.
|
|
984
|
+
- **`loginTokenStorageMethod`** — The `LOCAL_STORAGE` and `SESSION_STORAGE` options in the `sessionStorageMethod` enum are defined but not yet implemented. Only `HTTP_ONLY_COOKIE` (with the `x-session-token` header fallback for native app clients) is fully functional.
|
|
985
|
+
- **SMTP sender address** — The sender Gmail address in `SendMail.js` is hardcoded. Edit it in the SDK source if your service needs a different address.
|
|
986
|
+
- **MongoDB only** — `DatabaseConnector` is built for MongoDB. There is no support for other databases.
|
|
987
|
+
- **MongoDB port hardcoded** — The MongoDB port is hardcoded to `27017`. The host is configurable via credentials but the port is not.
|