Skip to content

Commit

Permalink
Use event maps (typed EventEmitter)
Browse files Browse the repository at this point in the history
  • Loading branch information
matheus23 committed Mar 15, 2022
1 parent 2400efd commit 95aa96a
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 54 deletions.
2 changes: 1 addition & 1 deletion src/auth/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export type ChannelOptions = {
handleMessage: (event: MessageEvent) => void
}

type ChannelData = string | ArrayBufferLike | Blob | ArrayBufferView
export type ChannelData = string | ArrayBufferLike | Blob | ArrayBufferView

export const createWssChannel = async (options: ChannelOptions): Promise<Channel> => {
const { username, handleMessage } = options
Expand Down
23 changes: 12 additions & 11 deletions src/auth/linking/consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@ import * as did from "../../did/index.js"
import * as storage from "../../storage/index.js"
import * as ucan from "../../ucan/index.js"
import { impl as auth } from "../implementation.js"
import { EventEmitter } from "../../common/event-emitter.js"
import { EventEmitter, EventSource } from "../../common/event-emitter.js"
import { USERNAME_STORAGE_KEY } from "../../common/index.js"
import { LinkingError, LinkingStep, LinkingWarning, handleLinkingError, tryParseMessage } from "../linking.js"

import type { EventListener } from "../../common/event-emitter.js"
import type { Maybe, Result } from "../../common/index.js"

export type AccountLinkingConsumer = {
on: OnChallenge & OnLink & OnDone
export type AccountLinkingConsumer = EventSource<ConsumerEventMap> & {
cancel: () => void
}

type OnChallenge = (event: "challenge", listener: (args: { pin: number[] }) => void) => void
type OnLink = (event: "link", listener: (args: { approved: boolean; username: string }) => void) => void
type OnDone = (event: "done", listener: () => void) => void
export interface ConsumerEventMap {
"challenge": { pin: number[] }
"link": { approved: boolean; username: string }
"done": undefined
}

type LinkingState = {
username: Maybe<string>
Expand All @@ -38,9 +38,9 @@ type LinkingState = {
* @param options.username username of the account
* @returns an account linking event emitter and cancel function
*/
export const createConsumer = async (options: { username: string }): Promise<AccountLinkingConsumer> => {
export const createConsumer = async (options: { username: string; signal?: AbortSignal }): Promise<AccountLinkingConsumer> => {
const { username } = options
let eventEmitter: Maybe<EventEmitter> = new EventEmitter()
let eventEmitter: Maybe<EventEmitter<ConsumerEventMap>> = new EventEmitter()
const ls: LinkingState = {
username,
sessionKey: null,
Expand Down Expand Up @@ -93,7 +93,7 @@ export const createConsumer = async (options: { username: string }): Promise<Acc
}

const done = async () => {
eventEmitter?.dispatchEvent("done")
eventEmitter?.dispatchEvent("done", undefined)
eventEmitter = null
channel.close()
clearInterval(rsaExchangeInterval)
Expand All @@ -114,7 +114,8 @@ export const createConsumer = async (options: { username: string }): Promise<Acc
}, 2000)

return {
on: (event: string, listener: EventListener) => { eventEmitter?.addEventListener(event, listener) },
addEventListener: (...args) => eventEmitter?.addEventListener(...args),
removeEventListener: (...args) => eventEmitter?.removeEventListener(...args),
cancel: done
}
}
Expand Down
36 changes: 17 additions & 19 deletions src/auth/linking/producer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,24 @@ import { KeyUse, SymmAlg, HashAlg, CharSize } from "keystore-idb/lib/types.js"
import * as did from "../../did/index.js"
import * as ucan from "../../ucan/index.js"
import { impl as auth } from "../implementation.js"
import { EventEmitter } from "../../common/event-emitter.js"
import { EventEmitter, EventSource } from "../../common/event-emitter.js"
import { LinkingError, LinkingStep, LinkingWarning, handleLinkingError, tryParseMessage } from "../linking.js"

import type { Maybe, Result } from "../../common/index.js"
import type { EventListener } from "../../common/event-emitter.js"

export type AccountLinkingProducer = {
on: OnChallenge & OnLink & OnDone
export type AccountLinkingProducer = EventSource<ProducerEventMap> & {
cancel: () => void
}

type OnChallenge = (
event: "challenge",
listener: (
args: {
pin: number[]
confirmPin: () => void
rejectPin: () => void
}
) => void
) => void
type OnLink = (event: "link", listener: (args: { approved: boolean; username: string }) => void) => void
type OnDone = (event: "done", listener: () => void) => void
export interface ProducerEventMap {
"challenge": {
pin: number[]
confirmPin: () => void
rejectPin: () => void
}
"link": { approved: boolean; username: string }
"done": undefined
}


type LinkingState = {
Expand All @@ -52,7 +47,7 @@ export const createProducer = async (options: { username: string }): Promise<Acc
throw new LinkingError(`Producer cannot delegate for username ${username}`)
}

let eventEmitter: Maybe<EventEmitter> = new EventEmitter()
let eventEmitter: Maybe<EventEmitter<ProducerEventMap>> = new EventEmitter()
const ls: LinkingState = {
username,
sessionKey: null,
Expand Down Expand Up @@ -122,6 +117,8 @@ export const createProducer = async (options: { username: string }): Promise<Acc
const finishDelegation = async (delegationMessage: string, approved: boolean): Promise<void> => {
await channel.send(delegationMessage)

if (ls.username == null) return // or throw error?

eventEmitter?.dispatchEvent("link", { approved, username: ls.username })
resetLinkingState()
}
Expand All @@ -132,15 +129,16 @@ export const createProducer = async (options: { username: string }): Promise<Acc
}

const cancel = async () => {
eventEmitter?.dispatchEvent("done")
eventEmitter?.dispatchEvent("done", undefined)
eventEmitter = null
channel.close()
}

const channel = await auth.createChannel({ username, handleMessage })

return {
on: (event: string, listener: EventListener) => { eventEmitter?.addEventListener(event, listener) },
addEventListener: (...args) => eventEmitter?.addEventListener(...args),
removeEventListener: (...args) => eventEmitter?.removeEventListener(...args),
cancel
}
}
Expand Down
37 changes: 24 additions & 13 deletions src/common/event-emitter.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
export type EventListener = (...args: any[]) => void
type Events = { [event: string]: Set<EventListener> }
export type EventListener<E> = (event: E) => void

export class EventEmitter {
private readonly events: Events = {}
export interface EventSource<EventMap> /* extends EventTarget */ {
addEventListener<K extends keyof EventMap>(eventName: K, listener: EventListener<EventMap[K]>): void
removeEventListener<K extends keyof EventMap>(eventName: K, listener: EventListener<EventMap[K]>): void
}

public addEventListener(event: string, listener: EventListener): void {
if (typeof this.events[event] === "undefined") {
this.events[event] = new Set()
export class EventEmitter<EventMap> implements EventSource<EventMap> {
private readonly events: Map<keyof EventMap, Set<EventListener<unknown>>> = new Map()

public addEventListener<K extends keyof EventMap>(eventName: K, listener: EventListener<EventMap[K]>): void {
const eventSet = this.events.get(eventName)
if (eventSet == null) {
this.events.set(eventName, new Set([listener]) as Set<EventListener<unknown>>)
} else {
eventSet.add(listener as EventListener<unknown>)
}
this.events[event].add(listener)
}

public removeEventListener(event: string, listener: EventListener): void {
this.events[event].delete(listener)
public removeEventListener<K extends keyof EventMap>(eventName: K, listener: EventListener<EventMap[K]>): void {
const eventSet = this.events.get(eventName)
if (eventSet == null) return
eventSet.delete(listener as EventListener<unknown>)
if (eventSet.size === 0) {
this.events.delete(eventName)
}
}

public dispatchEvent(event: string, ...args: unknown[]): void {
this.events[event]?.forEach((listener: EventListener) => {
listener.apply(this, args)
public dispatchEvent<K extends keyof EventMap>(eventName: K, event: EventMap[K]): void {
this.events.get(eventName)?.forEach((listener: EventListener<EventMap[K]>) => {
listener.apply(this, [event])
})
}
}
20 changes: 10 additions & 10 deletions tests/auth/linking.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createProducer } from "../../src/auth/linking/producer.js"
import { EventEmitter } from "../../src/common/event-emitter.js"
import { debug, setImplementations } from "../../src/setup.js"

import type { Channel, ChannelOptions } from "../../src/auth/channel.js"
import type { Channel, ChannelData, ChannelOptions } from "../../src/auth/channel.js"

type MessageData = string | ArrayBufferLike | Blob | ArrayBufferView

Expand All @@ -24,7 +24,7 @@ type MessageData = string | ArrayBufferLike | Blob | ArrayBufferView
*/

describe("account linking", () => {
let channel: EventEmitter = new EventEmitter()
let channel: EventEmitter<Record<string, ChannelData>> = new EventEmitter()
let producerAccounts: Record<string, string[]> = {}
let consumerAccounts: Record<string, string[]> = {}

Expand Down Expand Up @@ -83,13 +83,13 @@ describe("account linking", () => {

const producer = await createProducer({ username: "elm-owl" })

producer.on("challenge", ({ confirmPin }) => {
producer.addEventListener("challenge", ({ confirmPin }) => {
confirmPin()
})

const consumer = await createConsumer({ username: "elm-owl" })

consumer.on("done", () => {
consumer.addEventListener("done", () => {
consumerDone = true
})

Expand All @@ -108,13 +108,13 @@ describe("account linking", () => {

const consumer = await createConsumer({ username: "elm-owl" })

consumer.on("done", () => {
consumer.addEventListener("done", () => {
consumerDone = true
})

const producer = await createProducer({ username: "elm-owl" })

producer.on("challenge", ({ confirmPin }) => {
producer.addEventListener("challenge", ({ confirmPin }) => {
confirmPin()
})

Expand All @@ -133,13 +133,13 @@ describe("account linking", () => {

const producer = await createProducer({ username: "elm-owl" })

producer.on("challenge", ({ rejectPin }) => {
producer.addEventListener("challenge", ({ rejectPin }) => {
rejectPin()
})

const consumer = await createConsumer({ username: "elm-owl" })

consumer.on("done", () => {
consumer.addEventListener("done", () => {
consumerDone = true
})

Expand All @@ -157,15 +157,15 @@ describe("account linking", () => {
const numConsumers = Math.round(Math.random() * 2 + 2)
const producer = await createProducer({ username: "elm-owl" })

producer.on("challenge", ({ confirmPin }) => {
producer.addEventListener("challenge", ({ confirmPin }) => {
confirmPin()
})

const promisedConsumers = Array.from(Array(numConsumers)).map(async () => {
const emitter = await createConsumer({ username: "elm-owl" })
const consumer = { emitter, done: false }

consumer.emitter.on("done", () => {
consumer.emitter.addEventListener("done", () => {
consumer.done = true
})

Expand Down

0 comments on commit 95aa96a

Please sign in to comment.