Skip to content
PushNowDocs

Developer documentation

TypeScript and JavaScript

Build the local npm package, authorize in a browser or Node, and send encrypted content.

Install the local package

From the repository root:

npm --prefix sdk/typescript ci
npm --prefix sdk/typescript run build
cd sdk/typescript
npm pack

Install the generated tarball in your consuming project:

npm install /absolute/path/to/sdk/typescript/pushnow-sdk-0.1.1.tgz

The package name is pushnow-sdk. Use npm install pushnow-sdk after publication, or install the local tarball during development. The package is ESM and includes TypeScript declarations; there is no CommonJS entry.

For an unbundled browser, serve the built dist/browser.js from your application and import it as an ES module. It includes the HPKE dependency. Use HTTPS or loopback HTTP and a runtime with WebCrypto, fetch, AbortController and structuredClone.

Use one scoped Authorization key

For server integrations and webhook-style sends, create one Dashboard API token and grant only the scopes it needs.

import { getAccount, listSources, sendMessage } from 'pushnow-sdk';

const auth = {
  api_url: 'https://api.pushnow.dev',
  authorization_key: process.env.PUSHNOW_API_KEY!,
};

const me = await getAccount(auth);
const sources = await listSources(auth);
const sent = await sendMessage(auth, {
  title: 'Build complete',
  body: 'The report is ready.',
  inboxOnly: true,
});

sendMessage requires send:write and calls /v2/messages. The backend encrypts it with the same-account sender key saved by the signed-in Web or app client. getAccount requires account:read; listSources requires sources:read. Use the encrypted sender flow below when you need v2 E2EE content and encrypted attachments.

Authorize once with an account token for E2EE

Use an account access token from a signed-in PushNow app or trusted dashboard session to create an account-bound sender authorization.

import { beginAccountLogin, finishAccountLogin } from 'pushnow-sdk';

const pending = await beginAccountLogin(
  'https://api.pushnow.dev',
  accountAccessToken,
  'My integration',
);
// Display these public values while the signed-in app approves the sender.
console.log(pending.authorization.user_code);
console.log(pending.fingerprint);

const config = await finishAccountLogin(pending);

The token is only transport authorization for /v2/account-authorizations. A scoped API token can call scoped HTTP endpoints, but it cannot encrypt v2 messages without the returned sender config.

config includes secrets. Keep it in memory for a browser session or a secure server-side secret store. Never embed it in public JavaScript, URLs or plaintext browser storage. For Dashboard integration, compare its user_id to the signed-in account and its api_url to the expected API origin, then call recipientsV2(config) to verify the directory. Clear it on logout or account change.

Send text and files

import { sendNotification } from 'pushnow-sdk';

const result = await sendNotification(config, {
  title: 'Build complete',
  body: 'The report is ready.',
  links: ['https://example.com/reports/latest'],
  files: [{
    data: new Blob(['Build passed']),
    name: 'build.txt',
    mime: 'text/plain',
  }],
}, {
  inboxOnly: true,
});
console.log(result.message_id, result.deduplicated);

Remove inboxOnly to notify all eligible devices. For selected devices, use deviceIds: [selectedDeviceID]; do not combine it with inboxOnly: true. scheduledAt and expiresAt are options in the third argument.

Use image or icon in the content argument with {data,name,mime} to upload and reference an image. File data may be a Blob, Uint8Array or ArrayBuffer.

The finalized MessageOptions.sound accepts 'default' | 'silent' | 'chime'. Pass it in the third argument to sendNotification, or the options argument to prepareMessageV2, not in MessageContent:

await sendNotification(config, { title: 'Quiet update', body: 'Ready to review.' }, {
  sound: 'silent',
});

Omission preserves default sound. Silent still requests a visible alert; chime requests the new app's bundled pushnow-chime.wav. Sound is public top-level routing metadata; content and files remain encrypted. No arbitrary filename, audio upload or critical-alert option is supported. iOS settings and Focus/DND apply. This contract is being implemented; migration, deployment and audible-device verification are pending. See rollout status.

Prepare and retry

import { recipientsV2, prepareMessageV2, submitMessageV2 } from 'pushnow-sdk';

const directory = await recipientsV2(config);
const prepared = await prepareMessageV2(config, directory, {
  title: 'Report ready', body: 'Open the app to review.',
}, { inboxOnly: true });

const result = await submitMessageV2(config, prepared);
// On an uncertain result, retry submitMessageV2(config, prepared).

Retain the exact prepared object. The SDK binds it to the account and source in memory; JSON serialization, cloning and re-import lose that binding and are rejected. This version does not provide durable outbox import. Use the CLI or a documented binding outbox workflow when you need restart-safe retries.

Redacted request events

await sendNotification(config, { title: 'Ready', body: 'Done' }, {
  onRequest(event) {
    console.log(event.method, event.path, event.status, event.durationMs, event.outcome);
  },
});

Events contain no headers, tokens, plaintext or raw response body. Paths use templates instead of IDs. A custom fetcher receives credentials and ciphertext and must be trusted; it is not a redacted logging hook.

HTTP errors expose APIError.status. Requests reject redirects and use a timeout. Pass an AbortSignal to cancel; cancellation does not prove a submission was rejected by the server.