@teincfood/core 0.10.0 → 0.10.2
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/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/local-node/service.js +11 -0
- package/dist/offline/random.d.ts +3 -0
- package/dist/offline/random.js +14 -0
- package/dist/offline/service.d.ts +0 -1
- package/dist/offline/service.js +2 -11
- package/dist/offline/store.js +5 -5
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -157,6 +157,7 @@ export { notifyAuthChanged, subscribeAuth } from "./adapters/auth";
|
|
|
157
157
|
export { isAuthenticated, hasActiveBusiness, canBusinessSync, setOfflineSyncEnabled, isOfflineSyncEnabled } from "./sync/sync-guard";
|
|
158
158
|
export { ACCESS_EXPIRED_CODE, SESSION_DEAD_CODES, SKIP_AUTH_RETRY, extractAuthFailure, isAccessExpired, isSessionDead, hasAuthRetrySkip, refreshSession, withAuthRetry, setOfflineSessionRecoveryProvider, } from "./auth/session";
|
|
159
159
|
export * from "./offline/license";
|
|
160
|
+
export * from "./offline/random";
|
|
160
161
|
export * from "./offline/store";
|
|
161
162
|
export * from "./offline/service";
|
|
162
163
|
export type { AuthFailure, SessionTokens } from "./auth/session";
|
package/dist/index.js
CHANGED
|
@@ -173,6 +173,7 @@ export { isAuthenticated, hasActiveBusiness, canBusinessSync, setOfflineSyncEnab
|
|
|
173
173
|
export { ACCESS_EXPIRED_CODE, SESSION_DEAD_CODES, SKIP_AUTH_RETRY, extractAuthFailure, isAccessExpired, isSessionDead, hasAuthRetrySkip, refreshSession, withAuthRetry, setOfflineSessionRecoveryProvider, } from "./auth/session";
|
|
174
174
|
// ── Offline device license + member unlock ──
|
|
175
175
|
export * from "./offline/license";
|
|
176
|
+
export * from "./offline/random";
|
|
176
177
|
export * from "./offline/store";
|
|
177
178
|
export * from "./offline/service";
|
|
178
179
|
// ── Image cache (durable) ──
|
|
@@ -10,6 +10,7 @@ import { applyPendingOrderEvent, applyOrderStateChangeEvent, notifyPendingOrders
|
|
|
10
10
|
import { applyMenuCommandLocal } from "../reference/menu-apply";
|
|
11
11
|
import { REFERENCE_CHANGED_EVENT_TYPE } from "../reference/events";
|
|
12
12
|
import { applyReferenceChangeEvent } from "../reference/events";
|
|
13
|
+
import { referenceSyncService } from "../reference/service";
|
|
13
14
|
import { openSyncDatabase } from "../db/connection";
|
|
14
15
|
import { insertOrder } from "../db/operations";
|
|
15
16
|
import { createLocalOrder, transitionLocalOrder } from "../domain/orders/service";
|
|
@@ -567,6 +568,16 @@ class LocalNodeServiceCore {
|
|
|
567
568
|
syncLogger.info("LocalNodeService", "Connected to peer", { deviceId: node.deviceId });
|
|
568
569
|
this.peerInfos.set(node.deviceId, { deviceId: node.deviceId, host: node.host, port: node.port });
|
|
569
570
|
localNodeStore.getState().addConnectedClient({ deviceId: node.deviceId, deviceName: node.deviceName, host: node.host, connectedAt: new Date().toISOString() });
|
|
571
|
+
// A terminal may have missed `reference_data.changed` while it was
|
|
572
|
+
// disconnected. Compare versions with the peer on reconnect so menu,
|
|
573
|
+
// members, hours and tax changes converge even on a dark LAN.
|
|
574
|
+
const connectivity = getConnectivityState();
|
|
575
|
+
if (this.businessId &&
|
|
576
|
+
(!connectivity.isOnline || connectivity.isCloudReachable === "unreachable")) {
|
|
577
|
+
void referenceSyncService
|
|
578
|
+
.sync({ businessId: this.businessId })
|
|
579
|
+
.catch((e) => syncLogger.warn("LocalNodeService", "LAN reference catch-up failed", { error: String(e) }));
|
|
580
|
+
}
|
|
570
581
|
},
|
|
571
582
|
onDisconnected: () => {
|
|
572
583
|
this.settleDial(node.deviceId);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Shared secure-random provider for offline keys, nonces and PIN salts. */
|
|
2
|
+
let provider = null;
|
|
3
|
+
export function setOfflineRandomBytesProvider(next) {
|
|
4
|
+
provider = next;
|
|
5
|
+
}
|
|
6
|
+
export function offlineRandomBytes(length) {
|
|
7
|
+
if (provider)
|
|
8
|
+
return provider(length);
|
|
9
|
+
const cryptoObj = globalThis.crypto;
|
|
10
|
+
if (cryptoObj?.getRandomValues) {
|
|
11
|
+
return cryptoObj.getRandomValues(new Uint8Array(length));
|
|
12
|
+
}
|
|
13
|
+
throw new Error("No secure random source available. Call setOfflineRandomBytesProvider().");
|
|
14
|
+
}
|
|
@@ -34,7 +34,6 @@ export interface OfflineSessionState {
|
|
|
34
34
|
keyId: string | null;
|
|
35
35
|
members: OfflineMemberCredential[];
|
|
36
36
|
}
|
|
37
|
-
export declare function setOfflineRandomBytesProvider(provider: ((length: number) => Uint8Array) | null): void;
|
|
38
37
|
/**
|
|
39
38
|
* First-time activation. Generates the device keypair, registers the public
|
|
40
39
|
* key, stores the signed license and enrolls the current member's PIN.
|
package/dist/offline/service.js
CHANGED
|
@@ -9,20 +9,11 @@ import { getAppInstanceIdAsync } from "../adapters/devices";
|
|
|
9
9
|
import { getHttpClient } from "../adapters/http";
|
|
10
10
|
import { getAuthToken } from "../adapters/auth";
|
|
11
11
|
import { syncLogger } from "../utils/sync-logger";
|
|
12
|
+
import { offlineRandomBytes } from "./random";
|
|
12
13
|
import { base64UrlDecode, base64UrlEncode, canonicalDeviceChallenge, decodeLicensePayload, evaluateOfflineLicense, generateOfflineDeviceKeypair, loadAndVerifyOfflineLicense, newOfflineNonce, signDeviceChallenge, verifyOfflineLicense, } from "./license";
|
|
13
14
|
import { clearOfflineDevice, createOfflineMemberCredential, getDevicePrivateKey, getOfflineMember, getStoredLicense, getStoredLicenseKeyId, getStoredLicensePublicKey, listOfflineMembers, markOfflineMemberLogin, removeOfflineMember, saveDevicePrivateKey, saveStoredLicense, saveStoredLicenseKeyId, saveStoredLicensePublicKey, verifyOfflineMemberPin, } from "./store";
|
|
14
|
-
let randomBytesProvider = null;
|
|
15
|
-
export function setOfflineRandomBytesProvider(provider) {
|
|
16
|
-
randomBytesProvider = provider;
|
|
17
|
-
}
|
|
18
15
|
function randomBytes(length) {
|
|
19
|
-
|
|
20
|
-
return randomBytesProvider(length);
|
|
21
|
-
const cryptoObj = globalThis.crypto;
|
|
22
|
-
if (cryptoObj?.getRandomValues) {
|
|
23
|
-
return cryptoObj.getRandomValues(new Uint8Array(length));
|
|
24
|
-
}
|
|
25
|
-
throw new Error("No secure random source available. Call setOfflineRandomBytesProvider().");
|
|
16
|
+
return offlineRandomBytes(length);
|
|
26
17
|
}
|
|
27
18
|
async function getPrivateKeyBytes() {
|
|
28
19
|
const encoded = await getDevicePrivateKey();
|
package/dist/offline/store.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { getItem, removeItem, setItem } from "../adapters/kv";
|
|
12
12
|
import { sha256Hex } from "../reference/entitlement-crypto";
|
|
13
|
+
import { offlineRandomBytes } from "./random";
|
|
13
14
|
export const OFFLINE_STORAGE_KEYS = {
|
|
14
15
|
LICENSE: "offline:license",
|
|
15
16
|
PUBLIC_KEY: "offline:license:public_key",
|
|
@@ -88,12 +89,11 @@ export async function clearOfflineDevice() {
|
|
|
88
89
|
}
|
|
89
90
|
const PIN_ROUNDS = 20000;
|
|
90
91
|
function randomHex(length) {
|
|
91
|
-
const
|
|
92
|
+
const bytes = offlineRandomBytes(Math.ceil(length / 2));
|
|
92
93
|
let out = "";
|
|
93
|
-
for (let i = 0; i < length; i++)
|
|
94
|
-
out +=
|
|
95
|
-
|
|
96
|
-
return out;
|
|
94
|
+
for (let i = 0; i < bytes.length; i++)
|
|
95
|
+
out += bytes[i].toString(16).padStart(2, "0");
|
|
96
|
+
return out.slice(0, length);
|
|
97
97
|
}
|
|
98
98
|
async function hashPin(pin, salt) {
|
|
99
99
|
let digest = `${salt}:${pin}`;
|
package/package.json
CHANGED