@hatk/hatk 0.0.1-alpha.0
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/backfill.d.ts +11 -0
- package/dist/backfill.d.ts.map +1 -0
- package/dist/backfill.js +328 -0
- package/dist/car.d.ts +5 -0
- package/dist/car.d.ts.map +1 -0
- package/dist/car.js +52 -0
- package/dist/cbor.d.ts +7 -0
- package/dist/cbor.d.ts.map +1 -0
- package/dist/cbor.js +89 -0
- package/dist/cid.d.ts +4 -0
- package/dist/cid.d.ts.map +1 -0
- package/dist/cid.js +39 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1663 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +43 -0
- package/dist/db.d.ts +134 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +1361 -0
- package/dist/feeds.d.ts +95 -0
- package/dist/feeds.d.ts.map +1 -0
- package/dist/feeds.js +144 -0
- package/dist/fts.d.ts +20 -0
- package/dist/fts.d.ts.map +1 -0
- package/dist/fts.js +762 -0
- package/dist/hydrate.d.ts +23 -0
- package/dist/hydrate.d.ts.map +1 -0
- package/dist/hydrate.js +75 -0
- package/dist/indexer.d.ts +14 -0
- package/dist/indexer.d.ts.map +1 -0
- package/dist/indexer.js +316 -0
- package/dist/labels.d.ts +29 -0
- package/dist/labels.d.ts.map +1 -0
- package/dist/labels.js +111 -0
- package/dist/lex-types.d.ts +401 -0
- package/dist/lex-types.d.ts.map +1 -0
- package/dist/lex-types.js +4 -0
- package/dist/lexicon-resolve.d.ts +14 -0
- package/dist/lexicon-resolve.d.ts.map +1 -0
- package/dist/lexicon-resolve.js +280 -0
- package/dist/logger.d.ts +4 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +23 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +148 -0
- package/dist/mst.d.ts +6 -0
- package/dist/mst.d.ts.map +1 -0
- package/dist/mst.js +30 -0
- package/dist/oauth/client.d.ts +16 -0
- package/dist/oauth/client.d.ts.map +1 -0
- package/dist/oauth/client.js +54 -0
- package/dist/oauth/crypto.d.ts +28 -0
- package/dist/oauth/crypto.d.ts.map +1 -0
- package/dist/oauth/crypto.js +101 -0
- package/dist/oauth/db.d.ts +47 -0
- package/dist/oauth/db.d.ts.map +1 -0
- package/dist/oauth/db.js +139 -0
- package/dist/oauth/discovery.d.ts +22 -0
- package/dist/oauth/discovery.d.ts.map +1 -0
- package/dist/oauth/discovery.js +50 -0
- package/dist/oauth/dpop.d.ts +11 -0
- package/dist/oauth/dpop.d.ts.map +1 -0
- package/dist/oauth/dpop.js +56 -0
- package/dist/oauth/hooks.d.ts +10 -0
- package/dist/oauth/hooks.d.ts.map +1 -0
- package/dist/oauth/hooks.js +40 -0
- package/dist/oauth/server.d.ts +86 -0
- package/dist/oauth/server.d.ts.map +1 -0
- package/dist/oauth/server.js +572 -0
- package/dist/opengraph.d.ts +34 -0
- package/dist/opengraph.d.ts.map +1 -0
- package/dist/opengraph.js +198 -0
- package/dist/schema.d.ts +51 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +358 -0
- package/dist/seed.d.ts +29 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +86 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +1024 -0
- package/dist/setup.d.ts +8 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +48 -0
- package/dist/test-browser.d.ts +14 -0
- package/dist/test-browser.d.ts.map +1 -0
- package/dist/test-browser.js +26 -0
- package/dist/test.d.ts +47 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +256 -0
- package/dist/views.d.ts +40 -0
- package/dist/views.d.ts.map +1 -0
- package/dist/views.js +178 -0
- package/dist/vite-plugin.d.ts +5 -0
- package/dist/vite-plugin.d.ts.map +1 -0
- package/dist/vite-plugin.js +86 -0
- package/dist/xrpc-client.d.ts +18 -0
- package/dist/xrpc-client.d.ts.map +1 -0
- package/dist/xrpc-client.js +54 -0
- package/dist/xrpc.d.ts +53 -0
- package/dist/xrpc.d.ts.map +1 -0
- package/dist/xrpc.js +139 -0
- package/fonts/Inter-Regular.woff +0 -0
- package/package.json +41 -0
- package/public/admin-auth.js +320 -0
- package/public/admin.html +2166 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
function g(s) {
|
|
2
|
+
const t = s instanceof Uint8Array ? s : new Uint8Array(s)
|
|
3
|
+
let o = ''
|
|
4
|
+
for (let e = 0; e < t.length; e++) o += String.fromCharCode(t[e])
|
|
5
|
+
return btoa(o).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
|
|
6
|
+
}
|
|
7
|
+
function f(s = 32) {
|
|
8
|
+
return g(crypto.getRandomValues(new Uint8Array(s)))
|
|
9
|
+
}
|
|
10
|
+
async function D(s) {
|
|
11
|
+
return new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(s)))
|
|
12
|
+
}
|
|
13
|
+
async function k(s) {
|
|
14
|
+
return g(await D(s))
|
|
15
|
+
}
|
|
16
|
+
async function E(s, t, o) {
|
|
17
|
+
const e = new TextEncoder(),
|
|
18
|
+
n = g(e.encode(JSON.stringify(s))),
|
|
19
|
+
r = g(e.encode(JSON.stringify(t))),
|
|
20
|
+
a = `${n}.${r}`,
|
|
21
|
+
i = await crypto.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, o, e.encode(a))
|
|
22
|
+
return `${a}.${g(i)}`
|
|
23
|
+
}
|
|
24
|
+
const x = 1,
|
|
25
|
+
d = 'dpop-keys',
|
|
26
|
+
p = 'dpop-key',
|
|
27
|
+
y = /* @__PURE__ */ new Map()
|
|
28
|
+
function m(s) {
|
|
29
|
+
const t = y.get(s)
|
|
30
|
+
if (t) return t
|
|
31
|
+
const o = new Promise((e, n) => {
|
|
32
|
+
const r = indexedDB.open(`appview-oauth-${s}`, x)
|
|
33
|
+
;((r.onerror = () => n(r.error)),
|
|
34
|
+
(r.onsuccess = () => e(r.result)),
|
|
35
|
+
(r.onupgradeneeded = (a) => {
|
|
36
|
+
const i = a.target.result
|
|
37
|
+
i.objectStoreNames.contains(d) || i.createObjectStore(d, { keyPath: 'id' })
|
|
38
|
+
}))
|
|
39
|
+
})
|
|
40
|
+
return (y.set(s, o), o)
|
|
41
|
+
}
|
|
42
|
+
async function $(s) {
|
|
43
|
+
const t = await m(s)
|
|
44
|
+
return new Promise((o, e) => {
|
|
45
|
+
const r = t.transaction(d, 'readonly').objectStore(d).get(p)
|
|
46
|
+
;((r.onsuccess = () => o(r.result || null)), (r.onerror = () => e(r.error)))
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
async function b(s, t, o) {
|
|
50
|
+
const e = await m(s)
|
|
51
|
+
return new Promise((n, r) => {
|
|
52
|
+
const i = e.transaction(d, 'readwrite').objectStore(d).put({
|
|
53
|
+
id: p,
|
|
54
|
+
privateKey: t,
|
|
55
|
+
publicJwk: o,
|
|
56
|
+
createdAt: Date.now(),
|
|
57
|
+
})
|
|
58
|
+
;((i.onsuccess = () => n()), (i.onerror = () => r(i.error)))
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
async function P(s) {
|
|
62
|
+
const t = await $(s)
|
|
63
|
+
if (t) return t
|
|
64
|
+
const o = await crypto.subtle.generateKey(
|
|
65
|
+
{ name: 'ECDSA', namedCurve: 'P-256' },
|
|
66
|
+
!1,
|
|
67
|
+
// non-extractable private key
|
|
68
|
+
['sign'],
|
|
69
|
+
),
|
|
70
|
+
e = await crypto.subtle.exportKey('jwk', o.publicKey)
|
|
71
|
+
return (await b(s, o.privateKey, e), { id: p, privateKey: o.privateKey, publicJwk: e, createdAt: Date.now() })
|
|
72
|
+
}
|
|
73
|
+
async function v(s) {
|
|
74
|
+
const t = await m(s)
|
|
75
|
+
return new Promise((o, e) => {
|
|
76
|
+
const r = t.transaction(d, 'readwrite').objectStore(d).delete(p)
|
|
77
|
+
;((r.onsuccess = () => o()), (r.onerror = () => e(r.error)))
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
async function w(s, t, o, e) {
|
|
81
|
+
const n = await P(s),
|
|
82
|
+
{ kty: r, crv: a, x: i, y: c } = n.publicJwk,
|
|
83
|
+
h = { alg: 'ES256', typ: 'dpop+jwt', jwk: { kty: r, crv: a, x: i, y: c } },
|
|
84
|
+
l = {
|
|
85
|
+
jti: f(16),
|
|
86
|
+
htm: t,
|
|
87
|
+
htu: o.split('?')[0],
|
|
88
|
+
iat: Math.floor(Date.now() / 1e3),
|
|
89
|
+
}
|
|
90
|
+
return (e && (l.ath = await k(e)), E(h, l, n.privateKey))
|
|
91
|
+
}
|
|
92
|
+
function U(s) {
|
|
93
|
+
const t = (e) => `appview_${s}_${e}`,
|
|
94
|
+
o = /* @__PURE__ */ new Set(['codeVerifier', 'oauthState', 'redirectUri'])
|
|
95
|
+
return {
|
|
96
|
+
get(e) {
|
|
97
|
+
return (o.has(e) ? sessionStorage : localStorage).getItem(t(e))
|
|
98
|
+
},
|
|
99
|
+
set(e, n) {
|
|
100
|
+
;(o.has(e) ? sessionStorage : localStorage).setItem(t(e), n)
|
|
101
|
+
},
|
|
102
|
+
remove(e) {
|
|
103
|
+
;(o.has(e) ? sessionStorage : localStorage).removeItem(t(e))
|
|
104
|
+
},
|
|
105
|
+
clear() {
|
|
106
|
+
for (const e of o) sessionStorage.removeItem(t(e))
|
|
107
|
+
for (const e of ['accessToken', 'refreshToken', 'tokenExpiresAt', 'userDid', 'clientId'])
|
|
108
|
+
localStorage.removeItem(t(e))
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const S = 5e3
|
|
113
|
+
async function A(s, t) {
|
|
114
|
+
const o = `appview_${s}_lock_${t}`,
|
|
115
|
+
e = `${Date.now()}_${Math.random()}`,
|
|
116
|
+
n = Date.now() + S
|
|
117
|
+
for (; Date.now() < n; ) {
|
|
118
|
+
const r = localStorage.getItem(o)
|
|
119
|
+
if (r) {
|
|
120
|
+
const a = parseInt(r.split('_')[0])
|
|
121
|
+
if (Date.now() - a > S) localStorage.removeItem(o)
|
|
122
|
+
else {
|
|
123
|
+
await new Promise((i) => setTimeout(i, 50))
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if ((localStorage.setItem(o, e), await new Promise((a) => setTimeout(a, 10)), localStorage.getItem(o) === e))
|
|
128
|
+
return e
|
|
129
|
+
}
|
|
130
|
+
return null
|
|
131
|
+
}
|
|
132
|
+
function O(s, t, o) {
|
|
133
|
+
const e = `appview_${s}_lock_${t}`
|
|
134
|
+
localStorage.getItem(e) === o && localStorage.removeItem(e)
|
|
135
|
+
}
|
|
136
|
+
const _ = 6e4
|
|
137
|
+
class K {
|
|
138
|
+
/**
|
|
139
|
+
* @param {object} opts
|
|
140
|
+
* @param {string} opts.server - Appview server URL (e.g. 'https://my-appview.example.com')
|
|
141
|
+
* @param {string} [opts.clientId] - OAuth client_id (defaults to current origin)
|
|
142
|
+
* @param {string} [opts.redirectUri] - Callback URL (defaults to current page)
|
|
143
|
+
* @param {string} [opts.scope] - OAuth scope (defaults to 'atproto')
|
|
144
|
+
*/
|
|
145
|
+
constructor({ server: t, clientId: o, redirectUri: e, scope: n }) {
|
|
146
|
+
;((this.server = t.replace(/\/$/, '')),
|
|
147
|
+
(this.clientId = o || window.location.origin),
|
|
148
|
+
(this.redirectUri = e || window.location.origin + window.location.pathname),
|
|
149
|
+
(this.scope = n || 'atproto'),
|
|
150
|
+
(this.namespace = this.clientId.replace(/[^a-z0-9]/gi, '_').slice(0, 32)),
|
|
151
|
+
(this.storage = U(this.namespace)),
|
|
152
|
+
(this._initPromise = null))
|
|
153
|
+
}
|
|
154
|
+
/** Ensure DPoP key exists in IndexedDB. */
|
|
155
|
+
async init() {
|
|
156
|
+
return (this._initPromise || (this._initPromise = P(this.namespace)), this._initPromise)
|
|
157
|
+
}
|
|
158
|
+
/** Start the OAuth login flow (redirects the browser). */
|
|
159
|
+
async login(t) {
|
|
160
|
+
await this.init()
|
|
161
|
+
const o = f(32),
|
|
162
|
+
e = await k(o),
|
|
163
|
+
n = f(16)
|
|
164
|
+
;(this.storage.set('codeVerifier', o),
|
|
165
|
+
this.storage.set('oauthState', n),
|
|
166
|
+
this.storage.set('clientId', this.clientId),
|
|
167
|
+
this.storage.set('redirectUri', this.redirectUri))
|
|
168
|
+
const r = `${this.server}/oauth/par`,
|
|
169
|
+
a = await w(this.namespace, 'POST', r),
|
|
170
|
+
i = new URLSearchParams({
|
|
171
|
+
client_id: this.clientId,
|
|
172
|
+
redirect_uri: this.redirectUri,
|
|
173
|
+
response_type: 'code',
|
|
174
|
+
code_challenge: e,
|
|
175
|
+
code_challenge_method: 'S256',
|
|
176
|
+
scope: this.scope,
|
|
177
|
+
state: n,
|
|
178
|
+
})
|
|
179
|
+
t && i.set('login_hint', t)
|
|
180
|
+
const c = await fetch(r, {
|
|
181
|
+
method: 'POST',
|
|
182
|
+
headers: {
|
|
183
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
184
|
+
DPoP: a,
|
|
185
|
+
},
|
|
186
|
+
body: i.toString(),
|
|
187
|
+
})
|
|
188
|
+
if (!c.ok) {
|
|
189
|
+
const u = await c.json().catch(() => ({}))
|
|
190
|
+
throw new Error(`PAR failed: ${u.error || c.status}`)
|
|
191
|
+
}
|
|
192
|
+
const { request_uri: h } = await c.json(),
|
|
193
|
+
l = new URLSearchParams({
|
|
194
|
+
request_uri: h,
|
|
195
|
+
client_id: this.clientId,
|
|
196
|
+
})
|
|
197
|
+
window.location.href = `${this.server}/oauth/authorize?${l}`
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Handle the OAuth callback after the redirect.
|
|
201
|
+
* Call this on page load — returns true if a callback was processed.
|
|
202
|
+
*/
|
|
203
|
+
async handleCallback() {
|
|
204
|
+
const t = new URLSearchParams(window.location.search),
|
|
205
|
+
o = t.get('code'),
|
|
206
|
+
e = t.get('state'),
|
|
207
|
+
n = t.get('error')
|
|
208
|
+
if (n) throw new Error(`OAuth error: ${n} - ${t.get('error_description') || ''}`)
|
|
209
|
+
if (!o || !e) return !1
|
|
210
|
+
const r = this.storage.get('oauthState')
|
|
211
|
+
if (e !== r) throw new Error('OAuth state mismatch')
|
|
212
|
+
const a = this.storage.get('codeVerifier'),
|
|
213
|
+
i = this.storage.get('clientId'),
|
|
214
|
+
c = this.storage.get('redirectUri')
|
|
215
|
+
if (!a || !i || !c) throw new Error('Missing OAuth session data')
|
|
216
|
+
await this.init()
|
|
217
|
+
const h = `${this.server}/oauth/token`,
|
|
218
|
+
l = await w(this.namespace, 'POST', h),
|
|
219
|
+
u = await fetch(h, {
|
|
220
|
+
method: 'POST',
|
|
221
|
+
headers: {
|
|
222
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
223
|
+
DPoP: l,
|
|
224
|
+
},
|
|
225
|
+
body: new URLSearchParams({
|
|
226
|
+
grant_type: 'authorization_code',
|
|
227
|
+
code: o,
|
|
228
|
+
redirect_uri: c,
|
|
229
|
+
client_id: i,
|
|
230
|
+
code_verifier: a,
|
|
231
|
+
}),
|
|
232
|
+
})
|
|
233
|
+
if (!u.ok) {
|
|
234
|
+
const I = await u.json().catch(() => ({}))
|
|
235
|
+
throw new Error(`Token exchange failed: ${I.error_description || u.statusText}`)
|
|
236
|
+
}
|
|
237
|
+
const T = await u.json()
|
|
238
|
+
return (
|
|
239
|
+
this._storeTokens(T),
|
|
240
|
+
this.storage.remove('codeVerifier'),
|
|
241
|
+
this.storage.remove('oauthState'),
|
|
242
|
+
this.storage.remove('redirectUri'),
|
|
243
|
+
window.history.replaceState({}, document.title, window.location.pathname),
|
|
244
|
+
!0
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
/** Whether the user is currently logged in (has a non-expired token). */
|
|
248
|
+
get isLoggedIn() {
|
|
249
|
+
return !!this.storage.get('accessToken') && !!this.storage.get('userDid')
|
|
250
|
+
}
|
|
251
|
+
/** The logged-in user's DID, or null. */
|
|
252
|
+
get did() {
|
|
253
|
+
return this.storage.get('userDid')
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Make an authenticated fetch request.
|
|
257
|
+
* Automatically adds DPoP proof and Authorization header.
|
|
258
|
+
* Auto-refreshes expired tokens.
|
|
259
|
+
*/
|
|
260
|
+
async fetch(t, o = {}) {
|
|
261
|
+
await this.init()
|
|
262
|
+
const e = t.startsWith('http') ? t : `${this.server}${t}`,
|
|
263
|
+
n = (o.method || 'GET').toUpperCase(),
|
|
264
|
+
r = await this._getValidToken()
|
|
265
|
+
if (!r) throw new Error('Not authenticated')
|
|
266
|
+
const a = await w(this.namespace, n, e, r),
|
|
267
|
+
i = {
|
|
268
|
+
...o.headers,
|
|
269
|
+
Authorization: `DPoP ${r}`,
|
|
270
|
+
DPoP: a,
|
|
271
|
+
}
|
|
272
|
+
return fetch(e, { ...o, method: n, headers: i })
|
|
273
|
+
}
|
|
274
|
+
/** Log out — clear all stored tokens and DPoP keys. */
|
|
275
|
+
async logout() {
|
|
276
|
+
;(this.storage.clear(), await v(this.namespace), (this._initPromise = null))
|
|
277
|
+
}
|
|
278
|
+
// --- Private ---
|
|
279
|
+
_storeTokens(t) {
|
|
280
|
+
;(this.storage.set('accessToken', t.access_token),
|
|
281
|
+
t.refresh_token && this.storage.set('refreshToken', t.refresh_token),
|
|
282
|
+
t.sub && this.storage.set('userDid', t.sub))
|
|
283
|
+
const o = Date.now() + (t.expires_in || 3600) * 1e3
|
|
284
|
+
this.storage.set('tokenExpiresAt', o.toString())
|
|
285
|
+
}
|
|
286
|
+
async _getValidToken() {
|
|
287
|
+
const t = this.storage.get('accessToken'),
|
|
288
|
+
o = parseInt(this.storage.get('tokenExpiresAt') || '0')
|
|
289
|
+
if (t && Date.now() < o - _) return t
|
|
290
|
+
const e = this.storage.get('refreshToken')
|
|
291
|
+
if (!e) return null
|
|
292
|
+
const n = await A(this.namespace, 'refresh')
|
|
293
|
+
if (!n) return (await new Promise((r) => setTimeout(r, 150)), this.storage.get('accessToken'))
|
|
294
|
+
try {
|
|
295
|
+
const r = this.storage.get('accessToken'),
|
|
296
|
+
a = parseInt(this.storage.get('tokenExpiresAt') || '0')
|
|
297
|
+
if (r && Date.now() < a - _) return r
|
|
298
|
+
const i = `${this.server}/oauth/token`,
|
|
299
|
+
c = await w(this.namespace, 'POST', i),
|
|
300
|
+
h = await fetch(i, {
|
|
301
|
+
method: 'POST',
|
|
302
|
+
headers: {
|
|
303
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
304
|
+
DPoP: c,
|
|
305
|
+
},
|
|
306
|
+
body: new URLSearchParams({
|
|
307
|
+
grant_type: 'refresh_token',
|
|
308
|
+
refresh_token: e,
|
|
309
|
+
client_id: this.storage.get('clientId') || this.clientId,
|
|
310
|
+
}),
|
|
311
|
+
})
|
|
312
|
+
if (!h.ok) return (this.storage.clear(), null)
|
|
313
|
+
const l = await h.json()
|
|
314
|
+
return (this._storeTokens(l), l.access_token)
|
|
315
|
+
} finally {
|
|
316
|
+
O(this.namespace, 'refresh', n)
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
export { K as OAuthClient }
|