-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: JongChan Choi <[email protected]>
- Loading branch information
Showing
13 changed files
with
165 additions
and
181 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
|
@@ -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( | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>; | ||
|
@@ -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; | ||
|
@@ -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(); | ||
}, []); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.