Skip to content

Commit

Permalink
Merge branch 'main' into auto-key-consent
Browse files Browse the repository at this point in the history
  • Loading branch information
sidvishnoi authored Oct 2, 2024
2 parents 68bc7eb + b38ad6d commit 10d8744
Show file tree
Hide file tree
Showing 21 changed files with 504 additions and 19 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ Inside this project, you'll see the following folders and files:
│ ├── content/ # Source code for the content scripts
│ │ └── keyAutoAdd/ # content scripts for automatic key addition to wallets
│ ├── popup/ # Source code for the popup UI
│ ├── pages/ # Source code for additional extension pages
│ ├── shared/ # Shared utilities
│ └── manifest.json # Extension's manifest - processed by Webpack depending on the target build
├── jest.config.ts
Expand Down
4 changes: 4 additions & 0 deletions esbuild/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export const options: BuildOptions = {
in: path.join(SRC_DIR, 'popup', 'index.tsx'),
out: path.join('popup', 'popup'),
},
{
in: path.join(SRC_DIR, 'pages', 'progress-connect', 'index.tsx'),
out: path.join('pages', 'progress-connect', 'progress-connect'),
},
],
bundle: true,
legalComments: 'none',
Expand Down
21 changes: 21 additions & 0 deletions esbuild/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ function liveReloadPlugin({ target }: { target: Target }): ESBuildPlugin {
}
);`;

const reloadScriptPages = `
new EventSource("http://localhost:${port}/esbuild").addEventListener(
"change",
(ev) => {
const data = JSON.parse(ev.data);
if (
data.added.some(s => s.includes("/pages/")) ||
data.updated.some(s => s.includes("/pages/"))
) {
globalThis.location.reload();
}
}
);`;

const reloadScriptContent = `
new EventSource("http://localhost:${port}/esbuild").addEventListener(
"change",
Expand Down Expand Up @@ -95,6 +109,13 @@ function liveReloadPlugin({ target }: { target: Target }): ESBuildPlugin {
loader: 'tsx' as const,
};
});
build.onLoad({ filter: /src\/pages\/.+\/index.tsx$/ }, async (args) => {
const contents = await readFile(args.path, 'utf8');
return {
contents: contents + '\n\n\n' + reloadScriptPages,
loader: 'tsx' as const,
};
});
build.onLoad({ filter: /src\/content\// }, async (args) => {
const contents = await readFile(args.path, 'utf8');
return {
Expand Down
4 changes: 4 additions & 0 deletions esbuild/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export const getPlugins = ({
from: path.join(SRC_DIR, 'popup', 'index.html'),
to: path.join(outDir, 'popup', 'index.html'),
},
{
from: path.join(SRC_DIR, 'pages', 'progress-connect', 'index.html'),
to: path.join(outDir, 'pages', 'progress-connect', 'index.html'),
},
{
from: path.join(SRC_DIR, '_locales/**/*'),
to: path.join(outDir, '_locales'),
Expand Down
10 changes: 8 additions & 2 deletions src/background/services/keyAutoAdd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,25 +89,29 @@ export class KeyAutoAddService {
};
this.browser.tabs.onRemoved.addListener(onTabCloseListener);

const ports = new Set<Runtime.Port>();
const onConnectListener: OnConnectCallback = (port) => {
if (port.name !== CONNECTION_NAME) return;
if (port.error) {
reject(new Error(port.error.message));
return;
}
ports.add(port);

port.postMessage({ action: 'BEGIN', payload });

port.onMessage.addListener(onMessageListener);

port.onDisconnect.addListener(() => {
ports.delete(port);
// wait for connect again so we can send message again if not connected,
// and not errored already (e.g. page refreshed)
});
};

const onMessageListener: OnPortMessageListener = (
message: KeyAutoAddToBackgroundMessage,
port,
) => {
if (message.action === 'SUCCESS') {
this.browser.runtime.onConnect.removeListener(onConnectListener);
Expand All @@ -124,8 +128,10 @@ export class KeyAutoAddService {
]),
);
} else if (message.action === 'PROGRESS') {
// can save progress to show in popup
// console.table(message.payload.steps);
// can also save progress to show in popup
for (const p of ports) {
if (p !== port) p.postMessage(message);
}
} else {
reject(new Error(`Unexpected message: ${JSON.stringify(message)}`));
}
Expand Down
91 changes: 89 additions & 2 deletions src/content/keyAutoAdd/lib/keyAutoAdd.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
// cSpell:ignore allowtransparency
import browser, { type Runtime } from 'webextension-polyfill';
import { CONNECTION_NAME } from '@/background/services/keyAutoAdd';
import {
errorWithKeyToJSON,
isErrorWithKey,
sleep,
withResolvers,
type ErrorWithKeyLike,
} from '@/shared/helpers';
import type {
Expand All @@ -24,6 +27,7 @@ const SYMBOL_SKIP = Symbol.for('skip');

export class KeyAutoAdd {
private port: Runtime.Port;
private ui: HTMLIFrameElement;

private stepsInput: Map<string, Step>;
private steps: StepWithStatus[];
Expand All @@ -45,6 +49,78 @@ export class KeyAutoAdd {
);
}

private setNotificationSize(size: 'notification' | 'fullscreen' | 'hidden') {
let styles: Partial<CSSStyleDeclaration>;
const defaultStyles: Partial<CSSStyleDeclaration> = {
outline: 'none',
border: 'none',
zIndex: '9999',
position: 'fixed',
top: '0',
left: '0',
};

if (size === 'notification') {
styles = {
width: '22rem',
height: '8rem',
position: 'fixed',
top: '1rem',
right: '1rem',
left: 'initial',
boxShadow: 'rgba(0, 0, 0, 0.1) 0px 0px 6px 3px',
borderRadius: '0.5rem',
};
} else if (size === 'fullscreen') {
styles = {
width: '100vw',
height: '100vh',
};
} else {
styles = {
width: '0',
height: '0',
position: 'absolute',
};
}

this.ui.style.cssText = '';
Object.assign(this.ui.style, defaultStyles);
Object.assign(this.ui.style, styles);

const iframeUrl = new URL(
browser.runtime.getURL('pages/progress-connect/index.html'),
);
const params = new URLSearchParams({ mode: size });
iframeUrl.hash = '?' + params.toString();
if (this.ui.src !== iframeUrl.href && size !== 'hidden') {
this.ui.src = iframeUrl.href;
}
}

private addNotification() {
const { resolve, reject, promise } = withResolvers<void>();
if (this.ui) {
resolve();
return promise;
}
const pageUrl = browser.runtime.getURL('pages/progress-connect/index.html');
const iframe = document.createElement('iframe');
iframe.setAttribute('allowtransparency', 'true');
iframe.src = pageUrl;
document.body.appendChild(iframe);
iframe.addEventListener('load', () => {
resolve();
sleep(500).then(() =>
this.postMessage('PROGRESS', { steps: this.steps }),
);
});
iframe.addEventListener('error', reject, { once: true });
this.ui = iframe;
this.setNotificationSize('hidden');
return promise;
}

private async run({
walletAddressUrl,
publicKey,
Expand All @@ -64,13 +140,24 @@ export class KeyAutoAdd {
details: typeof details === 'string' ? new Error(details) : details,
};
},
setNotificationSize: (size: 'notification' | 'fullscreen') => {
this.setNotificationSize(size);
},
};

await this.addNotification();
this.postMessage('PROGRESS', { steps: this.steps });

let prevStepId = '';
let prevStepResult: unknown = undefined;
for (let stepIdx = 0; stepIdx < this.steps.length; stepIdx++) {
const step = this.steps[stepIdx];
this.setStatus(stepIdx, 'active', {});
this.postMessage('PROGRESS', { steps: this.steps });
const stepInfo = this.stepsInput.get(step.name)!;
this.setStatus(stepIdx, 'active', {
expiresAt: stepInfo.maxDuration
? new Date(Date.now() + stepInfo.maxDuration).valueOf()
: undefined,
});
try {
prevStepResult = await this.stepsInput
.get(step.name)!
Expand Down
9 changes: 8 additions & 1 deletion src/content/keyAutoAdd/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { ErrorResponse } from '@/shared/messages';

export interface StepRunParams extends BeginPayload {
skip: (message: string | Error | ErrorWithKeyLike) => never;
setNotificationSize: (size: 'notification' | 'fullscreen') => void;
}

export type StepRun<T = unknown, R = void> = (
Expand All @@ -15,6 +16,7 @@ export type StepRun<T = unknown, R = void> = (
export interface Step<T = unknown, R = unknown> {
name: string;
run: StepRun<T, R>;
maxDuration?: number;
}

export type Details = Omit<ErrorResponse, 'success'>;
Expand All @@ -24,7 +26,11 @@ interface StepWithStatusBase {
status: string;
}
interface StepWithStatusNormal extends StepWithStatusBase {
status: 'pending' | 'active' | 'success';
status: 'pending' | 'success';
}
interface StepWithStatusActive extends StepWithStatusBase {
status: 'active';
expiresAt?: number;
}
interface StepWithStatusSkipped extends StepWithStatusBase {
status: 'skipped';
Expand All @@ -37,6 +43,7 @@ interface StepWithStatusError extends StepWithStatusBase {

export type StepWithStatus =
| StepWithStatusNormal
| StepWithStatusActive
| StepWithStatusSkipped
| StepWithStatusError;

Expand Down
21 changes: 17 additions & 4 deletions src/content/keyAutoAdd/testWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,19 @@ type AccountDetails = {
};
};

const waitForLogin: Step<never> = async ({ skip, keyAddUrl }) => {
let alreadyLoggedIn = false;
const waitForLogin: Step<never> = async ({
skip,
setNotificationSize,
keyAddUrl,
}) => {
let alreadyLoggedIn = window.location.href.startsWith(keyAddUrl);
if (!alreadyLoggedIn) setNotificationSize('notification');
try {
alreadyLoggedIn = await waitForURL(
(url) => (url.origin + url.pathname).startsWith(keyAddUrl),
{ timeout: LOGIN_WAIT_TIMEOUT },
);
setNotificationSize('fullscreen');
} catch (error) {
if (isTimedOut(error)) {
throw new ErrorWithKey('connectWalletKeyService_error_timeoutLogin');
Expand All @@ -45,7 +51,10 @@ const waitForLogin: Step<never> = async ({ skip, keyAddUrl }) => {
}
};

const getAccountDetails: Step<never, Account[]> = async (_) => {
const getAccountDetails: Step<never, Account[]> = async ({
setNotificationSize,
}) => {
setNotificationSize('fullscreen');
await sleep(1000);

const NEXT_DATA = document.querySelector('script#__NEXT_DATA__')?.textContent;
Expand Down Expand Up @@ -155,7 +164,11 @@ async function revokeKey(accountId: string, walletId: string, keyId: string) {

// region: Main
new KeyAutoAdd([
{ name: 'Waiting for login', run: waitForLogin },
{
name: 'Waiting for you to login',
run: waitForLogin,
maxDuration: LOGIN_WAIT_TIMEOUT,
},
{ name: 'Getting account details', run: getAccountDetails },
{ name: 'Revoking existing key', run: revokeExistingKey },
{ name: 'Finding wallet', run: findWallet },
Expand Down
2 changes: 1 addition & 1 deletion src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
},
"web_accessible_resources": [
{
"resources": ["polyfill/*"],
"resources": ["polyfill/*", "pages/progress-connect/*"],
"matches": ["<all_urls>"]
}
],
Expand Down
21 changes: 21 additions & 0 deletions src/pages/progress-connect/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { useUIMode } from '@/pages/progress-connect/context';
import { AppNotification } from '@/pages/progress-connect/components/AppNotification';
import { AppFullscreen } from '@/pages/progress-connect/components/AppFullScreen';

export default function App() {
const mode = useUIMode();

React.useEffect(() => {
const container = document.getElementById('container')!;
container.style.height = mode === 'fullscreen' ? '100vh' : 'auto';
document.body.style.backgroundColor =
mode === 'fullscreen' ? 'rgba(255, 255, 255, 0.95)' : 'white';
}, [mode]);

if (mode === 'fullscreen') {
return <AppFullscreen />;
} else {
return <AppNotification />;
}
}
25 changes: 25 additions & 0 deletions src/pages/progress-connect/components/AppFullScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { useState } from '@/pages/progress-connect/context';
import { useBrowser } from '@/popup/lib/context';
import { Steps } from './Steps';

export function AppFullscreen() {
const browser = useBrowser();
const state = useState();

const Logo = browser.runtime.getURL('assets/images/logo.svg');

return (
<div className="m-auto flex h-full w-full max-w-80 flex-col items-center justify-center space-y-2 p-4 text-center">
<header>
<img src={Logo} alt="" className="mx-auto mb-2 h-20" />
<h1 className="text-2xl text-strong">Web Monetization</h1>
<h2 className="text-lg text-weak">Connecting wallet…</h2>
</header>
<main className="w-full space-y-2 pt-2">
<Steps steps={state.steps} />
<p className="text-xs text-weak">{state.currentStep.name}</p>
</main>
</div>
);
}
Loading

0 comments on commit 10d8744

Please sign in to comment.