openvibers/openvibe.network Commits
Deploy drift and library tags await their GitHub token: unawaited, every call sent "Bearer [object Promise]" and GitHub answered 401 (#312)
Changes
@@@ -42,14 +42,17 @@ function driftSeconds(now = Date.now()) {* services(): [{ id, release, repository }] for what runs now (the registry's releases view).*/function createDeployDrift({ services, fetchImpl = globalThis.fetch, token = () => process.env.GITHUB_TOKEN || '', intervalMs = 3600e3, now = () => Date.now(), log = console } = {}) {-- const tokenNow = () => (typeof token === 'function' ? token() : token) || '';++ // The token may come from an async source (server/index.js reads the integration's): awaited, or every call sent++ // "Bearer [object Promise]" and GitHub answered 401 (from 2026-09-25 until 10-23).++ const tokenNow = async () => String((await (typeof token === 'function' ? token() : token)) || '');async function checkOne({ id, release, repository }) {const at = new Date(now()).toISOString();if (!SHA_RE.test(String(release || '')) || !REPO_RE.test(String(repository || ''))) { state.delete(id); return null; }const prev = state.get(id);try {++ const auth = await tokenNow();const res = await fetchImpl(`https://api.github.com/repos/${repository}/compare/${release}...main`, {-- headers: { Accept: 'application/vnd.github+json', 'User-Agent': 'OpenVibe.Network registry', ...(tokenNow() ? { Authorization: `Bearer ${tokenNow()}` } : {}) },++ headers: { Accept: 'application/vnd.github+json', 'User-Agent': 'OpenVibe.Network registry', ...(auth ? { Authorization: `Bearer ${auth}` } : {}) },signal: AbortSignal.timeout(10000),});if (res.status === 404) { state.set(id, { repo: repository, deployed: release, state: 'unknown', error: 'commit not on GitHub', checked_at: at }); return state.get(id); }@@@ -21,14 +21,17 @@ function latestOf(names) {}function createLibraryTags({ fetchImpl = globalThis.fetch, token = () => process.env.GITHUB_TOKEN || '', intervalMs = 3600e3, onUpdate = null, log = console } = {}) {-- const tokenNow = () => (typeof token === 'function' ? token() : token) || '';++ // Awaited: server/index.js passes an async token function, and unawaited it went out as "Bearer [object Promise]"++ // (GitHub's 401, so the registry kept the installed versions until 2026-10-23).++ const tokenNow = async () => String((await (typeof token === 'function' ? token() : token)) || '');const latest = new Map(); // package -> 'vX.Y.Z'let timer = null;async function refresh() {++ const auth = await tokenNow().catch(() => '');for (const [pkg, repo] of Object.entries(REPOS)) {try {const res = await fetchImpl(`https://api.github.com/repos/OpenVibers/${repo}/tags?per_page=100`, {-- headers: { Accept: 'application/vnd.github+json', 'User-Agent': 'OpenVibe.Network registry', ...(tokenNow() ? { Authorization: `Bearer ${tokenNow()}` } : {}) },++ headers: { Accept: 'application/vnd.github+json', 'User-Agent': 'OpenVibe.Network registry', ...(auth ? { Authorization: `Bearer ${auth}` } : {}) },signal: AbortSignal.timeout(10000),});if (!res.ok) continue;@@@ -2,7 +2,7 @@// Production drift (WS-S task 7): the deployed commit against main through GitHub's compare API —// current, behind (how many commits and since the oldest undeployed one), diverged, unknown; a failed// lookup keeps the last answer; the gauge gives seconds behind (0 when current); services that stop--// running are dropped; a non-commit release is skipped.++// running are dropped; a non-commit release is skipped; an async token is awaited.const assert = require('assert');const drift = require('../server/registry/deploy-drift');@@@ -68,6 +68,14 @@ const fetchImpl = async (url, opts) => {await d.refresh();assert.strictEqual(drift.current('media'), null, 'a service that stopped running is dropped');drift._reset();++ // server/index.js passes an async token function: it is awaited, never sent as "[object Promise]" (GitHub's 401).++ // No token at all: no Authorization header (anonymous, 60 an hour).++ asked.length = 0;++ await drift.createDeployDrift({ services: () => running.slice(0, 1), fetchImpl, token: async () => 'ghp_async', now: () => NOW, log: null }).refresh();++ assert.strictEqual(asked[0].auth, 'Bearer ghp_async');++ await drift.createDeployDrift({ services: () => running.slice(0, 1), fetchImpl, token: async () => null, now: () => NOW, log: null }).refresh();++ assert.strictEqual(asked[1].auth, undefined);++ drift._reset();// server/index.js feeds the checks from the registry's releases view.const eco = require('../server/registry/ecosystem').createEcosystemRegistry({ issuer: 'https://openvibe.network', fetchImpl: async () => { throw new Error('offline'); } });assert.strictEqual(typeof eco.releases, 'function', 'the registry exposes releases()');@@@ -21,5 +21,9 @@ assert.strictEqual(latestOf([]), null);assert.strictEqual(libs['openvibe-sdk'].distribution, 'https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.8.0');assert.strictEqual(libs['openvibe-shared'].release, 'v1.11.1');assert.strictEqual(libs['openvibe-contracts'].release, `v${require('openvibe-contracts/package.json').version}`, 'a refused lookup keeps the installed version');++ // An async token (server/index.js) is awaited, never sent as "[object Promise]".++ const auths = [];++ await createLibraryTags({ fetchImpl: async (url, opts) => { auths.push(opts.headers.Authorization); return fetchImpl(url); }, token: async () => 'ghp_async', log: null }).refresh();++ assert.ok(auths.length && auths.every((a) => a === 'Bearer ghp_async'), auths.join(','));console.log('library tags: all checks passed');})().catch((e) => { console.error(e); process.exit(1); });
@@ -42,14 +42,17 @@ function driftSeconds(now = Date.now()) {
@@ -42,14 +42,17 @@ function driftSeconds(now = Date.now()) {
* services(): [{ id, release, repository }] for what runs now (the registry's releases view).
* services(): [{ id, release, repository }] for what runs now (the registry's releases view).
*/
*/
function createDeployDrift({ services, fetchImpl = globalThis.fetch, token = () => process.env.GITHUB_TOKEN || '', intervalMs = 3600e3, now = () => Date.now(), log = console } = {}) {
function createDeployDrift({ services, fetchImpl = globalThis.fetch, token = () => process.env.GITHUB_TOKEN || '', intervalMs = 3600e3, now = () => Date.now(), log = console } = {}) {
- const tokenNow = () => (typeof token === 'function' ? token() : token) || '';
+ // The token may come from an async source (server/index.js reads the integration's): awaited, or every call sent
+ // "Bearer [object Promise]" and GitHub answered 401 (from 2026-09-25 until 10-23).
+ const tokenNow = async () => String((await (typeof token === 'function' ? token() : token)) || '');
async function checkOne({ id, release, repository }) {
async function checkOne({ id, release, repository }) {
const at = new Date(now()).toISOString();
const at = new Date(now()).toISOString();
if (!SHA_RE.test(String(release || '')) || !REPO_RE.test(String(repository || ''))) { state.delete(id); return null; }
if (!SHA_RE.test(String(release || '')) || !REPO_RE.test(String(repository || ''))) { state.delete(id); return null; }
const prev = state.get(id);
const prev = state.get(id);
try {
try {
+ const auth = await tokenNow();
const res = await fetchImpl(`https://api.github.com/repos/${repository}/compare/${release}...main`, {
const res = await fetchImpl(`https://api.github.com/repos/${repository}/compare/${release}...main`, {
- headers: { Accept: 'application/vnd.github+json', 'User-Agent': 'OpenVibe.Network registry', ...(tokenNow() ? { Authorization: `Bearer ${tokenNow()}` } : {}) },
+ headers: { Accept: 'application/vnd.github+json', 'User-Agent': 'OpenVibe.Network registry', ...(auth ? { Authorization: `Bearer ${auth}` } : {}) },
signal: AbortSignal.timeout(10000),
signal: AbortSignal.timeout(10000),
});
});
if (res.status === 404) { state.set(id, { repo: repository, deployed: release, state: 'unknown', error: 'commit not on GitHub', checked_at: at }); return state.get(id); }
if (res.status === 404) { state.set(id, { repo: repository, deployed: release, state: 'unknown', error: 'commit not on GitHub', checked_at: at }); return state.get(id); }
@@ -21,14 +21,17 @@ function latestOf(names) {
@@ -21,14 +21,17 @@ function latestOf(names) {
}
}
function createLibraryTags({ fetchImpl = globalThis.fetch, token = () => process.env.GITHUB_TOKEN || '', intervalMs = 3600e3, onUpdate = null, log = console } = {}) {
function createLibraryTags({ fetchImpl = globalThis.fetch, token = () => process.env.GITHUB_TOKEN || '', intervalMs = 3600e3, onUpdate = null, log = console } = {}) {
- const tokenNow = () => (typeof token === 'function' ? token() : token) || '';
+ // Awaited: server/index.js passes an async token function, and unawaited it went out as "Bearer [object Promise]"
+ // (GitHub's 401, so the registry kept the installed versions until 2026-10-23).
+ const tokenNow = async () => String((await (typeof token === 'function' ? token() : token)) || '');
const latest = new Map(); // package -> 'vX.Y.Z'
const latest = new Map(); // package -> 'vX.Y.Z'
let timer = null;
let timer = null;
async function refresh() {
async function refresh() {
+ const auth = await tokenNow().catch(() => '');
for (const [pkg, repo] of Object.entries(REPOS)) {
for (const [pkg, repo] of Object.entries(REPOS)) {
try {
try {
const res = await fetchImpl(`https://api.github.com/repos/OpenVibers/${repo}/tags?per_page=100`, {
const res = await fetchImpl(`https://api.github.com/repos/OpenVibers/${repo}/tags?per_page=100`, {
- headers: { Accept: 'application/vnd.github+json', 'User-Agent': 'OpenVibe.Network registry', ...(tokenNow() ? { Authorization: `Bearer ${tokenNow()}` } : {}) },
+ headers: { Accept: 'application/vnd.github+json', 'User-Agent': 'OpenVibe.Network registry', ...(auth ? { Authorization: `Bearer ${auth}` } : {}) },
signal: AbortSignal.timeout(10000),
signal: AbortSignal.timeout(10000),
});
});
if (!res.ok) continue;
if (!res.ok) continue;
@@ -2,7 +2,7 @@
@@ -2,7 +2,7 @@
// Production drift (WS-S task 7): the deployed commit against main through GitHub's compare API —
// Production drift (WS-S task 7): the deployed commit against main through GitHub's compare API —
// current, behind (how many commits and since the oldest undeployed one), diverged, unknown; a failed
// current, behind (how many commits and since the oldest undeployed one), diverged, unknown; a failed
// lookup keeps the last answer; the gauge gives seconds behind (0 when current); services that stop
// lookup keeps the last answer; the gauge gives seconds behind (0 when current); services that stop
-// running are dropped; a non-commit release is skipped.
+// running are dropped; a non-commit release is skipped; an async token is awaited.
const assert = require('assert');
const assert = require('assert');
const drift = require('../server/registry/deploy-drift');
const drift = require('../server/registry/deploy-drift');
@@ -68,6 +68,14 @@ const fetchImpl = async (url, opts) => {
@@ -68,6 +68,14 @@ const fetchImpl = async (url, opts) => {
await d.refresh();
await d.refresh();
assert.strictEqual(drift.current('media'), null, 'a service that stopped running is dropped');
assert.strictEqual(drift.current('media'), null, 'a service that stopped running is dropped');
drift._reset();
drift._reset();
+ // server/index.js passes an async token function: it is awaited, never sent as "[object Promise]" (GitHub's 401).
+ // No token at all: no Authorization header (anonymous, 60 an hour).
+ asked.length = 0;
+ await drift.createDeployDrift({ services: () => running.slice(0, 1), fetchImpl, token: async () => 'ghp_async', now: () => NOW, log: null }).refresh();
+ assert.strictEqual(asked[0].auth, 'Bearer ghp_async');
+ await drift.createDeployDrift({ services: () => running.slice(0, 1), fetchImpl, token: async () => null, now: () => NOW, log: null }).refresh();
+ assert.strictEqual(asked[1].auth, undefined);
+ drift._reset();
// server/index.js feeds the checks from the registry's releases view.
// server/index.js feeds the checks from the registry's releases view.
const eco = require('../server/registry/ecosystem').createEcosystemRegistry({ issuer: 'https://openvibe.network', fetchImpl: async () => { throw new Error('offline'); } });
const eco = require('../server/registry/ecosystem').createEcosystemRegistry({ issuer: 'https://openvibe.network', fetchImpl: async () => { throw new Error('offline'); } });
assert.strictEqual(typeof eco.releases, 'function', 'the registry exposes releases()');
assert.strictEqual(typeof eco.releases, 'function', 'the registry exposes releases()');
@@ -21,5 +21,9 @@ assert.strictEqual(latestOf([]), null);
@@ -21,5 +21,9 @@ assert.strictEqual(latestOf([]), null);
assert.strictEqual(libs['openvibe-sdk'].distribution, 'https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.8.0');
assert.strictEqual(libs['openvibe-sdk'].distribution, 'https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.8.0');
assert.strictEqual(libs['openvibe-shared'].release, 'v1.11.1');
assert.strictEqual(libs['openvibe-shared'].release, 'v1.11.1');
assert.strictEqual(libs['openvibe-contracts'].release, `v${require('openvibe-contracts/package.json').version}`, 'a refused lookup keeps the installed version');
assert.strictEqual(libs['openvibe-contracts'].release, `v${require('openvibe-contracts/package.json').version}`, 'a refused lookup keeps the installed version');
+ // An async token (server/index.js) is awaited, never sent as "[object Promise]".
+ const auths = [];
+ await createLibraryTags({ fetchImpl: async (url, opts) => { auths.push(opts.headers.Authorization); return fetchImpl(url); }, token: async () => 'ghp_async', log: null }).refresh();
+ assert.ok(auths.length && auths.every((a) => a === 'Bearer ghp_async'), auths.join(','));
console.log('library tags: all checks passed');
console.log('library tags: all checks passed');
})().catch((e) => { console.error(e); process.exit(1); });
})().catch((e) => { console.error(e); process.exit(1); });