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

@dustid/dust-go-connect

v0.1.15

Published

A library for connecting a frontend application to the DUST GO react native application

Readme

@dustid/dust-go-connect

DUST Go is a prototyping platform that allows developers to create mobile webapps that leverage DUST Identity's platform-dependent functionality without requiring an upfront investment into mobile toolchains.

Install

npm install @dustid/dust-go-connect

Usage

// NOTE: connector will be undefined if running outside DUST Go
import { connector } from '@dustid/dust-go-connect';

const scans: {data: string, metadata: Record<string, unknown>}[] = [];

connector?.add("unique-id-for-this-listener", (event: ScanEvent) => {
  if (event.type === 'scan') scans.push(event.payload)
  if (event.type === 'hide') // do something
  if (event.type === 'show') // do something
});
connector?.showScanner();

API

High Level API

scanAsync() Presents the scanner, and awaits a scan. Rejects if scanner is hidden.

// scanAsync(): Promise<ScanPayload>
const scan = await scanAsync();

DUST Go Connector

This is more low level, but more flexible.

connector.showScanner(): void

Shows the scanner modal over the main webview.

connector.hideScanner(): void

Hides the scanner modal, returning to the main webview. Note, this can be called even while the webview is in the "background", the webview isn't paused while the scanner is presented.

Events

type ScanPayload =
  | {
      type: 'DUST'
      data: string
      metadata?: ScanSettings
      exif?: ScanExif
    }
  | {
      type: 'QR'
      data: string
      metadata?: Record<string, any>
    }
  | { type: 'BARCODE'; data: string; metadata?: Record<string, any> }
  | { type: 'DATA_MATRIX'; data: string; metadata?: Record<string, any> }
  | { type: 'NFC'; data: string; metadata?: Record<string, any> }

ScanSettings can include optional Android Dragon capture fields: captureSource, usbVendorId, usbProductId, usbDeviceName, androidCameraId, dragonBackend, and dragonErrorCode.

EXIF (protocol v5)

A DUST scan carries the capture's photographic EXIF, keyed by canonical tag name — the same names exifr produces:

type ScanExif = {
  source: 'camera' | 'synthesized'
  tags: Record<string, string | number | number[]>
}

// e.g.
{
  source: 'camera',
  tags: {
    Make: 'Apple',
    Model: 'iPhone 17 Pro',
    Software: '26.2.1',
    ExposureTime: 0.01,
    FNumber: 1.78,
    ISO: 1600,
    FocalLength: 6.765,
    LensModel: 'iPhone 17 Pro back camera 6.765mm f/1.78',
    ExifImageWidth: 640,
    ExifImageHeight: 480,
    Orientation: 1,
    ProcessingSoftware: 'dust-go 1.1.0 (25)',
    UserComment: 'scan-7-h29fk1a2', // the scan id, as DICE wrote its session uuid
  }
}

Three things to know about it:

  • source is not decoration. camera means the tags were read out of the EXIF the capture's own encoder wrote. synthesized means the capture carried none (Dragon UVC frames have no EXIF at all) and the app assembled identity and commanded values instead — never measurements. This is the same Attested/Commanded/Applied distinction as appliedAxes (ADR-0004); do not present a synthesized value as what the instrument did.
  • The tags describe the submitted image, not the sensor frame. The phone rotates and resizes a loupe capture before encoding, so Orientation is normalized and the geometry tags report the submitted size (typically 640x480).
  • The same tags are in three places, so no consumer has to change to see them: stamped into the image bytes (for backends that parse the stored file, like the legacy scan-api admin page), on payload.exif, and flattened as canonical keys in metadata alongside exifSource (for tooling that reads only the metadata bag, like the apid console).

Only a curated tag set is carried — no MakerNote-sized blobs, and nothing the app cannot name. Absent on hosts older than v5.

| Scan Events | | --------------------------------------- | | { type: 'scan', payload: ScanPayload} | | { type: 'show' } | | { type: 'hide' } |

connector.dispatch(event: ScanEvent): void

Add a listener with a unique identifier.

connector.add(id: string, cb: (event: ScanEvent) => void): void

Remove the listener with the provided identifier.

connector.remove(id: string): void

Check if a listener has been added for the given id

connector.has(id: string): boolean

Remove all event listeners

connector.clear();

Restricting a capture to specific Dust Go builds (protocol v4)

A prescribed capture can name which builds of the capture app may take it, so a measurement series is not silently mixed across app versions that behave differently. The policy is carried on CaptureRequirements.app and answered rejected before the shutter is armed — by the host, and by this library before it even sends the command.

await runCaptureAsync({
  requestId,
  condition,
  requirements: {
    // Every constraint declared here must hold.
    app: { minBuild: 128, minVersion: '1.5.0' },
    device: 'iPhone 15 Pro',
  },
})
// Rejects with reason 'app-version-unsupported' (reported build is outside the
// policy) or 'app-version-unknown' (the app is too old to report its build).

build is Dust Go's monotonic build number — iOS buildNumber / Android versionCode, which never reset — so minBuild is the reliable way to say "this fix or later".

The two version forms compare differently, on purpose:

  • minVersion / maxVersion order dotted numeric segments (1.10.0 is newer than 1.9.3) and ignore any -suffix, so they cannot separate a prerelease from its release.
  • versions: [...] is an exact string match: ['1.5.0'] admits 1.5.0 and rejects 1.5.0-beta.2, and naming the prerelease explicitly is the only way to admit it.

The build constraints come in the same two shapes — inclusive bounds and an exact allowlist:

  • minBuild / maxBuild are inclusive boundsminBuild: 128 rejects every build below 128, which is what you want for "this fix or later".
  • builds: [...] is an exact allowlist. Reach for it when the set is not a range: pinning a run to the builds it started on, or excluding one bad build while keeping the others around it.

A host that cannot report a constrained field is refused, not passed, unless the policy sets allowUnknown: true — which waives only the constraints whose value is unknown, leaving the rest enforced.

The same comparison is available directly, for gating UI before a capture is attempted:

import {
  evaluateAppRequirement,
  getCaptureCapabilities,
  hostEnforcesRequirement,
} from '@dustid/dust-go-connect'

const capabilities = getCaptureCapabilities() // null on hosts that announced nothing
const verdict = evaluateAppRequirement({ minBuild: 128 }, capabilities?.app)
if (!verdict.ok) {
  // verdict.message is operator-readable; verdict.reason is machine-readable.
}

// Which requirements this host checks itself. A requirement missing from the
// list is one the host would ignore, so gate on it yourself — absence of a
// declaration is never evidence of capability.
hostEnforcesRequirement(capabilities, 'app')

Auth

On some platforms, it's necessary to rewrite the redirects from the app to the OAuth2 server to work around platform limitations. It's recommended to always wrap those redirects with this call, and let the connector no-op when no workaround is required.

connector.rewriteRedirect(url: URL): URL