@theanomaly/nova-link-sdk
v0.2.72
Published
Authentication SDK for Nova Link
Readme
NovaLink SDK – System Overview
This repository packages the NovaLink authentication experience as both a reusable Web Component (Lit-based) and an ergonomic JavaScript SDK that can be consumed from ES modules, CommonJS, or as a global UMD script. It ships the widget UI, provider-specific auth flows, wallet integrations, and a thin API client so host applications can delegate login, profile access, and wallet insight to NovaLink’s backend.
Installation & Quick Start
- Install the SDK:
npm install @theanomaly/nova-link-sdk - Initialize once on the client:
import NovaLink from '@theanomaly/nova-link-sdk'; NovaLink.init({ apiKey: 'your-api-key', telegramBotId: 'your-public-bot-id', // optional, needed for TG Mini App enabledProviders: ['google','telegram','email','ethereum'], // defaults to all container: document.getElementById('modal-root'), // optional }); - Interact with the modal via promises or callbacks:
// Promise-based login NovaLink.loginAsync() .then(user => console.log('User logged in', user)) .catch(error => console.error('Login failed', error.code, error.message)); // Callback variant NovaLink.login((profile, error) => { if (error) return console.error('Login failed', error); console.log('Authenticated profile', profile); }); const profile = await NovaLink.getCurrentUser(); // resolves null when logged out await NovaLink.logoutAsync(); // clears token + widget state NovaLink.closeAuth(); // manually hide modal if needed - The widget renders into
document.bodyby default. Passcontainerto target another DOM node.
Packaging & Build
package.json(@theanomaly/nova-link-sdk) exposesdist/nova-link-sdk.{cjs|esm|umd}.jsplustypes/. Consumers canimport NovaLink from '@theanomaly/nova-link-sdk'or drop the UMD bundle directly into a<script>.rollup.config.jsbuilds all formats with a single entry (src/index.js), injects the SDK version and critical env vars (BACKEND_BASE_URL,TELEGRAM_BOT_NAME,WALLET_CONNECT_ID) via@rollup/plugin-replace, and copies any static assets.npm run build/npm run devare the main workflows.dist/holds the compiled bundles, whiletypes/nova-link-sdk.d.tssurfaces TypeScript definitions for the SDK, widget, token manager, and API service.- Ops scripts:
bumpAndPublish.tsbumps the patch version, runs the build, andnpm publishes via Bun;demoBuild.shcopies the UMD build into a sibling example app;telegram-stats-immediate.jsis an optional drop-in script that reports Telegram WebApp usage metrics straight tohttps://api.novalink.io/telegram/stat.
Repository Map
src/index.js– SDK façade (exposes theNovaLinksingleton).nova-link-widget.js– the LitElement Web Component implementing the modal UI and all runtime logic.components/– stateless view templates (initial-view,email-input-view,code-entry-view,profile-view, etc.), basic atoms (login-button,input-field, icons), andwallet-info-elementwhich is a standalone Lit custom element.auth/– service layer:api-service.js,token-manager.js, andwallet-connector.js(WalletConnect/AppKit + Wagmi adapter).utils/– shared styles, helpers (validateEmail,formatTimeRemaining), and font loaders to inject the Sofia Pro family into Shadow DOM scopes.examples/basic-usage.html– vanilla HTML playground showing init/login/logout flows, wallet info, and Telegram mini-app verification via the UMD build.PAYMENT_SDK_DOCS.md– aspirational documentation for a payment confirmation API (NovaLink.confirmPayment), which is not implemented in the current codebase.
Using NovaLink with Next.js / SSR
- Web Components require browser APIs, so defer SDK usage until after hydration.
- Dynamic import inside
useEffect:const [novaLink, setNovaLink] = useState(null); useEffect(() => { import('@theanomaly/nova-link-sdk').then(({ default: NovaLink }) => { NovaLink.init({ apiKey: 'your-api-key' }); setNovaLink(NovaLink); }); }, []); - Client-only wrapper via
next/dynamic:const NovaLinkWrapper = dynamic(() => Promise.resolve(({ apiKey }) => { useEffect(() => { import('@theanomaly/nova-link-sdk').then(({ default: NovaLink }) => NovaLink.init({ apiKey }) ); }, [apiKey]); return <button onClick={() => NovaLink.loginAsync()}>Login</button>; }), { ssr: false }); next/script+ UMD bundle: includehttps://unpkg.com/@theanomaly/nova-link-sdk/dist/nova-link-sdk.umd.jswithstrategy="afterInteractive"and runwindow.NovaLink.init(...)insideonLoad.- Always guard with
if (typeof window === 'undefined')before referencingNovaLink, and only callinitonce per page load.
SDK Entry Point (src/index.js)
- Guards against SSR: immediately bails if
window/documentare undefined and logs a warning. NovaLinkSDKstores init config (API key, Telegram bot details, enabled providers, DOM container) and ensures thenova-link-widgetcustom element is registered by importing../nova-link-widget.js.init(config)validates options, normalizesenabledProviders, resolves env-derived defaults for backend URLs/Telegram bot name, and either creates or updates the widget element (a fixed-position modal appended toconfig.containerordocument.body). Returnsfalseon config errors so hosts can fail fast.loginAsync()/login(callback)show the widget, track the pending promise/callback, and wait forauth-token-changedevents emitted by the widget.closeAuth()hides the modal and cancels an in-flight login (rejecting the stored promise/callback withAUTHENTICATION_CANCELLED).logoutAsync()/logout(callback)delegate to the widget’slogout(), then resolve immediately (the widget clears local storage and emits state change events).getCurrentUser()lazily creates the widget if needed, asks it to refresh the profile (_refreshProfile), and resolves with the cached_userProfile.verifyTGMiniAppUser(miniAppAuthToken)andshowWalletInfo()are thin wrappers that ensure init has happened, then call the widget’s methods (which in turn hit the API and update the view).
Widget Architecture (nova-link-widget.js)
- Built with Lit’s reactive system:
static propertiesdeclares all reflected attributes (api-key,backend-base-url,telegram-bot-id,enabled-providers) plus internal state (_view,_isLoading,_authToken,_userProfile, OTP timers, provider toggles, etc.).willUpdateparses the JSONenabledProvidersattribute into_enabledProvidersArray. - Lifecycle:
connectedCallback: registers amessagelistener for popup responses, wireswindow.onTelegramAuth, instantiatesTokenManager/ApiService, checks for Telegram Mini App context viaisTMA(), loads any stored token, and bootstraps WalletConnect (ifWALLET_CONNECT_IDwas injected).disconnectedCallback: removes listeners, clears popups/timers, and unregisters Telegram handlers to avoid leaks.
- Rendering: A
switch(this._view)powers the modal. Views includeinitial,email_input,code_entry,success,error,profile, andwallet_info. Each view function receives handler callbacks and overlay elements for loading/popup states or wallet signing instructions. - Close semantics: the
ContainerViewcomponent includes an×button that firesnovalink-close-requested. The parent SDK listens for this event to hide/destroy the modal. - Events: every successful auth calls
_dispatchAuthTokenEvent, which emitsauth-token-changed(detail contains token + expiry). Host apps can listen for it directly if they embed<nova-link-widget>manually. - Mini App mode:
isTMA()toggles auto-login;_handleTelegramMiniAppLogin()grabsretrieveRawInitData(), posts it to/api/v1/telegram-miniapp/verify, and ensures the spinner stays up at least 500 ms so Telegram users see progress.
Service Layer (auth/)
token-manager.jsnamespaceslocalStorageentries by API key, decodes JWT payloads to determine expiry, and exposesgetTokenExpiration()for UI display. Stale/invalid entries get wiped automatically.api-service.jswrapsfetchcalls to the NovaLink backend:- Profile:
GET /api/v1/profileandGET /api/v1/profile/balances. - Google OAuth kickoff:
GET /api/v1/auth/google/start. - Telegram WebApp flow:
POST /api/v1/auth/telegram/{start|callback}. - Email OTP:
POST /api/v1/auth/email/request-code&/verify-code. - Token exchange:
POST /api/v1/auth/exchange-token. - Ethereum auth:
GET /api/v1/auth/ethereum/nonce/:addressandPOST /api/v1/auth/ethereum/verify-signature. - Telegram Mini App verification endpoints.
- Profile:
wallet-connector.jsintegrates WalletConnect v2 via Reown AppKit + Wagmi. It sets up the modal, watches account changes, requests a signing payload (nonce/message) from the backend, signs with the connected wallet, and hands the response back to the widget via callbacks. Ethereum auth respectsenabledProvidersso the option disappears if the host disablesethereum.
UI Components & Styling
- Presentational helpers live under
components/. Notable pieces:initial-view.jsrenders provider buttons (Google, Telegram, Email, Ethereum) via the sharedLoginButtonatom and inline SVG icons. WhenisMiniAppis true, it shows an auto-login message instead of buttons.email-input-view.jsandcode-entry-view.jshandle the OTP UX with theInputFieldWeb Component, error display, resend controls, and countdown timers (formatTimeRemaining).profile-view.jsandwallet-info-element.jsvisualise profile metadata, linked auth providers, connected wallets, and live balances; addresses have copy buttons via the Clipboard API.overlay-components.jsdescribes three overlay layers (popup blockers, wallet signing spinner, and a placeholder for generic loading).container-view.jsstandardises the modal chrome (blurred glass background, the NovaLink logo, and the close button) and emits the close event.
utils/styles.jssupplies base CSS (fonts, button styles, error states) that each view inherits.utils/font-loader.jsinjects the Sofia Pro font stack intodocument.adoptedStyleSheets, with fallback variants infont-loader-link.js/font-loader-fallback.js.
Authentication & Wallet Flows
- Existing sessions:
_loadStoredToken()reads fromTokenManager, refreshes the profile, optionally shows the profile view (ifauto-show-profileis set) or auto-approves mini-app sessions. - Google:
ApiService.startGoogleAuth()returns anauthUrl._openAuthPopup()opens a centered window and tracks it via_popupCheckInterval. The popup postsGOOGLE_AUTH_RESULT/GOOGLE_AUTH_ERRORviawindow.postMessage;_handleAuthMessageexchanges the receivedexchangeTokenfor a JWT through/auth/exchange-token, then funnels success into_processAuthResult. - Telegram Web:
_handleTelegramLoginClick()hits/auth/telegram/start, the popup sends either a serializedtgAuthResult:string or a JSON event, and_handleTelegramAuth()relays it to/auth/telegram/callback. - Telegram Mini App: handled entirely client-side (no popups) using
retrieveRawInitData()+/telegram-miniapp/verify. Hosts can also callNovaLink.verifyTGMiniAppUser(token)if they already obtained a short-lived token from the Mini App JS SDK. - Email OTP:
_handleEmailSubmit()validates viavalidateEmail(), requests a code, starts both expiration (10 min) and resend (60 s) timers, and moves tocode_entry._handleCodeSubmit()verifies the 6-digit code, handles rate-limit errors, stores the token, and transitions tosuccess. - Ethereum wallets:
_handleEthWalletClick()invokes the WalletConnect modal (if aWALLET_CONNECT_IDwas configured during the build).watchAccount()auto-triggers signing when the account connects;_handleWalletSigningStateChange()gives the UI a blocking overlay until the signature and verification succeed. - Wallet balances:
showWalletInfo()requires an authenticated user, fetches/profile/balances, and passes the response towallet-info-element, which supports copying addresses and showing per-chain token holdings. - Every success path shares
_processAuthResult(provider, authToken, userId): store the token, emitauth-token-changed, refresh the profile, show a success message, clear timers/popup state, and auto-close after 1 s (the SDK’sloginAsyncpromise resolves with the refreshed profile).
Host Integration Details
NovaLink.initaccepts{ apiKey, telegramBotId, container, enabledProviders }.enabledProviderscan be any subset of['google','telegram','email','ethereum']; invalid entries are filtered out with a console warning. When omitted or empty, every provider is enabled.- The widget reflects the config through attributes (e.g.,
<nova-link-widget enabled-providers='["email"]'>) so you can embed the element manually if desired. - The modal is appended once and re-used; repeated
loginAsync()calls simply fade it back in.transitionDuration(300 ms) gates the fade-out beforedisplay: none. - SSR/Next.js guidance is outlined above (“Using NovaLink with Next.js / SSR”). Web Components rely on browser APIs, so always gate SDK access behind client-only checks; attempting to import the SDK server-side will trip the guard at the top of
src/index.js. - Events: besides
auth-token-changed, the widget emitsnovalink-close-requestedwhenever the user hits the close button or when_handleCloseWidget()auto-fires after success. Host apps can listen for either to sync their own UI (andNovaLink.closeAuth()reuses_hideWidgetModal()to animate the fade-out).
Supporting Docs & Examples
examples/basic-usage.htmldemonstrates every public SDK method (init, login promise/callback, getCurrentUser, logout, showWalletInfo, verifyTGMiniAppUser) plus UI helpers to show the returned profile. It relies on the built UMD bundle living underdist/.PAYMENT_SDK_DOCS.mdoutlines an upcoming payment confirmation surface. Treat it as a roadmap; there is noNovaLink.confirmPaymentimplementation yet, so referencing it will throw unless future code lands.lit.mdis a quick reference of Lit idioms (decorators, property declarations) for contributors touching the widget/components.
Extensibility & Gotchas
- All network requests depend on the env-injected
BACKEND_BASE_URL. When authoring new features, update.env, then rebuild sorollup-plugin-replacebakes the values into the bundle. - Fonts are loaded from
https://assets.connectnova.link. If those assets are unreachable (e.g., CSP), switch to theloadSofiaProFontViaLinkor fallback loader and ensure hosts allow the origin. - Popup blockers:
_openAuthPopupthrows if the window was blocked, and_handleAuthErrorroutes users to an error view with a retry button. Host apps should surface the rejection fromloginAsync()so users know to allow popups. - Token lifetime:
TokenManager.storeTokennaively decodes JWTs; if backend payloads change, update the decode logic to keep expiration accurate. Always clearlocalStorageif verification fails to avoid partial sessions. - Ensure
WALLET_CONNECT_IDis set before enabling the Ethereum provider; otherwise_handleEthWalletClickpushes the UI into the error view with “Wallet connection not available.” - Because the SDK manipulates the DOM directly, only call
NovaLink.initonce per page load. Subsequent calls return early but will reconfigureenabledProviders/Telegram IDs on the existing widget instance.
With these pieces in mind, you can confidently navigate the codebase, extend authentication providers, tweak the UI, or integrate NovaLink into host applications regardless of framework. The SDK entry point coordinates lifecycle and host communication, the widget orchestrates UX and API calls, and the supporting modules (auth services, UI components, utilities, scripts) keep the system modular and easy to evolve.
Development
npm install # install dependencies
npm run build # rollup build (CJS/ESM/UMD)
npm run dev # rollup -w for iterative development
npx http-server . --port 8181 # serve repo root to test examples/basic-usage.html- Create a
.envfrom.env.exampleso rollup can inlineBACKEND_BASE_URL,TELEGRAM_BOT_NAME, andWALLET_CONNECT_ID. - Rebuild after changing env vars—they are baked in at compile time.
examples/basic-usage.htmlpulls fromdist/, so rerunnpm run buildwhenever source files change before refreshing the example.
