diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0118f44..d6bf11e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.11.0
+
+This version adds support for Metadata and iOS deep links, and expands Tags support when updating or ending Live Activities.
+
## 1.10.0
### New Features
diff --git a/README.md b/README.md
index 7da79bb..560ad77 100644
--- a/README.md
+++ b/README.md
@@ -1,50 +1,22 @@
-# ActivitySmith Node SDK
+# ActivitySmith Node.js SDK
-The ActivitySmith Node SDK provides convenient access to the ActivitySmith API from server-side JavaScript and TypeScript applications.
-
-## Documentation
-
-See [API reference](https://activitysmith.com/docs/api-reference/introduction)
-
-## Table of Contents
-
-- [Installation](#installation)
-- [Setup](#setup)
-- [Push Notifications](#push-notifications)
- - [Send a Push Notification](#send-a-push-notification)
- - [Rich Push Notifications with Media](#rich-push-notifications-with-media)
- - [Actionable Push Notifications](#actionable-push-notifications)
-- [Live Activities](#live-activities)
- - [Start & Update Live Activity](#start--update-live-activity)
- - [End Live Activity](#end-live-activity)
- - [Live Activity Action](#live-activity-action)
- - [Icons and Badges](#icons-and-badges)
- - [Live Activity Colors](#live-activity-colors)
-- [Widgets](#widgets)
-- [App Icon Badge Count](#app-icon-badge-count)
-- [Channels](#channels)
-- [Tags](#tags)
+[Documentation](https://activitysmith.com/docs/sdks/node)
## Installation
-```sh
+Install the ActivitySmith Node.js SDK with npm:
+
+```bash
npm install activitysmith
```
-## Setup
+## Quickstart
-```ts
-import ActivitySmith from "activitysmith";
-
-const activitysmith = new ActivitySmith({
- apiKey: process.env.ACTIVITYSMITH_API_KEY,
-});
-```
-
-CommonJS:
+1. [Create an API key](https://activitysmith.com/app/keys)
+2. Set `ACTIVITYSMITH_API_KEY` or pass `apiKey` when creating the client.
```js
-const ActivitySmith = require("activitysmith");
+import ActivitySmith from "activitysmith";
const activitysmith = new ActivitySmith({
apiKey: process.env.ACTIVITYSMITH_API_KEY,
@@ -55,11 +27,11 @@ const activitysmith = new ActivitySmith({
### Send a Push Notification
-
-
-
+Send an immediate notification for a completed task or event.
-```ts
+
+
+```js
await activitysmith.notifications.send({
title: "New subscription 💸",
message: "Customer upgraded to Pro plan",
@@ -68,24 +40,19 @@ await activitysmith.notifications.send({
### Rich Push Notifications with Media
-
-
-
+
-```ts
+```js
await activitysmith.notifications.send({
title: "Homepage ready",
message: "Your agent finished the redesign.",
media: "https://cdn.example.com/output/homepage-v2.png",
- redirection: "https://github.com/acme/web/pull/482",
});
```
-Send images, videos, or audio with your push notifications, press and hold to preview media directly from the notification, then tap through to open the linked content.
+Attach images, videos, or audio to your Push Notifications. Press and hold the notification to preview the media.
-
-
-
+
What will work:
@@ -94,23 +61,51 @@ What will work:
- direct video file URL: `.mp4`, `.mov`, etc.
- URL that responds with a proper media `Content-Type`, even if the path has no extension
+`media` cannot be combined with `actions`.
+
+### Push Notifications with Redirection
+
+Open a web page, run an iOS Shortcut, or open an app when someone taps the notification. `redirection` supports:
+
+- **HTTP/HTTPS:** Web pages, e.g. `https://example.com`
+- **Shortcuts:** Run Jarvis with `shortcuts://run-shortcut?name=Jarvis`
+- **App deep links:** Installed apps or specific content within them
+ - **Spotify:** A track, e.g. `spotify:track:6rqhFgbbKwnb9MLmUQDhG6`
+ - **Termius:** `termius://` to open the app
+ - **Claude:** `claude://code` to open the Code tab
+ - **ChatGPT:** `chatgpt://` to open the app
+
+```js
+await activitysmith.notifications.send({
+ title: "Homepage ready",
+ message: "Your agent finished the redesign.",
+ redirection: "https://github.com/acme/web/pull/482",
+});
+```
+
### Actionable Push Notifications
-
-
-
+
-Push notification `redirection` and `actions` are optional. Use them to open HTTPS URLs, run a specific iPhone Shortcut with `shortcuts://run-shortcut?name=...`, or trigger backend webhook workflows.
-Webhooks are executed by the ActivitySmith backend.
+`open_url` actions open a web page, run an iOS Shortcut, or open an app when someone taps the button. Supported links:
-```ts
+- **HTTP/HTTPS:** Web pages, e.g. `https://example.com`
+- **Shortcuts:** Run Jarvis with `shortcuts://run-shortcut?name=Jarvis`
+- **App deep links:** Installed apps or specific content within them
+ - **Spotify:** A track, e.g. `spotify:track:6rqhFgbbKwnb9MLmUQDhG6`
+ - **Termius:** `termius://` to open the app
+ - **Claude:** `claude://code` to open the Code tab
+ - **ChatGPT:** `chatgpt://` to open the app
+
+Webhooks are executed by the ActivitySmith backend and must use HTTPS.
+
+```js
await activitysmith.notifications.send({
title: "New subscription 💸",
message: "Customer upgraded to Pro plan",
- redirection: "https://crm.example.com/customers/cus_9f3a1d", // Optional
actions: [
{
- title: "Open CRM Profile",
+ title: "Open CRM",
type: "open_url",
url: "https://crm.example.com/customers/cus_9f3a1d",
},
@@ -129,20 +124,37 @@ await activitysmith.notifications.send({
plan: "pro",
},
},
- ], // Optional (max 4)
+ ],
});
```
## Live Activities
-There are six types of Live Activities:
+Choose the Live Activity type that matches what you want to show:
+
+
+
+**Stats**: Show up to 8 labeled values on your Lock Screen, from revenue and orders to uptime and conversion.
+
+
+
+**Metrics**: Track two related values with segmented bars, such as CPU and memory.
+
+
+
+**Segmented Progress**: Show progress through a known set of steps, like build, test, deploy, and verify.
+
+
-- `stats`: best for showing business numbers side by side, such as revenue, sales, new users, conversion, refunds, or any other value you want visible at a glance
-- `metrics`: best for live percentage values that change often, like server CPU, memory usage, disk usage, or error rate
-- `segmented_progress`: best for anything that moves through clear stages, like deployments, onboarding flows, backups, ETL pipelines, migrations, and AI agent runs
-- `progress`: best for tracking real-time progress with percentage, like tasks, backups, migrations, syncs, or uploads
-- `alert`: best for status updates, such as feature adoption, reactivation, onboarding blockers, incidents, escalations, and other operational states
-- `timer`: best for countdowns and elapsed runtime, like benchmark runs, uploads, backups, transcodes, and long-running jobs
+**Progress**: Show percentage progress for jobs that move continuously toward completion.
+
+
+
+**Alert**: Show status updates with a clear message, badge, and icon. When you add an action button, `color` controls the button tint.
+
+
+
+**Timer**: Count down from a duration, or count up from 00:00 while a job runs.
### Start & Update Live Activity
@@ -150,9 +162,7 @@ Use a stable `streamKey` to identify the metric, job, deployment, or system you
#### Stats
-
-
-
+
```ts
await activitysmith.liveActivities.stream("sales-hourly", {
@@ -174,9 +184,7 @@ await activitysmith.liveActivities.stream("sales-hourly", {
#### Metrics
-
-
-
+
```ts
await activitysmith.liveActivities.stream("prod-web-1", {
@@ -194,9 +202,7 @@ await activitysmith.liveActivities.stream("prod-web-1", {
#### Segmented Progress
-
-
-
+
```ts
await activitysmith.liveActivities.stream("nightly-backup", {
@@ -212,9 +218,7 @@ await activitysmith.liveActivities.stream("nightly-backup", {
#### Progress
-
-
-
+
```ts
await activitysmith.liveActivities.stream("search-reindex", {
@@ -229,9 +233,7 @@ await activitysmith.liveActivities.stream("search-reindex", {
#### Alert
-
-
-
+
```ts
await activitysmith.liveActivities.stream("customer-ops", {
@@ -247,9 +249,7 @@ await activitysmith.liveActivities.stream("customer-ops", {
#### Timer
-
-
-
+
```ts
await activitysmith.liveActivities.stream("benchmark-run", {
@@ -269,7 +269,7 @@ To start at 00:00 and count up, set `counts_down: false` and leave out `duration
### End Live Activity
-Call `endStream(...)` with the same `streamKey` to dismiss the Live Activity. You can include final values before it is removed. By default, iOS removes the Live Activity after two minutes. Set `auto_dismiss_minutes` to choose a different dismissal time, including `0` for immediate dismissal.
+Call `endStream(...)` with the same `streamKey` to dismiss the Live Activity. You can include final values before it is removed. Set `auto_dismiss_seconds` to dismiss it after a delay in seconds, or `auto_dismiss_minutes` for minutes. Use `0` for immediate dismissal. Seconds take precedence if both are set.
```ts
await activitysmith.liveActivities.endStream("prod-web-1", {
@@ -281,25 +281,82 @@ await activitysmith.liveActivities.endStream("prod-web-1", {
{ label: "CPU", value: 7, unit: "%" },
{ label: "MEM", value: 38, unit: "%" },
],
- auto_dismiss_minutes: 2,
+ auto_dismiss_seconds: 30,
},
});
```
+### Icons and Badges
+
+Add more context to Live Activities with icons and badges.
+
+#### Icon
+
+Supported Live Activity types: `stats`, `metrics`, `progress`, `segmented_progress`, `alert`, and `timer`.
+
+
+
+```ts
+await activitysmith.liveActivities.stream("prod-web-1", {
+ content_state: ActivitySmith.contentState({
+ title: "Server Health",
+ subtitle: "prod-web-1",
+ type: "metrics",
+ icon: ActivitySmith.alertIcon("server.rack", { color: "blue" }),
+ metrics: [
+ { label: "CPU", value: 18, unit: "%" },
+ { label: "MEM", value: 42, unit: "%" },
+ ],
+ }),
+});
+```
+
+The `icon.symbol` value is an Apple SF Symbol name. Browse the catalog with one of these tools:
+
+- [ActivitySmith app](https://apps.apple.com/us/app/activitysmith/id6752254835) - Open Settings -> SF Symbols to browse 45 hand-picked icons ready to use
+- [SF Symbols](https://developer.apple.com/sf-symbols/) - Apple's official macOS app
+- [Interactful](https://apps.apple.com/app/interactful/id1528095640) - free third-party iOS app listing all SF Symbols under Foundations -> Iconography
+
+#### Badge
+
+Badges are supported by `alert`, `progress`, and `segmented_progress` Live Activities.
+
+
+
+```ts
+await activitysmith.liveActivities.stream("nightly-database-backup", {
+ content_state: ActivitySmith.contentState({
+ title: "Nightly Database Backup",
+ subtitle: "verify restore",
+ type: "progress",
+ badge: ActivitySmith.alertBadge("S3", { color: "cyan" }),
+ percentage: 62,
+ }),
+});
+```
+
+### Live Activity Colors
+
+Choose from these colors for the Live Activity accent, including progress bars and action buttons, or apply them to an individual icon or badge:
+
+`lime`, `green`, `cyan`, `blue`, `purple`, `magenta`, `red`, `orange`, `yellow`, `gray`
+
### Live Activity Action
-Live Activities can include an action button.
+
-- `open_url`: open an HTTPS URL.
-- `open_url` with a `shortcuts://` URL: run an Apple Shortcut, for example to open an app.
-- `webhook`: trigger a backend GET/POST workflow.
+Live Activities can include an action button.
-
-
-
+- `open_url`: Open a web page or run an iOS Shortcut
+- `webhook`: Trigger a backend GET/POST workflow
#### Open URL action
+Open a web page or run an iOS Shortcut when someone taps the button. Supported links:
+
+- **HTTP/HTTPS:** Web pages, e.g. `https://example.com`
+- **Shortcuts:** Run Jarvis with `shortcuts://run-shortcut?name=Jarvis`
+
```ts
await activitysmith.liveActivities.stream("prod-web-1", {
content_state: {
@@ -314,7 +371,7 @@ await activitysmith.liveActivities.stream("prod-web-1", {
action: {
title: "Dashboard",
type: "open_url",
- url: "https://ops.example.com/servers/prod-web-1",
+ url: "https://status.example.com/servers/prod-web-1",
},
});
```
@@ -322,13 +379,15 @@ await activitysmith.liveActivities.stream("prod-web-1", {
#### Apple Shortcut action
```ts
-await activitysmith.liveActivities.stream("deploy-payments-api", {
+await activitysmith.liveActivities.stream("prod-web-1", {
content_state: {
- title: "Deploying payments-api",
- subtitle: "Running database migrations",
- type: "segmented_progress",
- number_of_steps: 5,
- current_step: 3,
+ title: "Server Health",
+ subtitle: "prod-web-1",
+ type: "metrics",
+ metrics: [
+ { label: "CPU", value: 76, unit: "%" },
+ { label: "MEM", value: 52, unit: "%" },
+ ],
},
action: {
title: "Chat with Jarvis",
@@ -364,9 +423,7 @@ await activitysmith.liveActivities.stream("search-reindex", {
#### Secondary action
-
-
-
+
Use `secondary_action` when you want a second button beside the primary `action`.
@@ -411,76 +468,13 @@ await activitysmith.liveActivities.stream("agent-approval", {
});
```
-### Icons and Badges
-
-Add more context to Live Activities with icons and badges.
+## Lock Screen Widgets
-#### Icon
+
-Supported Live Activity types: `stats`, `metrics`, `progress`, `segmented_progress`, `alert`, and `timer`.
+ActivitySmith lets you display any value on your Lock Screen with widgets - SaaS metrics, revenue, signups, uptime, habits, or anything else you want to track. Create a metric in the [web app](https://activitysmith.com/app/widgets), then update the metric value using our API, add a widget to your lock screen and it will fetch the latest update automatically.
-
-
-
-
-```ts
-await activitysmith.liveActivities.stream("prod-web-1", {
- content_state: ActivitySmith.contentState({
- title: "Server Health",
- subtitle: "prod-web-1",
- type: "metrics",
- icon: ActivitySmith.alertIcon("server.rack", { color: "blue" }),
- metrics: [
- { label: "CPU", value: 18, unit: "%" },
- { label: "MEM", value: 42, unit: "%" },
- ],
- }),
-});
-```
-
-The `icon.symbol` value is an Apple SF Symbol name. Browse the catalog with one of these tools:
-
-- [ActivitySmith app](https://apps.apple.com/us/app/activitysmith/id6752254835) - Open Settings -> SF Symbols to browse 45 hand-picked icons ready to use
-- [SF Symbols](https://developer.apple.com/sf-symbols/) - Apple's official macOS app
-- [Interactful](https://apps.apple.com/app/interactful/id1528095640) - free third-party iOS app listing all SF Symbols under Foundations -> Iconography
-
-#### Badge
-
-Badges are supported by `alert`, `progress`, and `segmented_progress` Live Activities.
-
-
-
-
-
-```ts
-await activitysmith.liveActivities.stream("nightly-database-backup", {
- content_state: ActivitySmith.contentState({
- title: "Nightly Database Backup",
- subtitle: "verify restore",
- type: "progress",
- badge: ActivitySmith.alertBadge("S3", { color: "cyan" }),
- percentage: 62,
- }),
-});
-```
-
-### Live Activity Colors
-
-Choose from these colors for the Live Activity accent, including progress bars and action buttons, or apply them to an individual icon or badge:
-
-`lime`, `green`, `cyan`, `blue`, `purple`, `magenta`, `red`, `orange`, `yellow`, `gray`
-
-## Widgets
-
-
-
-
-
-ActivitySmith lets you display any value on your Lock Screen with widgets - SaaS metrics, revenue, signups, uptime, habits, or anything else you want to track. Create a metric in the web app, then update the metric value using our API, add a widget to your lock screen and it will fetch the latest update automatically.
-
-
-
-
+
Use the metric key to update its value.
@@ -496,65 +490,60 @@ await activitysmith.metrics.update("prod.status", "healthy");
## App Icon Badge Count
-
-
-
+
Show the number you care about on your ActivitySmith app icon. Track MRR, a customer count, a stock price, or any other value you want to keep in view.
-Set or update the badge value.
+### Set or update the badge value
-```ts
+```js
await activitysmith.badgeCount(8333);
```
-To clear the badge, set its value to 0.
+### Clear the badge
-```ts
+Pass `0` to clear the badge.
+
+```js
await activitysmith.badgeCount(0);
```
-## Channels
-
-Use `channels` to target specific team members or devices
+## Metadata
-### Push Notifications
+Metadata adds extra information to Push Notification and Live Activity details in ActivitySmith. It does not appear in the notification or Live Activity on your device.
-```ts
+```js
await activitysmith.notifications.send({
title: "New subscription 💸",
message: "Customer upgraded to Pro plan",
- channels: ["sales", "customer-success"],
+ metadata: {
+ customer_id: "382",
+ plan: "Pro",
+ amount: 29,
+ trial: false,
+ },
});
-```
-
-### Live Activities
-```ts
-await activitysmith.liveActivities.start({
+await activitysmith.liveActivities.stream("customer-import", {
content_state: {
- title: "Nightly Database Backup",
- subtitle: "verify restore",
+ title: "Customer Import",
type: "progress",
- percentage: 62,
+ percentage: 60,
+ },
+ metadata: {
+ job_id: "import-382",
+ records: 1200,
},
- channels: ["sales", "customer-success"],
});
```
-### App Icon Badge Count
-
-```ts
-await activitysmith.badgeCount(3, {
- channels: ["sales", "customer-success"],
-});
-```
+Values can be strings, numbers, or booleans. Metadata supports up to 50 entries and 16 KB of JSON, with keys up to 100 characters and strings up to 4,000 characters. Nested objects, arrays, and null values are not supported.
## Tags
Use `tags` to organize and filter your Push Notification and Live Activity history. Tags are created automatically when you first use them.
-```ts
+```js
await activitysmith.notifications.send({
title: "New subscription 💸",
message: "Customer upgraded to Pro plan",
@@ -562,26 +551,56 @@ await activitysmith.notifications.send({
});
```
+On Live Activity stream updates and legacy `update` or `end` calls, omit `tags` to keep existing Tags, supply a list to replace them, or pass `tags: []` to clear them.
+
+```js
+await activitysmith.liveActivities.update({
+ activity_id: "YOUR_ACTIVITY_ID",
+ content_state: { title: "Customer Import", percentage: 60 },
+ tags: [],
+});
+```
+
+## Channels
+
+Use `channels` to target specific team members or devices when sending Push Notifications, Live Activities, or App Icon Badge Count updates. Omit it for account-wide delivery.
+
+```js
+await activitysmith.notifications.send({
+ title: "New subscription 💸",
+ message: "Customer upgraded to Pro plan",
+ channels: ["sales", "customer-success"],
+});
+
+await activitysmith.liveActivities.stream("nightly-backup", {
+ content_state: {
+ title: "Nightly database backup",
+ number_of_steps: 3,
+ current_step: 1,
+ type: "segmented_progress",
+ },
+ channels: ["ios-builds"],
+});
+
+await activitysmith.badgeCount(3, {
+ channels: ["sales", "customer-success"],
+});
+```
+
## Error Handling
-```ts
+SDK calls return promises, so you can wrap API calls with `try/catch`:
+
+```js
try {
- await activitysmith.notifications.send({
- title: "New subscription 💸",
- });
+ await activitysmith.notifications.send({ title: "Hello" });
} catch (error) {
console.error(error);
}
```
-## TypeScript Support
-
-This package is written in TypeScript and ships with type definitions out of the box.
-
-## Requirements
-
-- Node.js 18 or newer
+## Additional Resources
-## License
+### [NPM Package](https://www.npmjs.com/package/activitysmith)
-MIT
+Install the ActivitySmith Node.js SDK from npm
diff --git a/generated/apis/AppIconBadgesApi.ts b/generated/apis/AppIconBadgesApi.ts
index 3346b05..0456e22 100644
--- a/generated/apis/AppIconBadgesApi.ts
+++ b/generated/apis/AppIconBadgesApi.ts
@@ -15,12 +15,13 @@
import * as runtime from '../runtime';
import type {
+ AppIconBadgeCountUpdateError,
AppIconBadgeCountUpdateRequest,
AppIconBadgeCountUpdateResponse,
BadRequestError,
ForbiddenError,
- NoRecipientsError,
RateLimitError,
+ UpdateAppIconBadgeCount422Response,
} from '../models/index';
export interface UpdateAppIconBadgeCountRequest {
diff --git a/generated/models/index.ts b/generated/models/index.ts
index 0572d22..035a9f2 100644
--- a/generated/models/index.ts
+++ b/generated/models/index.ts
@@ -75,6 +75,79 @@ export interface AlertPayload {
*/
body?: string;
}
+/**
+ *
+ * @export
+ * @interface AppIconBadgeCountUpdateError
+ */
+export interface AppIconBadgeCountUpdateError {
+ /**
+ *
+ * @type {string}
+ * @memberof AppIconBadgeCountUpdateError
+ */
+ error: string;
+ /**
+ *
+ * @type {string}
+ * @memberof AppIconBadgeCountUpdateError
+ */
+ code: AppIconBadgeCountUpdateErrorCodeEnum;
+ /**
+ *
+ * @type {string}
+ * @memberof AppIconBadgeCountUpdateError
+ */
+ message: string;
+ /**
+ *
+ * @type {number}
+ * @memberof AppIconBadgeCountUpdateError
+ */
+ badge: number;
+ /**
+ *
+ * @type {number}
+ * @memberof AppIconBadgeCountUpdateError
+ */
+ devices_targeted?: number;
+ /**
+ *
+ * @type {number}
+ * @memberof AppIconBadgeCountUpdateError
+ */
+ devices_updated: number;
+ /**
+ *
+ * @type {number}
+ * @memberof AppIconBadgeCountUpdateError
+ */
+ users_updated?: number;
+ /**
+ * Deprecated compatibility alias for devices_updated.
+ * @type {number}
+ * @memberof AppIconBadgeCountUpdateError
+ * @deprecated
+ */
+ devices_notified?: number;
+ /**
+ *
+ * @type {Array}
+ * @memberof AppIconBadgeCountUpdateError
+ */
+ effective_channel_slugs?: Array;
+}
+
+
+/**
+ * @export
+ */
+export const AppIconBadgeCountUpdateErrorCodeEnum = {
+ DeviceDisconnected: 'badge_device_disconnected',
+ UpdateFailed: 'badge_update_failed'
+} as const;
+export type AppIconBadgeCountUpdateErrorCodeEnum = typeof AppIconBadgeCountUpdateErrorCodeEnum[keyof typeof AppIconBadgeCountUpdateErrorCodeEnum];
+
/**
*
* @export
@@ -113,17 +186,31 @@ export interface AppIconBadgeCountUpdateResponse {
*/
badge: number;
/**
- *
+ * Number of devices whose App Icon Badge Count was updated.
* @type {number}
* @memberof AppIconBadgeCountUpdateResponse
*/
- devices_notified: number;
+ devices_updated: number;
/**
- *
+ * Number of account users with at least one updated device.
* @type {number}
* @memberof AppIconBadgeCountUpdateResponse
*/
- users_notified: number;
+ users_updated: number;
+ /**
+ * Deprecated compatibility alias for devices_updated.
+ * @type {number}
+ * @memberof AppIconBadgeCountUpdateResponse
+ * @deprecated
+ */
+ devices_notified?: number;
+ /**
+ * Deprecated compatibility alias for users_updated.
+ * @type {number}
+ * @memberof AppIconBadgeCountUpdateResponse
+ * @deprecated
+ */
+ users_notified?: number;
/**
*
* @type {Array}
@@ -849,12 +936,24 @@ export type LiveActivityColor = typeof LiveActivityColor[keyof typeof LiveActivi
* @interface LiveActivityEndRequest
*/
export interface LiveActivityEndRequest {
+ /**
+ * Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it.
+ * @type {{ [key: string]: MetadataValue; }}
+ * @memberof LiveActivityEndRequest
+ */
+ metadata?: { [key: string]: MetadataValue; };
/**
*
* @type {string}
* @memberof LiveActivityEndRequest
*/
activity_id: string;
+ /**
+ * Tags for notification history. Omit to keep existing Tags, supply an array to replace them, or send an empty array to clear them.
+ * @type {Array}
+ * @memberof LiveActivityEndRequest
+ */
+ tags?: Array;
/**
*
* @type {ContentStateEnd}
@@ -936,11 +1035,23 @@ export interface LiveActivityLimitError {
*/
limit: number;
/**
- * Current number of active Live Activities.
+ * Highest number of active Live Activities among the targeted devices.
* @type {number}
* @memberof LiveActivityLimitError
*/
active: number;
+ /**
+ * Number of targeted devices that have reached the enforced iOS Live Activity concurrency threshold. Included only when targeted devices have mixed capacity.
+ * @type {number}
+ * @memberof LiveActivityLimitError
+ */
+ blocked_devices?: number;
+ /**
+ * Total number of targeted devices. Included only when targeted devices have mixed capacity.
+ * @type {number}
+ * @memberof LiveActivityLimitError
+ */
+ targeted_devices?: number;
}
/**
* Start a new Live Activity. The response includes activity_id for later update and end calls.
@@ -948,6 +1059,12 @@ export interface LiveActivityLimitError {
* @interface LiveActivityStartRequest
*/
export interface LiveActivityStartRequest {
+ /**
+ * Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it.
+ * @type {{ [key: string]: MetadataValue; }}
+ * @memberof LiveActivityStartRequest
+ */
+ metadata?: { [key: string]: MetadataValue; };
/**
*
* @type {ContentStateStart}
@@ -1040,6 +1157,18 @@ export interface LiveActivityStartResponse {
* @interface LiveActivityStreamDeleteRequest
*/
export interface LiveActivityStreamDeleteRequest {
+ /**
+ * Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it.
+ * @type {{ [key: string]: MetadataValue; }}
+ * @memberof LiveActivityStreamDeleteRequest
+ */
+ metadata?: { [key: string]: MetadataValue; };
+ /**
+ * Optional tags to organize and filter notification history.
+ * @type {Array}
+ * @memberof LiveActivityStreamDeleteRequest
+ */
+ tags?: Array;
/**
*
* @type {StreamContentState}
@@ -1217,6 +1346,12 @@ export type LiveActivityStreamPutResponseOperationEnum = typeof LiveActivityStre
* @interface LiveActivityStreamRequest
*/
export interface LiveActivityStreamRequest {
+ /**
+ * Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it.
+ * @type {{ [key: string]: MetadataValue; }}
+ * @memberof LiveActivityStreamRequest
+ */
+ metadata?: { [key: string]: MetadataValue; };
/**
*
* @type {StreamContentState}
@@ -1266,12 +1401,24 @@ export interface LiveActivityStreamRequest {
* @interface LiveActivityUpdateRequest
*/
export interface LiveActivityUpdateRequest {
+ /**
+ * Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it.
+ * @type {{ [key: string]: MetadataValue; }}
+ * @memberof LiveActivityUpdateRequest
+ */
+ metadata?: { [key: string]: MetadataValue; };
/**
*
* @type {string}
* @memberof LiveActivityUpdateRequest
*/
activity_id: string;
+ /**
+ * Tags for notification history. Omit to keep existing Tags, supply an array to replace them, or send an empty array to clear them.
+ * @type {Array}
+ * @memberof LiveActivityUpdateRequest
+ */
+ tags?: Array;
/**
*
* @type {ContentStateUpdate}
@@ -1339,6 +1486,12 @@ export const LiveActivityWebhookMethod = {
} as const;
export type LiveActivityWebhookMethod = typeof LiveActivityWebhookMethod[keyof typeof LiveActivityWebhookMethod];
+/**
+ * @type MetadataValue
+ *
+ * @export
+ */
+export type MetadataValue = boolean | number | string;
/**
*
* @export
@@ -1460,7 +1613,7 @@ export interface PushNotificationAction {
*/
type: PushNotificationActionType;
/**
- * Action URL. For open_url, use an HTTP or HTTPS URL or a shortcuts://run-shortcut?name=... URL that runs a specific iPhone Shortcut. For webhook, use an HTTPS URL called by the ActivitySmith backend.
+ * Action URL. For open_url, use HTTP, HTTPS, Shortcuts, or an installed app’s custom URL scheme, such as spotify:// or spotify:track:123. Custom app schemes require iOS 1.13.4 build 2 or later; no web fallback is provided. Internal and executable schemes are blocked. For webhook, use an HTTPS URL called by the ActivitySmith backend.
* @type {string}
* @memberof PushNotificationAction
*/
@@ -1496,6 +1649,12 @@ export type PushNotificationActionType = typeof PushNotificationActionType[keyof
*/
export interface PushNotificationRequest {
[key: string]: any | any;
+ /**
+ * Additional information shown in notification and Live Activity details in ActivitySmith. Not displayed in the Push Notification or Live Activity on the device. Values must be strings, finite numbers, or booleans. At most 50 entries and 16 KB of serialized UTF-8 JSON. Omit on updates to preserve existing Metadata; send {} to clear it.
+ * @type {{ [key: string]: MetadataValue; }}
+ * @memberof PushNotificationRequest
+ */
+ metadata?: { [key: string]: MetadataValue; };
/**
*
* @type {string}
@@ -1521,7 +1680,7 @@ export interface PushNotificationRequest {
*/
media?: string;
/**
- * Optional HTTP URL, HTTPS URL, or shortcuts://run-shortcut?name=... URL opened when the user taps the notification body. Use shortcuts://run-shortcut?name=... to run a specific iPhone Shortcut that already exists on the user's device. Overrides the default tap target from `media` when both are provided.
+ * Optional HTTP, HTTPS, Shortcuts, or installed app URL opened when the user taps the notification body. Custom schemes such as spotify:// and spotify:track:123 require iOS 1.13.4 build 2 or later and an installed handler; no web fallback is provided. Internal and executable schemes are blocked. Overrides the default tap target from media.
* @type {string}
* @memberof PushNotificationRequest
*/
@@ -1835,3 +1994,9 @@ export const StreamContentStateStepColorsEnum = {
} as const;
export type StreamContentStateStepColorsEnum = typeof StreamContentStateStepColorsEnum[keyof typeof StreamContentStateStepColorsEnum];
+/**
+ * @type UpdateAppIconBadgeCount422Response
+ *
+ * @export
+ */
+export type UpdateAppIconBadgeCount422Response = AppIconBadgeCountUpdateError | NoRecipientsError;
diff --git a/generated/openapi-source.json b/generated/openapi-source.json
new file mode 100644
index 0000000..dc05717
--- /dev/null
+++ b/generated/openapi-source.json
@@ -0,0 +1,6 @@
+{
+ "repository": "ActivitySmithHQ/activitysmith-backend",
+ "commit": "49ad7b083b50f53bc3d81edb77ca6597845160a2",
+ "path": "openapi.json",
+ "sha256": "b7e33cd485df8ddc5d7fb7b57b61106fd6f8ff3ffa7cc7bef4ac1ccbbb0cede7"
+}
diff --git a/package-lock.json b/package-lock.json
index 33d0408..d5b632c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "activitysmith",
- "version": "1.10.0",
+ "version": "1.11.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "activitysmith",
- "version": "1.10.0",
+ "version": "1.11.0",
"license": "MIT",
"devDependencies": {
"typescript": "^5.3.3",
diff --git a/package.json b/package.json
index c69c536..d0b2e27 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "activitysmith",
- "version": "1.10.0",
+ "version": "1.11.0",
"description": "Official ActivitySmith Node.js SDK",
"keywords": [
"activitysmith",
@@ -44,7 +44,7 @@
"node": ">=18"
},
"scripts": {
- "clean": "tsc -b --clean",
+ "clean": "tsc -b --clean && node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
"build": "npm run clean && tsc -b",
"prepublishOnly": "npm run build",
"test": "npm run build && vitest run"
diff --git a/src/ActivitySmith.ts b/src/ActivitySmith.ts
index d262b48..ab7a7d0 100644
--- a/src/ActivitySmith.ts
+++ b/src/ActivitySmith.ts
@@ -6,12 +6,17 @@ import {
PushNotificationsApi,
} from "../generated/index";
-const SDK_VERSION = "1.10.0";
+const SDK_VERSION = "1.11.0";
const SDK_HEADER_NAME = "X-ActivitySmith-SDK";
-const SDK_HEADER_VALUE = `node-v${SDK_VERSION}`;
+const DEFAULT_SDK_NAME = "node";
+const SDK_HEADER_VALUE = `${DEFAULT_SDK_NAME}-v${SDK_VERSION}`;
export interface ActivitySmithOptions {
apiKey: string;
+ sdk?: {
+ name: string;
+ version: string;
+ };
}
type PushRequestBody = Parameters[0]["pushNotificationRequest"];
@@ -123,6 +128,24 @@ function compactObject>(value: T): T {
) as T;
}
+function sdkHeaderValue(sdk: ActivitySmithOptions["sdk"]): string {
+ if (!sdk) {
+ return SDK_HEADER_VALUE;
+ }
+
+ const name = sdk.name.trim().toLowerCase();
+ const version = sdk.version.trim();
+
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
+ throw new Error("ActivitySmith: sdk.name must start with a letter and contain only letters and numbers");
+ }
+ if (!version) {
+ throw new Error("ActivitySmith: sdk.version is required");
+ }
+
+ return `${name}-v${version}`;
+}
+
function contentState(value: LiveActivityContentState): LiveActivityContentState {
return compactObject(value);
}
@@ -379,7 +402,7 @@ export class ActivitySmith {
const config = new Configuration({
accessToken: opts.apiKey,
headers: {
- [SDK_HEADER_NAME]: SDK_HEADER_VALUE,
+ [SDK_HEADER_NAME]: sdkHeaderValue(opts.sdk),
},
});
diff --git a/tests/public-surface.test.js b/tests/public-surface.test.js
new file mode 100644
index 0000000..e713288
--- /dev/null
+++ b/tests/public-surface.test.js
@@ -0,0 +1,11 @@
+import { readdirSync } from "node:fs";
+import { createRequire } from "node:module";
+import { expect, it } from "vitest";
+
+const require = createRequire(import.meta.url);
+it("does not package removed internal APIs or models", () => {
+ const generated = require("../dist/generated/index.js");
+ expect(generated).not.toHaveProperty("PublicApi");
+ const files = readdirSync(new URL("../dist/", import.meta.url), { recursive: true });
+ expect(files.filter(file => /(?:PublicApi|HealthResponse|HealthCheck|ChangelogEntry|ChangelogItem|ChangelogListResponse)\.(?:js|d\.ts)$/.test(file))).toEqual([]);
+});
diff --git a/tests/resources.test.js b/tests/resources.test.js
index 4899022..695d107 100644
--- a/tests/resources.test.js
+++ b/tests/resources.test.js
@@ -806,3 +806,73 @@ describe("resource wrappers", () => {
expect(updateSpy).toHaveBeenCalledWith(request, undefined);
});
});
+
+
+describe("legacy Tags serialization", () => {
+ afterEach(() => vi.unstubAllGlobals());
+ for (const method of ["update", "end"]) {
+ for (const tags of [undefined, ["billing"], []]) {
+ it(`${method} serializes Tags ${JSON.stringify(tags)}`, async () => {
+ const ActivitySmith = require("../dist/src/index.js");
+ let body;
+ vi.stubGlobal("fetch", async (_url, init) => {
+ body = JSON.parse(init.body);
+ return new Response(JSON.stringify({success: true}), {status: 200, headers: {"Content-Type": "application/json"}});
+ });
+ const client = new ActivitySmith({ apiKey: "test" });
+ await client.liveActivities[method]({activity_id: "activity-1", content_state: {title: "Job"}, tags});
+ expect(Object.hasOwn(body, "tags")).toBe(tags !== undefined);
+ expect(body.tags).toEqual(tags);
+ });
+ }
+ }
+});
+
+
+describe("Metadata serialization", () => {
+ afterEach(() => vi.unstubAllGlobals());
+ for (const method of ["send", "start", "update", "end", "stream", "endStream"]) {
+ for (const metadata of [undefined, {}, { order: "382", ready: false, count: 0, empty: "", ratio: 1.25 }]) {
+ it(`${method} preserves Metadata ${JSON.stringify(metadata)}`, async () => {
+ const ActivitySmith = require("../dist/src/index.js");
+ let body;
+ vi.stubGlobal("fetch", async (_url, init) => {
+ body = JSON.parse(init.body);
+ return new Response(JSON.stringify({success: true}), {status: 200, headers: {"Content-Type": "application/json"}});
+ });
+ const client = new ActivitySmith({apiKey: "test"});
+ if (method === "send") await client.notifications.send({title: "Job", metadata});
+ else {
+ const request = {activity_id: "activity-1", content_state: {title: "Job"}, metadata};
+ if (method === "stream" || method === "endStream") await client.liveActivities[method]("job", request);
+ else await client.liveActivities[method](request);
+ }
+ expect(Object.hasOwn(body, "metadata")).toBe(metadata !== undefined);
+ expect(body.metadata).toEqual(metadata);
+ expect(body.content_state?.metadata).toBeUndefined();
+ });
+ }
+ }
+});
+
+
+describe("External URLs and final stream fields", () => {
+ afterEach(() => vi.unstubAllGlobals());
+ it("preserves custom destinations and explicit empty final history fields", async () => {
+ const ActivitySmith = require("../dist/src/index.js");
+ let body;
+ vi.stubGlobal("fetch", async (_url, init) => {
+ body = JSON.parse(init.body);
+ return new Response(JSON.stringify({success:true}), {status:200, headers:{"Content-Type":"application/json"}});
+ });
+ const client = new ActivitySmith({apiKey:"test"});
+ for (const url of ["http://example.com", "https://example.com", "shortcuts://run-shortcut?name=Test", "spotify://", "spotify:track:123"]) {
+ await client.notifications.send({title:"Job",redirection:url,actions:[{title:"Open",type:"open_url",url}]});
+ expect(body.redirection).toBe(url); expect(body.actions[0].url).toBe(url);
+ }
+ for (const tags of [undefined, [], ["finished"]]) {
+ await client.liveActivities.endStream("job", {tags, metadata:{}});
+ expect(body.tags).toEqual(tags); expect(body.metadata).toEqual({});
+ }
+ });
+});
diff --git a/tests/smoke.test.js b/tests/smoke.test.js
index 354ef97..499803e 100644
--- a/tests/smoke.test.js
+++ b/tests/smoke.test.js
@@ -4,6 +4,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
const require = createRequire(import.meta.url);
const packageJson = require("../package.json");
const expectedSdkHeader = `node-v${packageJson.version}`;
+const expectedCliSdkHeader = `cli-v${packageJson.version}`;
describe("smoke", () => {
const originalFetch = globalThis.fetch;
@@ -69,4 +70,34 @@ describe("smoke", () => {
expect(call[1]?.headers?.["X-ActivitySmith-SDK"]).toBe(expectedSdkHeader);
}
});
+
+ it("allows official wrappers to override the SDK tracking identity", async () => {
+ const fetchSpy = vi.fn(async () =>
+ new Response(
+ JSON.stringify({
+ success: true,
+ devices_notified: 1,
+ }),
+ {
+ status: 200,
+ headers: { "content-type": "application/json" },
+ },
+ ),
+ );
+
+ globalThis.fetch = fetchSpy;
+
+ const ActivitySmith = require("../dist/src/index.js");
+ const client = new ActivitySmith({
+ apiKey: "test",
+ sdk: { name: "cli", version: packageJson.version },
+ });
+
+ await client.notifications.send({ title: "Build Failed" });
+
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
+ expect(fetchSpy.mock.calls[0][1]?.headers?.["X-ActivitySmith-SDK"]).toBe(
+ expectedCliSdkHeader,
+ );
+ });
});