-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Popup ready signal to service worker #174
Changes from 7 commits
fddafca
831ea52
5564da1
8e03a5d
3f788eb
ccd44ae
7e7355b
6829bf6
3cba6f5
a9d0478
861180e
20472ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,3 +62,4 @@ packages/*/package | |
|
||
apps/extension/chromium-profile | ||
|
||
scripts/private | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { useEffect, useRef } from 'react'; | ||
import { PopupResponse, PopupType, Ready } from '../message/popup'; | ||
|
||
type IsReady = boolean | undefined; | ||
|
||
// signals that react is ready (mounted) to service worker | ||
export const usePopupReady = (isReady: IsReady = undefined) => { | ||
const sentMessagesRef = useRef(new Set()); | ||
const searchParams = new URLSearchParams(window.location.search); | ||
const popupId = searchParams.get('popupId'); | ||
JasonMHasperhoven marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
useEffect(() => { | ||
if (popupId && (isReady === undefined || isReady) && !sentMessagesRef.current.has(popupId)) { | ||
sentMessagesRef.current.add(popupId); | ||
|
||
void chrome.runtime.sendMessage({ | ||
type: PopupType.Ready, | ||
data: { | ||
popupId, | ||
}, | ||
} as PopupResponse<Ready>); | ||
} | ||
}, [popupId, isReady]); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,9 +11,10 @@ import { OriginRecord } from '../storage/types'; | |
export enum PopupType { | ||
TxApproval = 'TxApproval', | ||
OriginApproval = 'OriginApproval', | ||
Ready = 'PopupReady', | ||
JasonMHasperhoven marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
export type PopupMessage = TxApproval | OriginApproval; | ||
export type PopupMessage = TxApproval | OriginApproval | Ready; | ||
export type PopupRequest<T extends PopupMessage = PopupMessage> = InternalRequest<T>; | ||
export type PopupResponse<T extends PopupMessage = PopupMessage> = InternalResponse<T>; | ||
|
||
|
@@ -34,6 +35,14 @@ export type TxApproval = InternalMessage< | |
} | ||
>; | ||
|
||
export type Ready = InternalMessage< | ||
PopupType.Ready, | ||
null, | ||
{ | ||
popupId: string; | ||
} | ||
>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (blocking)defining an todo:Don't use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for example, this PR presently assures the compiler this call is ok, even though it's not an intended use of this message type: const readyResponse: { popupId: string } = await popup<Ready>({ type: PopupType.Ready, request: null }); this verifies the user is logged in, and then thankfully throws before opening a popup when the popup method can't identify a route for |
||
|
||
export const isPopupRequest = (req: unknown): req is PopupRequest => | ||
req != null && | ||
typeof req === 'object' && | ||
|
@@ -42,8 +51,19 @@ export const isPopupRequest = (req: unknown): req is PopupRequest => | |
typeof req.type === 'string' && | ||
req.type in PopupType; | ||
|
||
export const isPopupResponse = (res: unknown): res is PopupResponse => | ||
res != null && | ||
typeof res === 'object' && | ||
('data' in res || 'error' in res) && | ||
'type' in res && | ||
typeof res.type === 'string' && | ||
res.type in PopupType; | ||
|
||
export const isOriginApprovalRequest = (req: unknown): req is InternalRequest<OriginApproval> => | ||
isPopupRequest(req) && req.type === PopupType.OriginApproval && 'origin' in req.request; | ||
isPopupRequest(req) && req.type === PopupType.OriginApproval; | ||
|
||
export const isTxApprovalRequest = (req: unknown): req is InternalRequest<TxApproval> => | ||
isPopupRequest(req) && req.type === PopupType.TxApproval && 'authorizeRequest' in req.request; | ||
isPopupRequest(req) && req.type === PopupType.TxApproval; | ||
JasonMHasperhoven marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
export const isPopupReadyResponse = (res: unknown): res is InternalResponse<Ready> => | ||
isPopupResponse(res) && res.type === PopupType.Ready; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,11 @@ | ||
import { sessionExtStorage } from './storage/session'; | ||
import { PopupMessage, PopupRequest, PopupType } from './message/popup'; | ||
import { | ||
isPopupReadyResponse, | ||
PopupMessage, | ||
PopupRequest, | ||
PopupResponse, | ||
PopupType, | ||
} from './message/popup'; | ||
import { PopupPath } from './routes/popup/paths'; | ||
import type { InternalRequest, InternalResponse } from '@penumbra-zone/types/internal-msg/shared'; | ||
import { Code, ConnectError } from '@connectrpc/connect'; | ||
|
@@ -18,9 +24,10 @@ const isChromeResponderDroppedError = ( | |
export const popup = async <M extends PopupMessage>( | ||
req: PopupRequest<M>, | ||
): Promise<M['response']> => { | ||
await spawnPopup(req.type); | ||
// We have to wait for React to bootup, navigate to the page, and render the components | ||
await new Promise(resolve => setTimeout(resolve, 800)); | ||
const popupId = crypto.randomUUID(); | ||
await spawnPopup(req.type, popupId); | ||
await popupReady(popupId); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: we should add a code comment here that informs devs that this is necessary given it takes a bit of time for the popup to be ready to accept messages. |
||
|
||
const response = await chrome.runtime | ||
.sendMessage<InternalRequest<M>, InternalResponse<M>>(req) | ||
.catch((e: unknown) => { | ||
|
@@ -30,6 +37,7 @@ export const popup = async <M extends PopupMessage>( | |
throw e; | ||
} | ||
}); | ||
|
||
if (response && 'error' in response) { | ||
throw errorFromJson(response.error, undefined, ConnectError.from(response)); | ||
} else { | ||
|
@@ -73,8 +81,8 @@ const throwIfNeedsLogin = async () => { | |
} | ||
}; | ||
|
||
const spawnPopup = async (pop: PopupType) => { | ||
const popUrl = new URL(chrome.runtime.getURL('popup.html')); | ||
const spawnPopup = async (pop: PopupType, popupId: string) => { | ||
const popUrl = new URL(chrome.runtime.getURL(`popup.html?popupId=${popupId}`)); | ||
JasonMHasperhoven marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
await throwIfNeedsLogin(); | ||
|
||
|
@@ -89,3 +97,26 @@ const spawnPopup = async (pop: PopupType) => { | |
throw Error('Unknown popup type'); | ||
} | ||
}; | ||
|
||
const POPUP_READY_TIMEOUT = 60 * 1000; | ||
|
||
const popupReady = async (popupId: string): Promise<void> => { | ||
return new Promise((resolve, reject): void => { | ||
setTimeout(() => { | ||
reject(new Error('Popup ready timed out')); | ||
}, POPUP_READY_TIMEOUT); | ||
JasonMHasperhoven marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const handlePopupReady = (res: PopupResponse): void => { | ||
if (!isPopupReadyResponse(res)) { | ||
return; | ||
} | ||
|
||
if ('data' in res && res.data.popupId === popupId) { | ||
chrome.runtime.onMessage.removeListener(handlePopupReady); | ||
resolve(); | ||
} | ||
}; | ||
|
||
chrome.runtime.onMessage.addListener(handlePopupReady); | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: revert gitignore diff