@arsite/sdk
v0.2.1
Published
Build applications for ArSite — an intelligence layer for Canada, built in Canada
Readme
@arsite/sdk
TypeScript SDK to build applications for ArSite — an intelligence layer for Canada, built in Canada.
Your app hosts its own backend + UI. ArSite renders the UI in a sandboxed iframe inside its platform shell and proxies every API call through its gateway — auth, entitlement, per-cycle metering, and HMAC signing all happen on the platform side. Your job is to render React in the iframe and to verify HMAC-signed requests on your backend.
Install
npm install @arsite/sdkreact is an optional peer dependency — only required if you use @arsite/sdk/iframe/react.
Quick start with the scaffolder
npx create-arsite-app \
--id=my-app --name="My App" \
--framework=next-iframe --dir=./my-app
cd my-app
npm install
npm run devThen in a separate terminal:
npx @arsite/cli dev # local gateway proxy on :7400 with fake auth + HMAC
npx @arsite/cli validate # schema + frame ping + healthcheck pingAPI surface
@arsite/sdk/types — manifest
import { defineHostedApp } from '@arsite/sdk/types';
export default defineHostedApp({
id: 'my-module',
name: 'My Module',
version: '0.1.0',
description: '...',
icon: 'database',
category: 'intelligence',
permissions: ['my-module:read', 'my-module:api'],
routes: [{ path: '/', label: 'Home', icon: 'home' }],
dependencies: [],
integration: 'hosted',
hosted: {
frame: { url: 'https://my-module.example.com/embed' },
upstream: { baseUrl: 'https://my-module-api.example.com', auth: { type: 'none' } },
signing: { secretRef: 'my-module.signing_secret' },
healthcheck: { path: '/health' },
routes: [
{ method: 'GET', pattern: '/v1/search', billingUnits: 1 },
{ method: 'GET', pattern: '/v1/items/:id', billingUnits: 0.5 },
],
},
});@arsite/sdk/iframe — iframe runtime
import { createArsiteClient } from '@arsite/sdk/iframe';
const arsite = await createArsiteClient();
arsite.user; // { id, email, name, role, locale }
arsite.tenant; // { id, slug, name, planTier }
arsite.app; // { id, version, subscriptionTier, quotaRemaining }
arsite.theme; // design-token map injected by parent
const res = await arsite.api('/v1/search?q=foo');
// → GET https://arsite.ca/api/apps/my-module/v1/search?q=foo
// with short-lived Bearer JWT; ArSite signs the upstream call with HMAC,
// meters the route's billingUnits, writes a usage_event row.
arsite.resize.auto(); // auto-resize iframe based on body height
arsite.toast.success('Saved'); // surface a toast in the parent ArSite shell
arsite.events.on('tenantChange', () => location.reload());@arsite/sdk/iframe/react — React adapters
import { ArsiteProvider, useArsiteUser, useArsiteApi } from '@arsite/sdk/iframe/react';
export function App() {
return (
<ArsiteProvider>
<Greeting />
</ArsiteProvider>
);
}
function Greeting() {
const user = useArsiteUser();
return <p>Hi {user?.name}</p>;
}Other hooks: useArsite(), useArsiteTenant(), useArsiteApp(), useArsiteTheme(), useArsiteApi().
@arsite/sdk/server — verify gateway-signed requests on your backend
import { verifyArsiteRequest } from '@arsite/sdk/server';
export async function GET(req: Request): Promise<Response> {
const body = await req.text();
const url = new URL(req.url);
const verified = await verifyArsiteRequest(
{
method: 'GET',
path: url.pathname + url.search,
body,
headers: Object.fromEntries(req.headers),
},
{ signingSecret: process.env.ARSITE_SIGNING_SECRET! },
);
// verified.user.id, verified.tenant.id, verified.appId are now trustworthy
return Response.json({ greeting: `hello ${verified.user.id}` });
}Reference impl for non-TypeScript backends
The canonical-string format is small and language-agnostic. See DataFun's service/datafun_api/arsite_auth.py (Python + FastAPI, ~50 lines) as the reference implementation.
Manifest variants
The manifest is a discriminated union on integration. Pick the variant that matches how your app surfaces in ArSite:
| Variant | Helper | UI | When |
| --------------------- | ------------------ | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| hosted (frame) | defineHostedApp | iframed UI (hosted.ui: 'frame', frame URL required) | You host a backend + a UI you want embedded in ArSite. |
| hosted (api-only) | defineApiOnlyApp | none (hosted.ui: 'api-only', no frame) | You only expose a metered REST API. Declare at least one non-frame tab (docs/api/usage). Reached via the gateway and GET/POST /api/v1/apps/{id}/{path}. |
| listed | defineListedApp | external link | Your product lives on your own site. Optional billing.mode: 'rest-proxy' still meters an API through ArSite. |
defineHostedApp stays backward compatible — hosted.ui defaults to 'frame', route kind defaults to 'frame', and the composition fields default off. Use appManifestSchema to parse a manifest whose variant you do not know ahead of time.
Multi-tab apps
routes[].kind ('frame' | 'docs' | 'api' | 'usage', default 'frame') selects what the shell renders for each tab. docs/api/usage are platform-rendered and available to every variant, so API-only and listed apps still present docs and usage.
Cross-app composition
Declare a dependency on another published app's composable routes:
exposesPublicApi: true, // expose your own routes
appDependencies: [ // depend on another app
{ appId: 'other-app', routes: ['/v1/data'], reason: 'fetch base data' },
],
hosted: {
routes: [{ method: 'GET', pattern: '/v1/report', billingUnits: 1, composable: true }],
}With the user's consent, your backend mints a delegated token at POST /api/apps/{yourApp}/delegate/{upstreamApp}/token, then calls /api/apps/{upstreamApp}/{path} with it. Usage is billed to the user's subscription to the upstream app.
@arsite/sdk/theme — native look
Match the ArSite shell without reverse-engineering the design tokens:
// tailwind.config.ts
import { arsiteTailwindPreset } from '@arsite/sdk/theme/tailwind';
export default { presets: [arsiteTailwindPreset], content: ['./src/**/*.{ts,tsx}'] };import { createArsiteClient } from '@arsite/sdk/iframe';
const client = await createArsiteClient();
client.applyTheme(); // writes the host theme to :root as --arsite-* CSS vars
client.events.on('themeChange', () => client.applyTheme()); // track live themeAlso exported: arsiteTokens (the palette/scale constants), applyTheme, and tokenized React primitives (Button, Card, Input, Callout, Badge, Table) at @arsite/sdk/theme/react.
Examples
Runnable manifests for every variant live in examples/: hosted-frame-next, api-only-express, listed-rest-proxy, composed-app, native-themed.
License
MIT
