UNPKG

next-auth-pubkey

Version:

A light-weight Lightning and Nostr auth provider for your Next.js app that's entirely self-hosted and plugs seamlessly into the next-auth framework.

112 lines (111 loc) 4.72 kB
"use client"; import { useEffect } from "react"; import { useRouter } from "next/navigation"; import { hardConfig } from "../main/config/hard.js"; import { formatLightningAuth } from "../utils/lnurl.js"; import { maxNetworkRequestsFailures } from "./constants.js"; /** * A React hook that, on mount, will poll the API and checks if the Lightning auth QR has been scanned. * If enough time elapses without a sign in attempt, the page will be refreshed. * Once a success status is received from polling, the user will be redirected to the `next-auth` redirect url. * * This hook is designed for use in both the pages router and the app router. * * @param {object} session - a session object generated by invoking the `createLightningAuth` method * * @returns {Object} * @returns {String} lnurl - the raw LNURL, should be made available for copy-pasting * @returns {String} qr - a url pointing the lnurl-auth QR Code image, should be used in the src prop of img tags * @returns {String} button - a deep-link that will open in Lightning enabled wallets, should be used in the href prop of anchor tags */ export function useLightningPolling(session) { const router = useRouter(); useEffect(() => { let pollTimeoutId; let createIntervalId; let networkRequestCount = 0; let errorUrl; const pollController = new AbortController(); // cleanup when the hook unmounts of polling is successful const cleanup = () => { clearTimeout(pollTimeoutId); clearInterval(createIntervalId); pollController.abort(); }; // redirect user to error page if something goes wrong function error(e) { console.error(e); if (errorUrl) { window.location.replace(errorUrl); } else { // if no errorUrl exists send to defaul `next-auth` error page const params = new URLSearchParams(); params.append("error", "OAuthSignin"); window.location.replace(`/api/auth/error?${params.toString()}`); } } // poll the api to see if the user has successfully authenticated function poll() { if (!session?.data?.k1) return; const k1 = session.data.k1; const params = new URLSearchParams({ k1 }); return fetch(hardConfig.apis.poll, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: params, cache: "default", signal: pollController.signal, }) .then((r) => { if (r.status === 410) { // if resource not found throw error immediately, // this means the user's auth session has been deleted. networkRequestCount = maxNetworkRequestsFailures; } return r.json(); }) .catch((e) => { // if there are more than X network errors, then trigger redirect networkRequestCount++; if (networkRequestCount >= maxNetworkRequestsFailures) { pollTimeoutId = setTimeout(poll, session.intervals.poll); throw e; } }) .then((d) => { if (d && d.error) { if (d.url) errorUrl = d.url; throw new Error(d.message || d.error); } if (d) networkRequestCount = 0; if (d && d.message) throw new Error(d.message); pollTimeoutId = setTimeout(poll, session.intervals.poll); if (d && d.success) { cleanup(); let url = new URL(session.query.redirectUri); url.searchParams.append("state", session.query.state); url.searchParams.append("code", k1); router.replace(url.toString()); } }) .catch((e) => { if (!pollController.signal.aborted) { error(e); } }); } function create() { router.refresh(); } pollTimeoutId = setTimeout(poll, session.intervals.poll); createIntervalId = setInterval(create, session.intervals.create); return () => cleanup(); }, []); const { lnurl, qr, button } = formatLightningAuth(session.data.lnurl); return { lnurl, qr, button }; }