Skip to content

Commit

Permalink
Add pubsub package
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidNic11 committed Nov 23, 2023
1 parent 42da60e commit cc91466
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 35 deletions.
41 changes: 6 additions & 35 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions pub-sub/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./pub-sub";
export * from "./usePubSub";
export * from "./usePubSubState";
72 changes: 72 additions & 0 deletions pub-sub/pub-sub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
type ChannelListeners = { __listeners: Array<Listener<BaseMessage>> }
type ChannelFragments = { [topic: string]: Channel | undefined }
type Channel = ChannelListeners & ChannelFragments

type Unsubscribe = () => void

export type Topic = string[]
export type Listener<Message extends BaseMessage> = (message: Message) => void
export type BaseMessage = { type: string }

const symbol = Symbol.for("r2wc.pubsub")

declare global {
interface Window {
[symbol]: Channel
}
}

function createChannel(): Channel {
return { __listeners: [] } as unknown as Channel
}

export const subscribe = <Message extends BaseMessage>(
topic: Topic,
listener?: Listener<Message>,
): Unsubscribe | undefined => {
if (!listener) return

if (!window[symbol]) {
window[symbol] = createChannel()
}

let channel: Channel = window[symbol]

for (const fragment of topic) {
if (!channel[fragment]) channel[fragment] = createChannel()
channel = channel[fragment] as Channel
}

channel.__listeners.push(listener as Listener<BaseMessage>)

return () => {
const index = channel.__listeners.indexOf(listener as Listener<BaseMessage>)
if (index > -1) {
channel.__listeners.splice(index, 1)
}
}
}

export const publish = <Message extends BaseMessage>(
topic: Topic,
message: Message,
): void => {
if (!window[symbol]) {
window[symbol] = createChannel()
}

const listeners: Array<Listener<BaseMessage>> = []

let channel: Channel = window[symbol]

for (const fragment of topic) {
listeners.unshift(...channel.__listeners)

if (!channel[fragment]) channel[fragment] = createChannel()

channel = channel[fragment] as Channel
}

channel.__listeners.forEach((subscriber) => subscriber(message))
listeners.forEach((subscriber) => subscriber(message))
}
24 changes: 24 additions & 0 deletions pub-sub/usePubSub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useEffect, useCallback, useRef } from "react";

import { BaseMessage, publish, subscribe } from "./pub-sub";

const _internalTopicStabilizer = "-||-||-";

export const usePubSub = <TMessage extends BaseMessage>(
topics: string[],
handler?: (data: TMessage) => void
): ((data: TMessage) => void) => {
const stableHandler = useRef(handler);

// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(
() => subscribe(topics, stableHandler?.current),
[topics.join(_internalTopicStabilizer)]
);

// eslint-disable-next-line react-hooks/exhaustive-deps
return useCallback(
(data: TMessage) => publish(topics, data),
[topics.join(_internalTopicStabilizer)]
);
};
32 changes: 32 additions & 0 deletions pub-sub/usePubSubState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { BaseMessage } from "./pub-sub";

import { useState, useCallback } from "react";

import { usePubSub } from "./usePubSub";

export const usePubSubState = <TMessage extends BaseMessage>(
topics: string[],
initialValue?: TMessage,
filter?: (data: TMessage) => boolean
): [TMessage | undefined, (data: TMessage) => void] => {
const [state, setState] = useState(initialValue);

const filteredSetter = useCallback(
(data: TMessage) => {
if (!filter) {
setState(data);
return;
}

if (filter(data)) {
setState(data);
return;
}
},
[filter]
);

const publish = usePubSub(topics, filteredSetter);

return [state, publish];
};

0 comments on commit cc91466

Please sign in to comment.