Skip to content

Commit

Permalink
use listener
Browse files Browse the repository at this point in the history
  • Loading branch information
turbocrime committed Jul 12, 2024
1 parent b0666ea commit fb9404f
Showing 1 changed file with 119 additions and 82 deletions.
201 changes: 119 additions & 82 deletions packages/react/src/components/penumbra-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { PenumbraInjection, PenumbraInjectionState } from '@penumbra-zone/client';
import {
isPenumbraInjectionStateEvent,
PenumbraInjection,
PenumbraInjectionState,
} from '@penumbra-zone/client';
import { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react';
import { PenumbraManifest } from '../manifest';
import { PenumbraContext, penumbraContext } from '../penumbra-context';
import { assertManifestOrigin, injectionOfKey, keyOfInjection } from '../util';
import { PenumbraManifest } from '../manifest.js';
import { PenumbraContext, penumbraContext } from '../penumbra-context.js';
import { assertManifestOrigin, injectionOfKey, keyOfInjection } from '../util.js';

type PenumbraProviderProps = {
children?: ReactNode;
Expand All @@ -22,82 +26,67 @@ export const PenumbraProvider = ({

const [providerState, setProviderState] = useState(providerInjection?.state());
const [providerConnected, setProviderConnected] = useState(providerInjection?.isConnected());
const updateProviderState = useCallback(() => {
// skip uninitialized state
if (
providerState === undefined &&
providerConnected === undefined &&
providerInjection === undefined
)
return;

// skip final states
if (
providerConnected === false &&
(providerState === PenumbraInjectionState.Failed ||
providerState === PenumbraInjectionState.Disconnected)
)
return;

setProviderState(providerInjection?.state());
setProviderConnected(providerInjection?.isConnected());
}, [providerInjection, providerState, providerConnected, setProviderState, setProviderConnected]);

const [failure, setFailureError] = useState<Error>();
const setFailureUnknown = useCallback(
(cause: unknown) => {
if (failure)
console.error('Not replacing existing PenumbraProvider failure', { failure, cause });
else
setFailureError(cause instanceof Error ? cause : new Error('Unknown failure', { cause }));
},
(cause: unknown) =>
failure
? console.error('Not replacing existing PenumbraProvider failure', { failure, cause })
: setFailureError(cause instanceof Error ? cause : new Error('Unknown failure', { cause })),
[failure, setFailureError],
);

const [providerPort, setProviderPort] = useState<MessagePort>();
const [manifest, setManifest] = useState<PenumbraManifest>();

const createdContext: PenumbraContext = useMemo(
() => ({
failure,
manifest,
origin: providerOrigin,
// force destruction of provider on failure
useEffect(() => {
if (failure) {
setProviderState(PenumbraInjectionState.Failed);
setProviderConnected(false);
setProviderPort(undefined);
}
}, [failure]);

// require manifest to forward state
state: manifest && providerState,
// attach state event listener
useEffect(() => {
// require manifest, no failures
if (!manifest || failure) {
return;
}

// require manifest and no failures to forward injected methods
...(manifest && !failure
? {
port: providerConnected && providerPort,
connect: providerInjection?.connect,
request: providerInjection?.request,
disconnect: providerInjection?.disconnect,
}
: {}),
}),
[
failure,
manifest,
providerPort,
providerInjection?.connect,
providerInjection?.connect,
providerInjection?.disconnect,
providerOrigin,
providerState,
],
);
const listener = (evt: Event) => {
if (isPenumbraInjectionStateEvent(evt)) {
if (!providerInjection) {
setFailureError(new Error('State change event without injection'));
} else if (evt.detail.origin !== providerOrigin) {
setFailureError(new Error('State change from unexpected origin'));
} else if (evt.detail.state !== providerInjection.state()) {
console.warn('State change not verifiable');
} else {
setProviderState(providerInjection.state());
setProviderConnected(providerInjection.isConnected());
}
}
};

useEffect(() => updateProviderState());
const ac = new AbortController();
providerInjection?.addEventListener('penumbrastate', listener, {
signal: ac.signal,
});
return () => ac.abort();
}, [providerInjection, providerInjection?.addEventListener, manifest, failure]);

// fetch manifest to confirm presence of provider
useEffect(() => {
// require provider
if (!providerOrigin || !providerInjection) return;
// don't repeat
if (manifest) return;
// unnecessary if failed
if (failure) return;
if (!providerOrigin || !providerInjection) {
return;
}
// don't repeat, unnecessary if failed
if (!!manifest || failure) {
return;
}

// sync assertion
try {
Expand All @@ -107,34 +96,46 @@ export const PenumbraProvider = ({
return;
}

// async fetch
// abortable fetch
const ac = new AbortController();
void fetch(providerInjection.manifest, { signal: ac.signal })
.then(
async res => {
const fetchManifest = fetch(providerInjection.manifest, { signal: ac.signal }).catch(
(noAbortError: unknown) => {
// abort is not a failure
if (noAbortError instanceof Error && noAbortError.name === 'AbortError') {
return;
} else {
throw noAbortError;
}
},
);

// async handle response
void fetchManifest
.then(async res => {
const manifestJson: unknown = await res?.json();
if (manifestJson) {
// this cast is fairly safe coming from an extension manifest, where
// schema is enforced by chrome store.
const manifestJson = (await res.json()) as PenumbraManifest;
setManifest(manifestJson);
},
(noAbortError: unknown) => {
// abort is not a failure
if (noAbortError instanceof Error && noAbortError.name === 'AbortError') return;
else throw noAbortError;
},
)
setManifest(manifestJson as PenumbraManifest);
}
})
.catch(setFailureUnknown);

// useEffect cleanup
return () => ac.abort();
}, [providerOrigin, providerInjection, manifest, setManifest]);

// request effect
useEffect(() => {
if (!manifest || failure) return;
// require manifest, no failures
if (!manifest || failure) {
return;
}

switch (providerState) {
case PenumbraInjectionState.Present:
if (makeApprovalRequest) void providerInjection?.request().catch(setFailureUnknown);
if (makeApprovalRequest) {
void providerInjection?.request().catch(setFailureUnknown);
}
break;
default:
break;
Expand All @@ -143,14 +144,19 @@ export const PenumbraProvider = ({

// connect effect
useEffect(() => {
if (!manifest || failure) return;
// require manifest, no failures
if (!manifest || failure) {
return;
}

switch (providerState) {
case PenumbraInjectionState.Present:
if (!makeApprovalRequest)
if (!makeApprovalRequest) {
void providerInjection
?.connect()
.then(p => setProviderPort(p))
.catch(setFailureUnknown);
}
break;
case PenumbraInjectionState.Requested:
void providerInjection
Expand All @@ -163,5 +169,36 @@ export const PenumbraProvider = ({
}
}, [makeApprovalRequest, providerState, providerInjection?.connect, manifest, failure]);

const createdContext: PenumbraContext = useMemo(
() => ({
failure,
manifest,
origin: providerOrigin,

// require manifest to forward state
state: manifest && providerState,

// require manifest and no failures to forward injected methods
...(manifest && !failure
? {
port: providerConnected && providerPort,
connect: providerInjection?.connect,
request: providerInjection?.request,
disconnect: providerInjection?.disconnect,
}
: {}),
}),
[
failure,
manifest,
providerPort,
providerInjection?.connect,
providerInjection?.connect,
providerInjection?.disconnect,
providerOrigin,
providerState,
],
);

return <penumbraContext.Provider value={createdContext}>{children}</penumbraContext.Provider>;
};

0 comments on commit fb9404f

Please sign in to comment.