@panomapp/arc
v0.1.3
Published
Camera-readable ARC authentication toolkit for shared grid rendering, Vue scanning flows, Express server routes, and realistic end-to-end simulations.
Maintainers
Readme
@panomapp/arc
Camera-readable ARC authentication toolkit.

@panomapp/arc packages the full ARC stack in one place:
- shared grid and detection primitives
- Vue client helpers for rendering and camera scanning
- Express-friendly server routes
- realistic testing and simulation utilities
It is designed for device-linking flows like WhatsApp Web or Steam Guard, where one device shows a short-lived visual code and another authenticated device scans it.
Installation
Install only the entrypoints you need.
npm install @panomapp/arcOptional peer dependencies:
vuefor@panomapp/arc/clientexpressfor@panomapp/arc/server
Package entrypoints
@panomapp/arc
Shared types and low-level helpers:
applyArcAnchorspayloadBitshammingDistancerenderArcToPixelsdetectArcPatternFromRgbamakeArcFixturesmakeArcScenarios
@panomapp/arc/client
Vue-facing client helpers:
createArcHttpClient()useArcAuthScanner()useArcSlotPlayback()createArcScannerEngine()
@panomapp/arc/server
Server helpers:
createArcRouter()createArcRouteHarness()createArcChallenge()getArcChallengeStatus()scanArcPattern()bindArcChallengeUser()
@panomapp/arc/testing
Simulation and self-test helpers:
runArcCameraRoundTrip()runArcDifficultySweep()runArcRealisticAuthLoop()runArcNetworkProfileSweep()runArcStartupSelfTests()
Quick start
1. Server
import express, { Router } from 'express';
import {
createArcRouter,
} from '@panomapp/arc/server';
const app = express();
app.use('/auth/arc', createArcRouter({
routerFactory: () => Router(),
authContext: (req) => ({
ipHash: req.ip,
ipEnc: null,
userAgent: req.get('user-agent') ?? null,
userId: req.user?.id ?? null,
}),
issueSession: async ({ user, res, accessToken, refreshToken }) => {
res.json({ user, accessToken, refreshToken });
},
publicSessionUser: (user) => user,
getUserById: async (userId) => findUser(userId),
logArcLogin: async () => {},
authMiddleware,
optionalAuthMiddleware,
privacySanitizer,
requireFeature,
ipLimiter,
}));2. Client HTTP adapter
import axios from 'axios';
import { createArcHttpClient } from '@panomapp/arc/client';
const api = axios.create({ baseURL: '/api' });
const arcClient = createArcHttpClient(api);3. Code display page
import type { ArcSlot } from '@panomapp/arc';
import { useArcSlotPlayback } from '@panomapp/arc/client';Use useArcSlotPlayback() to render the active 4x4 ARC slot sequence on the device that needs to sign in.
4. Scanner page
import { ref } from 'vue';
import { useArcAuthScanner } from '@panomapp/arc/client';
const videoRef = ref<HTMLVideoElement | null>(null);
const canvasRef = ref<HTMLCanvasElement | null>(null);
const scanner = useArcAuthScanner({
videoRef,
canvasRef,
scan: (observedBits, candidateId) => arcClient.scan(observedBits, candidateId),
onVerified: async (result) => {
console.log('ARC verified', result.user?.id);
},
});Then:
await scanner.startCamera();
scanner.startScanning();Device-linking model
Typical ARC flow:
- Device A opens the ARC code page and requests
createChallenge(). - Device A displays the animated 4x4 ARC sequence.
- Device B is already authenticated and opens the scanner page.
- Device B scans the ARC sequence and sends
scan(observedBits, candidateId?). - The server locks onto the correct challenge, verifies three slots, and binds the challenge to the scanning user.
- Device A keeps polling
pollStatus(challengeId)until the challenge becomesverified. - Device A receives the issued session tokens and signs in.
Testing and simulation
Run the built-in self-test suite:
npm run selftestThis covers:
- baseline detector round-trips
- realistic camera degradation
- frontend-scanner plus backend-route auth loop
- network latency and jitter profiles
- PNG and JSON artifacts in
test-logs/
Notes
- The client entry is ESM-only at runtime.
- The server entry does not directly import Express; you pass your own router and middleware.
- The testing entry is intended for Node environments.
test-logs/is generated output and is not published.
