npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@blaaiz/signa-web-sdk

v3.0.0

Published

Signa and Signa ID for websites: open a KYC session, check if a wallet is verified, verify it, and ask for a person's verified data.

Readme

Signa web SDK

@blaaiz/signa-web-sdk adds Signa and Signa ID to a website. With it, a site can:

  • check if a wallet belongs to a verified Signa ID (status),
  • let the person verify and link the wallet (verify),
  • ask for the person's verified data, which the person approves (requestData),
  • open a business's Signa KYC session, where the person does the steps of the session (startSession).

verify, requestData and startSession show a page of the Signa ID site. See "Dialog or popup".

Install

npm install @blaaiz/signa-web-sdk
import { createSigna } from "@blaaiz/signa-web-sdk";

const signa = createSigna({ environment: "production" });

| environment | API | Signa ID site | | ---------------------- | ----------------------------- | --------------------------- | | production (default) | https://api-prod.blaaiz.com | https://id.blaaiz.com | | sandbox | https://api-dev.blaaiz.com | https://id-dev.blaaiz.com |

createSigna uses no browser API, so a module that creates the client can run during server rendering. Call status, verify, requestData and startSession in the browser only.

The package has no dependencies. It ships ES module and CommonJS builds with TypeScript types, and a standalone build for a script tag.

Use a script tag

A page with no bundler loads the standalone build from a CDN. It sets window.SignaWebSdk, which has the same createSigna:

<script src="https://cdn.jsdelivr.net/npm/@blaaiz/signa-web-sdk@3/dist/signa-web-sdk.min.js"></script>
<script>
  const signa = SignaWebSdk.createSigna({ environment: "production" });
</script>

unpkg.com/@blaaiz/signa-web-sdk@3 serves the same file. @3 gets every 3.x release. To add an integrity attribute, name an exact version, for example @3.0.0: the file at @3 changes with each release.

Dialog or popup

verify(), requestData() and startSession() take a mode:

  • embedded (the default): a dialog on your page, with the Signa ID page in an iframe. The person does not leave your page. The browser does not block the dialog.
  • popup: a separate window. Call the method from a click handler, or the browser blocks the window and the promise rejects with code: 'blocked'.

In both modes, the person signs in to Signa ID with an email code each time, as in Sumsub ID. The Signa ID pages offer no passkey and keep no sign-in between calls.

Rules for both modes:

  • Only one Signa dialog or popup can be open on the page. A second call for the same wallet or token returns the same promise. Any other call rejects with code: 'in_progress'.
  • The SDK accepts a message only from its own dialog or popup, from the Signa ID site of its environment.

Rules for embedded:

  • If your site sends a Content-Security-Policy with frame-src or child-src, add the Signa ID site.
  • The dialog lets the iframe use the camera (allow="camera") for the liveness check. If your site sends a Permissions-Policy header, it must allow the camera for the Signa ID site, for example camera=(self "https://id.blaaiz.com"). The browser asks for the camera in the name of your site.
  • The person closes the dialog with its close button or Escape.
  • Some wallet extensions do not run inside another site's frame. If the person's wallet does not appear in the dialog, use mode: 'popup' for verify().

Rules for popup:

  • Do not send Cross-Origin-Opener-Policy: same-origin on the page. Use same-origin-allow-popups or no header. With same-origin, the popup loses its link to the page and cannot report back.

Check a wallet

A smart-contract exchange with no backend can use this too.

  1. Call signa.status() when the page loads. This call opens no window.

  2. Call signa.verify() from the click handler of a button:

    await signa.status({ wallet, chainId: 8453 }); // on page load
    button.onclick = async () => {
      const status = await signa.verify({ wallet, chainId: 8453 });
      if (status.verified) {
        // Let the person continue.
      }
    };

The types:

interface SignaStatus {
  verified: boolean;
  level: number | null;
  country: string | null;
  expires_at: string | null;
  attestations: {
    chain_id: number;
    uid: string;
    explorer_url: string | null;
  }[];
}

interface SignaVerifyOptions {
  wallet: string;
  chainId?: number;
  mode?: "embedded" | "popup"; // the default is 'embedded'
}

status() takes the same options without mode.

How verify() works:

  1. The SDK reads GET /api/v1/signa-id/public/wallets/<address>/status. This call needs no cookie. A verified wallet returns at once, with no dialog or window.
  2. If the wallet is not verified, the SDK shows the connect page: the dialog's iframe loads /embed/connect?wallet=…&chain_id=…&origin=…, and a popup opens /connect?… with the same parameters. The person signs in with an email code. If necessary, the person verifies their identity in the same dialog or popup. Then the person links the wallet.
  3. After the person links the wallet, or selects "Return to the site", the page sends { type: 'signa:done' } to the SDK. The SDK removes the dialog or closes the popup. The message holds no personal data.
  4. The SDK reads the status again and returns it.

