updated max line length

+3 -5
eslint.config.js
··· 6 6 import react from 'eslint-plugin-react' 7 7 import reactHooks from 'eslint-plugin-react-hooks' 8 8 import tsdoc from 'eslint-plugin-tsdoc' 9 + import {defineConfig} from 'eslint/config' 9 10 import globals from 'globals' 10 11 import path from 'node:path' 11 12 import tseslint from 'typescript-eslint' 12 13 13 14 const gitignore = path.resolve(import.meta.dirname, '.gitignore') 14 15 15 - export default tseslint.config( 16 + export default defineConfig( 16 17 includeIgnoreFile(gitignore, '.gitignore'), 17 18 18 19 // all files by default get shared globals ··· 54 55 '@typescript-eslint/restrict-template-expressions': 'off', 55 56 56 57 // I need to be able to do `while(true)`, come on yall... 57 - '@typescript-eslint/no-unnecessary-condition': [ 58 - 'warn', 59 - {allowConstantLoopConditions: 'always'}, 60 - ], 58 + '@typescript-eslint/no-unnecessary-condition': ['warn', {allowConstantLoopConditions: 'always'}], 61 59 62 60 // this breaks when I want to use a type parameter to allow type checking inline arguments 63 61 // it's "unnecessary type parameters" or an `as` declaration, which I don't like
+1 -1
prettier.config.js
··· 3 3 * @type {import("prettier").Config} 4 4 */ 5 5 export default { 6 - printWidth: 100, 6 + printWidth: 120, 7 7 tabWidth: 2, 8 8 semi: false, 9 9 singleQuote: true,
+1 -3
src/client/components/feed-import-nytimes.tsx
··· 113 113 return ( 114 114 <div className="feed-import-nytimes"> 115 115 <button type="button" onClick={importAll} disabled={importing$.value}> 116 - {importing$.value 117 - ? `Importing... (${imported$.value}/${NYTIMES_FEEDS.length})` 118 - : 'Import All NY Times Feeds'} 116 + {importing$.value ? `Importing... (${imported$.value}/${NYTIMES_FEEDS.length})` : 'Import All NY Times Feeds'} 119 117 </button> 120 118 121 119 {errors$.value.length > 0 && (
+1 -3
src/client/components/feed-import-podcasts.tsx
··· 61 61 return ( 62 62 <div className="feed-import-podcasts"> 63 63 <button type="button" onClick={importAll} disabled={importing$.value}> 64 - {importing$.value 65 - ? `Importing... (${imported$.value}/${PODCAST_FEEDS.length})` 66 - : 'Import Podcast Feeds'} 64 + {importing$.value ? `Importing... (${imported$.value}/${PODCAST_FEEDS.length})` : 'Import Podcast Feeds'} 67 65 </button> 68 66 69 67 {errors$.value.length > 0 && (
+1 -3
src/client/components/feed-import-tech.tsx
··· 60 60 return ( 61 61 <div className="feed-import-tech"> 62 62 <button type="button" onClick={importAll} disabled={importing$.value}> 63 - {importing$.value 64 - ? `Importing... (${imported$.value}/${TECH_FEEDS.length})` 65 - : 'Import Tech Feeds'} 63 + {importing$.value ? `Importing... (${imported$.value}/${TECH_FEEDS.length})` : 'Import Tech Feeds'} 66 64 </button> 67 65 68 66 {errors$.value.length > 0 && (
+1 -5
src/client/page-app.tsx
··· 22 22 const wsurl = `${wsproto}://${wshost}/stream` 23 23 24 24 return ( 25 - <SkypodProvider 26 - identityFallback={identityFallback} 27 - connectionFallback={connectionFallback} 28 - websocketUrl={wsurl} 29 - > 25 + <SkypodProvider identityFallback={identityFallback} connectionFallback={connectionFallback} websocketUrl={wsurl}> 30 26 <RealmConnectionManager /> 31 27 <PeerList /> 32 28 <FeedImportNYTimes />
+2 -6
src/client/root/context-database.tsx
··· 13 13 useDbSignal: <T>(querier: DbQuerier<T>) => ReadonlySignal<T | undefined> 14 14 } 15 15 16 - export const DatabaseProvider: preact.FunctionComponent<{children: preact.ComponentChildren}> = ( 17 - props, 18 - ) => { 16 + export const DatabaseProvider: preact.FunctionComponent<{children: preact.ComponentChildren}> = (props) => { 19 17 const db = useMemo(() => new Database(), []) 20 18 21 19 function useDbSignal<T>(querier: DbQuerier<T>): ReadonlySignal<T | undefined> { ··· 32 30 return signal 33 31 } 34 32 35 - return ( 36 - <DatabaseContext.Provider value={{db, useDbSignal}}>{props.children}</DatabaseContext.Provider> 37 - ) 33 + return <DatabaseContext.Provider value={{db, useDbSignal}}>{props.children}</DatabaseContext.Provider> 38 34 } 39 35 40 36 export function useDatabase() {
+3 -14
src/client/skypod/action-dispatch/context.tsx
··· 8 8 9 9 import {Action, ActionMap, ActionOpts} from '#skypod/actions' 10 10 11 - export type MiddlewareFn = ( 12 - this: undefined, 13 - action: Action, 14 - ) => void | Action[] | Promise<void | Action[]> 11 + export type MiddlewareFn = (this: undefined, action: Action) => void | Action[] | Promise<void | Action[]> 15 12 16 13 export type MiddlewareRouterFn<K extends keyof ActionMap> = ( 17 14 this: undefined, ··· 89 86 } 90 87 }, 91 88 92 - action: <N extends keyof ActionMap>( 93 - msg: N, 94 - dat: ActionMap[N]['dat'], 95 - opt?: ActionOpts, 96 - ): ActionMap[N] => { 89 + action: <N extends keyof ActionMap>(msg: N, dat: ActionMap[N]['dat'], opt?: ActionOpts): ActionMap[N] => { 97 90 const clk = identity.clock.now() 98 91 return {typ: 'act', clk, msg, dat, opt} as ActionMap[N] 99 92 }, ··· 135 128 }, 136 129 }) 137 130 138 - return ( 139 - <ActionDispatchContext.Provider value={context.current}> 140 - {props.children} 141 - </ActionDispatchContext.Provider> 142 - ) 131 + return <ActionDispatchContext.Provider value={context.current}>{props.children}</ActionDispatchContext.Provider> 143 132 } 144 133 145 134 export function useActionDispatch() {
+2 -14
src/client/skypod/feed-processor/worker.ts
··· 47 47 48 48 async processPending() { 49 49 const pendingFeeds = this.#db.feeds.where('lastRefresh.status').equals('pending') 50 - return await this.#db.withLock( 51 - 'feeds', 52 - pendingFeeds, 53 - this.#clock, 54 - this.#owner, 55 - this.#processLocked, 56 - ) 50 + return await this.#db.withLock('feeds', pendingFeeds, this.#clock, this.#owner, this.#processLocked) 57 51 } 58 52 59 53 async processUrls(urls: string[]) { 60 54 const requestedFeeds = this.#db.feeds.where('url').anyOf(urls) 61 - return await this.#db.withLock( 62 - 'feeds', 63 - requestedFeeds, 64 - this.#clock, 65 - this.#owner, 66 - this.#processLocked, 67 - ) 55 + return await this.#db.withLock('feeds', requestedFeeds, this.#clock, this.#owner, this.#processLocked) 68 56 } 69 57 70 58 #poll = () => {
+1 -4
src/cmd/register-ident.ts
··· 15 15 pubkey: await jwkExport.parseAsync(keypair.publicKey), 16 16 } 17 17 18 - const jwt = await generateSignableJwt(payload) 19 - .setIssuedAt() 20 - .setExpirationTime('1m') 21 - .sign(keypair.privateKey) 18 + const jwt = await generateSignableJwt(payload).setIssuedAt().setExpirationTime('1m').sign(keypair.privateKey) 22 19 23 20 console.log('Generated Preauth JWT:') 24 21 console.log(jwt)
+5 -12
src/common/crypto/cipher.ts
··· 43 43 const nonce = encoder.encode(`${nonceStr}-nonce-cryptosystem`) 44 44 const derivedsalt = new Uint8Array([...salt, ...nonce]) 45 45 46 - return await crypto.subtle.deriveKey( 47 - {...deriveAlgo, salt: derivedsalt, iterations}, 48 - derivedkey, 49 - encrAlgo, 50 - false, 51 - ['encrypt', 'decrypt'], 52 - ) 46 + return await crypto.subtle.deriveKey({...deriveAlgo, salt: derivedsalt, iterations}, derivedkey, encrAlgo, false, [ 47 + 'encrypt', 48 + 'decrypt', 49 + ]) 53 50 } 54 51 55 52 /** ··· 96 93 async encrypt(data: string | Uint8Array): Promise<string> { 97 94 const iv = crypto.getRandomValues(new Uint8Array(12)) 98 95 const encoded = asUint8Array(data) 99 - const encrypted = await crypto.subtle.encrypt( 100 - {...encrAlgo, iv}, 101 - this.#cryptokey, 102 - encoded as BufferSource, 103 - ) 96 + const encrypted = await crypto.subtle.encrypt({...encrAlgo, iv}, this.#cryptokey, encoded as BufferSource) 104 97 105 98 // output = [iv + encrypted] which gives us the auth tag 106 99
+13 -15
src/common/crypto/jwts.ts
··· 20 20 * schema describing a decoded JWT. 21 21 * **important** - this does no claims validation, only decoding from string to JWT! 22 22 */ 23 - export const jwtSchema: z.ZodType<JWTToken, string> = z 24 - .jwt({abort: true}) 25 - .transform((token, ctx) => { 26 - try { 27 - const claims = jose.decodeJwt(token) 28 - return {claims, token} 29 - } catch (e) { 30 - ctx.issues.push({ 31 - code: 'custom', 32 - message: `error while decoding token: ${e}`, 33 - input: token, 34 - }) 23 + export const jwtSchema: z.ZodType<JWTToken, string> = z.jwt({abort: true}).transform((token, ctx) => { 24 + try { 25 + const claims = jose.decodeJwt(token) 26 + return {claims, token} 27 + } catch (e) { 28 + ctx.issues.push({ 29 + code: 'custom', 30 + message: `error while decoding token: ${e}`, 31 + input: token, 32 + }) 35 33 36 - return z.NEVER 37 - } 38 - }) 34 + return z.NEVER 35 + } 36 + }) 39 37 40 38 /** 41 39 * schema describing a verified payload in a JWT.
+2 -9
src/common/socket.ts
··· 66 66 } 67 67 68 68 /** exactly take socket, but will additionally apply a json decoding */ 69 - export async function takeSocketJson<T>( 70 - ws: WebSocket, 71 - schema: z.ZodType<T>, 72 - signal?: AbortSignal, 73 - ): Promise<T> { 69 + export async function takeSocketJson<T>(ws: WebSocket, schema: z.ZodType<T>, signal?: AbortSignal): Promise<T> { 74 70 const data = await takeSocket(ws, signal) 75 71 return parseJson.pipe(schema).parseAsync(data) 76 72 } ··· 191 187 * exactly stream socket, but will additionally apply a json decoding 192 188 * messages not validating will end the stream with an error 193 189 */ 194 - export async function* streamSocketJson( 195 - ws: WebSocket, 196 - config?: Partial<ConfigProps>, 197 - ): AsyncGenerator { 190 + export async function* streamSocketJson(ws: WebSocket, config?: Partial<ConfigProps>): AsyncGenerator { 198 191 for await (const message of streamSocket(ws, config)) { 199 192 yield parseJson.parseAsync(message) 200 193 }
+1 -4
src/realm/client/components/connection-manager.tsx
··· 81 81 </p> 82 82 <p> 83 83 Exchange an invite: 84 - <textarea 85 - value={invitation.value} 86 - onInput={(e) => (invitation.value = e.currentTarget.value)} 87 - /> 84 + <textarea value={invitation.value} onInput={(e) => (invitation.value = e.currentTarget.value)} /> 88 85 <button type="button" onClick={exchange}> 89 86 Exchange 90 87 </button>
+1 -3
src/realm/client/context-identity.tsx
··· 45 45 }, [store, value$, error$]) 46 46 47 47 return value$.value ? ( 48 - <RealmIdentityContext.Provider value={value$.value}> 49 - {props.children} 50 - </RealmIdentityContext.Provider> 48 + <RealmIdentityContext.Provider value={value$.value}>{props.children}</RealmIdentityContext.Provider> 51 49 ) : ( 52 50 props.fallback({loading: !error$.value, error: error$.value}) 53 51 )
+1 -4
src/realm/client/service-connection-peer.ts
··· 34 34 super({ 35 35 initiator, 36 36 config: { 37 - iceServers: [ 38 - {urls: 'stun:stun.l.google.com:19302'}, 39 - {urls: 'stun:stun1.l.google.com:19302'}, 40 - ], 37 + iceServers: [{urls: 'stun:stun.l.google.com:19302'}, {urls: 'stun:stun1.l.google.com:19302'}], 41 38 }, 42 39 }) 43 40
+1 -3
src/realm/client/service-connection.ts
··· 333 333 334 334 switch (parse.data.msg) { 335 335 case 'realm.rtc.signal': { 336 - const jwt = await jwtPayload(protocol.realmRtcSignalPayloadSchema).parseAsync( 337 - parse.data.dat.signed, 338 - ) 336 + const jwt = await jwtPayload(protocol.realmRtcSignalPayloadSchema).parseAsync(parse.data.dat.signed) 339 337 340 338 // backwards from our perspective 341 339 const remoteid = parse.data.dat.localid
+1 -5
src/realm/protocol/index.ts
··· 7 7 export * from './brands' 8 8 export * from './messages' 9 9 10 - export function makeError( 11 - error: ProtocolError, 12 - detail: string, 13 - seq?: number, 14 - ): z.infer<typeof errorMessageSchema> { 10 + export function makeError(error: ProtocolError, detail: string, seq?: number): z.infer<typeof errorMessageSchema> { 15 11 return { 16 12 typ: 'err', 17 13 msg: error.message,
+3 -14
src/realm/protocol/messages.ts
··· 4 4 import {IdentBrand} from './brands' 5 5 import {deviceCapsSchema, deviceInfoSchema} from './device' 6 6 import {LogicalClock} from './logical-clock' 7 - import { 8 - makeEmptyRequestSchema, 9 - makeEventSchema, 10 - makeRequestSchema, 11 - makeResponseSchema, 12 - } from './schema' 7 + import {makeEmptyRequestSchema, makeEventSchema, makeRequestSchema, makeResponseSchema} from './schema' 13 8 14 9 export const serverPeerIdSchema = z.literal('server') 15 10 export type ServerPeerId = z.infer<typeof serverPeerIdSchema> ··· 18 13 19 14 /// preauth 20 15 21 - export const preauthRegisterReqSchema = makeRequestSchema( 22 - 'preauth.register', 23 - z.object({pubkey: jwkSchema}), 24 - ) 16 + export const preauthRegisterReqSchema = makeRequestSchema('preauth.register', z.object({pubkey: jwkSchema})) 25 17 26 18 export const preauthAuthnReqSchema = makeEmptyRequestSchema('preauth.authn') 27 19 ··· 70 62 }), 71 63 ) 72 64 73 - export const realmRtcPongResponseSchema = makeResponseSchema( 74 - 'realm.rtc.pong', 75 - z.object({peerClocks: peerClocksSchema}), 76 - ) 65 + export const realmRtcPongResponseSchema = makeResponseSchema('realm.rtc.pong', z.object({peerClocks: peerClocksSchema})) 77 66 78 67 export const realmRtcAnnounceRequestSchema = makeRequestSchema( 79 68 'realm.rtc.announce',
+4 -18
src/realm/server/handler-preauth.ts
··· 5 5 import {jwtPayload, jwtSchema, JWTToken, verifyJwtToken} from '#common/crypto/jwts' 6 6 import {normalizeError, ProtocolError} from '#common/errors' 7 7 import {takeSocket} from '#common/socket' 8 - import { 9 - IdentBrand, 10 - IdentID, 11 - preauthReqSchema, 12 - PreauthResponse, 13 - RealmBrand, 14 - RealmID, 15 - } from '#realm/protocol/index' 8 + import {IdentBrand, IdentID, preauthReqSchema, PreauthResponse, RealmBrand, RealmID} from '#realm/protocol/index' 16 9 17 10 import * as realms from './state' 18 11 ··· 21 14 * - if the realm does not exist (by realm id), we create a new one, and add the identity (success). 22 15 * - if the realm /does/ exist, we verify the message is a signed JWT our already registered pubkey. 23 16 */ 24 - export async function preauthHandler( 25 - ws: WebSocket, 26 - signal?: AbortSignal, 27 - ): Promise<realms.AuthenticatedIdentity> { 17 + export async function preauthHandler(ws: WebSocket, signal?: AbortSignal): Promise<realms.AuthenticatedIdentity> { 28 18 const timeout = timeoutSignal(3000) 29 19 const combinedSignal = combineSignals(signal, timeout.signal) 30 20 ··· 75 65 const realm = realmMap.require(realmid) 76 66 77 67 if (!invitation.claims.jti) throw new Error('invitation requires nonce!') 78 - if (!(await realms.validateNonce(realmid, invitation.claims.jti))) 79 - throw new Error('invitation already used!') 68 + if (!(await realms.validateNonce(realmid, invitation.claims.jti))) throw new Error('invitation already used!') 80 69 81 70 const inviterid = IdentBrand.parse(invitation.claims.iss) 82 71 const inviterkey = realm.storage.identities.require(inviterid) ··· 107 96 } 108 97 } 109 98 110 - async function preauthResponse( 111 - auth: realms.AuthenticatedIdentity, 112 - seq?: number, 113 - ): Promise<PreauthResponse> { 99 + async function preauthResponse(auth: realms.AuthenticatedIdentity, seq?: number): Promise<PreauthResponse> { 114 100 const peers: Array<IdentID | 'server'> = Array.from(auth.realm.sockets.keys()) 115 101 peers.unshift('server') 116 102
+4 -16
src/realm/server/handler-realm.ts
··· 24 24 * ance we've retrieved authentication details, we go into the main realm loop. 25 25 * read messages as they come in and dispatch actions. 26 26 */ 27 - export async function realmHandler( 28 - ws: WebSocket, 29 - auth: realm.AuthenticatedIdentity, 30 - signal?: AbortSignal, 31 - ) { 27 + export async function realmHandler(ws: WebSocket, auth: realm.AuthenticatedIdentity, signal?: AbortSignal) { 32 28 realmBroadcast(auth, await buildRtcPeerJoined(auth)) 33 29 34 30 try { ··· 77 73 } 78 74 } 79 75 80 - async function buildRtcPeerJoined( 81 - auth: realm.AuthenticatedIdentity, 82 - ): Promise<protocol.RealmRtcPeerJoinedEvent> { 76 + async function buildRtcPeerJoined(auth: realm.AuthenticatedIdentity): Promise<protocol.RealmRtcPeerJoinedEvent> { 83 77 return { 84 78 typ: 'evt', 85 79 msg: 'realm.rtc.peer-joined', ··· 112 106 recipients: protocol.IdentID[] | boolean = false, 113 107 ) { 114 108 const echo = recipients === true || Array.isArray(recipients) 115 - const recips = Array.isArray(recipients) 116 - ? recipients 117 - : Array.from(auth.realm.storage.identities.keys()) 109 + const recips = Array.isArray(recipients) ? recipients : Array.from(auth.realm.storage.identities.keys()) 118 110 const json = JSON.stringify(payload) 119 111 120 112 for (const recip of recips) { ··· 129 121 } 130 122 } 131 123 132 - async function socketPeerPing( 133 - ws: WebSocket, 134 - auth: realm.AuthenticatedIdentity, 135 - ping: protocol.RealmRtcPingRequest, 136 - ) { 124 + async function socketPeerPing(ws: WebSocket, auth: realm.AuthenticatedIdentity, ping: protocol.RealmRtcPingRequest) { 137 125 console.debug('ping from', auth.identid, ping.dat) 138 126 139 127 const peerClocks = await auth.realm.storage.buildSyncState()
+2 -11
src/realm/server/state-storage.ts
··· 88 88 return new RealmStorage(realmid, identities, db, metapath) 89 89 } 90 90 91 - static async ensure( 92 - realmid: RealmID, 93 - registrantid: IdentID, 94 - registrantkey: CryptoKey, 95 - ): Promise<RealmStorage> { 91 + static async ensure(realmid: RealmID, registrantid: IdentID, registrantkey: CryptoKey): Promise<RealmStorage> { 96 92 try { 97 93 return await this.open(realmid) 98 94 } catch { ··· 118 114 #db: Level 119 115 #metadataPath: string 120 116 121 - private constructor( 122 - realmid: RealmID, 123 - identities: StrictMap<IdentID, CryptoKey>, 124 - db: Level, 125 - metadataPath: string, 126 - ) { 117 + private constructor(realmid: RealmID, identities: StrictMap<IdentID, CryptoKey>, db: Level, metadataPath: string) { 127 118 this.realmid = realmid 128 119 this.identities = identities 129 120
+1 -5
src/skypod/actions.ts
··· 18 18 export type ActionOpts = z.infer<typeof actionOptionsSchema> 19 19 export type ActionMap = {[K in Action as K['msg']]: K} 20 20 21 - export function makeAction<N extends Action['msg']>( 22 - msg: N, 23 - clk: LCTimestamp, 24 - dat: ActionMap[N]['dat'], 25 - ): ActionMap[N] { 21 + export function makeAction<N extends Action['msg']>(msg: N, clk: LCTimestamp, dat: ActionMap[N]['dat']): ActionMap[N] { 26 22 return {typ: 'act', clk, msg, dat} as ActionMap[N] 27 23 }