style: lint
This commit is contained in:
@@ -7,8 +7,8 @@ import { Client } from '../src/client'
|
||||
|
||||
class Deferred<T> {
|
||||
promise: Promise<T>
|
||||
resolve!: (value: T) => void
|
||||
reject!: (error: unknown) => void
|
||||
resolve!: (value: T) => void
|
||||
|
||||
constructor() {
|
||||
this.promise = new Promise<T>((resolve, reject) => {
|
||||
@@ -19,17 +19,12 @@ class Deferred<T> {
|
||||
}
|
||||
|
||||
class FakeConnection<C = undefined> implements ClientConnection<WebSocketEvent<C>> {
|
||||
readonly sent: Array<WebSocketEvent<C>> = []
|
||||
readonly pongs: number[] = []
|
||||
closed = false
|
||||
readonly pongs: number[] = []
|
||||
readonly sent: Array<WebSocketEvent<C>> = []
|
||||
|
||||
constructor(private readonly events: ClientEvents<WebSocketEvent<C>>) {}
|
||||
|
||||
send(message: WebSocketEvent<C>) {
|
||||
this.sent.push(message)
|
||||
return true
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.closed) {
|
||||
return
|
||||
@@ -43,13 +38,18 @@ class FakeConnection<C = undefined> implements ClientConnection<WebSocketEvent<C
|
||||
this.pongs.push(Date.now())
|
||||
return true
|
||||
}
|
||||
|
||||
send(message: WebSocketEvent<C>) {
|
||||
this.sent.push(message)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class FakeConnector<C = undefined> implements ClientConnector<WebSocketEvent<C>> {
|
||||
readonly attempts: Array<{
|
||||
connection?: FakeConnection<C>
|
||||
deferred: Deferred<ClientConnection<WebSocketEvent<C>>>
|
||||
events: ClientEvents<WebSocketEvent<C>>
|
||||
connection?: FakeConnection<C>
|
||||
}> = []
|
||||
|
||||
connect(events: ClientEvents<WebSocketEvent<C>>) {
|
||||
@@ -58,6 +58,15 @@ class FakeConnector<C = undefined> implements ClientConnector<WebSocketEvent<C>>
|
||||
return deferred.promise
|
||||
}
|
||||
|
||||
emit(message: WebSocketEvent<C>, index = this.attempts.length - 1) {
|
||||
const attempt = this.attempts[index]
|
||||
if (!attempt) {
|
||||
throw new Error(`Missing fake connector attempt at index ${index}.`)
|
||||
}
|
||||
|
||||
attempt.events.message(message)
|
||||
}
|
||||
|
||||
open(index = this.attempts.length - 1) {
|
||||
const attempt = this.attempts[index]
|
||||
if (!attempt) {
|
||||
@@ -78,29 +87,11 @@ class FakeConnector<C = undefined> implements ClientConnector<WebSocketEvent<C>>
|
||||
|
||||
attempt.deferred.reject(error)
|
||||
}
|
||||
|
||||
emit(message: WebSocketEvent<C>, index = this.attempts.length - 1) {
|
||||
const attempt = this.attempts[index]
|
||||
if (!attempt) {
|
||||
throw new Error(`Missing fake connector attempt at index ${index}.`)
|
||||
}
|
||||
|
||||
attempt.events.message(message)
|
||||
}
|
||||
}
|
||||
|
||||
function serverEvent<E extends WebSocketEvent['type']>(
|
||||
type: E,
|
||||
data: WebSocketEventOf<E>['data'],
|
||||
): WebSocketEventOf<E> {
|
||||
return {
|
||||
type,
|
||||
data,
|
||||
metadata: {
|
||||
source: { kind: 'plugin', plugin: { id: 'server' }, id: 'server-1' },
|
||||
event: { id: `${type}-1` },
|
||||
},
|
||||
} as WebSocketEventOf<E>
|
||||
async function flushAsyncTasks() {
|
||||
await flushMicrotasks()
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
}
|
||||
|
||||
async function flushMicrotasks() {
|
||||
@@ -108,9 +99,18 @@ async function flushMicrotasks() {
|
||||
await Promise.resolve()
|
||||
}
|
||||
|
||||
async function flushAsyncTasks() {
|
||||
await flushMicrotasks()
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
function serverEvent<E extends WebSocketEvent['type']>(
|
||||
type: E,
|
||||
data: WebSocketEventOf<E>['data'],
|
||||
): WebSocketEventOf<E> {
|
||||
return {
|
||||
data,
|
||||
metadata: {
|
||||
event: { id: `${type}-1` },
|
||||
source: { id: 'server-1', kind: 'plugin', plugin: { id: 'server' } },
|
||||
},
|
||||
type,
|
||||
} as WebSocketEventOf<E>
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
@@ -165,8 +165,8 @@ describe('client', () => {
|
||||
|
||||
expect(client.connectionStatus).toBe('authenticating')
|
||||
expect(connection.sent.at(-1)).toMatchObject({
|
||||
type: 'module:authenticate',
|
||||
data: { token: 'secret' },
|
||||
type: 'module:authenticate',
|
||||
})
|
||||
|
||||
connector.emit(serverEvent('module:authenticated', { authenticated: true }))
|
||||
@@ -176,13 +176,13 @@ describe('client', () => {
|
||||
|
||||
expect(client.connectionStatus).toBe('announcing')
|
||||
expect(announceEvent).toMatchObject({
|
||||
type: 'extension:module:announce',
|
||||
data: { name: 'test-plugin' },
|
||||
type: 'extension:module:announce',
|
||||
})
|
||||
|
||||
connector.emit(serverEvent('extension:module:announced', {
|
||||
name: 'test-plugin',
|
||||
identity: announceEvent.data.identity,
|
||||
name: 'test-plugin',
|
||||
}))
|
||||
|
||||
await expect(connected).resolves.toBeUndefined()
|
||||
@@ -208,11 +208,11 @@ describe('client', () => {
|
||||
const announceEvent = connection.sent.at(-1) as WebSocketEventOf<'extension:module:announce'>
|
||||
|
||||
connector.emit(serverEvent('registry:modules:sync', {
|
||||
modules: [{ name: 'test-plugin', identity: announceEvent.data.identity }],
|
||||
modules: [{ identity: announceEvent.data.identity, name: 'test-plugin' }],
|
||||
}))
|
||||
connector.emit(serverEvent('extension:module:announced', {
|
||||
name: 'test-plugin',
|
||||
identity: announceEvent.data.identity,
|
||||
name: 'test-plugin',
|
||||
}))
|
||||
|
||||
await expect(connected).resolves.toBeUndefined()
|
||||
@@ -293,22 +293,22 @@ describe('client', () => {
|
||||
await connected
|
||||
|
||||
const sent = client.send({
|
||||
type: 'input:text',
|
||||
data: { text: 'hello' },
|
||||
type: 'input:text',
|
||||
})
|
||||
|
||||
expect(sent).toBe(true)
|
||||
expect(connection.sent.at(-1)).toMatchObject({
|
||||
type: 'input:text',
|
||||
data: { text: 'hello' },
|
||||
metadata: {
|
||||
event: { id: expect.any(String) },
|
||||
source: {
|
||||
kind: 'plugin',
|
||||
id: expect.any(String),
|
||||
kind: 'plugin',
|
||||
plugin: { id: 'test-extension' },
|
||||
},
|
||||
event: { id: expect.any(String) },
|
||||
},
|
||||
type: 'input:text',
|
||||
})
|
||||
expect(onAnySend).toHaveBeenCalledWith(connection.sent.at(-1))
|
||||
})
|
||||
@@ -370,8 +370,8 @@ describe('client', () => {
|
||||
expect(listener).toHaveBeenCalledWith(input)
|
||||
expect(onAnyMessage).toHaveBeenCalledWith(input)
|
||||
expect(connection.sent.at(-1)).toMatchObject({
|
||||
type: 'transport:connection:heartbeat',
|
||||
data: { kind: 'pong' },
|
||||
type: 'transport:connection:heartbeat',
|
||||
})
|
||||
})
|
||||
|
||||
@@ -390,11 +390,11 @@ describe('client', () => {
|
||||
await connected
|
||||
|
||||
client.send({
|
||||
type: 'input:text',
|
||||
data: { text: 'hello' },
|
||||
metadata: {
|
||||
event: { id: undefined },
|
||||
},
|
||||
type: 'input:text',
|
||||
})
|
||||
|
||||
expect(connection.sent.at(-1)?.metadata.event.id).toEqual(expect.any(String))
|
||||
@@ -406,8 +406,8 @@ describe('client', () => {
|
||||
autoConnect: false,
|
||||
autoReconnect: false,
|
||||
connector,
|
||||
heartbeat: false,
|
||||
handshake: 'manual',
|
||||
heartbeat: false,
|
||||
name: 'test-extension',
|
||||
})
|
||||
|
||||
@@ -440,8 +440,8 @@ describe('client', () => {
|
||||
|
||||
const announceEvent = connection.sent.at(-1) as WebSocketEventOf<'extension:module:announce'>
|
||||
connector.emit(serverEvent('extension:module:announced', {
|
||||
name: 'test-plugin',
|
||||
identity: announceEvent.data.identity,
|
||||
name: 'test-plugin',
|
||||
}))
|
||||
|
||||
await expect(client.ensureConnected()).resolves.toBeUndefined()
|
||||
@@ -470,8 +470,8 @@ describe('client', () => {
|
||||
|
||||
const announceEvent = connection.sent.at(-1) as WebSocketEventOf<'extension:module:announce'>
|
||||
connector.emit(serverEvent('extension:module:announced', {
|
||||
name: 'test-plugin',
|
||||
identity: announceEvent.data.identity,
|
||||
name: 'test-plugin',
|
||||
}))
|
||||
|
||||
await expect(client.ready()).resolves.toBeUndefined()
|
||||
|
||||
@@ -8,12 +8,12 @@ import { InvalidMessageError, parseEvent, stringifyEvent } from '../src/codec'
|
||||
describe('server-sdk codec', () => {
|
||||
it('parses SuperJSON and plain JSON protocol events', () => {
|
||||
const event: WebSocketEvent = {
|
||||
type: 'module:authenticate',
|
||||
data: { token: 'secret' },
|
||||
metadata: {
|
||||
source: { kind: 'plugin', plugin: { id: 'plugin-1' }, id: 'plugin-1' },
|
||||
event: { id: 'event-1' },
|
||||
source: { id: 'plugin-1', kind: 'plugin', plugin: { id: 'plugin-1' } },
|
||||
},
|
||||
type: 'module:authenticate',
|
||||
}
|
||||
|
||||
expect(parseEvent(stringifySuperJson(event))).toEqual(event)
|
||||
@@ -21,7 +21,7 @@ describe('server-sdk codec', () => {
|
||||
})
|
||||
|
||||
it('throws debuggable errors for invalid messages', () => {
|
||||
const source = { type: 'module:authenticate', data: 'secret' }
|
||||
const source = { data: 'secret', type: 'module:authenticate' }
|
||||
|
||||
try {
|
||||
parseEvent(JSON.stringify(source))
|
||||
@@ -35,7 +35,7 @@ describe('server-sdk codec', () => {
|
||||
})
|
||||
|
||||
it('rejects array event data with validation context', () => {
|
||||
const source = { type: 'module:authenticate', data: ['secret'] }
|
||||
const source = { data: ['secret'], type: 'module:authenticate' }
|
||||
|
||||
expect(() => parseEvent(JSON.stringify(source))).toThrow(InvalidMessageError)
|
||||
|
||||
@@ -64,8 +64,8 @@ describe('server-sdk codec', () => {
|
||||
|
||||
it('stringifies protocol events with SuperJSON', () => {
|
||||
expect(stringifyEvent({
|
||||
type: 'module:authenticate',
|
||||
data: { token: 'secret' },
|
||||
type: 'module:authenticate',
|
||||
})).toContain('module:authenticate')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,26 +7,17 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { createWebSocketExtensionPeer } from '../src/extension-peer'
|
||||
|
||||
type Listener = (data: WebSocketBaseEvent<string, unknown>) => void | Promise<void>
|
||||
type Listener = (data: WebSocketBaseEvent<string, unknown>) => Promise<void> | void
|
||||
|
||||
class FakeClient implements ExtensionPeerClient {
|
||||
readonly sent: WebSocketEventOptionalSource[] = []
|
||||
readonly connect = vi.fn(async () => {})
|
||||
readonly close = vi.fn(() => {})
|
||||
readonly connect = vi.fn(async () => {})
|
||||
readonly listeners = new Map<keyof WebSocketEvents, Set<Listener>>()
|
||||
|
||||
send(data: WebSocketEventOptionalSource): boolean {
|
||||
this.sent.push(data)
|
||||
return true
|
||||
}
|
||||
|
||||
sendOrThrow(data: WebSocketEventOptionalSource): void {
|
||||
this.sent.push(data)
|
||||
}
|
||||
readonly sent: WebSocketEventOptionalSource[] = []
|
||||
|
||||
onEvent<E extends keyof WebSocketEvents>(
|
||||
event: E,
|
||||
callback: (data: WebSocketBaseEvent<E, WebSocketEvents[E]>) => void | Promise<void>,
|
||||
callback: (data: WebSocketBaseEvent<E, WebSocketEvents[E]>) => Promise<void> | void,
|
||||
) {
|
||||
let listeners = this.listeners.get(event)
|
||||
if (!listeners) {
|
||||
@@ -41,21 +32,30 @@ class FakeClient implements ExtensionPeerClient {
|
||||
listeners?.delete(listener)
|
||||
}
|
||||
}
|
||||
|
||||
send(data: WebSocketEventOptionalSource): boolean {
|
||||
this.sent.push(data)
|
||||
return true
|
||||
}
|
||||
|
||||
sendOrThrow(data: WebSocketEventOptionalSource): void {
|
||||
this.sent.push(data)
|
||||
}
|
||||
}
|
||||
|
||||
class FakeConnector implements ClientConnector<WebSocketEvent> {
|
||||
readonly attempts: Array<{
|
||||
events: ClientEvents<WebSocketEvent>
|
||||
connection: ClientConnection<WebSocketEvent>
|
||||
events: ClientEvents<WebSocketEvent>
|
||||
}> = []
|
||||
|
||||
connect(events: ClientEvents<WebSocketEvent>) {
|
||||
const connection: ClientConnection<WebSocketEvent> = {
|
||||
send: () => true,
|
||||
close: () => events.close({ code: 1000, reason: 'closed', wasClean: true }),
|
||||
send: () => true,
|
||||
}
|
||||
|
||||
this.attempts.push({ events, connection })
|
||||
this.attempts.push({ connection, events })
|
||||
return connection
|
||||
}
|
||||
}
|
||||
@@ -69,16 +69,16 @@ describe('websocket extension peer', () => {
|
||||
it('authenticates the websocket peer separately from the extension session', async () => {
|
||||
const fakeClient = new FakeClient()
|
||||
const peer = createWebSocketExtensionPeer({
|
||||
client: fakeClient,
|
||||
extension: {
|
||||
id: 'airi-extension-chess',
|
||||
version: '1.0.0',
|
||||
sessionId: 'session-1',
|
||||
version: '1.0.0',
|
||||
},
|
||||
client: fakeClient,
|
||||
})
|
||||
|
||||
await peer.connect()
|
||||
peer.authenticatePeer({ token: 'secret', peerId: 'peer-1' })
|
||||
peer.authenticatePeer({ peerId: 'peer-1', token: 'secret' })
|
||||
peer.announceExtension()
|
||||
|
||||
expect(fakeClient.connect).toHaveBeenCalled()
|
||||
@@ -87,32 +87,32 @@ describe('websocket extension peer', () => {
|
||||
'extension:announce',
|
||||
])
|
||||
expect(fakeClient.sent[0]).toMatchObject({
|
||||
type: 'peer:authenticate',
|
||||
data: {
|
||||
token: 'secret',
|
||||
peerId: 'peer-1',
|
||||
token: 'secret',
|
||||
},
|
||||
type: 'peer:authenticate',
|
||||
})
|
||||
expect(fakeClient.sent[1]).toMatchObject({
|
||||
type: 'extension:announce',
|
||||
data: {
|
||||
identity: {
|
||||
id: 'airi-extension-chess',
|
||||
version: '1.0.0',
|
||||
sessionId: 'session-1',
|
||||
version: '1.0.0',
|
||||
},
|
||||
},
|
||||
type: 'extension:announce',
|
||||
})
|
||||
})
|
||||
|
||||
it('announces extension modules under the owning extension identity', () => {
|
||||
const fakeClient = new FakeClient()
|
||||
const peer = createWebSocketExtensionPeer({
|
||||
client: fakeClient,
|
||||
extension: {
|
||||
id: 'airi-extension-chess',
|
||||
sessionId: 'session-1',
|
||||
},
|
||||
client: fakeClient,
|
||||
})
|
||||
|
||||
peer.announceModule({
|
||||
@@ -122,31 +122,31 @@ describe('websocket extension peer', () => {
|
||||
})
|
||||
|
||||
expect(fakeClient.sent[0]).toMatchObject({
|
||||
type: 'extension:module:announce',
|
||||
data: {
|
||||
name: 'Chess Gamelet',
|
||||
identity: {
|
||||
id: 'chess-gamelet',
|
||||
extension: {
|
||||
id: 'airi-extension-chess',
|
||||
sessionId: 'session-1',
|
||||
},
|
||||
id: 'chess-gamelet',
|
||||
},
|
||||
name: 'Chess Gamelet',
|
||||
possibleEvents: [],
|
||||
},
|
||||
type: 'extension:module:announce',
|
||||
})
|
||||
})
|
||||
|
||||
it('creates a manual peer client without auto-connect or auto-reconnect by default', async () => {
|
||||
const connector = new FakeConnector()
|
||||
const peer = createWebSocketExtensionPeer({
|
||||
clientOptions: {
|
||||
connector,
|
||||
},
|
||||
extension: {
|
||||
id: 'airi-extension-chess',
|
||||
sessionId: 'session-1',
|
||||
},
|
||||
clientOptions: {
|
||||
connector,
|
||||
},
|
||||
})
|
||||
|
||||
expect(connector.attempts).toHaveLength(0)
|
||||
|
||||
Reference in New Issue
Block a user