Rules for integrators:

  • Safari allows only a short time between the click and a popup. If status() ran in the last 60 seconds and the wallet is not verified, verify() shows the page at once, before any request.
  • If the person closes the dialog or the popup, the promise resolves with the current status. That status is not verified.
  • An address that is not 0x plus 40 hex characters rejects at once.
  • status() keeps each result for 60 seconds. The API also caches it for 60 seconds.
  • If the status call fails, the promise rejects. Call again to retry.

Rules on the Signa ID side:

  • The origin parameter must be an exact https origin, or http://localhost with a port. Otherwise the page shows an error and sends nothing.
  • The page names the site only after the site proves its origin. The page sends a nonce to origin, and the SDK on that page sends it back. Until then, the page says "A site".
  • The page never reports on its own. It does not send a message until the person acts in it. Otherwise any site could test which wallets a person owns.
  • /connect refuses to work inside a frame, and /embed/connect works only inside one.
  • A self-check goes to the capture page in the same dialog or popup, then comes back to the connect page. The person stays signed in for this trip only.
  • The page checks that the wallet extension uses the requested address and network. A different address stops the link with a message.
  • "Return to the site" does not mean verified. The public status can still say not verified, for example when a document expired.

Ask for a person's data

A business can ask a Signa ID owner to release identity data to it. The business's server creates the request with the Blaaiz API (POST /api/external/signa-id/release-requests) and gets a request_token. The business's page then calls signa.requestData().

button.onclick = async () => {
  try {
    const { releaseId, code } = await signa.requestData({ requestToken });
    // Send releaseId and code to your server. Your server calls
    // POST /api/external/signa-id/releases/exchange with { code } in 5 minutes.
  } catch (error) {
    // error.code is 'closed', 'blocked' or 'in_progress'.
  }
};

The types:

interface SignaReleaseOptions {
  requestToken: string; // the request_token, 43 base64url characters
  mode?: "embedded" | "popup"; // the default is 'embedded'
}

interface SignaRelease {
  releaseId: string; // uuid
  code: string; // one use, 5 minutes, 43 base64url characters
}

How requestData() works:

  1. The SDK shows the release page at once, before any request. A popup opens /r/<request_token>?origin=<the page's origin>. The dialog's iframe loads /embed/release?origin=<the page's origin>, with no token in the URL.
  2. The page sends { type: 'signa:hello', nonce } to origin. The SDK answers { type: 'signa:ready', nonce }. In embedded mode, the answer also carries request_token. The page does nothing else until this answer comes. With no answer in 10 seconds, it shows "This link does not work".
  3. The page calls POST /api/v1/kyc-capture/signa-release/session with {token, origin}. The service compares origin with the origin that the business gave at create. A different origin, or a bad or expired token, shows "This link does not work". The page also stops when the origin in the response is not the origin in its URL.
  4. The person signs in to Signa ID with an email code.
  5. POST …/signa-release/preview with {token}. The outcome decides the screen:
    • UNDER_REVIEW: "Your Signa ID is being checked" and "Close". The copy never names the type of check.
    • NOT_COVERED: "Your Signa ID cannot share this" and "Close".
    • READY: "Review what you share". A READY answer also has consent_hash: the service's SHA-256 (64 lowercase hex characters) of the release terms and the verification. A READY answer without a valid consent_hash shows "Something went wrong".
  6. "Review what you share" shows the business name, the host that gets the data, the reason, "Access for 30 days, until ", what the business gets (data_shared) and the verification card. It shows only the requested scopes.
  7. "Share" sends POST …/signa-release/consent with {token, verification_id, consent_hash}. The service calculates the hash again for the current terms. A different hash gets a refusal, which shows on the screen.
    • The service accepts the consent only from a sign-in in the last 10 minutes, or a confirmation in that time. If not, it answers 403 with code: "confirmation_required", and the page asks for a new email code (/api/v1/signa-id/confirm/email and /confirm/email/verify). Then the page sends the consent one more time. If the answer is confirmation_required again, the page shows an error and does not ask again until the person selects "Share".
  8. The page runs the normal liveness start and complete (/api/v1/kyc-capture/start and /complete) on the same token. A cancelled selfie goes back to "Review what you share".
  9. POST …/signa-release/complete with {token} returns {release_id, code, code_expires_at}. The page sends { type: 'signa:release', nonce, release_id, code } to the origin from the session response, with the nonce that the SDK answered. The SDK removes the dialog or closes the popup.
  10. "Decline" calls POST …/signa-release/decline with {token} and shows "You did not share your data" with "Close". As with "under review" and "not covered", the page closes only when the person selects "Close", so the time it stays open says nothing to your page.

