@hatk/hatk 0.0.1-alpha.23 → 0.0.1-alpha.25
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/adapter.d.ts +19 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +94 -0
- package/dist/backfill.d.ts.map +1 -1
- package/dist/backfill.js +12 -0
- package/dist/cli.js +186 -66
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -1
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +5 -1
- package/dist/dev-entry.d.ts +8 -0
- package/dist/dev-entry.d.ts.map +1 -0
- package/dist/dev-entry.js +109 -0
- package/dist/feeds.d.ts +4 -0
- package/dist/feeds.d.ts.map +1 -1
- package/dist/feeds.js +41 -2
- package/dist/hooks.d.ts +7 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +11 -1
- package/dist/labels.d.ts +14 -0
- package/dist/labels.d.ts.map +1 -1
- package/dist/labels.js +13 -1
- package/dist/main.js +49 -17
- package/dist/oauth/server.d.ts +2 -0
- package/dist/oauth/server.d.ts.map +1 -1
- package/dist/oauth/server.js +91 -1
- package/dist/oauth/session.d.ts +9 -0
- package/dist/oauth/session.d.ts.map +1 -0
- package/dist/oauth/session.js +65 -0
- package/dist/opengraph.d.ts +10 -0
- package/dist/opengraph.d.ts.map +1 -1
- package/dist/opengraph.js +102 -4
- package/dist/pds-proxy.d.ts +39 -0
- package/dist/pds-proxy.d.ts.map +1 -0
- package/dist/pds-proxy.js +173 -0
- package/dist/renderer.d.ts +27 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +46 -0
- package/dist/response.d.ts +16 -0
- package/dist/response.d.ts.map +1 -0
- package/dist/response.js +69 -0
- package/dist/scanner.d.ts +21 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +88 -0
- package/dist/server-init.d.ts +8 -0
- package/dist/server-init.d.ts.map +1 -0
- package/dist/server-init.js +59 -0
- package/dist/server.d.ts +26 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +473 -616
- package/dist/setup.d.ts +7 -0
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +13 -1
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +12 -22
- package/dist/vite-plugin.d.ts +1 -1
- package/dist/vite-plugin.d.ts.map +1 -1
- package/dist/vite-plugin.js +245 -75
- package/dist/xrpc.d.ts +13 -0
- package/dist/xrpc.d.ts.map +1 -1
- package/dist/xrpc.js +87 -1
- package/package.json +8 -5
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev mode entry point — loaded through Vite's module runner.
|
|
3
|
+
* Boots hatk infrastructure and exports the fetch handler.
|
|
4
|
+
*/
|
|
5
|
+
import { loadConfig } from "./config.js";
|
|
6
|
+
import { loadLexicons, storeLexicons, discoverCollections, buildSchemas } from "./database/schema.js";
|
|
7
|
+
import { discoverViews } from "./views.js";
|
|
8
|
+
import { initDatabase, migrateSchema, getSchemaDump } from "./database/db.js";
|
|
9
|
+
import { createAdapter } from "./database/adapter-factory.js";
|
|
10
|
+
import { getDialect } from "./database/dialect.js";
|
|
11
|
+
import { setSearchPort } from "./database/fts.js";
|
|
12
|
+
import { configureRelay } from "./xrpc.js";
|
|
13
|
+
import { initOAuth } from "./oauth/server.js";
|
|
14
|
+
import { initServer } from "./server-init.js";
|
|
15
|
+
import { createHandler, registerCoreHandlers } from "./server.js";
|
|
16
|
+
import { startIndexer } from "./indexer.js";
|
|
17
|
+
import { getCursor } from "./database/db.js";
|
|
18
|
+
import { runBackfill } from "./backfill.js";
|
|
19
|
+
import { rebuildAllIndexes } from "./database/fts.js";
|
|
20
|
+
import { relayHttpUrl } from "./config.js";
|
|
21
|
+
import { validateLexicons } from '@bigmoves/lexicon';
|
|
22
|
+
import { log } from "./logger.js";
|
|
23
|
+
import { mkdirSync } from 'node:fs';
|
|
24
|
+
import { dirname, resolve } from 'node:path';
|
|
25
|
+
process.env.DEV_MODE = '1';
|
|
26
|
+
// Boot sequence (mirrors main.ts but exports handler instead of starting server)
|
|
27
|
+
const configPath = 'hatk.config.ts';
|
|
28
|
+
const configDir = dirname(resolve(configPath));
|
|
29
|
+
const config = await loadConfig(configPath);
|
|
30
|
+
configureRelay(config.relay);
|
|
31
|
+
const lexicons = loadLexicons(resolve(configDir, 'lexicons'));
|
|
32
|
+
const lexiconErrors = validateLexicons([...lexicons.values()]);
|
|
33
|
+
if (lexiconErrors) {
|
|
34
|
+
for (const [nsid, errors] of Object.entries(lexiconErrors)) {
|
|
35
|
+
for (const err of errors)
|
|
36
|
+
console.error(`Invalid lexicon ${nsid}: ${err}`);
|
|
37
|
+
}
|
|
38
|
+
throw new Error('Invalid lexicons');
|
|
39
|
+
}
|
|
40
|
+
storeLexicons(lexicons);
|
|
41
|
+
const collections = config.collections.length > 0 ? config.collections : discoverCollections(lexicons);
|
|
42
|
+
discoverViews();
|
|
43
|
+
const engineDialect = getDialect(config.databaseEngine);
|
|
44
|
+
const { schemas, ddlStatements } = buildSchemas(lexicons, collections, engineDialect);
|
|
45
|
+
if (config.database !== ':memory:') {
|
|
46
|
+
mkdirSync(dirname(config.database), { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
const { adapter, searchPort } = await createAdapter(config.databaseEngine);
|
|
49
|
+
setSearchPort(searchPort);
|
|
50
|
+
await initDatabase(adapter, config.database, schemas, ddlStatements);
|
|
51
|
+
await migrateSchema(schemas);
|
|
52
|
+
// Write db/schema.sql
|
|
53
|
+
try {
|
|
54
|
+
const { mkdirSync, writeFileSync } = await import('node:fs');
|
|
55
|
+
const schemaDir = resolve(configDir, 'db');
|
|
56
|
+
mkdirSync(schemaDir, { recursive: true });
|
|
57
|
+
const schemaDump = await getSchemaDump();
|
|
58
|
+
writeFileSync(resolve(schemaDir, 'schema.sql'), `-- This file is auto-generated by hatk on startup. Do not edit.\n-- Database engine: ${config.databaseEngine}\n\n${schemaDump}\n`);
|
|
59
|
+
log(`[hatk] Schema written to db/schema.sql`);
|
|
60
|
+
}
|
|
61
|
+
catch { }
|
|
62
|
+
// Initialize handlers from server/ directory
|
|
63
|
+
await initServer(resolve(configDir, 'server'));
|
|
64
|
+
// Register built-in dev.hatk.* handlers so callXrpc() can find them
|
|
65
|
+
registerCoreHandlers(collections, config.oauth);
|
|
66
|
+
if (config.oauth) {
|
|
67
|
+
await initOAuth(config.oauth, config.plc, config.relay);
|
|
68
|
+
}
|
|
69
|
+
// Start indexer
|
|
70
|
+
const collectionSet = new Set(collections);
|
|
71
|
+
const cursor = await getCursor('relay');
|
|
72
|
+
startIndexer({
|
|
73
|
+
relayUrl: config.relay,
|
|
74
|
+
collections: collectionSet,
|
|
75
|
+
signalCollections: config.backfill.signalCollections ? new Set(config.backfill.signalCollections) : undefined,
|
|
76
|
+
pinnedRepos: config.backfill.repos ? new Set(config.backfill.repos) : undefined,
|
|
77
|
+
cursor,
|
|
78
|
+
fetchTimeout: config.backfill.fetchTimeout,
|
|
79
|
+
maxRetries: config.backfill.maxRetries,
|
|
80
|
+
parallelism: config.backfill.parallelism,
|
|
81
|
+
ftsRebuildInterval: config.ftsRebuildInterval,
|
|
82
|
+
});
|
|
83
|
+
// Run backfill in background (no restart in dev mode)
|
|
84
|
+
runBackfill({
|
|
85
|
+
pdsUrl: relayHttpUrl(config.relay),
|
|
86
|
+
plcUrl: config.plc,
|
|
87
|
+
collections: collectionSet,
|
|
88
|
+
config: config.backfill,
|
|
89
|
+
}).then(() => rebuildAllIndexes(Array.from(collectionSet)))
|
|
90
|
+
.catch((err) => console.error('[backfill]', err.message));
|
|
91
|
+
// Export the handler for Vite middleware
|
|
92
|
+
export const handler = createHandler({
|
|
93
|
+
collections: Array.from(collectionSet),
|
|
94
|
+
publicDir: null, // Vite serves static assets in dev
|
|
95
|
+
oauth: config.oauth,
|
|
96
|
+
admins: config.admins,
|
|
97
|
+
});
|
|
98
|
+
/** Re-scan server/ directory to pick up changed handlers in dev mode. */
|
|
99
|
+
export async function reloadServer() {
|
|
100
|
+
await initServer(resolve(configDir, 'server'));
|
|
101
|
+
}
|
|
102
|
+
export { renderPage } from "./renderer.js";
|
|
103
|
+
export { getRenderer } from "./renderer.js";
|
|
104
|
+
export { callXrpc } from "./xrpc.js";
|
|
105
|
+
export { parseSessionCookie, getSessionCookieName } from "./oauth/session.js";
|
|
106
|
+
log(`[hatk] Dev server ready`);
|
|
107
|
+
log(` Relay: ${config.relay}`);
|
|
108
|
+
log(` Database: ${config.database}`);
|
|
109
|
+
log(` Collections: ${collections.join(', ')}`);
|
package/dist/feeds.d.ts
CHANGED
|
@@ -71,13 +71,17 @@ export declare function defineFeed(opts: FeedOpts): {
|
|
|
71
71
|
view?: string;
|
|
72
72
|
label: string;
|
|
73
73
|
hydrate?: (ctx: HydrateContext<any>) => Promise<unknown[]>;
|
|
74
|
+
__type: "feed";
|
|
74
75
|
} | {
|
|
75
76
|
generate: (ctx: any) => Promise<Checked<FeedResult>>;
|
|
76
77
|
collection?: never;
|
|
77
78
|
view?: never;
|
|
78
79
|
label: string;
|
|
79
80
|
hydrate: (ctx: HydrateContext<any>) => Promise<unknown[]>;
|
|
81
|
+
__type: "feed";
|
|
80
82
|
};
|
|
83
|
+
/** Register a single feed from a scanned server/ module. */
|
|
84
|
+
export declare function registerFeed(name: string, generator: ReturnType<typeof defineFeed>): void;
|
|
81
85
|
export declare function initFeeds(feedsDir: string): Promise<void>;
|
|
82
86
|
/** Execute a feed and run its hydrate pipeline if present. */
|
|
83
87
|
export declare function executeFeed(name: string, params: Record<string, string>, cursor: string | undefined, limit: number, viewer?: {
|
package/dist/feeds.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feeds.d.ts","sourceRoot":"","sources":["../src/feeds.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"feeds.d.ts","sourceRoot":"","sources":["../src/feeds.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAE7C,YAAY,EAAE,cAAc,EAAE,GAAG,EAAE,CAAA;AAEnC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAA;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,IAAI,EAAE,CAAC,EAAE,CAAA;IACT,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE;QAAE,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;KAAE,CAAA;IAC9D,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC9B,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IAC7D,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACzE,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC9C,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAA;IAC7D,QAAQ,EAAE,CAAC,CAAC,SAAS;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAA;CACtG;AAkBD,KAAK,YAAY,GAAG,CAClB,GAAG,EAAE,WAAW,GAAG;IAAE,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;CAAE,KAClE,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAA;AAEjC,KAAK,QAAQ,GACT;IACE,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,YAAY,CAAA;IACtB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;CAC3D,GACD;IACE,UAAU,CAAC,EAAE,KAAK,CAAA;IAClB,IAAI,CAAC,EAAE,KAAK,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,YAAY,CAAA;IACtB,OAAO,EAAE,CAAC,GAAG,EAAE,cAAc,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;CAC1D,CAAA;AAEL,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,EAAE,EAAE;QAAE,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;KAAE,CAAA;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,CAAA;IAC7D,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAC1E,IACe,CAAC,SAAS;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,MAAM,EAAE,OAAO,YAAY,KAAG,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAmDvG;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ;oBACoB,GAAG;gBA3E9C,MAAM;WACX,MAAM;WACN,MAAM;cAEH,CAAC,GAAG,EAAE,cAAc,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC;;;oBAuEH,GAAG;iBApE7C,KAAK;WACX,KAAK;WACL,MAAM;aAEJ,CAAC,GAAG,EAAE,cAAc,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC;;EAiE9D;AAED,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,IAAI,CAuCzF;AAID,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8D/D;AAED,8DAA8D;AAC9D,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAC9B,OAAO,CAAC;IAAE,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAczE;AAED,wBAAgB,SAAS,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,CAE7D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD"}
|
package/dist/feeds.js
CHANGED
|
@@ -59,7 +59,46 @@ export function createPaginate(deps) {
|
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
61
|
export function defineFeed(opts) {
|
|
62
|
-
return { ...opts, generate: (ctx) => opts.generate({ ...ctx, ok: (v) => v }) };
|
|
62
|
+
return { __type: 'feed', ...opts, generate: (ctx) => opts.generate({ ...ctx, ok: (v) => v }) };
|
|
63
|
+
}
|
|
64
|
+
/** Register a single feed from a scanned server/ module. */
|
|
65
|
+
export function registerFeed(name, generator) {
|
|
66
|
+
const handler = {
|
|
67
|
+
name,
|
|
68
|
+
label: generator.label || name,
|
|
69
|
+
collection: generator.collection,
|
|
70
|
+
view: generator.view,
|
|
71
|
+
generate: async (params, cursor, limit, viewer) => {
|
|
72
|
+
const paginateDeps = {
|
|
73
|
+
db: { query: querySQL },
|
|
74
|
+
cursor,
|
|
75
|
+
limit,
|
|
76
|
+
packCursor,
|
|
77
|
+
unpackCursor,
|
|
78
|
+
};
|
|
79
|
+
const ctx = {
|
|
80
|
+
db: { query: querySQL },
|
|
81
|
+
params,
|
|
82
|
+
cursor,
|
|
83
|
+
limit,
|
|
84
|
+
viewer,
|
|
85
|
+
packCursor,
|
|
86
|
+
unpackCursor,
|
|
87
|
+
isTakendown: isTakendownDid,
|
|
88
|
+
filterTakendownDids,
|
|
89
|
+
paginate: createPaginate(paginateDeps),
|
|
90
|
+
};
|
|
91
|
+
const result = await generator.generate(ctx);
|
|
92
|
+
if (Array.isArray(result)) {
|
|
93
|
+
return { uris: result.map((r) => r.uri || r) };
|
|
94
|
+
}
|
|
95
|
+
return { uris: result.uris, cursor: result.cursor };
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
if (typeof generator.hydrate === 'function') {
|
|
99
|
+
handler.hydrate = generator.hydrate;
|
|
100
|
+
}
|
|
101
|
+
feeds.set(name, handler);
|
|
63
102
|
}
|
|
64
103
|
const feeds = new Map();
|
|
65
104
|
export async function initFeeds(feedsDir) {
|
|
@@ -75,7 +114,7 @@ export async function initFeeds(feedsDir) {
|
|
|
75
114
|
for (const file of files) {
|
|
76
115
|
const name = file.replace(/\.(ts|js)$/, '');
|
|
77
116
|
const scriptPath = resolve(feedsDir, file);
|
|
78
|
-
const mod = await import(__rewriteRelativeImportExtension(scriptPath));
|
|
117
|
+
const mod = await import(__rewriteRelativeImportExtension(/* @vite-ignore */ `${scriptPath}?t=${Date.now()}`));
|
|
79
118
|
const generator = mod.default;
|
|
80
119
|
const handler = {
|
|
81
120
|
name,
|
package/dist/hooks.d.ts
CHANGED
|
@@ -5,11 +5,18 @@ export type OnLoginCtx = {
|
|
|
5
5
|
/** Trigger a backfill for a DID if it hasn't been indexed yet. */
|
|
6
6
|
ensureRepo: (did: string) => Promise<void>;
|
|
7
7
|
};
|
|
8
|
+
export declare function defineHook(event: 'on-login', handler: (ctx: OnLoginCtx) => Promise<void>): {
|
|
9
|
+
__type: "hook";
|
|
10
|
+
event: "on-login";
|
|
11
|
+
handler: (ctx: OnLoginCtx) => Promise<void>;
|
|
12
|
+
};
|
|
8
13
|
/**
|
|
9
14
|
* Discover and load the on-login hook from the project's `hooks/` directory.
|
|
10
15
|
* Looks for `on-login.ts` or `on-login.js`. Safe to call if no hook exists.
|
|
11
16
|
*/
|
|
12
17
|
export declare function loadOnLoginHook(hooksDir: string): Promise<void>;
|
|
18
|
+
/** Register a hook from a scanned server/ module. */
|
|
19
|
+
export declare function registerHook(event: string, handler: Function): void;
|
|
13
20
|
/** Fire the on-login hook if loaded. Errors are logged but never block login. */
|
|
14
21
|
export declare function fireOnLoginHook(did: string): Promise<void>;
|
|
15
22
|
//# sourceMappingURL=hooks.d.ts.map
|
package/dist/hooks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AA2BA,0EAA0E;AAC1E,MAAM,MAAM,UAAU,GAAG;IACvB,0CAA0C;IAC1C,GAAG,EAAE,MAAM,CAAA;IACX,kEAAkE;IAClE,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3C,CAAA;AAMD;;;GAGG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQrE;AAQD,iFAAiF;AACjF,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOhE"}
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AA2BA,0EAA0E;AAC1E,MAAM,MAAM,UAAU,GAAG;IACvB,0CAA0C;IAC1C,GAAG,EAAE,MAAM,CAAA;IACX,kEAAkE;IAClE,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3C,CAAA;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC;;;mBAA5B,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC;EAExF;AAMD;;;GAGG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQrE;AAQD,qDAAqD;AACrD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAKnE;AAED,iFAAiF;AACjF,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOhE"}
|
package/dist/hooks.js
CHANGED
|
@@ -32,6 +32,9 @@ import { resolve } from 'node:path';
|
|
|
32
32
|
import { log } from "./logger.js";
|
|
33
33
|
import { setRepoStatus } from "./database/db.js";
|
|
34
34
|
import { triggerAutoBackfill } from "./indexer.js";
|
|
35
|
+
export function defineHook(event, handler) {
|
|
36
|
+
return { __type: 'hook', event, handler };
|
|
37
|
+
}
|
|
35
38
|
let onLoginHook = null;
|
|
36
39
|
/**
|
|
37
40
|
* Discover and load the on-login hook from the project's `hooks/` directory.
|
|
@@ -43,7 +46,7 @@ export async function loadOnLoginHook(hooksDir) {
|
|
|
43
46
|
const path = existsSync(tsPath) ? tsPath : existsSync(jsPath) ? jsPath : null;
|
|
44
47
|
if (!path)
|
|
45
48
|
return;
|
|
46
|
-
const mod = await import(__rewriteRelativeImportExtension(path));
|
|
49
|
+
const mod = await import(__rewriteRelativeImportExtension(/* @vite-ignore */ `${path}?t=${Date.now()}`));
|
|
47
50
|
onLoginHook = mod.default;
|
|
48
51
|
log('[hooks] on-login hook loaded');
|
|
49
52
|
}
|
|
@@ -52,6 +55,13 @@ async function ensureRepo(did) {
|
|
|
52
55
|
await setRepoStatus(did, 'pending');
|
|
53
56
|
triggerAutoBackfill(did);
|
|
54
57
|
}
|
|
58
|
+
/** Register a hook from a scanned server/ module. */
|
|
59
|
+
export function registerHook(event, handler) {
|
|
60
|
+
if (event === 'on-login') {
|
|
61
|
+
onLoginHook = handler;
|
|
62
|
+
log('[hooks] on-login hook registered');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
55
65
|
/** Fire the on-login hook if loaded. Errors are logged but never block login. */
|
|
56
66
|
export async function fireOnLoginHook(did) {
|
|
57
67
|
if (!onLoginHook)
|
package/dist/labels.d.ts
CHANGED
|
@@ -13,6 +13,15 @@ export interface LabelRuleContext {
|
|
|
13
13
|
value: Record<string, any>;
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
+
export interface LabelModule {
|
|
17
|
+
definition?: LabelDefinition;
|
|
18
|
+
evaluate?: (ctx: LabelRuleContext) => Promise<string[]>;
|
|
19
|
+
}
|
|
20
|
+
export declare function defineLabels(module: LabelModule): {
|
|
21
|
+
definition?: LabelDefinition;
|
|
22
|
+
evaluate?: (ctx: LabelRuleContext) => Promise<string[]>;
|
|
23
|
+
__type: "labels";
|
|
24
|
+
};
|
|
16
25
|
/**
|
|
17
26
|
* Discover and load label rule modules from the `labels/` directory.
|
|
18
27
|
*
|
|
@@ -23,6 +32,11 @@ export interface LabelRuleContext {
|
|
|
23
32
|
* @param labelsDir - Absolute path to the `labels/` directory
|
|
24
33
|
*/
|
|
25
34
|
export declare function initLabels(labelsDir: string): Promise<void>;
|
|
35
|
+
/** Register a single label module from a scanned server/ module. */
|
|
36
|
+
export declare function registerLabelModule(name: string, labelMod: {
|
|
37
|
+
definition?: LabelDefinition;
|
|
38
|
+
evaluate?: (ctx: LabelRuleContext) => Promise<string[]>;
|
|
39
|
+
}): void;
|
|
26
40
|
/**
|
|
27
41
|
* Evaluate all loaded label rules against a record and persist any resulting labels.
|
|
28
42
|
* Called after each record is indexed. Rule errors are logged but never block indexing.
|
package/dist/labels.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"labels.d.ts","sourceRoot":"","sources":["../src/labels.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAIlD,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE;QACF,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;QACtD,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KACtD,CAAA;IACD,MAAM,EAAE;QACN,GAAG,EAAE,MAAM,CAAA;QACX,GAAG,EAAE,MAAM,CAAA;QACX,GAAG,EAAE,MAAM,CAAA;QACX,UAAU,EAAE,MAAM,CAAA;QAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAC3B,CAAA;CACF;AAYD;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCjE;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC1C,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAC3B,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBhB;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAuCvG;AAED,yEAAyE;AACzE,wBAAgB,mBAAmB,IAAI,eAAe,EAAE,CAEvD"}
|
|
1
|
+
{"version":3,"file":"labels.d.ts","sourceRoot":"","sources":["../src/labels.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAIlD,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE;QACF,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;QACtD,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KACtD,CAAA;IACD,MAAM,EAAE;QACN,GAAG,EAAE,MAAM,CAAA;QACX,GAAG,EAAE,MAAM,CAAA;QACX,GAAG,EAAE,MAAM,CAAA;QACX,UAAU,EAAE,MAAM,CAAA;QAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAC3B,CAAA;CACF;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;CACxD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW;iBAJjC,eAAe;eACjB,CAAC,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;;EAKxD;AAYD;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCjE;AAED,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE;IAAE,UAAU,CAAC,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;CAAE,GAAG,IAAI,CAO3J;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC1C,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAC3B,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBhB;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAuCvG;AAED,yEAAyE;AACzE,wBAAgB,mBAAmB,IAAI,eAAe,EAAE,CAEvD"}
|
package/dist/labels.js
CHANGED
|
@@ -38,6 +38,9 @@ import { resolve } from 'node:path';
|
|
|
38
38
|
import { readdirSync } from 'node:fs';
|
|
39
39
|
import { querySQL, runSQL, insertLabels, getSchema } from "./database/db.js";
|
|
40
40
|
import { log, emit } from "./logger.js";
|
|
41
|
+
export function defineLabels(module) {
|
|
42
|
+
return { __type: 'labels', ...module };
|
|
43
|
+
}
|
|
41
44
|
const rules = [];
|
|
42
45
|
let labelDefs = [];
|
|
43
46
|
let labelSrc = 'self';
|
|
@@ -63,7 +66,7 @@ export async function initLabels(labelsDir) {
|
|
|
63
66
|
for (const file of files) {
|
|
64
67
|
const name = file.replace(/\.(ts|js)$/, '');
|
|
65
68
|
const scriptPath = resolve(labelsDir, file);
|
|
66
|
-
const mod = await import(__rewriteRelativeImportExtension(scriptPath));
|
|
69
|
+
const mod = await import(__rewriteRelativeImportExtension(/* @vite-ignore */ `${scriptPath}?t=${Date.now()}`));
|
|
67
70
|
const handler = mod.default;
|
|
68
71
|
if (handler.definition) {
|
|
69
72
|
labelDefs.push(handler.definition);
|
|
@@ -82,6 +85,15 @@ export async function initLabels(labelsDir) {
|
|
|
82
85
|
log(`[labels] ${labelDefs.length} label definitions loaded`);
|
|
83
86
|
}
|
|
84
87
|
}
|
|
88
|
+
/** Register a single label module from a scanned server/ module. */
|
|
89
|
+
export function registerLabelModule(name, labelMod) {
|
|
90
|
+
if (labelMod.definition) {
|
|
91
|
+
labelDefs.push(labelMod.definition);
|
|
92
|
+
}
|
|
93
|
+
if (labelMod.evaluate) {
|
|
94
|
+
rules.push({ name, evaluate: labelMod.evaluate });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
85
97
|
/**
|
|
86
98
|
* Evaluate all loaded label rules against a record and persist any resulting labels.
|
|
87
99
|
* Called after each record is indexed. Rule errors are logged but never block indexing.
|
package/dist/main.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
3
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
4
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
5
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
return path;
|
|
9
|
+
};
|
|
10
|
+
import { mkdirSync, writeFileSync, existsSync } from 'node:fs';
|
|
3
11
|
import { dirname, resolve } from 'node:path';
|
|
4
12
|
import { log } from "./logger.js";
|
|
5
13
|
import { loadConfig } from "./config.js";
|
|
@@ -15,13 +23,15 @@ import { initOpengraph } from "./opengraph.js";
|
|
|
15
23
|
import { initLabels, getLabelDefinitions } from "./labels.js";
|
|
16
24
|
import { startIndexer } from "./indexer.js";
|
|
17
25
|
import { rebuildAllIndexes } from "./database/fts.js";
|
|
18
|
-
import {
|
|
26
|
+
import { createHandler, registerCoreHandlers } from "./server.js";
|
|
27
|
+
import { serve } from "./adapter.js";
|
|
19
28
|
import { validateLexicons } from '@bigmoves/lexicon';
|
|
20
29
|
import { relayHttpUrl } from "./config.js";
|
|
21
30
|
import { runBackfill } from "./backfill.js";
|
|
22
31
|
import { initOAuth } from "./oauth/server.js";
|
|
23
32
|
import { loadOnLoginHook } from "./hooks.js";
|
|
24
33
|
import { initSetup } from "./setup.js";
|
|
34
|
+
import { initServer } from "./server-init.js";
|
|
25
35
|
function logMemory(phase) {
|
|
26
36
|
const mem = process.memoryUsage();
|
|
27
37
|
log(`[mem] ${phase}: heap=${Math.round(mem.heapUsed / 1024 / 1024)}MB rss=${Math.round(mem.rss / 1024 / 1024)}MB external=${Math.round(mem.external / 1024 / 1024)}MB arrayBuffers=${Math.round(mem.arrayBuffers / 1024 / 1024)}MB`);
|
|
@@ -52,7 +62,6 @@ if (collections.length === 0) {
|
|
|
52
62
|
log(`[main] Loaded config: ${collections.length} collections`);
|
|
53
63
|
// Discover view defs from lexicons
|
|
54
64
|
discoverViews();
|
|
55
|
-
await loadOnLoginHook(resolve(configDir, 'hooks'));
|
|
56
65
|
const engineDialect = getDialect(config.databaseEngine);
|
|
57
66
|
const { schemas, ddlStatements } = buildSchemas(lexicons, collections, engineDialect);
|
|
58
67
|
for (const s of schemas) {
|
|
@@ -77,8 +86,27 @@ const migrationChanges = await migrateSchema(schemas);
|
|
|
77
86
|
if (migrationChanges.length > 0) {
|
|
78
87
|
log(`[main] Applied ${migrationChanges.length} schema migration(s)`);
|
|
79
88
|
}
|
|
80
|
-
// 3b. Run setup hooks
|
|
81
|
-
|
|
89
|
+
// 3b. Run setup hooks, feeds, xrpc, og, labels
|
|
90
|
+
const serverDir = resolve(configDir, 'server');
|
|
91
|
+
if (existsSync(serverDir)) {
|
|
92
|
+
// New: single server/ directory
|
|
93
|
+
await initServer(serverDir);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// Legacy: separate directories
|
|
97
|
+
await initSetup(resolve(configDir, 'setup'));
|
|
98
|
+
await loadOnLoginHook(resolve(configDir, 'hooks'));
|
|
99
|
+
await initFeeds(resolve(configDir, 'feeds'));
|
|
100
|
+
log(`[main] Feeds initialized: ${listFeeds().map((f) => f.name).join(', ') || 'none'}`);
|
|
101
|
+
await initXrpc(resolve(configDir, 'xrpc'));
|
|
102
|
+
log(`[main] XRPC handlers initialized: ${listXrpc().join(', ') || 'none'}`);
|
|
103
|
+
await initOpengraph(resolve(configDir, 'og'));
|
|
104
|
+
log(`[main] OpenGraph initialized`);
|
|
105
|
+
await initLabels(resolve(configDir, 'labels'));
|
|
106
|
+
log(`[main] Labels initialized: ${getLabelDefinitions().length} definitions`);
|
|
107
|
+
}
|
|
108
|
+
// Register built-in dev.hatk.* handlers so callXrpc() can find them
|
|
109
|
+
registerCoreHandlers(collections, config.oauth);
|
|
82
110
|
// Write db/schema.sql (after setup, so setup-created tables are included)
|
|
83
111
|
try {
|
|
84
112
|
const schemaDir = resolve(configDir, 'db');
|
|
@@ -100,17 +128,6 @@ try {
|
|
|
100
128
|
}
|
|
101
129
|
}
|
|
102
130
|
catch { }
|
|
103
|
-
// 4. Initialize feeds, xrpc handlers, og, labels from directories
|
|
104
|
-
await initFeeds(resolve(configDir, 'feeds'));
|
|
105
|
-
log(`[main] Feeds initialized: ${listFeeds()
|
|
106
|
-
.map((f) => f.name)
|
|
107
|
-
.join(', ') || 'none'}`);
|
|
108
|
-
await initXrpc(resolve(configDir, 'xrpc'));
|
|
109
|
-
log(`[main] XRPC handlers initialized: ${listXrpc().join(', ') || 'none'}`);
|
|
110
|
-
await initOpengraph(resolve(configDir, 'og'));
|
|
111
|
-
log(`[main] OpenGraph initialized`);
|
|
112
|
-
await initLabels(resolve(configDir, 'labels'));
|
|
113
|
-
log(`[main] Labels initialized: ${getLabelDefinitions().length} definitions`);
|
|
114
131
|
if (config.oauth) {
|
|
115
132
|
await initOAuth(config.oauth, config.plc, config.relay);
|
|
116
133
|
log(`[main] OAuth initialized (issuer: ${config.oauth.issuer})`);
|
|
@@ -142,7 +159,22 @@ function runBackfillAndRestart() {
|
|
|
142
159
|
console.error('[main] Backfill error:', err.message);
|
|
143
160
|
});
|
|
144
161
|
}
|
|
145
|
-
|
|
162
|
+
const handler = createHandler({
|
|
163
|
+
collections,
|
|
164
|
+
publicDir: config.publicDir,
|
|
165
|
+
oauth: config.oauth,
|
|
166
|
+
admins: config.admins,
|
|
167
|
+
onResync: runBackfillAndRestart,
|
|
168
|
+
});
|
|
169
|
+
// Detect SvelteKit build output and use it as fallback handler
|
|
170
|
+
let fallback = undefined;
|
|
171
|
+
const sveltekitHandler = resolve(configDir, 'build', 'handler.js');
|
|
172
|
+
if (existsSync(sveltekitHandler)) {
|
|
173
|
+
const sk = await import(__rewriteRelativeImportExtension(/* @vite-ignore */ sveltekitHandler));
|
|
174
|
+
fallback = sk.handler;
|
|
175
|
+
log(`[main] SvelteKit handler loaded from build/handler.js`);
|
|
176
|
+
}
|
|
177
|
+
serve(handler, config.port, undefined, fallback);
|
|
146
178
|
log(`\nhatk running:`);
|
|
147
179
|
log(` Relay: ${config.relay}`);
|
|
148
180
|
log(` Database: ${config.database}`);
|
package/dist/oauth/server.d.ts
CHANGED
|
@@ -64,10 +64,12 @@ export declare function handlePar(config: OAuthConfig, body: Record<string, stri
|
|
|
64
64
|
expires_in: number;
|
|
65
65
|
}>;
|
|
66
66
|
export declare function buildAuthorizeRedirect(config: OAuthConfig, request: any): string;
|
|
67
|
+
export declare function serverLogin(config: OAuthConfig, handle: string): Promise<string>;
|
|
67
68
|
export declare function handleCallback(config: OAuthConfig, code: string, state: string | null, iss: string | null): Promise<{
|
|
68
69
|
requestUri: string;
|
|
69
70
|
clientRedirectUri: string;
|
|
70
71
|
clientState: string | null;
|
|
72
|
+
did: string;
|
|
71
73
|
}>;
|
|
72
74
|
export declare function handleToken(config: OAuthConfig, body: Record<string, string>, dpopHeader: string, requestUrl: string): Promise<any>;
|
|
73
75
|
export declare function refreshPdsSession(config: OAuthConfig, session: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/oauth/server.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/oauth/server.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AA2E/C,wBAAsB,SAAS,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBrG;AAID,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;;;;;;;;;;;;;;;;;;;EAqBxE;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;;;;;EAO/E;AAED,wBAAgB,OAAO;;;;;;;;;;;;;;;;;;;;;;EAWtB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;;;;;;;;;EAcpE;AAID,wBAAsB,SAAS,CAC7B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CA2ItD;AAID,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,GAAG,MAAM,CAShF;AAID,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CA8FjB;AAID,wBAAsB,cAAc,CAClC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,GAAG,EAAE,MAAM,GAAG,IAAI,GACjB,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAyHrG;AAID,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,GAAG,CAAC,CAUd;AA0JD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACtF,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAmEpF;AAID,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CA0BjC"}
|
package/dist/oauth/server.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// packages/hatk/src/oauth/server.ts
|
|
2
2
|
import { generateKeyPair, importPrivateKey, computeJwkThumbprint, signJwt, parseJwt, verifyEs256, importPublicKey, randomToken, sha256, base64UrlEncode, } from "./crypto.js";
|
|
3
3
|
import { parseDpopProof, createDpopProof } from "./dpop.js";
|
|
4
|
+
import { initSession } from "./session.js";
|
|
4
5
|
import { resolveClient, validateRedirectUri, isLoopbackClient } from "./client.js";
|
|
5
6
|
import { discoverAuthServer, resolveHandle } from "./discovery.js";
|
|
6
7
|
import { getServerKey, storeServerKey, storeOAuthRequest, getOAuthRequest, deleteOAuthRequest, storeAuthCode, consumeAuthCode, storeSession, checkAndStoreDpopJti, cleanupExpiredOAuth, storeRefreshToken, getRefreshToken, revokeRefreshToken, } from "./db.js";
|
|
@@ -57,6 +58,8 @@ export async function initOAuth(_config, plcUrl, relayUrl) {
|
|
|
57
58
|
}
|
|
58
59
|
serverPrivateKey = await importPrivateKey(serverPrivateJwk);
|
|
59
60
|
serverJkt = await computeJwkThumbprint(serverPublicJwk);
|
|
61
|
+
// Initialize SSR session cookie signing
|
|
62
|
+
initSession(serverPrivateJwk, _config.cookieName);
|
|
60
63
|
// Periodic cleanup of expired OAuth data
|
|
61
64
|
setInterval(() => cleanupExpiredOAuth().catch(() => { }), 60_000);
|
|
62
65
|
}
|
|
@@ -258,6 +261,93 @@ export function buildAuthorizeRedirect(config, request) {
|
|
|
258
261
|
});
|
|
259
262
|
return `${request.pds_auth_server}/oauth/authorize?${params}`;
|
|
260
263
|
}
|
|
264
|
+
// --- Server-initiated login (no DPoP required from browser) ---
|
|
265
|
+
export async function serverLogin(config, handle) {
|
|
266
|
+
// Resolve handle to DID
|
|
267
|
+
let did = handle;
|
|
268
|
+
if (!did.startsWith('did:')) {
|
|
269
|
+
did = await resolveHandle(handle, _relayUrl);
|
|
270
|
+
}
|
|
271
|
+
// Discover PDS auth server
|
|
272
|
+
const discovery = await discoverAuthServer(did, _plcUrl);
|
|
273
|
+
const pdsAuthServer = discovery.authServerEndpoint;
|
|
274
|
+
// Create PKCE for PAR to PDS
|
|
275
|
+
const pdsCodeVerifier = randomToken();
|
|
276
|
+
const pdsCodeChallenge = base64UrlEncode(await sha256(pdsCodeVerifier));
|
|
277
|
+
const pdsState = randomToken();
|
|
278
|
+
// PAR to the PDS
|
|
279
|
+
const parEndpoint = discovery.authServerMetadata.pushed_authorization_request_endpoint || `${pdsAuthServer}/oauth/par`;
|
|
280
|
+
const serverDpopProof = await createDpopProof(serverPrivateJwk, serverPublicJwk, 'POST', parEndpoint);
|
|
281
|
+
const scope = config.scopes?.join(' ') || 'atproto transition:generic';
|
|
282
|
+
const pdsParBody = new URLSearchParams({
|
|
283
|
+
client_id: pdsClientId(config.issuer, config),
|
|
284
|
+
redirect_uri: pdsRedirectUri(config.issuer),
|
|
285
|
+
response_type: 'code',
|
|
286
|
+
code_challenge: pdsCodeChallenge,
|
|
287
|
+
code_challenge_method: 'S256',
|
|
288
|
+
scope,
|
|
289
|
+
login_hint: handle,
|
|
290
|
+
state: pdsState,
|
|
291
|
+
});
|
|
292
|
+
let pdsRequestUri;
|
|
293
|
+
const pdsParRes = await fetch(parEndpoint, {
|
|
294
|
+
method: 'POST',
|
|
295
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', DPoP: serverDpopProof },
|
|
296
|
+
body: pdsParBody.toString(),
|
|
297
|
+
});
|
|
298
|
+
if (!pdsParRes.ok) {
|
|
299
|
+
const errBody = await pdsParRes.json().catch(() => ({}));
|
|
300
|
+
if (errBody.error === 'use_dpop_nonce') {
|
|
301
|
+
const nonce = pdsParRes.headers.get('DPoP-Nonce');
|
|
302
|
+
if (nonce) {
|
|
303
|
+
const retryProof = await createDpopProof(serverPrivateJwk, serverPublicJwk, 'POST', parEndpoint, undefined, nonce);
|
|
304
|
+
const retryRes = await fetch(parEndpoint, {
|
|
305
|
+
method: 'POST',
|
|
306
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', DPoP: retryProof },
|
|
307
|
+
body: pdsParBody.toString(),
|
|
308
|
+
});
|
|
309
|
+
if (!retryRes.ok) {
|
|
310
|
+
const retryErr = await retryRes.json().catch(() => ({}));
|
|
311
|
+
throw new Error(`PDS PAR failed: ${retryRes.status} ${retryErr.error_description || retryErr.error || ''}`);
|
|
312
|
+
}
|
|
313
|
+
const retryData = await retryRes.json();
|
|
314
|
+
pdsRequestUri = retryData.request_uri;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
throw new Error(`PDS PAR failed: ${pdsParRes.status} ${errBody.error_description || errBody.error || ''}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
const pdsParData = await pdsParRes.json();
|
|
323
|
+
pdsRequestUri = pdsParData.request_uri;
|
|
324
|
+
}
|
|
325
|
+
// Store the request so the callback can find it
|
|
326
|
+
const requestUri = `urn:ietf:params:oauth:request_uri:${randomToken()}`;
|
|
327
|
+
const expiresAt = Math.floor(Date.now() / 1000) + 600;
|
|
328
|
+
await storeOAuthRequest(requestUri, {
|
|
329
|
+
clientId: pdsClientId(config.issuer, config),
|
|
330
|
+
redirectUri: '/',
|
|
331
|
+
scope,
|
|
332
|
+
state: pdsState,
|
|
333
|
+
codeChallenge: '',
|
|
334
|
+
codeChallengeMethod: 'S256',
|
|
335
|
+
dpopJkt: serverJkt,
|
|
336
|
+
pdsRequestUri,
|
|
337
|
+
pdsAuthServer,
|
|
338
|
+
pdsCodeVerifier,
|
|
339
|
+
pdsState,
|
|
340
|
+
did,
|
|
341
|
+
loginHint: handle,
|
|
342
|
+
expiresAt,
|
|
343
|
+
});
|
|
344
|
+
// Build redirect URL to PDS
|
|
345
|
+
const params = new URLSearchParams({
|
|
346
|
+
request_uri: pdsRequestUri,
|
|
347
|
+
client_id: pdsClientId(config.issuer, config),
|
|
348
|
+
});
|
|
349
|
+
return `${pdsAuthServer}/oauth/authorize?${params}`;
|
|
350
|
+
}
|
|
261
351
|
// --- OAuth Callback (PDS redirects here) ---
|
|
262
352
|
export async function handleCallback(config, code, state, iss) {
|
|
263
353
|
// Find the matching OAuth request by pds_state (unique per PAR)
|
|
@@ -356,7 +446,7 @@ export async function handleCallback(config, code, state, iss) {
|
|
|
356
446
|
if (request.state)
|
|
357
447
|
params.set('state', request.state);
|
|
358
448
|
const clientRedirectUri = `${request.redirect_uri}?${params}`;
|
|
359
|
-
return { requestUri: request.request_uri, clientRedirectUri, clientState: request.state };
|
|
449
|
+
return { requestUri: request.request_uri, clientRedirectUri, clientState: request.state, did };
|
|
360
450
|
}
|
|
361
451
|
// --- Token Endpoint ---
|
|
362
452
|
export async function handleToken(config, body, dpopHeader, requestUrl) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function getSessionCookieName(): string;
|
|
2
|
+
export declare function initSession(privateJwk: JsonWebKey, cookieName?: string): void;
|
|
3
|
+
export declare function createSessionCookie(did: string): Promise<string>;
|
|
4
|
+
export declare function sessionCookieHeader(value: string, secure: boolean): string;
|
|
5
|
+
export declare function clearSessionCookieHeader(): string;
|
|
6
|
+
export declare function parseSessionCookie(request: Request): Promise<{
|
|
7
|
+
did: string;
|
|
8
|
+
} | null>;
|
|
9
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/oauth/session.ts"],"names":[],"mappings":"AASA,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAG7E;AAYD,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMtE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAU1E;AAED,wBAAgB,wBAAwB,IAAI,MAAM,CAEjD;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAoB1F"}
|