@atproto/lex-server 0.0.1 → 0.0.3

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/src/nodejs.ts CHANGED
@@ -66,6 +66,8 @@ export function upgradeWebSocket(request: Request): {
66
66
  return { response, socket }
67
67
  }
68
68
 
69
+ const kUpgradeEvent = Symbol.for('@atproto/lex-server:upgrade')
70
+
69
71
  function handleWebSocketUpgrade(
70
72
  req: IncomingMessage,
71
73
  response: Response,
@@ -98,6 +100,8 @@ function handleWebSocketUpgrade(
98
100
  // @TODO find a way to properly "close" the _socket when the server is
99
101
  // shutting down (might require replacing http-terminator with a local
100
102
  // implementation)
103
+
104
+ req.emit(kUpgradeEvent, ws)
101
105
  })
102
106
  }
103
107
 
@@ -138,26 +142,10 @@ function toRequest(req: IncomingMessage): Request {
138
142
  const url = new URL(req.url ?? '/', `${protocol}://${host}`)
139
143
  const headers = toHeaders(req.headers)
140
144
  const body = toBody(req)
141
-
142
- const abortController = new AbortController()
143
- const abort = (err?: Error) => abortController.abort(err)
144
-
145
- req.on('close', abort)
146
- req.on('error', abort)
147
- req.on('end', abort)
148
-
149
- abortController.signal.addEventListener(
150
- 'abort',
151
- () => {
152
- req.off('close', abort)
153
- req.off('error', abort)
154
- req.off('end', abort)
155
- },
156
- { once: true },
157
- )
145
+ const signal = requestSignal(req)
158
146
 
159
147
  return new Request(url, {
160
- signal: abortController.signal,
148
+ signal,
161
149
  method: req.method,
162
150
  headers,
163
151
  body,
@@ -168,6 +156,58 @@ function toRequest(req: IncomingMessage): Request {
168
156
  })
169
157
  }
170
158
 
159
+ function requestSignal(req: IncomingMessage): AbortSignal {
160
+ if (req.destroyed) return AbortSignal.abort()
161
+
162
+ const abortController = new AbortController()
163
+
164
+ const abort = (err?: Error | WebSocket) => {
165
+ abortController.abort(err instanceof Error ? err : undefined)
166
+
167
+ req.off('close', abort)
168
+ req.off('error', abort)
169
+ req.off('end', abort)
170
+ req.off(kUpgradeEvent, abort)
171
+ }
172
+
173
+ req.on('close', abort)
174
+ req.on('error', abort)
175
+ req.on('end', abort)
176
+ req.on(kUpgradeEvent, abort)
177
+
178
+ return abortController.signal
179
+ }
180
+
181
+ function requestCompletion(req: IncomingMessage): Promise<void> {
182
+ if (req.destroyed) return Promise.resolve()
183
+
184
+ // Unlike the abort signal, we complete the promise only when the request
185
+ // is fully done, accounting for websocket upgrade.
186
+ return new Promise((resolve) => {
187
+ const cleanup = () => {
188
+ req.off('close', done)
189
+ req.off('error', done)
190
+ req.off('end', done)
191
+ req.off(kUpgradeEvent, onUpgrade)
192
+ }
193
+
194
+ const onUpgrade = (ws: WebSocket) => {
195
+ cleanup()
196
+ ws.addEventListener('close', () => resolve())
197
+ }
198
+
199
+ const done = () => {
200
+ resolve()
201
+ cleanup()
202
+ }
203
+
204
+ req.on('close', done)
205
+ req.on('error', done)
206
+ req.on('end', done)
207
+ req.on(kUpgradeEvent, onUpgrade)
208
+ })
209
+ }
210
+
171
211
  function toHeaders(headers: IncomingHttpHeaders): Headers {
172
212
  const result = new Headers()
173
213
  for (const [key, value] of Object.entries(headers)) {
@@ -202,14 +242,14 @@ function toBody(req: IncomingMessage): null | ReadableStream<Uint8Array> {
202
242
  }
203
243
 
204
244
  export type NetAddr = {
245
+ transport: 'tcp'
205
246
  hostname: string
206
247
  port: number
207
- transport: 'tcp'
208
248
  }
209
249
 
210
250
  export type NodeConnectionInfo = {
211
- localAddr?: NetAddr
212
- remoteAddr?: NetAddr
251
+ completed: Promise<void>
252
+ remoteAddr: NetAddr | undefined
213
253
  }
214
254
 
215
255
  export interface HandlerFunction {
@@ -233,21 +273,15 @@ async function handleRequest(
233
273
 
234
274
  function toConnectionInfo(req: IncomingMessage): NodeConnectionInfo {
235
275
  const { socket } = req
276
+
236
277
  return {
237
- localAddr:
238
- socket.localAddress != null
239
- ? {
240
- hostname: socket.localAddress,
241
- port: socket.localPort!,
242
- transport: 'tcp',
243
- }
244
- : undefined,
278
+ completed: requestCompletion(req),
245
279
  remoteAddr:
246
280
  socket.remoteAddress != null
247
281
  ? {
282
+ transport: 'tcp',
248
283
  hostname: socket.remoteAddress,
249
284
  port: socket.remotePort!,
250
- transport: 'tcp',
251
285
  }
252
286
  : undefined,
253
287
  }
@@ -0,0 +1,375 @@
1
+ import * as crypto from '@atproto/crypto'
2
+ import {
3
+ AtprotoDid,
4
+ AtprotoDidDocument,
5
+ Did,
6
+ matchesIdentifier,
7
+ } from '@atproto/did'
8
+ import { fromBase64, isPlainObject, utf8FromBase64 } from '@atproto/lex-data'
9
+ import { DidString, isDidString } from '@atproto/lex-schema'
10
+ import {
11
+ CreateDidResolverOptions,
12
+ createDidResolver,
13
+ } from '@atproto-labs/did-resolver'
14
+ import { LexServerAuthError } from './errors.js'
15
+ import { LexRouterAuth } from './lex-server.js'
16
+
17
+ const BEARER_PREFIX = 'Bearer '
18
+
19
+ /**
20
+ * A function to check and record nonce uniqueness.
21
+ */
22
+ export type UniqueNonceChecker = (nonce: string) => Promise<boolean>
23
+
24
+ export type ServiceAuthOptions = CreateDidResolverOptions & {
25
+ /**
26
+ * Expected audience ("aud") claim in the JWT token. Set to `null` to skip
27
+ * audience verification (not recommended).
28
+ */
29
+ audience: null | DidString
30
+ /**
31
+ * Function to check and record nonce uniqueness. The value checked here must
32
+ * be unique within {@link ServiceAuthOptions.maxAge} seconds before and after
33
+ * the current time.
34
+ *
35
+ * @param nonce - The nonce to check.
36
+ */
37
+ unique: UniqueNonceChecker
38
+ /**
39
+ * Maximum age of the JWT token in seconds.
40
+ *
41
+ * @default 300 (5 minutes)
42
+ */
43
+ maxAge?: number
44
+ }
45
+
46
+ export type ServiceAuthCredentials = {
47
+ did: AtprotoDid
48
+ didDocument: AtprotoDidDocument
49
+ jwt: ParsedJwt
50
+ }
51
+
52
+ /**
53
+ * Creates an authentication handler for LexRouter that verifies AT protocol
54
+ * "service auth" JWT bearer tokens signed by decentralized identifiers (DIDs).
55
+ */
56
+ export function serviceAuth({
57
+ audience,
58
+ maxAge = 5 * 60,
59
+ unique,
60
+ ...options
61
+ }: ServiceAuthOptions): LexRouterAuth<ServiceAuthCredentials> {
62
+ const didResolver = createDidResolver(options)
63
+
64
+ return async ({ request, method }) => {
65
+ const { signal } = request
66
+ const jwt = await parseJwtBearer(request, {
67
+ lxm: method.nsid,
68
+ maxAge,
69
+ audience,
70
+ unique,
71
+ })
72
+
73
+ let didDocument: AtprotoDidDocument = await didResolver
74
+ .resolve(jwt.payload.iss, { signal })
75
+ .catch((cause) => {
76
+ throw new LexServerAuthError(
77
+ 'AuthenticationRequired',
78
+ 'Could not resolve DID document',
79
+ { Bearer: { error: 'DidResolutionFailed' } },
80
+ { cause },
81
+ )
82
+ })
83
+
84
+ const key = getAtprotoSigningKey(didDocument)
85
+
86
+ if (!key || !(await verifyJwt(jwt, key))) {
87
+ signal.throwIfAborted()
88
+
89
+ // Try refreshing the DID document in case it was updated
90
+ didDocument = await didResolver
91
+ .resolve(jwt.payload.iss, { signal, noCache: true })
92
+ .catch((cause) => {
93
+ throw new LexServerAuthError(
94
+ 'AuthenticationRequired',
95
+ 'Could not resolve DID document',
96
+ { Bearer: { error: 'DidResolutionFailed' } },
97
+ { cause },
98
+ )
99
+ })
100
+
101
+ // Verify again with the fresh key (if it changed)
102
+ const keyFresh = getAtprotoSigningKey(didDocument)
103
+ if (!keyFresh || key === keyFresh || !(await verifyJwt(jwt, keyFresh))) {
104
+ throw new LexServerAuthError(
105
+ 'AuthenticationRequired',
106
+ 'Invalid JWT signature',
107
+ { Bearer: { error: 'BadJwtSignature' } },
108
+ )
109
+ }
110
+ }
111
+
112
+ return {
113
+ did: didDocument.id,
114
+ didDocument,
115
+ jwt,
116
+ }
117
+ }
118
+ }
119
+
120
+ async function verifyJwt(jwt: ParsedJwt, key: Did<'key'>) {
121
+ try {
122
+ return await crypto.verifySignature(key, jwt.message, jwt.signature, {
123
+ jwtAlg: jwt.header.alg,
124
+ allowMalleableSig: true,
125
+ })
126
+ } catch (cause) {
127
+ throw new LexServerAuthError(
128
+ 'AuthenticationRequired',
129
+ 'Could not verify JWT signature',
130
+ { Bearer: { error: 'BadJwtSignature' } },
131
+ { cause },
132
+ )
133
+ }
134
+ }
135
+
136
+ function getAtprotoSigningKey(
137
+ didDocument: AtprotoDidDocument,
138
+ ): null | Did<'key'> {
139
+ try {
140
+ const key = didDocument.verificationMethod?.find(
141
+ isAtprotoVerificationMethod,
142
+ didDocument,
143
+ )
144
+
145
+ if (key?.publicKeyMultibase) {
146
+ if (key.type === 'EcdsaSecp256r1VerificationKey2019') {
147
+ const keyBytes = crypto.multibaseToBytes(key.publicKeyMultibase)
148
+ return crypto.formatDidKey(crypto.P256_JWT_ALG, keyBytes)
149
+ } else if (key.type === 'EcdsaSecp256k1VerificationKey2019') {
150
+ const keyBytes = crypto.multibaseToBytes(key.publicKeyMultibase)
151
+ return crypto.formatDidKey(crypto.SECP256K1_JWT_ALG, keyBytes)
152
+ } else if (key.type === 'Multikey') {
153
+ const parsed = crypto.parseMultikey(key.publicKeyMultibase)
154
+ return crypto.formatDidKey(parsed.jwtAlg, parsed.keyBytes)
155
+ }
156
+ }
157
+ } catch {
158
+ // Invalid key, ignore
159
+ }
160
+
161
+ return null
162
+ }
163
+
164
+ function isAtprotoVerificationMethod<
165
+ V extends string | { id: string; type: string; publicKeyMultibase?: string },
166
+ >(
167
+ this: AtprotoDidDocument,
168
+ vm: V,
169
+ ): vm is Exclude<V, string> & {
170
+ id: `${string}#atproto`
171
+ } {
172
+ return typeof vm === 'object' && matchesIdentifier(this.id, 'atproto', vm.id)
173
+ }
174
+
175
+ async function parseJwtBearer(
176
+ request: Request,
177
+ options: ParseJwtOptions,
178
+ ): Promise<ParsedJwt> {
179
+ const authorization = request.headers.get('authorization')
180
+ if (!authorization?.startsWith(BEARER_PREFIX)) {
181
+ throw new LexServerAuthError(
182
+ 'AuthenticationRequired',
183
+ 'Bearer token required',
184
+ { Bearer: { error: 'MissingBearer' } },
185
+ )
186
+ }
187
+
188
+ const token = authorization.slice(BEARER_PREFIX.length).trim()
189
+
190
+ return parseJwt(token, options)
191
+ }
192
+
193
+ export type ParseJwtOptions = {
194
+ maxAge: number
195
+ audience: null | DidString
196
+ unique: UniqueNonceChecker
197
+ lxm: string
198
+ }
199
+
200
+ export type ParsedJwt = {
201
+ header: HeaderObject
202
+ payload: PayloadObject
203
+ message: Uint8Array
204
+ signature: Uint8Array
205
+ }
206
+
207
+ async function parseJwt(
208
+ token: string,
209
+ options: ParseJwtOptions,
210
+ ): Promise<ParsedJwt> {
211
+ const {
212
+ length,
213
+ 0: headerB64,
214
+ 1: payloadB64,
215
+ 2: signatureB64,
216
+ } = token.split('.')
217
+ if (length !== 3) {
218
+ throw new LexServerAuthError(
219
+ 'AuthenticationRequired',
220
+ 'Invalid JWT token',
221
+ { Bearer: { error: 'BadJwt' } },
222
+ )
223
+ }
224
+
225
+ let header: HeaderObject
226
+ try {
227
+ header = jsonFromBase64(headerB64, isHeaderObject)
228
+ } catch (cause) {
229
+ throw new LexServerAuthError(
230
+ 'AuthenticationRequired',
231
+ 'Invalid JWT token',
232
+ { Bearer: { error: 'BadJwt' } },
233
+ { cause },
234
+ )
235
+ }
236
+
237
+ if (
238
+ header.alg === 'none' ||
239
+ // service tokens are not OAuth 2.0 access tokens
240
+ // https://datatracker.ietf.org/doc/html/rfc9068
241
+ header.typ === 'at+jwt' ||
242
+ // "refresh+jwt" is a non-standard type used by the @atproto packages
243
+ header.typ === 'refresh+jwt' ||
244
+ // "DPoP" proofs are not meant to be used as service tokens
245
+ // https://datatracker.ietf.org/doc/html/rfc9449
246
+ header.typ === 'dpop+jwt'
247
+ ) {
248
+ throw new LexServerAuthError(
249
+ 'AuthenticationRequired',
250
+ 'Invalid JWT token',
251
+ { Bearer: { error: 'BadJwt' } },
252
+ )
253
+ }
254
+
255
+ let payload: PayloadObject
256
+ try {
257
+ payload = jsonFromBase64(payloadB64, isPayloadObject)
258
+ } catch (cause) {
259
+ throw new LexServerAuthError(
260
+ 'AuthenticationRequired',
261
+ 'Invalid JWT token',
262
+ { Bearer: { error: 'BadJwt' } },
263
+ { cause },
264
+ )
265
+ }
266
+
267
+ if (options.audience !== null && options.audience !== payload.aud) {
268
+ throw new LexServerAuthError('AuthenticationRequired', 'Invalid audience', {
269
+ Bearer: { error: 'InvalidAudience' },
270
+ })
271
+ }
272
+
273
+ const now = Math.floor(Date.now() / 1000)
274
+
275
+ if (payload.nbf != null && now < payload.nbf) {
276
+ throw new LexServerAuthError(
277
+ 'AuthenticationRequired',
278
+ 'JWT token not yet valid',
279
+ { Bearer: { error: 'JwtNotYetValid' } },
280
+ )
281
+ }
282
+
283
+ if (now > payload.exp) {
284
+ throw new LexServerAuthError(
285
+ 'AuthenticationRequired',
286
+ 'JWT token expired',
287
+ { Bearer: { error: 'JwtExpired' } },
288
+ )
289
+ }
290
+
291
+ // Prevent issuer from generating very long-lived tokens
292
+ if (
293
+ timeDiff(now, payload.exp) > options.maxAge ||
294
+ timeDiff(now, payload.iat) > options.maxAge
295
+ ) {
296
+ throw new LexServerAuthError(
297
+ 'AuthenticationRequired',
298
+ 'JWT token too old',
299
+ { Bearer: { error: 'JwtTooOld' } },
300
+ )
301
+ }
302
+
303
+ if (payload.lxm != null && typeof payload.lxm !== options.lxm) {
304
+ throw new LexServerAuthError(
305
+ 'AuthenticationRequired',
306
+ 'Invalid JWT lexicon method ("lxm")',
307
+ { Bearer: { error: 'BadJwtLexiconMethod' } },
308
+ )
309
+ }
310
+
311
+ if (payload.nonce != null && !(await (0, options.unique)(payload.nonce))) {
312
+ throw new LexServerAuthError(
313
+ 'AuthenticationRequired',
314
+ 'Replay attack detected: nonce is not unique',
315
+ { Bearer: { error: 'NonceNotUnique' } },
316
+ )
317
+ }
318
+
319
+ return {
320
+ header,
321
+ payload,
322
+ message: textEncoder.encode(`${headerB64}.${payloadB64}`),
323
+ signature: fromBase64(signatureB64, 'base64url'),
324
+ }
325
+ }
326
+
327
+ const textEncoder = /*#__PURE__*/ new TextEncoder()
328
+
329
+ type HeaderObject = { alg: string; typ?: string }
330
+ function isHeaderObject(obj: unknown): obj is HeaderObject {
331
+ return (
332
+ isPlainObject(obj) &&
333
+ typeof obj.alg === 'string' &&
334
+ (obj.typ === undefined || typeof obj.typ === 'string')
335
+ )
336
+ }
337
+
338
+ type PayloadObject = {
339
+ iss: DidString
340
+ aud: DidString
341
+ exp: number
342
+ iat?: number
343
+ nbf?: number
344
+ lxm?: string
345
+ nonce?: string
346
+ }
347
+ export function isPayloadObject(obj: unknown): obj is PayloadObject {
348
+ return (
349
+ isPlainObject(obj) &&
350
+ typeof obj.iss === 'string' &&
351
+ typeof obj.aud === 'string' &&
352
+ (obj.lxm === undefined || typeof obj.lxm === 'string') &&
353
+ (obj.nonce === undefined || typeof obj.nonce === 'string') &&
354
+ (obj.iat === undefined || isPositiveInt(obj.iat)) &&
355
+ (obj.nbf === undefined || isPositiveInt(obj.nbf)) &&
356
+ isPositiveInt(obj.exp) &&
357
+ isDidString(obj.iss) &&
358
+ isDidString(obj.aud)
359
+ )
360
+ }
361
+
362
+ function timeDiff(t1: number, t2?: number): number {
363
+ if (t2 === undefined) return 0
364
+ return Math.abs(t1 - t2)
365
+ }
366
+
367
+ function isPositiveInt(value: unknown): value is number {
368
+ return typeof value === 'number' && Number.isInteger(value) && value > 0
369
+ }
370
+
371
+ function jsonFromBase64<T>(b64: string, isType: (obj: unknown) => obj is T): T {
372
+ const obj = JSON.parse(utf8FromBase64(b64, 'base64url'))
373
+ if (isType(obj)) return obj
374
+ throw new Error('Invalid type')
375
+ }
package/dist/example.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=example.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"example.d.ts","sourceRoot":"","sources":["../src/example.ts"],"names":[],"mappings":""}
package/dist/example.js DELETED
@@ -1,36 +0,0 @@
1
- "use strict";
2
- /* eslint-disable @typescript-eslint/no-namespace */
3
- /* eslint-disable n/no-extraneous-import */
4
- /* eslint-disable import/no-unresolved */
5
- /* eslint-env node */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- const lex_1 = require("@atproto/lex");
8
- const index_js_1 = require("./index.js");
9
- const nodejs_js_1 = require("./nodejs.js");
10
- var com;
11
- (function (com) {
12
- let example;
13
- (function (example) {
14
- let echo;
15
- (function (echo) {
16
- echo.nsid = 'com.example.echo';
17
- echo.message = lex_1.l.typedObject(echo.nsid, 'message', lex_1.l.object({
18
- message: lex_1.l.string(),
19
- }));
20
- echo.main = lex_1.l.subscription(echo.nsid, lex_1.l.params({
21
- message: lex_1.l.string({ minLength: 1 }),
22
- interval: lex_1.l.optional(lex_1.l.integer({ minimum: 0, default: 500 })),
23
- }), lex_1.l.typedUnion([lex_1.l.typedRef(() => echo.message)], false));
24
- })(echo = example.echo || (example.echo = {}));
25
- })(example = com.example || (com.example = {}));
26
- })(com || (com = {}));
27
- const router = new index_js_1.LexRouter({ upgradeWebSocket: nodejs_js_1.upgradeWebSocket })
28
- //
29
- .add(com.example.echo, async function* ({ params: { interval, message } }) {
30
- while (true) {
31
- yield com.example.echo.message.$build({ message });
32
- await new Promise((resolve) => setTimeout(resolve, interval));
33
- }
34
- });
35
- (0, nodejs_js_1.serve)(router, { port: 8080, host: '0.0.0.0' });
36
- //# sourceMappingURL=example.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"example.js","sourceRoot":"","sources":["../src/example.ts"],"names":[],"mappings":";AAAA,oDAAoD;AACpD,2CAA2C;AAC3C,yCAAyC;AACzC,qBAAqB;;AAErB,sCAAgC;AAChC,yCAAsC;AACtC,2CAAqD;AAErD,IAAU,GAAG,CAuBZ;AAvBD,WAAU,GAAG;IACX,IAAiB,OAAO,CAqBvB;IArBD,WAAiB,OAAO;QACtB,IAAiB,IAAI,CAmBpB;QAnBD,WAAiB,IAAI;YACN,SAAI,GAAG,kBAAkB,CAAA;YAEzB,YAAO,GAAG,OAAC,CAAC,WAAW,CAClC,KAAA,IAAI,EACJ,SAAS,EACT,OAAC,CAAC,MAAM,CAAC;gBACP,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;aACpB,CAAC,CACH,CAAA;YAEY,SAAI,GAAG,OAAC,CAAC,YAAY,CAChC,KAAA,IAAI,EACJ,OAAC,CAAC,MAAM,CAAC;gBACP,OAAO,EAAE,OAAC,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;gBACnC,QAAQ,EAAE,OAAC,CAAC,QAAQ,CAAC,OAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;aAC9D,CAAC,EACF,OAAC,CAAC,UAAU,CAAC,CAAC,OAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAA,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CACjD,CAAA;QACH,CAAC,EAnBgB,IAAI,GAAJ,YAAI,KAAJ,YAAI,QAmBpB;IACH,CAAC,EArBgB,OAAO,GAAP,WAAO,KAAP,WAAO,QAqBvB;AACH,CAAC,EAvBS,GAAG,KAAH,GAAG,QAuBZ;AAED,MAAM,MAAM,GAAG,IAAI,oBAAS,CAAC,EAAE,gBAAgB,EAAhB,4BAAgB,EAAE,CAAC;IAChD,EAAE;KACD,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;IACvE,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAA;QAClD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAA;IAC/D,CAAC;AACH,CAAC,CAAC,CAAA;AAEJ,IAAA,iBAAK,EAAC,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAA","sourcesContent":["/* eslint-disable @typescript-eslint/no-namespace */\n/* eslint-disable n/no-extraneous-import */\n/* eslint-disable import/no-unresolved */\n/* eslint-env node */\n\nimport { l } from '@atproto/lex'\nimport { LexRouter } from './index.js'\nimport { serve, upgradeWebSocket } from './nodejs.js'\n\nnamespace com {\n export namespace example {\n export namespace echo {\n export const nsid = 'com.example.echo'\n\n export const message = l.typedObject(\n nsid,\n 'message',\n l.object({\n message: l.string(),\n }),\n )\n\n export const main = l.subscription(\n nsid,\n l.params({\n message: l.string({ minLength: 1 }),\n interval: l.optional(l.integer({ minimum: 0, default: 500 })),\n }),\n l.typedUnion([l.typedRef(() => message)], false),\n )\n }\n }\n}\n\nconst router = new LexRouter({ upgradeWebSocket })\n //\n .add(com.example.echo, async function* ({ params: { interval, message } }) {\n while (true) {\n yield com.example.echo.message.$build({ message })\n await new Promise((resolve) => setTimeout(resolve, interval))\n }\n })\n\nserve(router, { port: 8080, host: '0.0.0.0' })\n"]}
@@ -1,15 +0,0 @@
1
- import { LexError, LexErrorCode } from '@atproto/lex-data';
2
- export type WWWAuthenticate = {
3
- [k: string]: Record<string, string>;
4
- };
5
- export declare function formatWWWAuthenticate(wwwAuthenticate: WWWAuthenticate): string;
6
- export declare class LexServerAuthError<N extends LexErrorCode = LexErrorCode> extends LexError<N> {
7
- readonly wwwAuthenticate?: WWWAuthenticate | undefined;
8
- name: string;
9
- constructor(error: N, message: string, wwwAuthenticate?: WWWAuthenticate | undefined, options?: ErrorOptions);
10
- get wwwAuthenticateHeader(): string;
11
- toJSON(): import("@atproto/lex-data").LexErrorData<any>;
12
- toResponse(): Response;
13
- static from(cause: LexError, wwwAuthenticate?: WWWAuthenticate): LexServerAuthError;
14
- }
15
- //# sourceMappingURL=lex-auth-error.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"lex-auth-error.d.ts","sourceRoot":"","sources":["../src/lex-auth-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAE1D,MAAM,MAAM,eAAe,GAAG;IAAE,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAA;AACrE,wBAAgB,qBAAqB,CACnC,eAAe,EAAE,eAAe,GAC/B,MAAM,CAWR;AAED,qBAAa,kBAAkB,CAC7B,CAAC,SAAS,YAAY,GAAG,YAAY,CACrC,SAAQ,QAAQ,CAAC,CAAC,CAAC;IAMjB,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe;IAL5C,IAAI,SAAuB;gBAGzB,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,MAAM,EACN,eAAe,CAAC,EAAE,eAAe,YAAA,EAC1C,OAAO,CAAC,EAAE,YAAY;IAKxB,IAAI,qBAAqB,IAAI,MAAM,CAElC;IAED,MAAM;IAKN,UAAU,IAAI,QAAQ;IAatB,MAAM,CAAC,IAAI,CACT,KAAK,EAAE,QAAQ,EACf,eAAe,CAAC,EAAE,eAAe,GAChC,kBAAkB;CAMtB"}
@@ -1,52 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LexServerAuthError = void 0;
4
- exports.formatWWWAuthenticate = formatWWWAuthenticate;
5
- const lex_data_1 = require("@atproto/lex-data");
6
- function formatWWWAuthenticate(wwwAuthenticate) {
7
- return Object.entries(wwwAuthenticate)
8
- .map(([type, params]) => {
9
- if (!params)
10
- return null;
11
- const paramsEnc = Object.entries(params)
12
- .filter(([_, val]) => val != null)
13
- .map(([name, val]) => `${name}=${JSON.stringify(val)}`);
14
- return paramsEnc?.length ? `${type} ${paramsEnc.join(', ')}` : type;
15
- })
16
- .filter(Boolean)
17
- .join(', ');
18
- }
19
- class LexServerAuthError extends lex_data_1.LexError {
20
- wwwAuthenticate;
21
- name = 'LexServerAuthError';
22
- constructor(error, message, wwwAuthenticate, options) {
23
- super(error, message, options);
24
- this.wwwAuthenticate = wwwAuthenticate;
25
- }
26
- get wwwAuthenticateHeader() {
27
- return formatWWWAuthenticate(this.wwwAuthenticate ?? {});
28
- }
29
- toJSON() {
30
- const { cause } = this;
31
- return cause instanceof lex_data_1.LexError ? cause.toJSON() : super.toJSON();
32
- }
33
- toResponse() {
34
- const { wwwAuthenticateHeader } = this;
35
- const headers = wwwAuthenticateHeader
36
- ? new Headers({
37
- 'WWW-Authenticate': wwwAuthenticateHeader,
38
- 'Access-Control-Expose-Headers': 'WWW-Authenticate', // CORS
39
- })
40
- : undefined;
41
- return Response.json(this.toJSON(), { status: 401, headers });
42
- }
43
- static from(cause, wwwAuthenticate) {
44
- if (cause instanceof LexServerAuthError)
45
- return cause;
46
- return new LexServerAuthError(cause.error, cause.message, wwwAuthenticate, {
47
- cause,
48
- });
49
- }
50
- }
51
- exports.LexServerAuthError = LexServerAuthError;
52
- //# sourceMappingURL=lex-auth-error.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"lex-auth-error.js","sourceRoot":"","sources":["../src/lex-auth-error.ts"],"names":[],"mappings":";;;AAGA,sDAaC;AAhBD,gDAA0D;AAG1D,SAAgB,qBAAqB,CACnC,eAAgC;IAEhC,OAAO,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC;SACnC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE;QACtB,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAA;QACxB,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;aACrC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,CAAC;aACjC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACzD,OAAO,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IACrE,CAAC,CAAC;SACD,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC;AAED,MAAa,kBAEX,SAAQ,mBAAW;IAMR;IALX,IAAI,GAAG,oBAAoB,CAAA;IAE3B,YACE,KAAQ,EACR,OAAe,EACN,eAAiC,EAC1C,OAAsB;QAEtB,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;QAHrB,oBAAe,GAAf,eAAe,CAAkB;IAI5C,CAAC;IAED,IAAI,qBAAqB;QACvB,OAAO,qBAAqB,CAAC,IAAI,CAAC,eAAe,IAAI,EAAE,CAAC,CAAA;IAC1D,CAAC;IAED,MAAM;QACJ,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAA;QACtB,OAAO,KAAK,YAAY,mBAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAA;IACpE,CAAC;IAED,UAAU;QACR,MAAM,EAAE,qBAAqB,EAAE,GAAG,IAAI,CAAA;QAEtC,MAAM,OAAO,GAAG,qBAAqB;YACnC,CAAC,CAAC,IAAI,OAAO,CAAC;gBACV,kBAAkB,EAAE,qBAAqB;gBACzC,+BAA+B,EAAE,kBAAkB,EAAE,OAAO;aAC7D,CAAC;YACJ,CAAC,CAAC,SAAS,CAAA;QAEb,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAA;IAC/D,CAAC;IAED,MAAM,CAAC,IAAI,CACT,KAAe,EACf,eAAiC;QAEjC,IAAI,KAAK,YAAY,kBAAkB;YAAE,OAAO,KAAK,CAAA;QACrD,OAAO,IAAI,kBAAkB,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,eAAe,EAAE;YACzE,KAAK;SACN,CAAC,CAAA;IACJ,CAAC;CACF;AA7CD,gDA6CC","sourcesContent":["import { LexError, LexErrorCode } from '@atproto/lex-data'\n\nexport type WWWAuthenticate = { [k: string]: Record<string, string> }\nexport function formatWWWAuthenticate(\n wwwAuthenticate: WWWAuthenticate,\n): string {\n return Object.entries(wwwAuthenticate)\n .map(([type, params]) => {\n if (!params) return null\n const paramsEnc = Object.entries(params)\n .filter(([_, val]) => val != null)\n .map(([name, val]) => `${name}=${JSON.stringify(val)}`)\n return paramsEnc?.length ? `${type} ${paramsEnc.join(', ')}` : type\n })\n .filter(Boolean)\n .join(', ')\n}\n\nexport class LexServerAuthError<\n N extends LexErrorCode = LexErrorCode,\n> extends LexError<N> {\n name = 'LexServerAuthError'\n\n constructor(\n error: N,\n message: string,\n readonly wwwAuthenticate?: WWWAuthenticate,\n options?: ErrorOptions,\n ) {\n super(error, message, options)\n }\n\n get wwwAuthenticateHeader(): string {\n return formatWWWAuthenticate(this.wwwAuthenticate ?? {})\n }\n\n toJSON() {\n const { cause } = this\n return cause instanceof LexError ? cause.toJSON() : super.toJSON()\n }\n\n toResponse(): Response {\n const { wwwAuthenticateHeader } = this\n\n const headers = wwwAuthenticateHeader\n ? new Headers({\n 'WWW-Authenticate': wwwAuthenticateHeader,\n 'Access-Control-Expose-Headers': 'WWW-Authenticate', // CORS\n })\n : undefined\n\n return Response.json(this.toJSON(), { status: 401, headers })\n }\n\n static from(\n cause: LexError,\n wwwAuthenticate?: WWWAuthenticate,\n ): LexServerAuthError {\n if (cause instanceof LexServerAuthError) return cause\n return new LexServerAuthError(cause.error, cause.message, wwwAuthenticate, {\n cause,\n })\n }\n}\n"]}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=subscripotion.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"subscripotion.d.ts","sourceRoot":"","sources":["../src/subscripotion.ts"],"names":[],"mappings":""}