Source Presences
Public registry of Source Presence extensions, served from
gits.hibna.com.tr/hibna/source-presences.
The Source Presence browser extension reads:
index.json— list of presences and their current versions.presences/<id>/metadata.json— presence manifest.presences/<id>/dist/presence.js— built CJS module (produced bynpm run build).presences/<id>/<icon>— optional 128×128 PNG.
Both index.json and each presence's dist/presence.js are committed by the
Gitea Actions workflow at .gitea/workflows/build.yml
on every push to main.
Add a new presence
mkdir -p presences/<id>/src
cat > presences/<id>/metadata.json <<'JSON'
{
"id": "<id>",
"name": "<Display Name>",
"description": "<short description>",
"version": "1.0.0",
"author": "<your name>",
"match": ["https://example.com/*"],
"tickInterval": 1000,
"sdkVersion": "^0.1.0"
}
JSON
Write your TypeScript entry at presences/<id>/src/presence.ts:
import { ActivityType, definePresence } from "@source/presence-sdk";
export default definePresence({
match: ["https://example.com/*"],
tick(ctx) {
ctx.setActivity({
type: ActivityType.WATCHING,
name: "Example",
details: document.title,
});
},
});
Push to main. Gitea Actions builds the presence, regenerates index.json,
and commits the artifacts back. The extension picks up the new version on its
next 6-hour refresh, or immediately when users hit "Refresh" / "Update all".
Local build
npm install
npm run build # builds every presence under presences/*
npm run index # regenerates index.json
npm run release # both
@source/presence-sdkis not a published npm package. The build pipeline aliases it to a local runtime shim (scripts/sdk-runtime.mjs) at bundle time, and TypeScript resolves the import to a local.d.ts(types/presence-sdk.d.ts). Don't add it topackage.json.
Presence API
PresenceDefinition shape (also defined in types/presence-sdk.d.ts):
| Field | Required | Description |
|---|---|---|
match |
yes | Array of match patterns. |
tickInterval |
no | Override default 1000ms tick. |
onLoad(ctx) |
no | Runs once when a matching tab loads the presence. |
tick(ctx) |
no | Runs every tickInterval; call ctx.setActivity(...) to publish. |
onUnload(ctx) |
no | Runs when the presence is torn down (tab nav / disable). |
PresenceContext:
ctx.url/ctx.tabActive— read-only state.ctx.setActivity(activity)/ctx.clear()— publish or clear.ctx.storage— namespaced async storage (get/set/delete/clear).ctx.log/warn/error— surfaced in the extension's background console.
Activity payload
{
type: ActivityType.PLAYING | LISTENING | WATCHING,
name: string,
details?: string,
state?: string,
url?: string,
largeImage?: string,
largeText?: string,
smallImage?: string,
smallText?: string,
startedAt?: number, // unix ms — Source renders an elapsed timer
endsAt?: number, // unix ms — Source renders a remaining timer
}
Notes
- Presences run in the content script's isolated world with full DOM read access. They cannot access page JS globals.
- The extension debounces identical payloads; calling
setActivitywith the same data on every tick is fine. - Keep presence builds small (< 100 KB). They're shipped with every install.