@1hub/onehub-sdk
v1.2.0
Published
Frontend SDK for 1hub mini-apps — Flutter bridge handlers (QR, NFC, SSO, utility), an HTTP client for the 1hub notification API, and QR login via RFC 8628 Device Authorization Grant.
Maintainers
Readme
@1hub/onehub-sdk
Frontend SDK for 1hub mini-apps. Three parts:
- Flutter bridge handlers —
window.flutter_inappwebview.callHandlerwrappers for QR scan, NFC scan, SSO, and native utilities (toast, share, exit, contact support). - Notification HTTP client — server-to-server client for the 1hub notification API, authenticated with a provider API key.
- QR login — RFC 8628 Device Authorization Grant for partner web apps to authenticate users via the 1hub mobile app.
Install
npm install @1hub/onehub-sdk
# react hooks (optional, only if you use the /react entry)
npm install reactNotification client
Used by a mini-app's backend (or any JS runtime) to push notifications to 1hub accounts. Authenticates with an x-api-key: pk_<keyId>.<secret> header. The backend derives provider from the authenticated key, so you do not — and must not — send it in the body.
import { createNotificationClient } from '@1hub/onehub-sdk';
const notifications = createNotificationClient({
apiKey: process.env.ONEHUB_API_KEY!, // 'pk_<keyId>.<secret>'
});
baseUrldefaults tohttps://api.1hub.global. Override it only if you point at a non-production environment.
// Single account
await notifications.sendToAccount({
accountId: '4e3a97f0-905a-4e26-931f-84193f138480',
title: 'Hello',
body: 'New activity in your mini-app',
tag: 'activity',
data: { deepLink: 'onehub://miniapp/ftu/home' },
});
// Multiple accounts
await notifications.sendToMultipleAccounts({
accountIds: ['acc-1', 'acc-2'],
title: 'System Announcement',
body: 'Scheduled maintenance tonight.',
});Errors
Non-2xx responses throw NotificationRequestError:
import { NotificationRequestError } from '@1hub/onehub-sdk';
try {
await notifications.sendToAccount({ /* ... */ });
} catch (err) {
if (err instanceof NotificationRequestError) {
console.error(err.status, err.code, err.message);
}
}| status | code | Cause |
|---------:|---------------------|--------------------------------------------------------|
| 401 | MISSING_API_KEY | x-api-key header missing |
| 401 | INVALID_API_KEY | Key malformed, unknown, inactive, or secret mismatch |
| 403 | INSUFFICIENT_SCOPE| Key valid but lacks notifications:send scope |
| 400 | (validation) | Missing/invalid fields in the request body |
Flutter bridge handlers
These require the mini-app to be running inside the 1hub Flutter webview (window.flutter_inappwebview must be present).
import {
openQrScan, closeQrScan, onQrEvent,
openNfcScan, closeNfcScan, onNfcEvent,
requestSSOCode,
showToast, shareFile, contactSupport, exitMiniApp,
} from '@1hub/onehub-sdk';
const sso = await requestSSOCode({
clientId: 'your-client-id',
redirectUri: 'https://your-app.example/callback',
scope: 'openid profile',
});
await showToast({ message: 'Đã lưu!' });React hooks
import { useSSO, useQrScan, useNfcScan } from '@1hub/onehub-sdk/react';Login with 1hub via QR code
startQrLogin implements the RFC 8628 Device Authorization Grant. A partner web app displays a QR code; the user scans it with the 1hub mobile app and approves; the partner receives OAuth tokens.
Security note: clientSecret must never be embedded in a browser SPA. Proxy the startQrLogin call through your backend server to keep the secret confidential.
To get a clientId and clientSecret, contact 1hub to register your OAuth client.
import { startQrLogin, QrLoginError } from '@1hub/onehub-sdk';
import QRCode from 'qrcode'; // any QR library
const session = await startQrLogin({
clientId: 'your-client-id',
clientSecret: process.env.ONEHUB_CLIENT_SECRET!, // keep server-side!
scopes: ['openid', 'profile'],
});
// Render the QR code for the user to scan with the 1hub mobile app
await QRCode.toCanvas(document.getElementById('qr-canvas'), session.qrData);
// Optionally display the user code as a fallback
console.log('Or enter code:', session.userCode); // e.g. "XB4M-9PQ2"
// Await the result
try {
const tokens = await session.result;
console.log('Access token:', tokens.access_token);
console.log('ID token:', tokens.id_token);
} catch (err) {
if (err instanceof QrLoginError) {
switch (err.code) {
case 'expired':
console.error('QR code expired — ask the user to refresh');
break;
case 'denied':
console.error('User denied the login request');
break;
case 'cancelled':
console.log('Login cancelled by the app');
break;
default:
console.error('QR login failed:', err.code, err.message);
}
}
}
// Cancel polling if the user navigates away
window.addEventListener('beforeunload', () => session.cancel());QrLoginSession
| Property | Type | Description |
|---------------------------|-------------------------|--------------------------------------------------------------|
| qrData | string | verification_uri_complete — encode as QR code |
| userCode | string | Human-readable code (e.g. XB4M-9PQ2) for manual entry |
| verificationUri | string | Base verification URL (without user_code query param) |
| verificationUriComplete | string | Full verification URL including user_code query param |
| sessionId | string | Opaque session identifier (device_code) for diagnostics |
| expiresIn | number | Seconds until the session expires (typically 300) |
| result | Promise<QrLoginResult>| Resolves with tokens on approval, rejects with QrLoginError|
| cancel | () => void | Stops polling; result rejects with code: 'cancelled' |
QrLoginErrorCode
| Code | Cause |
|------------------|----------------------------------------------------------|
| expired | QR code TTL elapsed (300 s) — refresh the QR |
| denied | User denied consent on the mobile app |
| cancelled | session.cancel() was called |
| network | Network failure after 3 consecutive retries |
| invalid_client | Bad clientId or clientSecret |
| invalid_grant | device_code not found or already consumed |
| unknown | Unexpected error |
License
MIT
