Skip to content

Commit

Permalink
so more refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
floodfx committed May 6, 2022
1 parent e93ec95 commit a3f1bc2
Show file tree
Hide file tree
Showing 12 changed files with 511 additions and 594 deletions.
2 changes: 1 addition & 1 deletion src/server/adaptor/http.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AnyLiveContext, HttpLiveComponentSocket, LiveComponent, LiveView, LiveViewTemplate } from "../live";
import { SessionData } from "../session";
import { HttpLiveViewSocket } from "../socket/live_socket";
import { HttpLiveViewSocket } from "../socket/liveSocket";
import { html, safe } from "../templates";
import { PageTitleDefaults } from "../templates/helpers/page_title";

Expand Down
15 changes: 15 additions & 0 deletions src/server/adaptor/jsonSerDe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { SerDe } from "./http";

/**
* A SerDe (serializer/deserializer) that uses JSON.stringify and JSON.parse.
* WARNING: this is not secure so should only be used for testing.
*/
export class JsonSerDe implements SerDe {
serialize<T>(obj: T): Promise<string> {
return Promise.resolve(JSON.stringify(obj));
}

deserialize<T>(data: string): Promise<T> {
return Promise.resolve(JSON.parse(data) as T);
}
}
2 changes: 1 addition & 1 deletion src/server/live/liveView.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { LiveComponent, LiveViewTemplate } from ".";
import { SessionData } from "../session";
import { LiveViewSocket } from "../socket/live_socket";
import { LiveViewSocket } from "../socket/liveSocket";

