Skip to content

Commit

Permalink
Fix captcha handling
Browse files Browse the repository at this point in the history
Captcha resolution wasn't IPC compatible. It depended on the
renderer process having direct access to `window` in a way that
hasn't been true since we upgraded to Electron 12.

The current event flow for captcha resolution is a bit roundabout,
in that it moves from:

contained webview -> main process -> renderer process

This could conceivably be shortened, but I don't know that its
necessarily valuable to pursue
  • Loading branch information
alts committed Feb 25, 2024
1 parent 889280f commit 105041c
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 58 deletions.
4 changes: 4 additions & 0 deletions src/common/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ export const actions = wireActions({
initialURL: string;
role: WindRole;
}>(),
// only exists due to IPC difficulties
closeCaptchaModal: action<{
response: string;
}>(),

// setup

Expand Down
17 changes: 16 additions & 1 deletion src/common/ipc.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IpcRenderer, OpenDialogOptions } from "electron";
import { ipcRenderer, IpcRenderer, OpenDialogOptions } from "electron";

export type AsyncIpcHandlers = {
showOpenDialog: (o: OpenDialogOptions) => Promise<string[]>;
Expand All @@ -11,6 +11,21 @@ export type SyncIpcHandlers = {
userAgent: (x: undefined) => string;
getImageURL: (p: string) => string;
getInjectURL: (p: string) => string;
onCaptchaResponse: (r: string) => null;
legacyMarketPath: () => string;
mainLogPath: () => string;
};

export const emitSyncIpcEvent = <K extends keyof SyncIpcHandlers>(
eventName: K,
arg: Parameters<SyncIpcHandlers[K]>[0]
): ReturnType<SyncIpcHandlers[K]> => {
return ipcRenderer.sendSync(eventName, arg);
};

export const emitAsyncIpcEvent = <K extends keyof AsyncIpcHandlers>(
eventName: K,
arg: Parameters<AsyncIpcHandlers[K]>[0]
): ReturnType<AsyncIpcHandlers[K]> => {
return ipcRenderer.invoke(eventName, arg) as ReturnType<AsyncIpcHandlers[K]>;
};
14 changes: 6 additions & 8 deletions src/main/inject/inject-captcha.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
interface ExtWindow {
onCaptcha: (response: string) => void;
captchaResponse: string;
}
const extWindow: ExtWindow = window as any;
import { contextBridge } from "electron";
import { emitSyncIpcEvent } from "common/ipc";
import "@goosewobbler/electron-redux/preload";

extWindow.onCaptcha = function (response: string) {
extWindow.captchaResponse = response;
};
contextBridge.exposeInMainWorld("onCaptcha", function (response: string) {
emitSyncIpcEvent("onCaptchaResponse", response);
});
16 changes: 1 addition & 15 deletions src/main/inject/inject-preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import qs from "querystring";
import { promises } from "fs";
import { Logger } from "common/logger";
import { Message } from "common/helpers/bridge";
import { AsyncIpcHandlers, SyncIpcHandlers } from "common/ipc";
import { emitAsyncIpcEvent, emitSyncIpcEvent } from "common/ipc";
import { Store } from "common/types";
import { convertMessage } from "common/helpers/bridge";
import "@goosewobbler/electron-redux/preload";
Expand All @@ -27,20 +27,6 @@ const memo = <A>(fn: () => A): (() => A) => {
};
};

const emitSyncIpcEvent = <K extends keyof SyncIpcHandlers>(
eventName: K,
arg: Parameters<SyncIpcHandlers[K]>[0]
): ReturnType<SyncIpcHandlers[K]> => {
return ipcRenderer.sendSync(eventName, arg);
};

const emitAsyncIpcEvent = <K extends keyof AsyncIpcHandlers>(
eventName: K,
arg: Parameters<AsyncIpcHandlers[K]>[0]
): ReturnType<AsyncIpcHandlers[K]> => {
return ipcRenderer.invoke(eventName, arg) as ReturnType<AsyncIpcHandlers[K]>;
};

export const mainWorldSupplement = {
nodeUrl: { parse, format },
electron: {
Expand Down
6 changes: 6 additions & 0 deletions src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ export function main() {
getInjectURL,
legacyMarketPath,
mainLogPath,
onCaptchaResponse: (response) => {
if (response) {
store.dispatch(actions.closeCaptchaModal({ response }));
}
return null;
},
},
{
showOpenDialog: async (options: OpenDialogOptions) => {
Expand Down
51 changes: 17 additions & 34 deletions src/renderer/modal-widgets/RecaptchaInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { hook } from "renderer/hocs/hook";
import styled from "renderer/styles";
import { ModalWidgetProps } from "common/modals";
import modals from "renderer/modals";
import watching, { Watcher } from "renderer/hocs/watching";
import {
RecaptchaInputParams,
RecaptchaInputResponse,
Expand All @@ -35,9 +36,9 @@ const WidgetDiv = styled.div`
}
`;

@watching
class RecaptchaInput extends React.PureComponent<RecaptchaInputProps, State> {
webview: Electron.WebviewTag;
checker: number;

constructor(props: RecaptchaInput["props"], context: any) {
super(props, context);
Expand All @@ -46,6 +47,21 @@ class RecaptchaInput extends React.PureComponent<RecaptchaInputProps, State> {
};
}

subscribe(watcher: Watcher) {
watcher.on(actions.closeCaptchaModal, async (store, action) => {
const { dispatch } = this.props;
const { response } = action.payload;
dispatch(
actions.closeModal({
wind: ambientWind(),
action: modals.recaptchaInput.action({
recaptchaResponse: response,
}),
})
);
});
}

render() {
const params = this.props.modal.widgetParams;
const { url } = params;
Expand All @@ -69,7 +85,6 @@ class RecaptchaInput extends React.PureComponent<RecaptchaInputProps, State> {

gotWebview = (wv: Electron.WebviewTag) => {
this.webview = wv;
this.clearChecker();

if (!this.webview) {
return;
Expand All @@ -78,39 +93,7 @@ class RecaptchaInput extends React.PureComponent<RecaptchaInputProps, State> {
this.webview.addEventListener("did-finish-load", () => {
this.setState({ loaded: true });
});

this.checker = window.setInterval(() => {
this.webview
.executeJavaScript(`window.captchaResponse`, false)
.then((response: string | undefined) => {
if (response) {
const { dispatch } = this.props;
dispatch(
actions.closeModal({
wind: ambientWind(),
action: modals.recaptchaInput.action({
recaptchaResponse: response,
}),
})
);
}
})
.catch((e) => {
console.error(e);
});
}, 500);
};

componentWillUnmount() {
this.clearChecker();
}

clearChecker() {
if (this.checker) {
clearInterval(this.checker);
this.checker = null;
}
}
}

interface State {
Expand Down

0 comments on commit 105041c

Please sign in to comment.