Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
287 changes: 273 additions & 14 deletions extension/background/index.ts

Large diffs are not rendered by default.

17 changes: 15 additions & 2 deletions extension/content/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,21 @@ window.addEventListener('message', async (evt: MessageEvent) => {
return;
}

// Forward to service worker and relay response back to page
const reply = await chrome.runtime.sendMessage(data);
// Forward to service worker and relay response back to page.
// If the service worker was reloaded/unavailable, still answer the page so
// callers get the bridge error instead of timing out with no details.
let reply: unknown;
try {
reply = await chrome.runtime.sendMessage(data);
} catch (error) {
reply = {
error: {
code: -32603,
message:
error instanceof Error ? error.message : `Iris extension bridge failed: ${String(error)}`,
},
};
}

const responseMessage = {
target: MESSAGE_TARGETS.WALLET_BRIDGE,
Expand Down
54 changes: 51 additions & 3 deletions extension/inpage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,41 @@ import { version } from '../../package.json';
// Inline constant to avoid imports
const MESSAGE_TARGET = 'IRIS';

function readableErrorMessage(error: unknown): string {
if (!error) return 'Wallet request failed without a response';
if (typeof error === 'string') {
const text = error.trim();
return text && text !== '[object Object]' ? text : 'Wallet request failed';
}
if (typeof error !== 'object') return String(error);

const value = error as Record<string, unknown>;
for (const key of ['message', 'reason', 'details']) {
const detail = value[key];
if (typeof detail === 'string' && detail.trim() && detail !== '[object Object]') {
return detail;
}
}
for (const key of ['error', 'data', 'cause']) {
const detail = readableErrorMessage(value[key]);
if (detail && detail !== 'Wallet request failed without a response') {
return detail;
}
}

try {
const serialized = JSON.stringify(error);
if (serialized && serialized !== '{}') return serialized;
} catch {
// ignore serialization failures
}
return 'Wallet request failed';
}

class NockProvider implements InjectedNockchain {
version = version;
provider = 'iris';

/**
* Make a request to the wallet
* @param args - Request arguments with method and params
Expand Down Expand Up @@ -44,7 +78,14 @@ class NockProvider implements InjectedNockchain {
}

if (data.reply?.error) {
reject(new Error(data.reply.error));
const error = data.reply.error;
const message = readableErrorMessage(error);
const wrapped = new Error(message);
(wrapped as Error & { originalError?: unknown }).originalError = error;
if (error && typeof error === 'object') {
Object.assign(wrapped, error);
}
reject(wrapped);
} else {
resolve(data.reply);
}
Expand All @@ -65,12 +106,19 @@ class NockProvider implements InjectedNockchain {
window.addEventListener('message', handler);
});
}

debug() {
return {
provider: 'iris',
version,
injected: true,
location: window.location.origin,
};
}
}

// Inject provider into window
const provider = new NockProvider();
(provider as InjectedNockchain).provider = 'iris';
(provider as InjectedNockchain).version = version;
(window as any).nockchain = provider;

// Announce provider availability
Expand Down
2 changes: 1 addition & 1 deletion extension/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "Iris Wallet",
"homepage_url": "https://iriswallet.io",
"version": "1.2.4",
"version": "1.3.0",
"description": "Iris Wallet - Browser Wallet for Nockchain",
"icons": {
"16": "icons/icon16.png",
Expand Down
3 changes: 3 additions & 0 deletions extension/popup/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ImportScreen } from './screens/onboarding/ImportScreen';
import { ImportSuccessScreen } from './screens/onboarding/ImportSuccessScreen';
import { ResumeBackupScreen } from './screens/onboarding/ResumeBackupScreen';
import { AddWalletStartScreen } from './screens/AddWalletStartScreen';
import { NocksterConnectScreen } from './screens/NocksterConnectScreen';
import { HomeScreen } from './screens/HomeScreen';
import { SendScreen } from './screens/SendScreen';
import { SendReviewScreen } from './screens/SendReviewScreen';
Expand Down Expand Up @@ -72,6 +73,8 @@ export function Router() {
return <CreateScreen />;
case 'wallet-add-import':
return <ImportScreen />;
case 'wallet-add-nockster':
return <NocksterConnectScreen />;
case 'wallet-add-backup':
return <BackupScreen />;
case 'wallet-add-verify':
Expand Down
30 changes: 30 additions & 0 deletions extension/popup/screens/AddWalletStartScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ import vectorBottomLeft from '../assets/vector-bottom-left.svg';
export function AddWalletStartScreen() {
const { navigate } = useStore();

async function handleConnectNockster() {
if (window.location.hash.slice(1) === 'nockster-pair') {
navigate('wallet-add-nockster');
return;
}

const url = chrome.runtime.getURL('popup/index.html#nockster-pair');
try {
await chrome.tabs.create({ url, active: true });
} catch {
window.open(url, '_blank');
}
window.close();
}

return (
<div className="relative w-[357px] h-[600px] bg-[var(--color-bg)] overflow-hidden">
<img
Expand Down Expand Up @@ -108,6 +123,21 @@ export function AddWalletStartScreen() {
I have a wallet
</button>

<button
onClick={() => void handleConnectNockster()}
className="h-12 px-5 py-[15px] bg-[var(--color-surface-800)] rounded-lg flex items-center justify-center transition-opacity hover:opacity-90"
style={{
fontFamily: 'var(--font-sans)',
fontSize: 'var(--font-size-base)',
fontWeight: 500,
lineHeight: 'var(--line-height-snug)',
letterSpacing: '0.01em',
color: 'var(--color-text-primary)',
}}
>
Connect Nockster
</button>

<button
onClick={() => navigate('home')}
className="h-12 px-5 py-[15px] bg-transparent rounded-lg flex items-center justify-center transition-opacity hover:opacity-70"
Expand Down
12 changes: 12 additions & 0 deletions extension/popup/screens/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export function HomeScreen() {
fetchWalletTransactions,
setSelectedTransaction,
isBalanceFetching,
balanceError,
isInitialized,
priceUsd,
priceChange24h,
Expand Down Expand Up @@ -851,6 +852,17 @@ export function HomeScreen() {
</>
)}
</div>
{balanceError ? (
<div
className="mt-2 rounded-[8px] px-3 py-2 text-[12px] leading-[16px]"
style={{
color: 'var(--color-red)',
backgroundColor: 'rgba(236, 80, 80, 0.12)',
}}
>
Balance refresh failed: {balanceError}
</div>
) : null}
</div>

{/* Actions */}
Expand Down
Loading