export interface LiveContext {
[key: string]: any;
Expand Down
8 changes: 5 additions & 3 deletions src/server/pubsub/PubSub.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
export type SubscriberFunction<T> = (data: T) => void;

export type SubscriberId = string;

export interface Subscriber {
subscribe<T>(topic: string, subscriber: SubscriberFunction<T>): Promise<string>;
unsubscribe(topic: string, subscriberId: string): Promise<void>;
subscribe<T extends { type: string }>(topic: string, subscriber: SubscriberFunction<T>): Promise<SubscriberId>;
unsubscribe(topic: string, subscriberId: SubscriberId): Promise<void>;
}

export interface Publisher {
broadcast<T>(topic: string, data: T): Promise<void>;
broadcast<T extends { type: string }>(topic: string, data: T): Promise<void>;
}

export interface PubSub extends Subscriber, Publisher {}
6 changes: 3 additions & 3 deletions src/server/pubsub/SingleProcessPubSub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import { Publisher, Subscriber, SubscriberFunction } from "./pubSub";
*/
const eventEmitter = new EventEmitter(); // use this singleton for all pubSub events

export class SingleProcessPubSub<T> implements Subscriber, Publisher {
export class SingleProcessPubSub implements Subscriber, Publisher {
private subscribers: Record<string, SubscriberFunction<any>> = {};

public async subscribe<T>(topic: string, subscriber: SubscriberFunction<T>): Promise<string> {
await eventEmitter.on(topic, subscriber);
await eventEmitter.addListener(topic, subscriber);
// store connection id for unsubscribe and return for caller
const subId = crypto.randomBytes(10).toString("hex");
this.subscribers[subId] = subscriber;
Expand All @@ -28,7 +28,7 @@ export class SingleProcessPubSub<T> implements Subscriber, Publisher {
public async unsubscribe(topic: string, subscriberId: string) {
// get subscriber function from id
const subscriber = this.subscribers[subscriberId];
await eventEmitter.off(topic, subscriber);
await eventEmitter.removeListener(topic, subscriber);
// remove subscriber from subscribers
delete this.subscribers[subscriberId];
}
Expand Down
2 changes: 1 addition & 1 deletion src/server/socket/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from "./liveViewManager";
export * from "./live_socket";
export * from "./liveSocket";
export * from "./wsMessageRouter";
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import { SessionData } from "express-session";
import { html } from "..";
import {
BaseLiveView,
LiveView,
LiveViewContext,
LiveViewMountParams,
LiveViewTemplate,
StringPropertyValues,
} from "../component";
import { HttpLiveViewSocket, LiveViewSocket, WsLiveViewSocket } from "./live_socket";
import { BaseLiveView, LiveView, LiveViewMountParams, LiveViewTemplate } from "../live";
import { SessionData } from "../session";
import { HttpLiveViewSocket, LiveViewSocket, WsLiveViewSocket } from "./liveSocket";

describe("test LiveViewSocket", () => {
let socket;
let component: LiveView<LiveViewContext, unknown>;
let component: LiveView;
let pageTitleCallback: jest.Mock<any, any>;
let pushEventCallback = jest.fn();
let pushRedirectCallback = jest.fn();
Expand All @@ -32,7 +25,7 @@ describe("test LiveViewSocket", () => {
repeatCallback = jest.fn();
sendCallback = jest.fn();
subscribeCallback = jest.fn();
socket = new WsLiveViewSocket<TestLVContext>(
socket = new WsLiveViewSocket(
"id",
pageTitleCallback,
pushEventCallback,
Expand All @@ -54,21 +47,21 @@ describe("test LiveViewSocket", () => {
it("http default handleParams does NOT change context", async () => {
const socket = new HttpLiveViewSocket<TestLVContext>("id");
component.mount({ _csrf_token: "csrf", _mounts: -1 }, {}, socket);
await component.handleParams({ foo: "baz" }, "", socket);
await component.handleParams(new URL("http://example.com/?foo=baz"), socket);
expect(socket.context.foo).toEqual("bar");
});

it("http render returns context view", async () => {
const socket = new HttpLiveViewSocket<TestLVContext>("id");
component.mount({ _csrf_token: "csrf", _mounts: -1 }, {}, socket);
await component.handleParams({ foo: "baz" }, "", socket);
await component.handleParams(new URL("http://example.com/?foo=baz"), socket);
expect(socket.context.foo).toEqual("bar");
const view = await component.render(socket.context, { csrfToken: "csrf", live_component: jest.fn() });
expect(view.toString()).toEqual("<div>bar</div>");
});

it("ws mount returns context", async () => {
const socket = new WsLiveViewSocket<TestLVContext>(
const socket = new WsLiveViewSocket(
"id",
pageTitleCallback,
pushEventCallback,
Expand All @@ -85,7 +78,7 @@ describe("test LiveViewSocket", () => {

it("calls all callbacks", async () => {
component = new TestLVPushAndSend();
const socket = new WsLiveViewSocket<TestLVPushAndSendContext>(
const socket = new WsLiveViewSocket(
"id",
pageTitleCallback,
pushEventCallback,
Expand All @@ -109,7 +102,7 @@ describe("test LiveViewSocket", () => {
});

it("tempAssign works to clear assigns", () => {
const socket = new WsLiveViewSocket<TestLVContext>(
const socket = new WsLiveViewSocket(
"id",
pageTitleCallback,
pushEventCallback,
Expand All @@ -134,17 +127,17 @@ describe("test LiveViewSocket", () => {
c.mount({ _csrf_token: "csrf", _mounts: -1 }, {}, socket);
expect(socket.redirect).toEqual({ to: "/new/path?param=mount", replace: false });
expect(socket.context.redirectedIn).toEqual("mount");
c.handleParams({}, "", socket);
c.handleParams(new URL("http://example.com"), socket);
expect(socket.redirect).toEqual({ to: "/new/path?param=handleParams", replace: true });
expect(socket.context.redirectedIn).toEqual("handleParams");
});
});

interface TestLVContext extends LiveViewContext {
interface TestLVContext {
foo: string;
}

class TestLiveView extends BaseLiveView<TestLVContext, {}> {
class TestLiveView extends BaseLiveView<TestLVContext> {
mount(params: LiveViewMountParams, session: Partial<SessionData>, socket: LiveViewSocket<TestLVContext>) {
socket.assign({ foo: "bar" });
}
Expand All @@ -154,23 +147,23 @@ class TestLiveView extends BaseLiveView<TestLVContext, {}> {
}
}

interface TestLVPushAndSendContext extends LiveViewContext {
interface TestLVPushAndSendContext {
foo: string;
}

class TestLVPushAndSend extends BaseLiveView<TestLVPushAndSendContext, {}> {
class TestLVPushAndSend extends BaseLiveView<TestLVPushAndSendContext> {
mount(params: LiveViewMountParams, session: Partial<SessionData>, socket: LiveViewSocket<TestLVPushAndSendContext>) {
socket.pageTitle("new page title");
socket.pushEvent("event", { data: "blah" });
socket.pushEvent({ type: "event", data: "blah" });
socket.pushPatch("path");
socket.pushPatch("path", { param: 1 });
socket.pushPatch("path", { param: 1 }, true);
socket.pushPatch("path", new URLSearchParams({ param: String(1) }));
socket.pushPatch("path", new URLSearchParams({ param: String(1) }), true);
socket.pushRedirect("/new/path");
socket.pushRedirect("/new/path", { param: 1 });
socket.pushRedirect("/new/path", { param: 1 }, true);
socket.pushRedirect("/new/path", new URLSearchParams({ param: String(1) }));
socket.pushRedirect("/new/path", new URLSearchParams({ param: String(1) }), true);
socket.putFlash("info", "Helpful message");
socket.repeat(() => {}, 1000);
socket.send("my_event");
socket.send({ type: "my_event" });
socket.subscribe("topic");
}

Expand All @@ -179,26 +172,26 @@ class TestLVPushAndSend extends BaseLiveView<TestLVPushAndSendContext, {}> {
}
}

interface TestRedirectingContext extends LiveViewContext {
interface TestRedirectingContext {
redirectedIn: "mount" | "handleParams";
}

class TestRedirectingLiveView extends BaseLiveView<TestRedirectingContext, {}> {
class TestRedirectingLiveView extends BaseLiveView<TestRedirectingContext> {
mount(params: LiveViewMountParams, session: Partial<SessionData>, socket: LiveViewSocket<TestRedirectingContext>) {
if (!socket.context.redirectedIn) {
socket.assign({ redirectedIn: "mount" });
socket.pushRedirect("/new/path", { param: "mount" }, false);
socket.pushRedirect("/new/path", new URLSearchParams({ param: "mount" }), false);
}
}

handleParams(params: StringPropertyValues<{}>, url: string, socket: LiveViewSocket<TestRedirectingContext>): void {
handleParams(url: URL, socket: LiveViewSocket<TestRedirectingContext>): void {
if (socket.context.redirectedIn === "mount") {
socket.assign({ redirectedIn: "handleParams" });
socket.pushRedirect("/new/path", { param: "handleParams" }, true);
socket.pushRedirect("/new/path", new URLSearchParams({ param: "handleParams" }), true);
}
}

render(ctx: TestRedirectingContext): LiveViewTemplate {
return html`<div>${ctx.foo}</div>`;
return html`<div>${ctx.redirectedIn}</div>`;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export interface LiveViewSocket<TContext extends LiveContext = AnyLiveContext> {
* @param replaceHistory whether to replace the current history entry or push a new one (defaults to false)
*/
// pushPatch(path: string, params: Record<string, string | number>): void;
pushPatch(path: string, params?: Record<string, string | number>, replaceHistory?: boolean): void;
pushPatch(path: string, params?: URLSearchParams, replaceHistory?: boolean): void;

/**
* Shutdowns the current `LiveView` and load another `LiveView` in its place without reloading the
Expand All @@ -71,7 +71,7 @@ export interface LiveViewSocket<TContext extends LiveContext = AnyLiveContext> {
* @param params the query params to update the path with
* @param replaceHistory whether to replace the current history entry or push a new one (defaults to false)
*/
pushRedirect(path: string, params?: Record<string, string | number>, replaceHistory?: boolean): void;
pushRedirect(path: string, params?: URLSearchParams, replaceHistory?: boolean): void;
/**
* Add flash to the socket for a given key and value.
* @param key
Expand Down Expand Up @@ -132,10 +132,10 @@ abstract class BaseLiveViewSocket<TContext extends LiveContext = AnyLiveContext>
pushEvent(pushEvent: AnyLivePushEvent) {
// no-op
}
pushPatch(path: string, params?: Record<string, string | number>, replaceHistory?: boolean) {
pushPatch(path: string, params?: URLSearchParams, replaceHistory?: boolean) {
// no-op
}
pushRedirect(path: string, params?: Record<string, string | number>, replaceHistory?: boolean) {
pushRedirect(path: string, params?: URLSearchParams, replaceHistory?: boolean) {
// no-op
}
putFlash(key: string, value: string) {
Expand Down Expand Up @@ -176,17 +176,8 @@ export class HttpLiveViewSocket<Context> extends BaseLiveViewSocket<Context> {
return this._redirect;
}

pushRedirect(path: string, params?: Record<string, string | number>, replaceHistory?: boolean): void {
let stringParams: string | undefined;
const urlParams = new URLSearchParams();
if (params && Object.keys(params).length > 0) {
for (const [key, value] of Object.entries(params)) {
urlParams.set(key, String(value));
}
stringParams = urlParams.toString();
}

const to = stringParams ? `${path}?${stringParams}` : path;
pushRedirect(path: string, params?: URLSearchParams, replaceHistory?: boolean): void {
const to = params ? `${path}?${params}` : path;
this._redirect = {
to,
replace: replaceHistory || false,
Expand All @@ -207,12 +198,8 @@ export class WsLiveViewSocket extends BaseLiveViewSocket {
// callbacks to the ComponentManager
private pageTitleCallback: (newPageTitle: string) => void;
private pushEventCallback: (pushEvent: AnyLivePushEvent) => void;
private pushPatchCallback: (path: string, params?: Record<string, string | number>, replaceHistory?: boolean) => void;
private pushRedirectCallback: (
path: string,
params?: Record<string, string | number>,
replaceHistory?: boolean
) => void;
private pushPatchCallback: (path: string, params?: URLSearchParams, replaceHistory?: boolean) => void;
private pushRedirectCallback: (path: string, params?: URLSearchParams, replaceHistory?: boolean) => void;
private putFlashCallback: (key: string, value: string) => void;
private repeatCallback: (fn: () => void, intervalMillis: number) => void;
private sendCallback: (info: AnyLiveInfo) => void;
Expand All @@ -222,8 +209,8 @@ export class WsLiveViewSocket extends BaseLiveViewSocket {
id: string,
pageTitleCallback: (newPageTitle: string) => void,
pushEventCallback: (pushEvent: AnyLivePushEvent) => void,
pushPatchCallback: (path: string, params?: Record<string, string | number>, replaceHistory?: boolean) => void,
pushRedirectCallback: (path: string, params?: Record<string, string | number>, replaceHistory?: boolean) => void,
pushPatchCallback: (path: string, params?: URLSearchParams, replaceHistory?: boolean) => void,
pushRedirectCallback: (path: string, params?: URLSearchParams, replaceHistory?: boolean) => void,
putFlashCallback: (key: string, value: string) => void,
repeatCallback: (fn: () => void, intervalMillis: number) => void,
sendCallback: (info: AnyLiveInfo) => void,
Expand All @@ -249,10 +236,10 @@ export class WsLiveViewSocket extends BaseLiveViewSocket {
pushEvent(pushEvent: AnyLivePushEvent) {
this.pushEventCallback(pushEvent);
}
pushPatch(path: string, params?: Record<string, string | number>, replaceHistory: boolean = false) {
pushPatch(path: string, params?: URLSearchParams, replaceHistory: boolean = false) {
this.pushPatchCallback(path, params, replaceHistory);
}
pushRedirect(path: string, params?: Record<string, string | number>, replaceHistory: boolean = false) {
pushRedirect(path: string, params?: URLSearchParams, replaceHistory: boolean = false) {
this.pushRedirectCallback(path, params, replaceHistory);
}
repeat(fn: () => void, intervalMillis: number) {
Expand Down
Loading

0 comments on commit a3f1bc2

Please sign in to comment.