Rules:

  • The SDK accepts signa:release only with a nonce that it answered, a uuid release_id and a 43-character code.
  • The page sends nothing for "under review", "not covered", a decline, a refusal or a close, so your page cannot tell them apart. Each one rejects with code: 'closed' when the dialog or popup closes.
  • When the SDK sees the dialog or popup closed, it waits one more check (500 ms) before it rejects, so a release sent just before the close still arrives.
  • /r/<request_token> refuses to work inside a frame, and /embed/release works only inside one. An origin that is not an exact https origin (or http://localhost with a port) shows "This link does not work".
  • The request token is a bearer credential for the request. Do not put it in a URL on your site, and do not log it.

Open a KYC session

A business that uses Signa (KYC as a service) can open a session on its own page. The SDK shows the Blaaiz capture page, with the same screens as the session's verification link, in a dialog (the default) or a popup.

  1. On your server, create the session: POST /api/external/compliance/kyc/sessions.

  2. On your server, get an access token for the session: POST /api/external/compliance/kyc/sessions/<id>/access-token. The answer is { access_token, expires_at }.

  3. Send the access_token to your page.

  4. On the page, call signa.startSession(). In popup mode, call it from the click handler of a button, or the browser blocks the window:

    button.onclick = async () => {
      try {
        const { sessionId } = await signa.startSession({ accessToken });
        // The person finished the steps. Your server gets the result.
      } catch (error) {
        // error.code is 'closed', 'blocked' (popup mode only) or 'in_progress'.
      }
    };
  5. On your server, read the result from the KYC webhook or from GET /api/external/compliance/kyc/sessions/<id>.

The types:

interface SignaSessionOptions {
  accessToken: string; // the access_token, 43 to 64 base64url characters
  mode?: 'embedded' | 'popup'; // the default is 'embedded'
}

interface SignaSession {
  sessionId: string; // uuid
}

How startSession() works:

  1. The SDK shows the capture page at once, before any request. A popup opens /c/<access_token>?origin=<the page's origin>. The dialog's iframe loads /c/embed?origin=<the page's origin>, with no token in the URL.
  2. The capture page sends { type: 'signa:hello', nonce } to origin. The SDK answers { type: 'signa:ready', nonce }. In embedded mode, the answer also carries access_token, so the token never appears in your page's DOM.
  3. The person does the steps of the session.
  4. When the steps are done, the capture page sends { type: 'signa:session', nonce, session_id } to origin, with the nonce that the SDK answered. The SDK resolves with sessionId.
  5. The capture page shows its done screen. After 5 seconds, or when the person selects "Continue", the popup closes. In embedded mode, the page sends { type: 'signa:close', nonce } and the SDK removes the dialog.

Rules:

  • The access token is the capture token of the session's verification link. The token is valid for 30 minutes from when it was made.
  • The access token call returns the same token while it has more than 10 minutes left. After that, it makes a new token and stops the old one. A popup that is still open on the old token stops working. When the person closes it, the promise rejects with code: 'closed'.
  • A verification link call makes a new capture token and stops the access token.
  • Only a session on the Blaaiz capture page has an access token. For other sessions, the access token call answers 422.
  • A resolved promise does not give a result. It tells you only that the person finished the steps. Your server must read the result.
  • On your server, use the session id that you stored when you created the session for this person. Do not trust a sessionId that your page sends to your server: a person can change it to the id of a different session.
  • The access token is a bearer credential for the person's check. Do not put it in a URL on your site, and do not log it.
  • The capture page sends no session data to your page before the steps are done. If the person closes the popup or the dialog (with its close button or Escape), the promise rejects with code: 'closed'. The session stays open, and the same access token works until it expires.
  • "Use Signa ID" on the capture page works in both modes. The person signs in with an email code each time.
  • When the SDK opened the popup, the popup does not go to the session's redirect_url. When the link opens in a new tab, the page works as a verification link.

Development

npm ci
npm run typecheck
npm test
npm run build       # dist/: index.js, index.cjs, signa-web-sdk.min.js and the .d.ts files
npm run test:dist   # loads each build the way an integrator does

The SDK and the Signa ID pages share the message protocol in src/protocol.ts. The frontend keeps a copy for its pages. A protocol change must land in both repositories, pages first, and the pages must keep accepting the messages of every published SDK version.

Publishing

The Publish workflow publishes the package to npm when a GitHub release is published from main. It needs the NPM_TOKEN repository secret: an npm automation token with publish rights on the @blaaiz scope. Before the release, set version in package.json and give the version's section in CHANGELOG.md its date. Name the release tag after the version (v1.0.0 for 1.0.0): the workflow stops when they differ. jsDelivr and unpkg serve the standalone build from npm, so a release is on the CDN too.

Branches

dev is the integration branch and main is production. Open pull requests against dev.

License

MIT