seitime-bridge/scripts/main.js
Vassili Minaev bc3aa10ab2 Initial scaffolding: settings + manifest push
Foundry v12 ESM module that pushes the world's PC actor manifest to a
companion Frappe app via webhook.

Structure:
- scripts/main.js: registers settings on init, exposes API on ready,
  wires createActor/deleteActor/periodic hooks. All push operations
  gated to game.users.activeGM to avoid duplicate pushes from co-GMs.
- scripts/settings.js: Frappe URL, shared secret, world ID, periodic
  interval. Secret uses `secret: true` for masked input but is still
  readable by all connected clients (documented in setting hint).
- scripts/api.js: bridgeFetch helper with X-Bridge-Secret/-World
  headers, pushManifest, testConnection.
- scripts/constants.js: MODULE_ID.

Public API exposed at globalThis.seitimeBridge:
  seitimeBridge.testConnection()  // round-trip empty manifest
  seitimeBridge.pushManifest()    // push current world manifest

dnd5e is declared as a related system in module.json.
Snapshot push and End Session macro come in subsequent commits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 02:26:53 -06:00

56 lines
1.5 KiB
JavaScript

/**
* Entry point for seitime-bridge. Registers settings, wires hooks, and
* exposes the public API on `globalThis.seitimeBridge` for use from
* Foundry macros and the F12 console.
*
* All push operations are gated to the primary connected GM
* (`game.users.activeGM`) so that worlds with multiple GMs don't
* duplicate webhook calls.
*/
import { MODULE_ID } from "./constants.js";
import { registerSettings } from "./settings.js";
import { pushManifest, testConnection } from "./api.js";
let manifestTimerId = null;
function isPrimaryGM() {
return game.user.isGM && game.users.activeGM?.id === game.user.id;
}
function safePushManifest(reason) {
if (!isPrimaryGM()) return;
pushManifest().catch((err) => {
console.warn(`[${MODULE_ID}] manifest push (${reason}) failed:`, err);
});
}
Hooks.once("init", () => {
registerSettings();
});
Hooks.once("ready", () => {
const mod = game.modules.get(MODULE_ID);
const api = { pushManifest, testConnection };
mod.api = api;
globalThis.seitimeBridge = api;
console.log(`[${MODULE_ID}] ready. Call seitimeBridge.testConnection() to verify config.`);
safePushManifest("ready");
const intervalMin = game.settings.get(MODULE_ID, "manifestSyncIntervalMinutes");
if (intervalMin > 0) {
manifestTimerId = setInterval(() => safePushManifest("periodic"), intervalMin * 60 * 1000);
}
});
Hooks.on("createActor", (actor) => {
if (actor.type !== "character") return;
safePushManifest("createActor");
});
Hooks.on("deleteActor", (actor) => {
if (actor.type !== "character") return;
safePushManifest("deleteActor");
});