@hivedev/hivesdk 1.0.35 → 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 +362 -114
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,13 +35,13 @@ Every service in the Hive ecosystem (NoteHive, and any future service) is built
|
|
|
35
35
|
|
|
36
36
|
## Concepts
|
|
37
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.
|
|
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
39
|
|
|
40
40
|
**Service registration** is a two-step process:
|
|
41
41
|
1. The service announces itself to HivePortal with its name and local/remote URLs.
|
|
42
|
-
2. HivePortal
|
|
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
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`.
|
|
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
45
|
|
|
46
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
47
|
|
|
@@ -55,19 +55,9 @@ npm install @hivedev/hivesdk
|
|
|
55
55
|
|
|
56
56
|
The SDK ships three pre-built files:
|
|
57
57
|
- `hive-server.js` — ESM bundle for Node.js backends
|
|
58
|
-
- `hive-server.cjs` —
|
|
58
|
+
- `hive-server.cjs` — CJS bundle for Node.js backends
|
|
59
59
|
- `hive-client.js` — ESM bundle for browser frontends
|
|
60
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
61
|
---
|
|
72
62
|
|
|
73
63
|
## Server SDK
|
|
@@ -77,6 +67,7 @@ import { initClient, login } from "@hivedev/hivesdk/client";
|
|
|
77
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.
|
|
78
68
|
|
|
79
69
|
```js
|
|
70
|
+
// ESM
|
|
80
71
|
import { initServer } from "@hivedev/hivesdk/server";
|
|
81
72
|
|
|
82
73
|
await initServer({
|
|
@@ -87,6 +78,18 @@ await initServer({
|
|
|
87
78
|
});
|
|
88
79
|
```
|
|
89
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
|
+
|
|
90
93
|
**Password resolution order:**
|
|
91
94
|
|
|
92
95
|
1. The `serverPassword` argument, if provided.
|
|
@@ -104,17 +107,25 @@ The password is used as the encryption key for all `.dat` configuration files. L
|
|
|
104
107
|
After `initServer`, call `registerService` to announce this service to HivePortal and receive configuration.
|
|
105
108
|
|
|
106
109
|
```js
|
|
110
|
+
// ESM
|
|
107
111
|
import { registerService } from "@hivedev/hivesdk/server";
|
|
108
112
|
|
|
109
113
|
await registerService();
|
|
110
114
|
```
|
|
111
115
|
|
|
116
|
+
```js
|
|
117
|
+
// CJS
|
|
118
|
+
const { registerService } = require("@hivedev/hivesdk/server");
|
|
119
|
+
|
|
120
|
+
await registerService();
|
|
121
|
+
```
|
|
122
|
+
|
|
112
123
|
What this does internally:
|
|
113
124
|
|
|
114
125
|
1. Sets the service's local URL (`http://<hostIp>:<servicePort>`) and remote URL in the internal registry.
|
|
115
126
|
2. POSTs to HivePortal's `/RegisterService` endpoint.
|
|
116
|
-
3. If HivePortal accepts the
|
|
117
|
-
4. Once
|
|
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`.
|
|
118
129
|
|
|
119
130
|
**On first run**, the terminal will show:
|
|
120
131
|
|
|
@@ -128,13 +139,41 @@ Saving configuration...
|
|
|
128
139
|
Service registered.
|
|
129
140
|
```
|
|
130
141
|
|
|
131
|
-
Once registered and approved, subsequent startups
|
|
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.
|
|
132
143
|
|
|
133
|
-
> In a clustered setup
|
|
144
|
+
> In a clustered setup, call `registerService` only from the primary process, after a short delay to allow workers to start up first.
|
|
134
145
|
|
|
135
146
|
```js
|
|
147
|
+
// ESM
|
|
136
148
|
import cluster from "cluster";
|
|
137
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");
|
|
138
177
|
|
|
139
178
|
if (cluster.isPrimary)
|
|
140
179
|
{
|
|
@@ -163,7 +202,9 @@ else
|
|
|
163
202
|
`handleHiveRequests` is an Express-compatible middleware that intercepts all Hive-related routes. Mount it before your own route handlers.
|
|
164
203
|
|
|
165
204
|
```js
|
|
205
|
+
// ESM
|
|
166
206
|
import express from "express";
|
|
207
|
+
import cookieParser from "cookie-parser";
|
|
167
208
|
import { handleHiveRequests } from "@hivedev/hivesdk/server";
|
|
168
209
|
|
|
169
210
|
const app = express();
|
|
@@ -172,7 +213,21 @@ app.use(express.json());
|
|
|
172
213
|
app.use(cookieParser());
|
|
173
214
|
app.use(handleHiveRequests); // Must come before your own routes
|
|
174
215
|
|
|
175
|
-
|
|
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
|
+
|
|
176
231
|
app.get("/my-route", (request, response) => { ... });
|
|
177
232
|
```
|
|
178
233
|
|
|
@@ -184,12 +239,12 @@ app.get("/my-route", (request, response) => { ... });
|
|
|
184
239
|
| `POST` | `/Logout` | Proxies logout to HivePortal, clears session |
|
|
185
240
|
| `GET` | `/ServiceUrls` | Returns local or remote URL for a named service |
|
|
186
241
|
| `POST` | `/IsLoggedIn` | Checks if the current session is valid with HivePortal |
|
|
187
|
-
| `POST` | `/IsLoggedInWithPermission` | Checks session validity and a specific permission |
|
|
242
|
+
| `POST` | `/IsLoggedInWithPermission` | Checks session validity and a specific named permission |
|
|
188
243
|
| `POST` | `/SaveUserFilter` | Saves a user-defined data filter (requires `FILTER_OPERATIONS` permission) |
|
|
189
244
|
| `POST` | `/PushNotificationToken` | Registers a push notification token for the current device |
|
|
190
|
-
| `GET` | `/hive-client.js` | Serves the built client-side SDK bundle |
|
|
245
|
+
| `GET` | `/hive-client.js` | Serves the built client-side SDK bundle (see [Client SDK](#client-sdk)) |
|
|
191
246
|
|
|
192
|
-
You do not need to implement any of these routes yourself.
|
|
247
|
+
You do not need to implement any of these routes yourself.
|
|
193
248
|
|
|
194
249
|
---
|
|
195
250
|
|
|
@@ -198,12 +253,20 @@ You do not need to implement any of these routes yourself. All of them are handl
|
|
|
198
253
|
On startup (after `initServer`), call `loadConfiguration` to decrypt and load credentials from disk into `process.env`.
|
|
199
254
|
|
|
200
255
|
```js
|
|
256
|
+
// ESM
|
|
201
257
|
import { loadConfiguration } from "@hivedev/hivesdk/server";
|
|
202
258
|
|
|
203
259
|
await loadConfiguration();
|
|
204
260
|
```
|
|
205
261
|
|
|
206
|
-
|
|
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):
|
|
207
270
|
|
|
208
271
|
| Variable | Contents |
|
|
209
272
|
|----------|----------|
|
|
@@ -211,22 +274,23 @@ This populates the following environment variables (only those with saved `.dat`
|
|
|
211
274
|
| `DATABASE_CREDENTIALS` | JSON string of database credentials, keyed by service name |
|
|
212
275
|
| `PERMISSIONS` | JSON string of permission definitions |
|
|
213
276
|
| `SMTP_CREDENTIALS` | JSON string of SMTP credentials |
|
|
214
|
-
| `SMTP_PASSWORD` | Extracted SMTP password, ready for use in
|
|
277
|
+
| `SMTP_PASSWORD` | Extracted SMTP password, ready for use in nodemailer |
|
|
215
278
|
|
|
216
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.
|
|
217
280
|
|
|
218
|
-
If a `.dat` file does not exist yet (e.g. first run before registration), that credential is
|
|
281
|
+
If a `.dat` file does not exist yet (e.g. first run before registration is approved), that credential is silently skipped.
|
|
219
282
|
|
|
220
|
-
>
|
|
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.
|
|
221
284
|
|
|
222
285
|
---
|
|
223
286
|
|
|
224
287
|
### 5. Connecting to the Database
|
|
225
288
|
|
|
226
|
-
|
|
289
|
+
After `loadConfiguration`, use `DatabaseConnector` to establish a MongoDB connection.
|
|
227
290
|
|
|
228
291
|
```js
|
|
229
|
-
import
|
|
292
|
+
// ESM — import directly from the SDK's built file
|
|
293
|
+
import DatabaseConnector from "./node_modules/@hivedev/hivesdk/DatabaseConnector.js";
|
|
230
294
|
|
|
231
295
|
const connected = await DatabaseConnector.connect();
|
|
232
296
|
|
|
@@ -236,11 +300,26 @@ if (!connected)
|
|
|
236
300
|
process.exit(1);
|
|
237
301
|
}
|
|
238
302
|
|
|
239
|
-
// DatabaseConnector.databaseObject is now a live
|
|
303
|
+
// DatabaseConnector.databaseObject is now a live MongoDB db instance
|
|
240
304
|
const collection = DatabaseConnector.databaseObject.collection("my_collection");
|
|
241
305
|
```
|
|
242
306
|
|
|
243
|
-
|
|
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.
|
|
244
323
|
|
|
245
324
|
**Disconnecting:**
|
|
246
325
|
|
|
@@ -252,15 +331,18 @@ await DatabaseConnector.disconnect();
|
|
|
252
331
|
|
|
253
332
|
### 6. Guarding Routes
|
|
254
333
|
|
|
255
|
-
Use `isLoggedIn` and `isLoggedInWithPermission` to protect your own route handlers. Both
|
|
334
|
+
Use `isLoggedIn` and `isLoggedInWithPermission` to protect your own route handlers. Both accept the request and response objects and a `bSendResponse` boolean.
|
|
256
335
|
|
|
257
|
-
When `bSendResponse` is `false`, the
|
|
336
|
+
When `bSendResponse` is `false`, the function returns a boolean and leaves the response untouched — use this to guard logic inside your own handlers.
|
|
258
337
|
|
|
259
|
-
When `bSendResponse` is `true`, the
|
|
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.
|
|
260
341
|
|
|
261
|
-
**Guarding a route with login check:**
|
|
342
|
+
**Guarding a route with a login check:**
|
|
262
343
|
|
|
263
344
|
```js
|
|
345
|
+
// ESM
|
|
264
346
|
import { isLoggedIn } from "@hivedev/hivesdk/server";
|
|
265
347
|
|
|
266
348
|
app.get("/my-protected-route", async (request, response) =>
|
|
@@ -278,11 +360,31 @@ app.get("/my-protected-route", async (request, response) =>
|
|
|
278
360
|
});
|
|
279
361
|
```
|
|
280
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
|
+
|
|
281
382
|
**Guarding a route with a permission check:**
|
|
282
383
|
|
|
283
384
|
The permission name must be set on `request.body.permissionName` before calling `isLoggedInWithPermission`.
|
|
284
385
|
|
|
285
386
|
```js
|
|
387
|
+
// ESM
|
|
286
388
|
import { isLoggedInWithPermission } from "@hivedev/hivesdk/server";
|
|
287
389
|
|
|
288
390
|
app.post("/admin-action", async (request, response) =>
|
|
@@ -302,13 +404,33 @@ app.post("/admin-action", async (request, response) =>
|
|
|
302
404
|
});
|
|
303
405
|
```
|
|
304
406
|
|
|
305
|
-
|
|
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
|
+
```
|
|
306
427
|
|
|
307
428
|
---
|
|
308
429
|
|
|
309
430
|
### 7. Sending Mail
|
|
310
431
|
|
|
311
432
|
```js
|
|
433
|
+
// ESM
|
|
312
434
|
import { sendMail } from "@hivedev/hivesdk/server";
|
|
313
435
|
|
|
314
436
|
await sendMail(
|
|
@@ -319,7 +441,19 @@ await sendMail(
|
|
|
319
441
|
);
|
|
320
442
|
```
|
|
321
443
|
|
|
322
|
-
|
|
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.
|
|
323
457
|
|
|
324
458
|
---
|
|
325
459
|
|
|
@@ -332,83 +466,141 @@ All utilities are exported from `@hivedev/hivesdk/server`.
|
|
|
332
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>`.
|
|
333
467
|
|
|
334
468
|
```js
|
|
469
|
+
// ESM
|
|
335
470
|
import { getAppDataDirectory } from "@hivedev/hivesdk/server";
|
|
336
|
-
|
|
337
471
|
const dataDir = getAppDataDirectory();
|
|
338
472
|
// e.g. "/home/user/.config/NoteHive"
|
|
339
473
|
```
|
|
340
474
|
|
|
475
|
+
```js
|
|
476
|
+
// CJS
|
|
477
|
+
const { getAppDataDirectory } = require("@hivedev/hivesdk/server");
|
|
478
|
+
const dataDir = getAppDataDirectory();
|
|
479
|
+
```
|
|
480
|
+
|
|
341
481
|
**`getHostIp()`**
|
|
342
482
|
|
|
343
|
-
Returns the first non-loopback IPv4 address of the host machine.
|
|
483
|
+
Returns the first non-loopback IPv4 address of the host machine.
|
|
344
484
|
|
|
345
485
|
```js
|
|
486
|
+
// ESM
|
|
346
487
|
import { getHostIp } from "@hivedev/hivesdk/server";
|
|
347
|
-
|
|
348
488
|
const ip = getHostIp(); // e.g. "192.168.1.42"
|
|
349
489
|
```
|
|
350
490
|
|
|
491
|
+
```js
|
|
492
|
+
// CJS
|
|
493
|
+
const { getHostIp } = require("@hivedev/hivesdk/server");
|
|
494
|
+
const ip = getHostIp();
|
|
495
|
+
```
|
|
496
|
+
|
|
351
497
|
**`findService(serviceName)`**
|
|
352
498
|
|
|
353
|
-
Discovers another Hive service on the LAN via UDP
|
|
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.
|
|
354
500
|
|
|
355
501
|
```js
|
|
502
|
+
// ESM
|
|
356
503
|
import { findService } from "@hivedev/hivesdk/server";
|
|
357
504
|
|
|
358
505
|
const service = await findService("NoteHive");
|
|
359
506
|
|
|
360
507
|
if (service)
|
|
361
508
|
{
|
|
362
|
-
console.log(service.urls.local);
|
|
509
|
+
console.log(service.urls.local); // "http://192.168.1.42:49161"
|
|
363
510
|
}
|
|
364
511
|
```
|
|
365
512
|
|
|
366
|
-
|
|
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
|
+
```
|
|
367
524
|
|
|
368
525
|
**`extractConfigurationForService()`**
|
|
369
526
|
|
|
370
527
|
Parses `process.env` and returns the configuration as structured objects, extracting the correct database credentials for the current service name.
|
|
371
528
|
|
|
372
529
|
```js
|
|
530
|
+
// ESM
|
|
373
531
|
import { extractConfigurationForService } from "@hivedev/hivesdk/server";
|
|
374
532
|
|
|
375
533
|
const { firebaseAdminCredentials, databaseCredentials, permissions, smtpCredentials } = extractConfigurationForService();
|
|
376
534
|
```
|
|
377
535
|
|
|
536
|
+
```js
|
|
537
|
+
// CJS
|
|
538
|
+
const { extractConfigurationForService } = require("@hivedev/hivesdk/server");
|
|
539
|
+
|
|
540
|
+
const { firebaseAdminCredentials, databaseCredentials, permissions, smtpCredentials } = extractConfigurationForService();
|
|
541
|
+
```
|
|
542
|
+
|
|
378
543
|
**`encryptAndStoreFile(data, fullPath, password)`**
|
|
379
544
|
|
|
380
|
-
Encrypts a string or Buffer with AES-256-CBC and writes it to disk.
|
|
545
|
+
Encrypts a string or Buffer with AES-256-CBC and writes it to disk. Creates the directory if it doesn't exist.
|
|
381
546
|
|
|
382
547
|
```js
|
|
548
|
+
// ESM
|
|
383
549
|
import { encryptAndStoreFile } from "@hivedev/hivesdk/server";
|
|
384
550
|
|
|
385
551
|
await encryptAndStoreFile("my secret data", "/path/to/file.dat", process.env.SERVER_PASSWORD);
|
|
386
552
|
```
|
|
387
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
|
+
|
|
388
561
|
**`decryptFileWithPassword(fullPath, password)`**
|
|
389
562
|
|
|
390
563
|
Reads and decrypts an encrypted `.dat` file. Returns the decrypted string, or `null` on failure.
|
|
391
564
|
|
|
392
565
|
```js
|
|
566
|
+
// ESM
|
|
393
567
|
import { decryptFileWithPassword } from "@hivedev/hivesdk/server";
|
|
394
568
|
|
|
395
569
|
const contents = await decryptFileWithPassword("/path/to/file.dat", process.env.SERVER_PASSWORD);
|
|
396
570
|
```
|
|
397
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
|
+
|
|
398
579
|
**`saveConfiguration(configurationObject)` / `loadConfiguration()`**
|
|
399
580
|
|
|
400
|
-
Normally called automatically by `registerService` and during startup respectively. Available for manual use if needed.
|
|
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.
|
|
401
582
|
|
|
402
583
|
```js
|
|
584
|
+
// ESM
|
|
403
585
|
import { saveConfiguration, loadConfiguration } from "@hivedev/hivesdk/server";
|
|
404
586
|
|
|
405
|
-
// Save a configuration object (any subset of keys is valid — only present keys are written)
|
|
406
587
|
await saveConfiguration({
|
|
407
588
|
databaseCredentials: { NoteHive: { host: "127.0.0.1", username: "admin", password: "pass", name: "notehive" } },
|
|
408
589
|
smtpCredentials: { password: "smtp-password" }
|
|
409
590
|
});
|
|
410
591
|
|
|
411
|
-
|
|
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
|
+
|
|
412
604
|
await loadConfiguration();
|
|
413
605
|
```
|
|
414
606
|
|
|
@@ -419,6 +611,7 @@ await loadConfiguration();
|
|
|
419
611
|
This is the recommended startup sequence for any Hive microservice.
|
|
420
612
|
|
|
421
613
|
```js
|
|
614
|
+
// ESM
|
|
422
615
|
import express from "express";
|
|
423
616
|
import cookieParser from "cookie-parser";
|
|
424
617
|
import cluster from "cluster";
|
|
@@ -435,7 +628,6 @@ app.use(express.json());
|
|
|
435
628
|
app.use(cookieParser());
|
|
436
629
|
app.use(handleHiveRequests);
|
|
437
630
|
|
|
438
|
-
// Your own routes
|
|
439
631
|
app.get("/", (request, response) => response.redirect("/Client/Pages/HomePage.html"));
|
|
440
632
|
|
|
441
633
|
if (cluster.isPrimary)
|
|
@@ -448,7 +640,6 @@ if (cluster.isPrimary)
|
|
|
448
640
|
|
|
449
641
|
await loadConfiguration();
|
|
450
642
|
|
|
451
|
-
// Delay slightly so workers are ready before registration begins
|
|
452
643
|
setTimeout(async () =>
|
|
453
644
|
{
|
|
454
645
|
await registerService();
|
|
@@ -476,33 +667,109 @@ else
|
|
|
476
667
|
}
|
|
477
668
|
```
|
|
478
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
|
+
|
|
479
730
|
---
|
|
480
731
|
|
|
481
732
|
## Client SDK
|
|
482
733
|
|
|
483
|
-
The client SDK is a browser bundle
|
|
734
|
+
The client SDK is a browser-only ESM bundle (`hive-client.js`). There is no CJS variant — it runs exclusively in the browser.
|
|
484
735
|
|
|
485
736
|
### 1. Loading the Client Bundle
|
|
486
737
|
|
|
487
|
-
|
|
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.
|
|
488
739
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
+
});
|
|
493
752
|
```
|
|
494
753
|
|
|
495
|
-
|
|
754
|
+
Include it in your HTML pages as a module script:
|
|
496
755
|
|
|
497
756
|
```html
|
|
498
757
|
<script type="module" src="/hive-client.js"></script>
|
|
499
758
|
```
|
|
500
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
|
+
|
|
501
768
|
---
|
|
502
769
|
|
|
503
770
|
### 2. Initialising the Client
|
|
504
771
|
|
|
505
|
-
Call `initClient` once on page load. It sets up app detection, cookie
|
|
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.
|
|
506
773
|
|
|
507
774
|
```js
|
|
508
775
|
import { initClient } from "/hive-client.js";
|
|
@@ -516,54 +783,45 @@ await initClient({});
|
|
|
516
783
|
|-----------|------|---------|-------------|
|
|
517
784
|
| `loginTokenStorageMethod` | `sessionStorageMethod` enum | `HTTP_ONLY_COOKIE` | How session tokens are stored on the client |
|
|
518
785
|
|
|
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
786
|
---
|
|
522
787
|
|
|
523
788
|
### 3. Login
|
|
524
789
|
|
|
525
|
-
Call `login()` to open the HivePortal login
|
|
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 `/`.
|
|
526
791
|
|
|
527
792
|
```js
|
|
528
793
|
import { login } from "/hive-client.js";
|
|
529
794
|
|
|
530
|
-
|
|
531
|
-
{
|
|
532
|
-
await login();
|
|
533
|
-
});
|
|
795
|
+
login();
|
|
534
796
|
```
|
|
535
797
|
|
|
536
|
-
|
|
798
|
+
You can wire it to any element:
|
|
537
799
|
|
|
538
|
-
|
|
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`.
|
|
539
807
|
|
|
540
808
|
---
|
|
541
809
|
|
|
542
810
|
### 4. Checking Login State
|
|
543
811
|
|
|
544
|
-
`initClient` automatically starts a periodic login check (every 30 seconds
|
|
545
|
-
|
|
546
|
-
Listen for this event to reactively show or hide UI based on login state:
|
|
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`.
|
|
547
813
|
|
|
548
814
|
```js
|
|
549
815
|
window.addEventListener("login-state-changed", (event) =>
|
|
550
816
|
{
|
|
551
817
|
const isLoggedIn = event.detail;
|
|
552
818
|
|
|
553
|
-
|
|
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
|
-
}
|
|
819
|
+
document.getElementById("login-prompt").style.display = isLoggedIn ? "none" : "block";
|
|
820
|
+
document.getElementById("dashboard").style.display = isLoggedIn ? "block" : "none";
|
|
563
821
|
});
|
|
564
822
|
```
|
|
565
823
|
|
|
566
|
-
The current login state is also available synchronously at any time
|
|
824
|
+
The current login state is also available synchronously at any time:
|
|
567
825
|
|
|
568
826
|
```js
|
|
569
827
|
if (window.IS_LOGGED_IN)
|
|
@@ -576,8 +834,6 @@ if (window.IS_LOGGED_IN)
|
|
|
576
834
|
|
|
577
835
|
### Full Client Bootstrap Example
|
|
578
836
|
|
|
579
|
-
A typical HTML page in a Hive service would look like this:
|
|
580
|
-
|
|
581
837
|
```html
|
|
582
838
|
<!DOCTYPE html>
|
|
583
839
|
<html lang="en">
|
|
@@ -598,10 +854,8 @@ A typical HTML page in a Hive service would look like this:
|
|
|
598
854
|
<script type="module">
|
|
599
855
|
import { initClient, login } from "/hive-client.js";
|
|
600
856
|
|
|
601
|
-
// Initialise the SDK — sets up app detection, cookies, and login polling
|
|
602
857
|
await initClient({});
|
|
603
858
|
|
|
604
|
-
// React to login state changes
|
|
605
859
|
window.addEventListener("login-state-changed", (event) =>
|
|
606
860
|
{
|
|
607
861
|
const isLoggedIn = event.detail;
|
|
@@ -609,11 +863,7 @@ A typical HTML page in a Hive service would look like this:
|
|
|
609
863
|
document.getElementById("dashboard").style.display = isLoggedIn ? "block" : "none";
|
|
610
864
|
});
|
|
611
865
|
|
|
612
|
-
|
|
613
|
-
document.getElementById("login-button").addEventListener("click", async () =>
|
|
614
|
-
{
|
|
615
|
-
await login();
|
|
616
|
-
});
|
|
866
|
+
document.getElementById("login-button").addEventListener("click", login);
|
|
617
867
|
</script>
|
|
618
868
|
|
|
619
869
|
</body>
|
|
@@ -624,7 +874,7 @@ A typical HTML page in a Hive service would look like this:
|
|
|
624
874
|
|
|
625
875
|
## Enumerations
|
|
626
876
|
|
|
627
|
-
The SDK exports several enumerations
|
|
877
|
+
The SDK exports several enumerations used throughout the Hive ecosystem.
|
|
628
878
|
|
|
629
879
|
**`userPrivileges`** — Numeric privilege levels assigned to users.
|
|
630
880
|
|
|
@@ -677,17 +927,15 @@ The SDK exports several enumerations from the client bundle. These are shared co
|
|
|
677
927
|
|
|
678
928
|
## Environment Variables Reference
|
|
679
929
|
|
|
680
|
-
These variables are either expected to be set externally or are populated by the SDK itself.
|
|
681
|
-
|
|
682
930
|
| Variable | Set by | Description |
|
|
683
931
|
|----------|--------|-------------|
|
|
684
932
|
| `SERVER_PASSWORD` | External / `setupPassword()` | Master password for encrypting/decrypting config files |
|
|
685
933
|
| `SERVICE_NAME` | `initServer` | Name of this service as registered with HivePortal |
|
|
686
934
|
| `SERVICE_PORT` | `initServer` | Port this service listens on |
|
|
687
935
|
| `REMOTE_URL` | `initServer` | Public-facing URL of this service (empty if LAN-only) |
|
|
688
|
-
| `HIVE_PORTAL_LAN_IP` | External | LAN IP
|
|
936
|
+
| `HIVE_PORTAL_LAN_IP` | External | LAN IP of HivePortal, used by `findService` for UDP discovery |
|
|
689
937
|
| `FIREBASE_ADMIN_CREDENTIALS` | `loadConfiguration` | JSON string of Firebase Admin credentials |
|
|
690
|
-
| `DATABASE_CREDENTIALS` | `loadConfiguration` | JSON string of
|
|
938
|
+
| `DATABASE_CREDENTIALS` | `loadConfiguration` | JSON string of DB credentials keyed by service name |
|
|
691
939
|
| `PERMISSIONS` | `loadConfiguration` | JSON string of permission definitions |
|
|
692
940
|
| `SMTP_CREDENTIALS` | `loadConfiguration` | JSON string of SMTP credentials |
|
|
693
941
|
| `SMTP_PASSWORD` | `loadConfiguration` | Extracted SMTP password, ready for nodemailer |
|
|
@@ -711,29 +959,29 @@ These variables are either expected to be set externally or are populated by the
|
|
|
711
959
|
┌────────────────────┼────────────────────┐
|
|
712
960
|
│ │ │
|
|
713
961
|
┌──────────▼──────────┐ │ ┌──────────▼──────────┐
|
|
714
|
-
│ NoteHive │
|
|
715
|
-
│ (port 49161) │
|
|
716
|
-
│ │
|
|
717
|
-
│ uses @hivedev/ │
|
|
718
|
-
│ hivesdk │
|
|
719
|
-
└──────────────────────┘
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
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
|
|
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.
|
|
730
978
|
|
|
731
979
|
---
|
|
732
980
|
|
|
733
981
|
## Known Limitations
|
|
734
982
|
|
|
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
|
|
736
|
-
- **`loginTokenStorageMethod`** — The `LOCAL_STORAGE` and `SESSION_STORAGE` options in the `sessionStorageMethod` enum are defined but not yet implemented
|
|
737
|
-
- **SMTP sender address** — The sender Gmail address in `SendMail.js` is hardcoded.
|
|
738
|
-
- **MongoDB only** — `DatabaseConnector` is built
|
|
739
|
-
- **
|
|
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.
|