feat(tsconnect-react): add useBuildIpnWorker hook for SharedWorker clients
- Add disconnect() to IpnWorkerClient (releases Web Lock without shutdown) - Widen IpnContext from IPN to IpnClient - Add useBuildIpnWorker(worker, config, runParams) hook with StrictMode-safe cleanup: disconnect() fires if the cleanup runs before connect() resolves, or immediately after if it resolves first - Export useBuildIpnWorker from package index - Add @webnet/tsconnect-worker to package dependencies Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@webnet/tsconnect": "*",
|
||||
"@webnet/tsconnect-redux": "*"
|
||||
"@webnet/tsconnect-redux": "*",
|
||||
"@webnet/tsconnect-worker": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.14",
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Provider, type ReactReduxContextValue, type ProviderProps } from "react-redux"
|
||||
import type { IpnAction, IpnState } from "@webnet/tsconnect-redux"
|
||||
import { createContext } from "react"
|
||||
import type { IPN } from "@webnet/tsconnect"
|
||||
import type { IpnClient } from "@webnet/tsconnect"
|
||||
|
||||
export type IpnStoreContext = ReturnType<typeof createIpnStoreContext>
|
||||
export const createIpnStoreContext = () =>
|
||||
createContext<ReactReduxContextValue<IpnState, IpnAction> | null>(null)
|
||||
|
||||
export type IpnContext = ReturnType<typeof createIpnContext>
|
||||
export const createIpnContext = () => createContext<IPN | null>(null)
|
||||
export const createIpnContext = () => createContext<IpnClient | null>(null)
|
||||
|
||||
export const IpnStoreProvider = (props: ProviderProps<IpnAction, IpnState>) =>
|
||||
Provider<IpnAction, IpnState>(props)
|
||||
|
||||
@@ -2,6 +2,8 @@ import { useEffect, useEffectEvent, useRef, useState } from "react"
|
||||
import type { initIPN, IPN } from "@webnet/tsconnect"
|
||||
import type { IpnDispatch, IpnState, IpnStore } from "@webnet/tsconnect-redux"
|
||||
import { runWithStore } from "@webnet/tsconnect-redux"
|
||||
import { IpnWorkerClient } from "@webnet/tsconnect-worker"
|
||||
import type { WorkerConfig } from "@webnet/tsconnect-worker"
|
||||
import {
|
||||
createDispatchHook,
|
||||
createSelectorHook,
|
||||
@@ -33,6 +35,36 @@ export function useBuildIpn(
|
||||
return ipn
|
||||
}
|
||||
|
||||
export function useBuildIpnWorker(
|
||||
worker: SharedWorker | string | URL | null,
|
||||
config: WorkerConfig | null = null,
|
||||
runParams: Parameters<IPN["run"]>[0] | null = null,
|
||||
): IpnWorkerClient | null {
|
||||
const clientRef = useRef<IpnWorkerClient | null>(null)
|
||||
const [client, setClient] = useState<IpnWorkerClient | null>(null)
|
||||
const connect = useEffectEvent(
|
||||
(worker: SharedWorker | string | URL, config: WorkerConfig) => {
|
||||
const sw = worker instanceof SharedWorker ? worker : new SharedWorker(worker, { type: "module" })
|
||||
let cleanedUp = false
|
||||
let activeClient: IpnWorkerClient | null = null
|
||||
IpnWorkerClient.connect(sw.port, config).then((c) => {
|
||||
if (cleanedUp) { c.disconnect(); return }
|
||||
activeClient = clientRef.current = c
|
||||
c.run(runParams ?? {})
|
||||
setClient(c)
|
||||
})
|
||||
return () => {
|
||||
cleanedUp = true
|
||||
if (activeClient) { activeClient.disconnect(); clientRef.current = null }
|
||||
}
|
||||
},
|
||||
)
|
||||
useEffect(() => {
|
||||
if (worker && config && !clientRef.current) return connect(worker, config)
|
||||
}, [worker, config])
|
||||
return client
|
||||
}
|
||||
|
||||
export const useIpnStore = useStore.withTypes<IpnStore>()
|
||||
export const useIpnSelector = useSelector.withTypes<IpnState>()
|
||||
export const useIpnDispatch = useDispatch.withTypes<IpnDispatch>()
|
||||
|
||||
@@ -11,6 +11,7 @@ export {
|
||||
createUseIpnSelector,
|
||||
createUseIpnStore,
|
||||
useBuildIpn,
|
||||
useBuildIpnWorker,
|
||||
useIpnDispatch,
|
||||
useIpnSelector,
|
||||
useIpnStore,
|
||||
|
||||
@@ -548,9 +548,19 @@ export class IpnWorkerClient implements IpnClient {
|
||||
if (ofList.length) opts.notifyOutgoingFiles?.(ofList as IPNOutgoingFile[])
|
||||
}
|
||||
|
||||
/** Release this client's hold on the worker without shutting the worker down.
|
||||
* The worker cleans up all resources opened by this client and shuts down
|
||||
* only if no other clients remain. Safe to call after {@link shutdown}. */
|
||||
disconnect(): void {
|
||||
this.#running = false
|
||||
this.#releaseLock?.()
|
||||
this.#releaseLock = null
|
||||
}
|
||||
|
||||
async shutdown(): Promise<void> {
|
||||
this.#running = false
|
||||
this.#releaseLock?.()
|
||||
this.#releaseLock = null
|
||||
await this.#call("shutdown", [])
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user