Skip to content

Commit

Permalink
change a lot (#17)
Browse files Browse the repository at this point in the history
Co-authored-by: JongChan Choi <[email protected]>
  • Loading branch information
안재원 (Jaewon Ahn) and disjukr authored Oct 14, 2022
1 parent f7832e9 commit 72dd395
Show file tree
Hide file tree
Showing 13 changed files with 165 additions and 181 deletions.
12 changes: 6 additions & 6 deletions src/glue/child-window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import {

export interface CreateChildWindowSocketConfig {
child: Window;
childOrigin: string;
childWindowOrigin: string;
onClosed?: () => void;
}
export async function createChildWindowSocket(
config: CreateChildWindowSocketConfig,
): Promise<Closer & Socket> {
const { child, childOrigin, onClosed } = config;
await handshake(child, childOrigin);
const { child, childWindowOrigin, onClosed } = config;
await handshake(child, childWindowOrigin);
const healthcheckId = setInterval(healthcheck, 100);
const glue = createGlue();
return {
Expand All @@ -25,7 +25,7 @@ export async function createChildWindowSocket(
const length = data.byteLength;
const success = postGlueMessage({
target: child,
targetOrigin: childOrigin,
targetOrigin: childWindowOrigin,
payload: data,
});
if (!success) close();
Expand All @@ -44,7 +44,7 @@ export async function createChildWindowSocket(
}

// wait syn -> send syn-ack -> wait ack
async function handshake(child: Window, childOrigin: string) {
async function handshake(child: Window, childWindowOrigin: string) {
let synAcked = false;
const wait = defer<void>();
const healthcheckId = setInterval(healthcheck, 100);
Expand Down Expand Up @@ -84,7 +84,7 @@ async function handshake(child: Window, childOrigin: string) {
synAcked = true;
const success = postGlueHandshakeMessage({
target: child,
targetOrigin: childOrigin,
targetOrigin: childWindowOrigin,
payload: "syn",
});
if (!success) abort(new Error("Failed to send syn-ack."));
Expand Down
2 changes: 1 addition & 1 deletion src/glue/iframe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export async function createIframeSocket(
);
const childWindowSocket = await createChildWindowSocket({
child: iframeWindow,
childOrigin: iframeOrigin,
childWindowOrigin: iframeOrigin,
onClosed,
});
onceIframeReloaded(iframeElement, childWindowSocket.close);
Expand Down
10 changes: 5 additions & 5 deletions src/glue/parent-window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import {

export interface CreateParentWindowSocketConfig {
parent?: Window | null;
parentOrigin: string;
parentWindowOrigin: string;
onClosed?: () => void;
}
export async function createParentWindowSocket(
config: CreateParentWindowSocketConfig,
): Promise<Closer & Socket> {
const { parent = globalThis.parent, parentOrigin, onClosed } = config;
const { parent = globalThis.parent, parentWindowOrigin, onClosed } = config;
if (!parent?.postMessage) throw new Error("There is no parent window.");
if (parent === globalThis.self) throw new Error("Invalid parent window.");
let handshakeIsDone = false;
Expand All @@ -33,7 +33,7 @@ export async function createParentWindowSocket(
const length = data.byteLength;
const success = postGlueMessage({
target: parent,
targetOrigin: parentOrigin,
targetOrigin: parentWindowOrigin,
payload: data,
});
if (!success) close();
Expand All @@ -51,7 +51,7 @@ export async function createParentWindowSocket(
function syn() {
const success = postGlueHandshakeMessage({
target: parent!,
targetOrigin: parentOrigin,
targetOrigin: parentWindowOrigin,
payload: "syn",
});
if (!success) close();
Expand All @@ -60,7 +60,7 @@ export async function createParentWindowSocket(
handshakeIsDone = true;
const success = postGlueHandshakeMessage({
target: parent!,
targetOrigin: parentOrigin,
targetOrigin: parentWindowOrigin,
payload: "ack",
});
if (!success) close();
Expand Down
46 changes: 46 additions & 0 deletions src/glue/parent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { defer } from "https://deno.land/x/[email protected]/core/runtime/async/observer.ts";
import { Socket } from "../socket.ts";
import { createIosSocket } from "../glue/ios.ts";
import { createAndroidSocket } from "../glue/android.ts";
import { createParentWindowSocket } from "../glue/parent-window.ts";

export type UnsubscribeFn = () => void;
export type SetSocketFn = (socket?: Socket, error?: Error) => void;
export function subscribeParentSocket(set: SetSocketFn): UnsubscribeFn {
let run = true;
const unsubscribe = () => (run = false);
const parent = globalThis.opener || globalThis.parent;
if (parent && parent !== globalThis.self) {
(async () => {
while (run) {
const closed = defer<void>();
try {
const socket = await createParentWindowSocket({
parent,
parentWindowOrigin: "*",
onClosed: () => closed.resolve(),
});
run && set(socket, undefined);
} catch (error) {
run && set(undefined, error);
}
await closed;
run && set(undefined, undefined);
}
})();
} else {
getAndroidOrIosSocket().then(
(socket) => run && set(socket),
).catch(
(error) => run && set(undefined, error),
);
}
return unsubscribe;
}

async function getAndroidOrIosSocket(): Promise<Socket | undefined> {
try {
return await Promise.any([createAndroidSocket(), createIosSocket()]);
} catch {}
return;
}
19 changes: 18 additions & 1 deletion src/jotai/iframe.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { useEffect } from "react";
import { atom, PrimitiveAtom, useSetAtom } from "jotai";
import { createWrpAtomSetFromSourceChannelAtom, WrpAtomSet } from "./index.ts";
import {
createWrpAtomSetFromSourceChannelAtom,
PrimitiveSocketAtom,
WrpAtomSet,
} from "./index.ts";
import { Socket } from "../socket.ts";
import {
createWrpChannel as createWrpChannelFn,
Expand All @@ -11,6 +15,19 @@ import {
UseWrpIframeSocketResult,
} from "../react/useWrpIframeSocket.ts";

export function useIframeWrpSocketAtomUpdateEffect(
socketAtom: PrimitiveSocketAtom,
): UseWrpIframeSocketResult {
const setSocket = useSetAtom(socketAtom);
const useWrpIframeSocketResult = useWrpIframeSocket();
const { socket } = useWrpIframeSocketResult;
useEffect(() => {
if (!socket) return;
setSocket(socket);
}, [socket]);
return useWrpIframeSocketResult;
}

export function useIframeWrpAtomSetUpdateEffect(
primitiveWrpAtomSetAtom: PrimitiveAtom<WrpAtomSet>,
createWrpChannel: (socket: Socket) => WrpChannel = createWrpChannelFn,
Expand Down
39 changes: 9 additions & 30 deletions src/jotai/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { Atom, atom } from "jotai";
import { Atom, atom, PrimitiveAtom } from "jotai";
import { selectAtom } from "jotai/utils";
import type { RpcClientImpl } from "https://deno.land/x/[email protected]/core/runtime/rpc.ts";
import { Type as WrpMessage } from "../generated/messages/pbkit/wrp/WrpMessage.ts";
import { Socket } from "../socket.ts";
import { createWrpChannel, WrpChannel } from "../channel.ts";
import { createWrpClientImpl } from "../rpc/client.ts";
import { createWrpGuest, WrpGuest } from "../guest.ts";
import { WrpGuest } from "../guest.ts";
import tee from "../tee.ts";

export type SocketAtom = Atom<Promise<Socket | undefined>>;
export type SocketAtom = PrimitiveSocketAtom | AsyncSocketAtom;
export type ChannelAtom = Atom<WrpChannel | undefined>;
export type GuestAtom = Atom<Promise<WrpGuest> | undefined>;
export type ClientImplAtom = Atom<RpcClientImpl | undefined>;

export type PrimitiveSocketAtom = PrimitiveAtom<Socket | undefined>;
export type AsyncSocketAtom = Atom<Promise<Socket | undefined>>;

export interface WrpAtomSet {
channelAtom: ChannelAtom;
guestAtom: GuestAtom;
Expand All @@ -36,32 +40,7 @@ export function createWrpAtomSetFromSourceChannelAtom(
async (get) => {
const sourceChannel = get(sourceChannelAtom);
if (!sourceChannel) return;
const listeners: ((message?: WrpMessage) => void)[] = [];
const guest = createWrpGuest({
channel: {
...sourceChannel,
async *listen() {
for await (const message of sourceChannel.listen()) {
yield message;
for (const listener of listeners) listener(message);
listeners.length = 0;
}
},
},
});
const channel: WrpChannel = {
...sourceChannel,
async *listen() {
while (true) {
const message = await new Promise<WrpMessage | undefined>(
(resolve) => listeners.push(resolve),
);
if (!message) break;
yield message;
}
},
};
return { channel, guest };
return tee(sourceChannel);
},
);
const channelAtom = selectAtom(
Expand Down
34 changes: 16 additions & 18 deletions src/jotai/parent.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
import { atom, useAtomValue } from "jotai";
import { atom, useAtomValue, useSetAtom } from "jotai";
import type { RpcClientImpl } from "https://deno.land/x/[email protected]/core/runtime/rpc.ts";
import { createIosSocket } from "../glue/ios.ts";
import { createAndroidSocket } from "../glue/android.ts";
import { createParentWindowSocket } from "../glue/parent-window.ts";
import { Socket } from "../socket.ts";
import { WrpChannel } from "../channel.ts";
import { WrpGuest } from "../guest.ts";
import useOnceEffect from "../react/useOnceEffect.ts";
import { subscribeParentSocket } from "../glue/parent.ts";
import {
ChannelAtom,
ClientImplAtom,
createWrpAtomSet,
GuestAtom,
SocketAtom,
PrimitiveSocketAtom,
WrpAtomSet,
} from "./index.ts";

export const socketAtom: SocketAtom = atom(async () => {
return await Promise.any([
createAndroidSocket(),
createIosSocket(),
createParentWindowSocket({
parentWindowOrigin: "*",
}),
createParentWindowSocket({
parent: globalThis.opener,
parentWindowOrigin: "*",
}),
]).catch(() => undefined);
});
/**
* Use it on root of your react application
*/
export function useInitParentSocketEffect() {
const setSocket = useSetAtom(socketAtom);
useOnceEffect(() => subscribeParentSocket(setSocket));
}

export const socketAtom: PrimitiveSocketAtom = atom<Socket | undefined>(
undefined,
);

const wrpAtomSet: WrpAtomSet = createWrpAtomSet(socketAtom);

Expand Down
2 changes: 0 additions & 2 deletions src/react/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,4 @@ export * from "./useWrpIframeSocket.ts";
export { default as useWrpIframeSocket } from "./useWrpIframeSocket.ts";
export * from "./useWrpParentSocket.ts";
export { default as useWrpParentSocket } from "./useWrpParentSocket.ts";
export * from "./useWrpServer.ts";
export { default as useWrpServer } from "./useWrpServer.ts";
export * as server from "./server.ts";
16 changes: 16 additions & 0 deletions src/react/useOnceEffect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useEffect, useRef } from "react";

/**
* Guaranteed to be called only once in a component's lifecycle.
* It called only once, even in strict mode.
*/
const useOnceEffect: typeof useEffect = (effect) => {
const effectHasFiredRef = useRef<true>();
useEffect(() => {
if (effectHasFiredRef.current) return;
else effectHasFiredRef.current = true;
return effect();
}, []);
};

export default useOnceEffect;
18 changes: 3 additions & 15 deletions src/react/useWrpIframeSocket.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { defer } from "https://deno.land/x/[email protected]/core/runtime/async/observer.ts";
import { Ref, useEffect, useRef, useState } from "react";
import { Ref, useRef, useState } from "react";
import { Closer, Socket } from "../socket.ts";
import { createIframeSocket } from "../glue/iframe.ts";
import useOnceEffect from "./useOnceEffect.ts";

export interface UseWrpIframeSocketResult {
iframeRef: Ref<HTMLIFrameElement>;
Expand All @@ -15,14 +16,14 @@ export default function useWrpIframeSocket(): UseWrpIframeSocketResult {
let unmounted = false;
let waitForReconnect = defer<void>();
const iframeElement = iframeRef.current!;
iframeElement.addEventListener("load", tryReconnect);
(async () => { // reconnection loop
while (true) {
if (unmounted) return;
try {
socket = await createIframeSocket({
iframeElement,
iframeOrigin: "*",
onClosed: tryReconnect,
});
setSocket(socket);
await waitForReconnect;
Expand All @@ -32,24 +33,11 @@ export default function useWrpIframeSocket(): UseWrpIframeSocketResult {
return () => {
unmounted = true;
tryReconnect();
void iframeElement.removeEventListener("load", tryReconnect);
};
function tryReconnect() {
if (socket) socket.close();
waitForReconnect.resolve();
waitForReconnect = defer<void>();
}
});
return { iframeRef, socket };
}

// Guaranteed to be called only once in a component's lifecycle.
// It called only once, even in strict mode.
const useOnceEffect: typeof useEffect = (effect) => {
const effectHasFiredRef = useRef<true>();
useEffect(() => {
if (effectHasFiredRef.current) return;
else effectHasFiredRef.current = true;
return effect();
}, []);
};
20 changes: 9 additions & 11 deletions src/react/useWrpParentSocket.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { useEffect, useState } from "react";
import { useState } from "react";
import { Socket } from "../socket.ts";
import { createAndroidSocket } from "../glue/android.ts";
import { createIosSocket } from "../glue/ios.ts";
import { createParentWindowSocket } from "../glue/parent-window.ts";
import useOnceEffect from "../react/useOnceEffect.ts";
import { subscribeParentSocket } from "../glue/parent.ts";

export interface UseWrpParentSocketResult {
socket: Socket | undefined;
Expand All @@ -14,12 +13,11 @@ export interface UseWrpParentSocketResult {
export default function useWrpParentSocket(): UseWrpParentSocketResult {
const [socket, setSocket] = useState<Socket | undefined>(undefined);
const [error, setError] = useState<Error | undefined>(undefined);
useEffect(() => {
Promise.any([
createAndroidSocket(),
createIosSocket(),
createParentWindowSocket({ parentWindowOrigin: "*" }),
]).then(setSocket).catch(setError);
}, []);
useOnceEffect(() =>
subscribeParentSocket((socket, error) => {
setSocket(socket);
setError(error);
})
);
return { socket, error };
}
Loading

0 comments on commit 72dd395

Please sign in to comment.