openvibers/openvibe.tips Commits
Tips: the key provider is openvibe-sdk/auth createNetworkKeys (SDK 0.40.0) (#36)
Changes
@@@ -452,6 +452,6 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [<!-- versions:start -->- openvibe-contracts: v0.129.0--- openvibe-sdk: v0.38.1++- openvibe-sdk: v0.40.0- openvibe-shared: v3.0.0<!-- versions:end -->@@@ -10,7 +10,7 @@"deployment": "Deployed internally, not launched: deployed with `sudo ovhost deploy tips`, /opt/openvibe.tips, unit openvibe-tips, 127.0.0.1:4610 loopback only; https://openvibe.tips still serves the OpenVibe.Sites placeholder. Principal tips; Events subscriptions billing.transaction.* and billing.receipt.* -> /internal/events (secret rotated 2026-09-24); TIPS_CHAT_ADAPTER is empty (no chat side effects); TIPS_CHAT_ADAPTER=chat delivers through OpenVibe.Chat's ingress once tips is granted chat.message.send, chat.event.publish and identity.subject.resolve.","database": "PostgreSQL 18 through PgBouncer (transaction mode) as the system of record, Valkey 9 for shared state (per-actor limits, overlay fan-out, overlay stream slots), ADR-035: database ov_tips from OpenVibe.Host's data role, schema in migrations/ (0001_initial expand, 0002_drop_legacy_import contract), async on openvibe-sdk/db v0.28.0, money paths serializable with row locks, PostgreSQL outbox and inbox.","contracts": "openvibe-contracts v0.129.0",-- "sdk": "openvibe-sdk v0.38.1",++ "sdk": "openvibe-sdk v0.40.0","shared": "openvibe-shared v3.0.0","notes": "Runtime, API, SSR pages, overlays, the Billing settlement consumer and the events outbox are built, tested and deployed, together with /metrics (loopback only), truthful /api/ready (Billing and Events checks), supporter privacy (anonymous, hidden amount, private message), goal and supporter-page settings, export and erasure (tips.interaction.erased), moderation (word filter, hide/show, invited moderators, tips.interaction.moderated) and per-actor limits. Capabilities (18 tips.*) and payload schemas are in openvibe-contracts. No billing.* event had arrived: Billing runs in shadow. A written internal threat review (docs/threat-review.md) with its fixes and test/security.test.js. Not done: an independent security review; the openvibe.tips launch. Live stays the place tips happen until the Billing cutover (blocked on the owner re-pointing the PowerChat webhook) and the launch rule in README.md holds.","features": [@@@ -14,7 +14,7 @@"express": "^4.21.2","iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.129.0",-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.38.1",++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.40.0","openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.0","pg": "^8.23.0"},@@@ -859,9 +859,9 @@}},"node_modules/openvibe-sdk": {-- "version": "0.38.1",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.38.1",-- "integrity": "sha512-Pn3nji9Nc+4rMRjhwO9AhhR80OW7cwLo/NLObiVR/4TlcOxyk/V8DLzGHQmFjNxqg0xLBj6MOiXYQR2qAxEycg==",++ "version": "0.40.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.40.0",++ "integrity": "sha512-fYX0zijJZDdwjXJGwroLDlb/wniY6NXVtgdPVJJHNgcdRmaAydpr6SKuxcLffmkTquyZQptwChsCvo+D6Wx+IA==","license": "MIT","engines": {"node": ">=20"@@@ -26,7 +26,7 @@"express": "^4.21.2","iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.129.0",-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.38.1",++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.40.0","openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.0","pg": "^8.23.0"},@@@ -46,7 +46,7 @@ function createApiAuth({ config, keys, userAuth }) {const header = String(req.headers.authorization || '');if (!header.startsWith('Bearer ')) return { principal: ANON };const token = header.slice(7).trim();-- if (!keys.ready()) return { error: [503, 'identity.unavailable', 'the Network signing key is not loaded yet'] };++ if (!keys.loaded()) return { error: [503, 'identity.unavailable', 'the Network signing key is not loaded yet'] };const payload = decodePayload(token);if (payload && typeof payload.sub === 'string' && PRINCIPAL_SUB.test(payload.sub)) {let r;@@@ -55,7 +55,7 @@ async function start() {const app = createApp({ config, db, valkey, registry });const { domain, keys, outbox } = app.locals;-- keys.start();++ keys.start().catch(() => { /* logged by the JWKS client */ });const timers = [];if (config.jobs.enabled) {@@@ -3,35 +3,24 @@/*** OpenVibe.Network client.*-- * keys the Network's RS256 signing keys through openvibe-sdk/auth: one process-wide JWKS client per URL keeps-- * them fresh, serves the last good keys through a Network outage, backs off, and refetches at once when a-- * token names a key it does not have (a rotation). OV_NETWORK_PUBLIC_KEY pins one key instead. Service++ * keys the Network's RS256 signing keys: openvibe-sdk/auth createNetworkKeys. The JWKS is retried every 30 s++ * until it loads, then kept fresh; the last good keys are served through a Network outage, and a token that++ * names a key the client does not have refetches at once (a rotation). OV_NETWORK_PUBLIC_KEY pins one key++ * instead. Service* tokens (audience openvibe.tips) and the browser's Network user JWT are both verified offline.*/--const crypto = require('crypto');const contracts = require('openvibe-contracts');--const { jwksClient, verifyUserToken, verifyServiceToken } = require('openvibe-sdk/auth');++const { createNetworkKeys, verifyUserToken, verifyServiceToken } = require('openvibe-sdk/auth');const { OpenVibeAuthClient } = require('openvibe-shared/auth-client');++/** keys.verifyOptions (spread into the verifiers), keys.loaded(), keys.start(), keys.stop(), keys.refresh(). */function createKeyProvider(config, { fetchImpl = globalThis.fetch, log = console } = {}) {-- const url = `${config.network.internalUrl}/api/.well-known/jwks`;-- const pinned = config.network.publicKey ? crypto.createPublicKey(config.network.publicKey) : null;-- const client = pinned ? null : jwksClient(url, { fetch: fetchImpl, log });-- /** Where a verification takes its keys from (openvibe-sdk/auth options). */-- const source = pinned ? { publicKey: pinned } : { jwks: url, fetch: fetchImpl };-- return {-- source,-- ready: () => (pinned ? true : client.status().ready),-- /** Fetch the keys now (boot, tests); true when there is a key to verify with. */-- async load() { if (client) await client.refresh().catch(() => {}); return pinned ? true : client.status().ready; },-- start() { if (client) client.start(); },-- stop() { if (client) client.stop(); },-- };++ return createNetworkKeys({ network: config.network.internalUrl, publicKey: config.network.publicKey || null, fetch: fetchImpl, log });}/** A service principal's token (audience openvibe.tips): contracts' result { ok, code, reason, claims }. */async function verifyService(config, keys, token) {-- return await verifyServiceToken(token, { ...keys.source, issuer: config.network.issuer, audience: config.audience, contracts });++ return await verifyServiceToken(token, { ...keys.verifyOptions, issuer: config.network.issuer, audience: config.audience, contracts });}/**@@@ -53,7 +42,7 @@ function createUserAuth(config, keys) {*/async function verify(token) {if (!token) return null;-- try { return await verifyUserToken(token, { ...keys.source, issuer: config.network.issuer }); } catch { return null; }++ try { return await verifyUserToken(token, { ...keys.verifyOptions, issuer: config.network.issuer }); } catch { return null; }}return { client, verify };}@@@ -48,7 +48,7 @@ function createTipsReadiness({ db, valkey = null, keys, config, outbox, releasechecks: [{ name: 'db', required: true, check: () => db.ready() },{ name: 'valkey', required: false, check: () => (valkey ? valkey.ready() : skip('VALKEY_URL unset: limits, overlay fan-out and stream slots are per process (one process only)')) },-- { name: 'network_jwks', required: false, check: () => (keys.ready() ? true : 'Network signing key not loaded yet: tokens cannot be verified') },++ { name: 'network_jwks', required: false, check: () => (keys.loaded() ? true : 'Network signing key not loaded yet: tokens cannot be verified') },{ name: 'billing', required: false, cacheMs: PING_TTL_MS, timeoutMs: 2500, check: probe(`${config.billing.url}/api/health`, fetchImpl) },{ name: 'events', required: false, cacheMs: outbox.enabled ? PING_TTL_MS : 0, timeoutMs: 2500, check: events },],@@@ -65,7 +65,7 @@ async function boot(opts = {}) {const ownValkey = opts.valkey === undefined && process.env.TIPS_TEST_STORE === 'pg' ? testValkey() : null;const valkey = opts.valkey || ownValkey;const app = createApp({ config, db: store.db, valkey, now: () => Date.now() + clock.offset, log, ...(opts.appOpts || {}) });-- await app.locals.keys.load();++ await app.locals.keys.refresh();const server = await new Promise((resolve) => { const s = http.createServer(app); s.listen(0, '127.0.0.1', () => resolve(s)); });const base = `http://127.0.0.1:${server.address().port}`;const domain = app.locals.domain;
@@ -452,6 +452,6 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [
@@ -452,6 +452,6 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [
<!-- versions:start -->
<!-- versions:start -->
- openvibe-contracts: v0.129.0
- openvibe-contracts: v0.129.0
-- openvibe-sdk: v0.38.1
+- openvibe-sdk: v0.40.0
- openvibe-shared: v3.0.0
- openvibe-shared: v3.0.0
<!-- versions:end -->
<!-- versions:end -->
@@ -10,7 +10,7 @@
@@ -10,7 +10,7 @@
"deployment": "Deployed internally, not launched: deployed with `sudo ovhost deploy tips`, /opt/openvibe.tips, unit openvibe-tips, 127.0.0.1:4610 loopback only; https://openvibe.tips still serves the OpenVibe.Sites placeholder. Principal tips; Events subscriptions billing.transaction.* and billing.receipt.* -> /internal/events (secret rotated 2026-09-24); TIPS_CHAT_ADAPTER is empty (no chat side effects); TIPS_CHAT_ADAPTER=chat delivers through OpenVibe.Chat's ingress once tips is granted chat.message.send, chat.event.publish and identity.subject.resolve.",
"deployment": "Deployed internally, not launched: deployed with `sudo ovhost deploy tips`, /opt/openvibe.tips, unit openvibe-tips, 127.0.0.1:4610 loopback only; https://openvibe.tips still serves the OpenVibe.Sites placeholder. Principal tips; Events subscriptions billing.transaction.* and billing.receipt.* -> /internal/events (secret rotated 2026-09-24); TIPS_CHAT_ADAPTER is empty (no chat side effects); TIPS_CHAT_ADAPTER=chat delivers through OpenVibe.Chat's ingress once tips is granted chat.message.send, chat.event.publish and identity.subject.resolve.",
"database": "PostgreSQL 18 through PgBouncer (transaction mode) as the system of record, Valkey 9 for shared state (per-actor limits, overlay fan-out, overlay stream slots), ADR-035: database ov_tips from OpenVibe.Host's data role, schema in migrations/ (0001_initial expand, 0002_drop_legacy_import contract), async on openvibe-sdk/db v0.28.0, money paths serializable with row locks, PostgreSQL outbox and inbox.",
"database": "PostgreSQL 18 through PgBouncer (transaction mode) as the system of record, Valkey 9 for shared state (per-actor limits, overlay fan-out, overlay stream slots), ADR-035: database ov_tips from OpenVibe.Host's data role, schema in migrations/ (0001_initial expand, 0002_drop_legacy_import contract), async on openvibe-sdk/db v0.28.0, money paths serializable with row locks, PostgreSQL outbox and inbox.",
"contracts": "openvibe-contracts v0.129.0",
"contracts": "openvibe-contracts v0.129.0",
- "sdk": "openvibe-sdk v0.38.1",
+ "sdk": "openvibe-sdk v0.40.0",
"shared": "openvibe-shared v3.0.0",
"shared": "openvibe-shared v3.0.0",
"notes": "Runtime, API, SSR pages, overlays, the Billing settlement consumer and the events outbox are built, tested and deployed, together with /metrics (loopback only), truthful /api/ready (Billing and Events checks), supporter privacy (anonymous, hidden amount, private message), goal and supporter-page settings, export and erasure (tips.interaction.erased), moderation (word filter, hide/show, invited moderators, tips.interaction.moderated) and per-actor limits. Capabilities (18 tips.*) and payload schemas are in openvibe-contracts. No billing.* event had arrived: Billing runs in shadow. A written internal threat review (docs/threat-review.md) with its fixes and test/security.test.js. Not done: an independent security review; the openvibe.tips launch. Live stays the place tips happen until the Billing cutover (blocked on the owner re-pointing the PowerChat webhook) and the launch rule in README.md holds.",
"notes": "Runtime, API, SSR pages, overlays, the Billing settlement consumer and the events outbox are built, tested and deployed, together with /metrics (loopback only), truthful /api/ready (Billing and Events checks), supporter privacy (anonymous, hidden amount, private message), goal and supporter-page settings, export and erasure (tips.interaction.erased), moderation (word filter, hide/show, invited moderators, tips.interaction.moderated) and per-actor limits. Capabilities (18 tips.*) and payload schemas are in openvibe-contracts. No billing.* event had arrived: Billing runs in shadow. A written internal threat review (docs/threat-review.md) with its fixes and test/security.test.js. Not done: an independent security review; the openvibe.tips launch. Live stays the place tips happen until the Billing cutover (blocked on the owner re-pointing the PowerChat webhook) and the launch rule in README.md holds.",
"features": [
"features": [
@@ -14,7 +14,7 @@
@@ -14,7 +14,7 @@
"express": "^4.21.2",
"express": "^4.21.2",
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.129.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.129.0",
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.38.1",
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.40.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
@@ -859,9 +859,9 @@
@@ -859,9 +859,9 @@
}
}
},
},
"node_modules/openvibe-sdk": {
"node_modules/openvibe-sdk": {
- "version": "0.38.1",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.38.1",
- "integrity": "sha512-Pn3nji9Nc+4rMRjhwO9AhhR80OW7cwLo/NLObiVR/4TlcOxyk/V8DLzGHQmFjNxqg0xLBj6MOiXYQR2qAxEycg==",
+ "version": "0.40.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.40.0",
+ "integrity": "sha512-fYX0zijJZDdwjXJGwroLDlb/wniY6NXVtgdPVJJHNgcdRmaAydpr6SKuxcLffmkTquyZQptwChsCvo+D6Wx+IA==",
"license": "MIT",
"license": "MIT",
"engines": {
"engines": {
"node": ">=20"
"node": ">=20"
@@ -26,7 +26,7 @@
@@ -26,7 +26,7 @@
"express": "^4.21.2",
"express": "^4.21.2",
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.129.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.129.0",
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.38.1",
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.40.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v3.0.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
@@ -46,7 +46,7 @@ function createApiAuth({ config, keys, userAuth }) {
@@ -46,7 +46,7 @@ function createApiAuth({ config, keys, userAuth }) {
const header = String(req.headers.authorization || '');
const header = String(req.headers.authorization || '');
if (!header.startsWith('Bearer ')) return { principal: ANON };
if (!header.startsWith('Bearer ')) return { principal: ANON };
const token = header.slice(7).trim();
const token = header.slice(7).trim();
- if (!keys.ready()) return { error: [503, 'identity.unavailable', 'the Network signing key is not loaded yet'] };
+ if (!keys.loaded()) return { error: [503, 'identity.unavailable', 'the Network signing key is not loaded yet'] };
const payload = decodePayload(token);
const payload = decodePayload(token);
if (payload && typeof payload.sub === 'string' && PRINCIPAL_SUB.test(payload.sub)) {
if (payload && typeof payload.sub === 'string' && PRINCIPAL_SUB.test(payload.sub)) {
let r;
let r;
@@ -55,7 +55,7 @@ async function start() {
@@ -55,7 +55,7 @@ async function start() {
const app = createApp({ config, db, valkey, registry });
const app = createApp({ config, db, valkey, registry });
const { domain, keys, outbox } = app.locals;
const { domain, keys, outbox } = app.locals;
- keys.start();
+ keys.start().catch(() => { /* logged by the JWKS client */ });
const timers = [];
const timers = [];
if (config.jobs.enabled) {
if (config.jobs.enabled) {
@@ -3,35 +3,24 @@
@@ -3,35 +3,24 @@
/**
/**
* OpenVibe.Network client.
* OpenVibe.Network client.
*
*
- * keys the Network's RS256 signing keys through openvibe-sdk/auth: one process-wide JWKS client per URL keeps
- * them fresh, serves the last good keys through a Network outage, backs off, and refetches at once when a
- * token names a key it does not have (a rotation). OV_NETWORK_PUBLIC_KEY pins one key instead. Service
+ * keys the Network's RS256 signing keys: openvibe-sdk/auth createNetworkKeys. The JWKS is retried every 30 s
+ * until it loads, then kept fresh; the last good keys are served through a Network outage, and a token that
+ * names a key the client does not have refetches at once (a rotation). OV_NETWORK_PUBLIC_KEY pins one key
+ * instead. Service
* tokens (audience openvibe.tips) and the browser's Network user JWT are both verified offline.
* tokens (audience openvibe.tips) and the browser's Network user JWT are both verified offline.
*/
*/
-const crypto = require('crypto');
const contracts = require('openvibe-contracts');
const contracts = require('openvibe-contracts');
-const { jwksClient, verifyUserToken, verifyServiceToken } = require('openvibe-sdk/auth');
+const { createNetworkKeys, verifyUserToken, verifyServiceToken } = require('openvibe-sdk/auth');
const { OpenVibeAuthClient } = require('openvibe-shared/auth-client');
const { OpenVibeAuthClient } = require('openvibe-shared/auth-client');
+/** keys.verifyOptions (spread into the verifiers), keys.loaded(), keys.start(), keys.stop(), keys.refresh(). */
function createKeyProvider(config, { fetchImpl = globalThis.fetch, log = console } = {}) {
function createKeyProvider(config, { fetchImpl = globalThis.fetch, log = console } = {}) {
- const url = `${config.network.internalUrl}/api/.well-known/jwks`;
- const pinned = config.network.publicKey ? crypto.createPublicKey(config.network.publicKey) : null;
- const client = pinned ? null : jwksClient(url, { fetch: fetchImpl, log });
- /** Where a verification takes its keys from (openvibe-sdk/auth options). */
- const source = pinned ? { publicKey: pinned } : { jwks: url, fetch: fetchImpl };
- return {
- source,
- ready: () => (pinned ? true : client.status().ready),
- /** Fetch the keys now (boot, tests); true when there is a key to verify with. */
- async load() { if (client) await client.refresh().catch(() => {}); return pinned ? true : client.status().ready; },
- start() { if (client) client.start(); },
- stop() { if (client) client.stop(); },
- };
+ return createNetworkKeys({ network: config.network.internalUrl, publicKey: config.network.publicKey || null, fetch: fetchImpl, log });
}
}
/** A service principal's token (audience openvibe.tips): contracts' result { ok, code, reason, claims }. */
/** A service principal's token (audience openvibe.tips): contracts' result { ok, code, reason, claims }. */
async function verifyService(config, keys, token) {
async function verifyService(config, keys, token) {
- return await verifyServiceToken(token, { ...keys.source, issuer: config.network.issuer, audience: config.audience, contracts });
+ return await verifyServiceToken(token, { ...keys.verifyOptions, issuer: config.network.issuer, audience: config.audience, contracts });
}
}
/**
/**
@@ -53,7 +42,7 @@ function createUserAuth(config, keys) {
@@ -53,7 +42,7 @@ function createUserAuth(config, keys) {
*/
*/
async function verify(token) {
async function verify(token) {
if (!token) return null;
if (!token) return null;
- try { return await verifyUserToken(token, { ...keys.source, issuer: config.network.issuer }); } catch { return null; }
+ try { return await verifyUserToken(token, { ...keys.verifyOptions, issuer: config.network.issuer }); } catch { return null; }
}
}
return { client, verify };
return { client, verify };
}
}
@@ -48,7 +48,7 @@ function createTipsReadiness({ db, valkey = null, keys, config, outbox, release
@@ -48,7 +48,7 @@ function createTipsReadiness({ db, valkey = null, keys, config, outbox, release
checks: [
checks: [
{ name: 'db', required: true, check: () => db.ready() },
{ name: 'db', required: true, check: () => db.ready() },
{ name: 'valkey', required: false, check: () => (valkey ? valkey.ready() : skip('VALKEY_URL unset: limits, overlay fan-out and stream slots are per process (one process only)')) },
{ name: 'valkey', required: false, check: () => (valkey ? valkey.ready() : skip('VALKEY_URL unset: limits, overlay fan-out and stream slots are per process (one process only)')) },
- { name: 'network_jwks', required: false, check: () => (keys.ready() ? true : 'Network signing key not loaded yet: tokens cannot be verified') },
+ { name: 'network_jwks', required: false, check: () => (keys.loaded() ? true : 'Network signing key not loaded yet: tokens cannot be verified') },
{ name: 'billing', required: false, cacheMs: PING_TTL_MS, timeoutMs: 2500, check: probe(`${config.billing.url}/api/health`, fetchImpl) },
{ name: 'billing', required: false, cacheMs: PING_TTL_MS, timeoutMs: 2500, check: probe(`${config.billing.url}/api/health`, fetchImpl) },
{ name: 'events', required: false, cacheMs: outbox.enabled ? PING_TTL_MS : 0, timeoutMs: 2500, check: events },
{ name: 'events', required: false, cacheMs: outbox.enabled ? PING_TTL_MS : 0, timeoutMs: 2500, check: events },
],
],
@@ -65,7 +65,7 @@ async function boot(opts = {}) {
@@ -65,7 +65,7 @@ async function boot(opts = {}) {
const ownValkey = opts.valkey === undefined && process.env.TIPS_TEST_STORE === 'pg' ? testValkey() : null;
const ownValkey = opts.valkey === undefined && process.env.TIPS_TEST_STORE === 'pg' ? testValkey() : null;
const valkey = opts.valkey || ownValkey;
const valkey = opts.valkey || ownValkey;
const app = createApp({ config, db: store.db, valkey, now: () => Date.now() + clock.offset, log, ...(opts.appOpts || {}) });
const app = createApp({ config, db: store.db, valkey, now: () => Date.now() + clock.offset, log, ...(opts.appOpts || {}) });
- await app.locals.keys.load();
+ await app.locals.keys.refresh();
const server = await new Promise((resolve) => { const s = http.createServer(app); s.listen(0, '127.0.0.1', () => resolve(s)); });
const server = await new Promise((resolve) => { const s = http.createServer(app); s.listen(0, '127.0.0.1', () => resolve(s)); });
const base = `http://127.0.0.1:${server.address().port}`;
const base = `http://127.0.0.1:${server.address().port}`;
const domain = app.locals.domain;
const domain = app.locals.domain;