@hivedev/hivesdk 1.0.35 → 1.0.37

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 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 administrators approve it. Once approved, HivePortal sends back an encrypted configuration payload containing database credentials, Firebase credentials, permissions, and SMTP credentials.
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`. This means credentials never sit on disk in plaintext.
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` — CommonJS bundle for Node.js backends
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 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`.
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 will load configuration from disk directly via `loadConfiguration` and will not need to go through this polling process again, unless credentials change.
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 (e.g. Node.js `cluster` module), call `registerService` only from the primary process, after a short delay to allow workers to start up first.
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
- // Your own routes go here
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. All of them are handled and proxied to HivePortal automatically.
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
- This populates the following environment variables (only those with saved `.dat` files are loaded):
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 `nodemailer` |
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 simply skipped.
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
- > 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.
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
- The SDK includes a MongoDB connector. After `loadConfiguration`, call `DatabaseConnector.connect()` to establish the connection.
289
+ After `loadConfiguration`, use `DatabaseConnector` to establish a MongoDB connection.
227
290
 
228
291
  ```js
229
- import DatabaseConnector from "@hivedev/hivesdk/server";
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 Mongo db instance
303
+ // DatabaseConnector.databaseObject is now a live MongoDB db instance
240
304
  const collection = DatabaseConnector.databaseObject.collection("my_collection");
241
305
  ```
242
306
 
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.
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 functions accept the request and response objects and a boolean `bSendResponse` flag.
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 functions return a boolean and leave the response untouched — use this when you need to guard logic inside a handler.
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 functions write the result directly to the response — this is used internally by `handleHiveRequests` for the `/IsLoggedIn` and `/IsLoggedInWithPermission` endpoints.
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
- 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.
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
- 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.
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. Used internally by `registerService` to build the local service URL.
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 broadcast to port 49153. Returns an object with `{ name, urls: { local, remote } }` or `null` if not found.
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); // "http://192.168.1.42:49161"
509
+ console.log(service.urls.local); // "http://192.168.1.42:49161"
363
510
  }
364
511
  ```
365
512
 
366
- Requires `HIVE_PORTAL_LAN_IP` environment variable to be set. If not set, returns a default localhost URL.
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
- // Load configuration from disk into process.env
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. It is automatically served at `/hive-client.js` by the `handleHiveRequests` middlewareyou do not need to copy or host the file yourself.
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
- Include the script in your HTML pages:
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
- ```html
490
- <script type="module">
491
- import { initClient, login } from "/hive-client.js";
492
- </script>
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
- Or via a module script tag:
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 signalling, and the periodic login state checker.
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 popup. The SDK handles the entire flow — opening the window, receiving credentials via `postMessage`, posting to `/Login`, and redirecting on success.
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
- document.getElementById("login-button").addEventListener("click", async () =>
531
- {
532
- await login();
533
- });
795
+ login();
534
796
  ```
535
797
 
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`.
798
+ You can wire it to any element:
537
799
 
538
- On success, the page redirects to `/`. On failure, the login page itself displays the error.
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 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:
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
- 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
- }
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 via `window.IS_LOGGED_IN`.
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
- // Wire up the login button
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 from the client bundle. These are shared constants used throughout the Hive ecosystem.
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 address of HivePortal, used by `findService` for UDP discovery |
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 database credentials keyed by service name |
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 │ 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.
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 (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.
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.
package/hive-server.cjs CHANGED
@@ -619,7 +619,7 @@ async function initServer({ serviceName, servicePort, remoteUrl, serverPassword
619
619
  if (!serverPassword) {
620
620
  serverPassword = process.env.SERVER_PASSWORD;
621
621
  if (!serverPassword) {
622
- serverPassword = await HiveServerGlobals_default.setupPassword();
622
+ throw new Error("Server password is not provided. Please set the SERVER_PASSWORD environment variable.");
623
623
  }
624
624
  }
625
625
  HiveServerGlobals_default.initialize({ serviceName, servicePort, remoteUrl });
package/hive-server.js CHANGED
@@ -592,7 +592,7 @@ async function initServer({ serviceName, servicePort, remoteUrl, serverPassword
592
592
  if (!serverPassword) {
593
593
  serverPassword = process.env.SERVER_PASSWORD;
594
594
  if (!serverPassword) {
595
- serverPassword = await HiveServerGlobals_default.setupPassword();
595
+ throw new Error("Server password is not provided. Please set the SERVER_PASSWORD environment variable.");
596
596
  }
597
597
  }
598
598
  HiveServerGlobals_default.initialize({ serviceName, servicePort, remoteUrl });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hivedev/hivesdk",
3
3
  "type": "module",
4
- "version": "1.0.35",
4
+ "version": "1.0.37",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
7
7
  "scripts": {