@injpass/connector
v2.1.8
Published
Lightweight SDK for embedding INJ Pass wallet in dApps - Now with popup-based authentication
Maintainers
Readme
@injpass/connector
Lightweight SDK for embedding INJ Pass wallet in your dApp via iframe.
⚡ What's New in v2.0
Popup-based Authentication Architecture - Completely redesigned to work around browser restrictions:
- ✅ No Storage Access API needed - Works seamlessly in Safari/Chrome without user prompts
- ✅ Bypasses iframe WebAuthn restrictions - Authentication happens in popup windows
- ✅ Full Storage Partitioning compatibility - Future-proof for Chrome's third-party cookie deprecation
- ✅ Better security - Origin validation and isolated authentication flow
Read the full architecture guide →
Features
- 🔐 Passkey Authentication - Secure, passwordless wallet access using WebAuthn
- 📱 Mobile Support - Works on iOS/Android browsers
- 🎨 Flexible UI - Floating, modal, or inline modes
- 🔒 Secure - Private keys never leave the iframe, authentication via popup
- ⚡ Lightweight - Zero dependencies, < 5KB gzipped
- 🔄 Cross-dApp - One wallet works across all dApps
- 🌐 Universal Browser Support - No more Storage Access API prompts!
How It Works (v2.0 Architecture)
sequenceDiagram
participant DApp
participant Iframe as INJ Pass iframe
participant Popup as Auth Popup
DApp->>Iframe: Load /embed in iframe
User->>Iframe: Click "Connect"
Iframe->>Popup: Open popup window
Popup->>User: Request Passkey
User->>Popup: Authenticate (biometrics)
Popup->>Iframe: Send wallet info
Popup->>Popup: Close
Iframe->>DApp: Forward wallet info
DApp->>DApp: Connected! ✅Why popup windows?
- ✅ Popup windows have full access to
localStorage(no partitioning) - ✅ WebAuthn works perfectly in top-level popup windows
- ✅ No browser prompts or permission requests needed
- ✅ Works identically across all browsers
Installation
npm install @injpass/connectoror
pnpm add @injpass/connectorQuick Start
1. Configure Environment Variables
Add to your .env file:
# Development
VITE_INJPASS_EMBED_URL=http://localhost:3001/embed
# Production (after deployment to Vercel)
# VITE_INJPASS_EMBED_URL=https://inj-pass-frontend-test.vercel.app/embed2. Configure CSP Headers (Recommended)
Add to your HTML or HTTP headers to allow the iframe:
<meta
http-equiv="Content-Security-Policy"
content="frame-src http://localhost:3001 'self'; script-src 'self' 'unsafe-inline'"
/>Note: In v2.0, you don't need to configure allow="publickey-credentials-get" anymore - authentication happens in popup windows, not in the iframe!
3. Use the SDK
import { InjPassConnector } from '@injpass/connector';
// Create connector instance - embedUrl is REQUIRED
const connector = new InjPassConnector({
embedUrl: import.meta.env.VITE_INJPASS_EMBED_URL // Vite
// or
// embedUrl: process.env.REACT_APP_INJPASS_EMBED_URL // Create React App
// embedUrl: process.env.NEXT_PUBLIC_INJPASS_EMBED_URL // Next.js
});
// Connect to wallet
const wallet = await connector.connect();
console.log('Connected:', wallet.address);
// Sign message
const signature = await wallet.signer.signMessage('Hello World');
console.log('Signature:', signature);
// Disconnect
connector.disconnect();Important:
embedUrlis now REQUIRED - you must provide it in the config- Use environment variables to manage URLs across environments
- The SDK automatically adds Passkey permissions to the iframe
Configuration Options
All options:
const connector = new InjPassConnector({
// REQUIRED: Embed URL
embedUrl: import.meta.env.VITE_INJPASS_EMBED_URL,
// Position (for floating mode)
position: {
bottom: '20px',
right: '20px'
},
// Size
size: {
width: '400px',
height: '300px'
},
// Display mode: 'floating' | 'modal' | 'inline'
mode: 'floating',
// Container ID (for inline mode)
containerId: 'wallet-container',
// Auto-hide after connection
autoHide: true
});Environment Variables by Framework
Vite:
VITE_INJPASS_EMBED_URL=http://localhost:3001/embedembedUrl: import.meta.env.VITE_INJPASS_EMBED_URLCreate React App:
REACT_APP_INJPASS_EMBED_URL=http://localhost:3001/embedembedUrl: process.env.REACT_APP_INJPASS_EMBED_URLNext.js:
NEXT_PUBLIC_INJPASS_EMBED_URL=http://localhost:3001/embedembedUrl: process.env.NEXT_PUBLIC_INJPASS_EMBED_URLDisplay Modes
Floating Mode (Default)
Fixed position overlay that appears on top of your dApp:
const connector = new InjPassConnector({
mode: 'floating',
position: { bottom: '20px', right: '20px' }
});Modal Mode
Full-screen modal with backdrop:
const connector = new InjPassConnector({
mode: 'modal',
size: { width: '500px', height: '400px' }
});Inline Mode
Embedded within a container element:
<div id="wallet-container" style="width: 100%; height: 500px;"></div>const connector = new InjPassConnector({
mode: 'inline',
containerId: 'wallet-container'
});API Reference
InjPassConnector
connect(): Promise<ConnectedWallet>
Displays the wallet iframe and waits for user to authenticate with Passkey.
Returns:
{
address: string; // Injective wallet address (inj1...)
walletName?: string; // User-defined wallet name
signer: InjPassSigner; // Signer instance for signing messages
}Throws:
Error('Already connected')- If already connectedError('Connection timeout')- If user doesn't authenticate within 60sError('Connection failed')- If authentication fails
disconnect(): void
Disconnects wallet and removes iframe from DOM.
show(): void
Shows the iframe (if hidden).
hide(): void
Hides the iframe.
InjPassSigner
signMessage(message: string): Promise<Uint8Array>
Signs a message using the wallet's private key (secp256k1).
Parameters:
message: String message to sign
Returns:
Promise<Uint8Array>: 64-byte signature (r + s)
Throws:
Error('Signing timeout')- If signing takes > 30sError(...)- If user rejects or signing fails
Security Considerations
1. Content Security Policy (CSP)
Recommended: Add frame-src directive to your CSP:
<!-- Development -->
<meta http-equiv="Content-Security-Policy"
content="frame-src http://localhost:3001 'self'" />
<!-- Production -->
<meta http-equiv="Content-Security-Policy"
content="frame-src https://your-injpass-domain.com 'self'" />2. Popup Windows (New in v2.0)
The SDK uses popup windows for authentication:
- ✅ Popups run in top-level browsing context (full storage access)
- ✅ WebAuthn works without restrictions
- ✅ Origin validation prevents unauthorized access
- ⚠️ Users must allow popups for your site
3. Origin Validation
The SDK validates message origins automatically:
- Development: Allows
localhostand same-origin - Production: Can be configured with whitelist (contact support)
4. Private Key Isolation
- Private keys are encrypted and stored in the INJ Pass domain only
- Keys never leave the secure context
- Only signatures are transmitted via postMessage
- Popup window closes immediately after authentication
5. HTTPS Only
The wallet works on:
- ✅
https://origins - ✅
http://localhost(development only) - ❌ Other
http://origins are blocked
Troubleshooting
❌ "Popup blocked"
Error: Popup window doesn't open when clicking connect.
Solution:
- Click "Always allow popups from this site" in browser
- Ensure
connect()is called directly from user interaction (not in async callback) - Check browser settings to allow popups
❌ "Connection timeout"
Causes:
- CSP blocks iframe loading
- User doesn't have platform authenticator
- Network issues
- User closed popup without authenticating
Solutions:
- Check CSP allows the iframe origin
- Verify user device has Touch ID/Face ID/Windows Hello
- Check browser console for iframe load errors
- Ensure
embedUrlis correct
❌ "No wallet found"
Solution:
- Create wallet at your INJ Pass instance's
/welcomepage - For local development:
http://localhost:3001/welcome - The embed page will guide users to create wallet if none exists
❌ "Origin mismatch" in console
Normal in development: You may see origin warnings in development mode - these are informational only and won't block functionality.
In production: If you need strict origin whitelisting, contact support.
Browser Support
| Browser | Version | Status | Notes | |---------|---------|--------|-------| | Chrome | 67+ | ✅ Full support | Desktop & Android | | Safari | 16+ | ✅ Full support | macOS & iOS - No Storage Access prompt needed! | | Edge | 79+ | ✅ Full support | Chromium-based | | Firefox | 60+ | ✅ Full support | Desktop & Android |
Requirements:
- ✅ WebAuthn platform authenticator (Touch ID/Face ID/Windows Hello)
- ✅ Browser must support
window.open()for popups - ✅ JavaScript enabled
Version History
v2.0.0 (2026-02-24) - Major Architecture Update
- 🎉 Complete rewrite: Popup-based authentication architecture
- ✅ No more Storage Access API prompts in Safari
- ✅ Bypasses iframe WebAuthn restrictions
- ✅ Future-proof for Chrome's third-party cookie deprecation
- ✅ Relaxed origin validation (allows localhost in dev mode)
- 📝 Comprehensive documentation update
- 🔧 Improved error handling and timeout management
v0.1.4 (2026-02-23)
- 🐛 Bug fixes and stability improvements
- 📝 Documentation updates
v0.1.1 (2026-02-23)
- ✅ Added Permissions Policy support for WebAuthn
- ✅ Automatically adds
allowattribute to iframe - 📝 Updated documentation with troubleshooting guide
v0.1.0 (2026-02-23)
- 🎉 Initial release
- ✅ Passkey-based authentication
- ✅ Floating, modal, and inline modes
- ✅ Cross-dApp wallet sharing
Migration from v0.x to v2.0
Good news: The API is 100% backward compatible! Just update:
npm update @injpass/connectorWhat changed internally:
- Authentication now happens in popup windows instead of directly in iframe
- No code changes needed in your dApp
- Better browser compatibility automatically
Optional improvements:
- Remove any Storage Access API handling code
- Update CSP to remove unnecessary permissions-policy directives
Best Practices
1. Environment-based Configuration
const connector = new InjPassConnector({
embedUrl: process.env.NODE_ENV === 'production'
? 'https://your-production-injpass.com/embed'
: 'http://localhost:3001/embed'
});2. Handle Popup Blockers
try {
const wallet = await connector.connect();
setWallet(wallet);
} catch (error) {
if (error.message.includes('Popup blocked')) {
alert('Please allow popups for this site to connect your wallet.');
} else if (error.message.includes('timeout')) {
alert('Connection timed out. Please try again.');
} else {
alert('Connection failed: ' + error.message);
}
}3. User Feedback
setLoading(true);
setStatus('Opening authentication window...');
try {
const wallet = await connector.connect();
setStatus('Connected successfully!');
setConnected(true);
} catch (error) {
setStatus('Connection failed');
} finally {
setLoading(false);
}4. Test Before Production
- Test on multiple browsers (Chrome, Safari, Firefox, Edge)
- Test on mobile devices (iOS Safari, Android Chrome)
- Test with different popup blocker settings
- Ensure popups work on first user interaction
Framework Examples
React
import { InjPassConnector } from '@injpass/connector';
import { useState } from 'react';
function WalletButton() {
const [connector] = useState(() => new InjPassConnector({
embedUrl: import.meta.env.VITE_INJPASS_EMBED_URL
}));
const [wallet, setWallet] = useState(null);
const [loading, setLoading] = useState(false);
const connect = async () => {
setLoading(true);
try {
const w = await connector.connect();
setWallet(w);
} catch (error) {
if (error.message.includes('Popup blocked')) {
alert('Please allow popups for this site');
}
console.error(error);
} finally {
setLoading(false);
}
};
const sign = async () => {
if (!wallet) return;
try {
const sig = await wallet.signer.signMessage('Hello INJ Pass!');
console.log('Signature:', sig);
} catch (error) {
console.error('Signing failed:', error);
}
};
return (
<div>
{!wallet ? (
<button onClick={connect} disabled={loading}>
{loading ? 'Connecting...' : 'Connect Wallet'}
</button>
) : (
<>
<p>Connected: {wallet.address}</p>
<button onClick={sign}>Sign Message</button>
<button onClick={() => connector.disconnect()}>Disconnect</button>
</>
)}
</div>
);
}Vue 3
<script setup>
import { InjPassConnector } from '@injpass/connector';
import { ref } from 'vue';
const connector = new InjPassConnector({
embedUrl: import.meta.env.VITE_INJPASS_EMBED_URL
});
const wallet = ref(null);
const loading = ref(false);
async function connect() {
loading.value = true;
try {
wallet.value = await connector.connect();
} catch (error) {
console.error(error);
alert('Connection failed: ' + error.message);
} finally {
loading.value = false;
}
}
async function signMessage() {
if (!wallet.value) return;
try {
const signature = await wallet.value.signer.signMessage('Hello!');
console.log('Signature:', signature);
} catch (error) {
console.error(error);
}
}
</script>
<template>
<div>
<button v-if="!wallet" @click="connect" :disabled="loading">
{{ loading ? 'Connecting...' : 'Connect Wallet' }}
</button>
<div v-else>
<p>Connected: {{ wallet.address }}</p>
<button @click="signMessage">Sign Message</button>
</div>
</div>
</template>Next.js (App Router)
'use client';
import { InjPassConnector } from '@injpass/connector';
import { useState } from 'react';
export default function WalletConnect() {
const [connector] = useState(() => new InjPassConnector({
embedUrl: process.env.NEXT_PUBLIC_INJPASS_EMBED_URL!
}));
const [wallet, setWallet] = useState(null);
const connect = async () => {
try {
const w = await connector.connect();
setWallet(w);
} catch (error) {
console.error(error);
}
};
return (
<button onClick={connect}>
{wallet ? `Connected: ${wallet.address}` : 'Connect'}
</button>
);
}Vanilla JavaScript
<!DOCTYPE html>
<html>
<head>
<title>INJ Pass Demo</title>
</head>
<body>
<button id="connect-btn">Connect Wallet</button>
<div id="status"></div>
<script type="module">
import { InjPassConnector } from '@injpass/connector';
const connector = new InjPassConnector({
embedUrl: 'http://localhost:3001/embed'
});
document.getElementById('connect-btn').addEventListener('click', async () => {
try {
const wallet = await connector.connect();
document.getElementById('status').textContent =
`Connected: ${wallet.address}`;
} catch (error) {
alert('Failed: ' + error.message);
}
});
</script>
</body>
</html>License
MIT
Support
- 📖 Documentation: https://docs.injpass.com
- 🐛 GitHub Issues: https://github.com/injective/inj-pass/issues
- 💬 Discord: https://discord.gg/injective
- 📦 NPM Package: https://www.npmjs.com/package/@injpass/connector
Made with ❤️ by the INJ Pass Team
