diff --git a/README.md b/README.md index 6beca54..00f9698 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ See [API reference](https://activitysmith.com/docs/api-reference/introduction) - [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) - [Channels](#channels) - [Widgets](#widgets) @@ -235,14 +237,6 @@ await activitysmith.liveActivities.stream("customer-ops", { }); ``` -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 - -`icon` and `badge` are optional. If you omit either one, that element is not shown in the Live Activity. - ### 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. @@ -316,6 +310,65 @@ await activitysmith.liveActivities.stream("search-reindex", { }); ``` +### Icons and Badges + +Add more context to Live Activities with icons and badges. + +#### Icon + +Supported Live Activity types: `stats`, `metrics`, `progress`, `segmented_progress`, and `alert`. + +

+ Metrics Live Activity with an SF Symbol icon on the iPhone Lock Screen +

+ +```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. + +

+ Progress Live Activity with a badge on the iPhone Lock Screen +

+ +```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` + ## Channels Channels are used to target specific team members or devices. Can be used for both push notifications and live activities. diff --git a/generated/models/index.ts b/generated/models/index.ts index 32373dd..6982bb7 100644 --- a/generated/models/index.ts +++ b/generated/models/index.ts @@ -112,7 +112,7 @@ export interface ChannelTarget { channels: Array; } /** - * End payload requires title. For segmented_progress include current_step and optionally number_of_steps. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message, with optional icon and badge. Type is optional when ending an existing activity. You can send an updated number_of_steps here if the workflow changed after start. + * End payload requires title. For segmented_progress include current_step and optionally number_of_steps. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message. Optional icon is supported by all Live Activity types. Optional badge is supported by alert, progress, and segmented_progress. Type is optional when ending an existing activity. You can send an updated number_of_steps here if the workflow changed after start. * @export * @interface ContentStateEnd */ @@ -173,13 +173,13 @@ export interface ContentStateEnd { */ message?: string; /** - * Optional SF Symbol icon for type=alert. + * Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, and stats. * @type {LiveActivityAlertIcon} * @memberof ContentStateEnd */ icon?: LiveActivityAlertIcon; /** - * Optional badge for type=alert. + * Optional badge. Supported by alert, progress, and segmented_progress. * @type {LiveActivityAlertBadge} * @memberof ContentStateEnd */ @@ -281,7 +281,7 @@ export const ContentStateEndStepColorsEnum = { export type ContentStateEndStepColorsEnum = typeof ContentStateEndStepColorsEnum[keyof typeof ContentStateEndStepColorsEnum]; /** - * Start payload requires title and type. For segmented_progress include number_of_steps and current_step. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message, with optional icon and badge. For segmented_progress, number_of_steps is not locked and can be changed in later update or end calls. + * Start payload requires title and type. For segmented_progress include number_of_steps and current_step. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message. Optional icon is supported by all Live Activity types. Optional badge is supported by alert, progress, and segmented_progress. For segmented_progress, number_of_steps is not locked and can be changed in later update or end calls. * @export * @interface ContentStateStart */ @@ -342,13 +342,13 @@ export interface ContentStateStart { */ message?: string; /** - * Optional SF Symbol icon for type=alert. + * Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, and stats. * @type {LiveActivityAlertIcon} * @memberof ContentStateStart */ icon?: LiveActivityAlertIcon; /** - * Optional badge for type=alert. + * Optional badge. Supported by alert, progress, and segmented_progress. * @type {LiveActivityAlertBadge} * @memberof ContentStateStart */ @@ -444,7 +444,7 @@ export const ContentStateStartStepColorsEnum = { export type ContentStateStartStepColorsEnum = typeof ContentStateStartStepColorsEnum[keyof typeof ContentStateStartStepColorsEnum]; /** - * Update payload requires title. For segmented_progress include current_step and optionally number_of_steps. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message, with optional icon and badge. Type is optional when updating an existing activity. You can increase or decrease number_of_steps during updates. + * Update payload requires title. For segmented_progress include current_step and optionally number_of_steps. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message. Optional icon is supported by all Live Activity types. Optional badge is supported by alert, progress, and segmented_progress. Type is optional when updating an existing activity. You can increase or decrease number_of_steps during updates. * @export * @interface ContentStateUpdate */ @@ -505,13 +505,13 @@ export interface ContentStateUpdate { */ message?: string; /** - * Optional SF Symbol icon for type=alert. + * Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, and stats. * @type {LiveActivityAlertIcon} * @memberof ContentStateUpdate */ icon?: LiveActivityAlertIcon; /** - * Optional badge for type=alert. + * Optional badge. Supported by alert, progress, and segmented_progress. * @type {LiveActivityAlertBadge} * @memberof ContentStateUpdate */ @@ -646,7 +646,7 @@ export interface LiveActivityAction { */ type: LiveActivityActionType; /** - * HTTPS URL. For open_url it is opened in browser. For webhook it is called by ActivitySmith backend. + * Action URL. For open_url, use an HTTPS or shortcuts:// URL. For webhook, use an HTTPS URL called by the ActivitySmith backend. * @type {string} * @memberof LiveActivityAction */ @@ -676,7 +676,7 @@ export const LiveActivityActionType = { export type LiveActivityActionType = typeof LiveActivityActionType[keyof typeof LiveActivityActionType]; /** - * Optional badge for Alert Live Activities. + * Optional badge for Live Activities. * @export * @interface LiveActivityAlertBadge */ @@ -696,7 +696,7 @@ export interface LiveActivityAlertBadge { color?: LiveActivityColor; } /** - * Optional SF Symbol icon for Alert Live Activities. + * Optional SF Symbol icon for Live Activities. * @export * @interface LiveActivityAlertIcon */ @@ -1313,7 +1313,7 @@ export interface PushNotificationAction { */ type: PushNotificationActionType; /** - * HTTPS URL. For open_url it is opened in browser. For webhook it is called by ActivitySmith backend. + * Action URL. For open_url, use an HTTPS or shortcuts:// URL. For webhook, use an HTTPS URL called by the ActivitySmith backend. * @type {string} * @memberof PushNotificationAction */ @@ -1374,7 +1374,7 @@ export interface PushNotificationRequest { */ media?: string; /** - * Optional HTTPS URL opened when user taps the notification body. Overrides the default tap target from `media` when both are provided. + * Optional HTTPS or shortcuts:// URL opened when user taps the notification body. Overrides the default tap target from `media` when both are provided. * @type {string} * @memberof PushNotificationRequest */ @@ -1571,13 +1571,13 @@ export interface StreamContentState { */ message?: string; /** - * Optional SF Symbol icon for type=alert. + * Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, and stats. * @type {LiveActivityAlertIcon} * @memberof StreamContentState */ icon?: LiveActivityAlertIcon; /** - * Optional badge for type=alert. + * Optional badge. Supported by alert, progress, and segmented_progress. * @type {LiveActivityAlertBadge} * @memberof StreamContentState */ diff --git a/tests/resources.test.js b/tests/resources.test.js index 505e040..575418f 100644 --- a/tests/resources.test.js +++ b/tests/resources.test.js @@ -69,6 +69,50 @@ describe("resource wrappers", () => { expect(sendSpy).toHaveBeenCalledWith({ pushNotificationRequest: payload }, undefined); }); + it("preserves shortcuts redirection for notifications.send", async () => { + const ActivitySmith = require("../dist/src/index.js"); + const generated = require("../dist/generated/index.js"); + + const sendSpy = vi + .spyOn(generated.PushNotificationsApi.prototype, "sendPushNotification") + .mockResolvedValue({ success: true }); + + const client = new ActivitySmith({ apiKey: "test" }); + const payload = { + title: "Task finished", + redirection: "shortcuts://run-shortcut?name=Jarvis", + }; + + await client.notifications.send(payload); + + expect(sendSpy).toHaveBeenCalledWith({ pushNotificationRequest: payload }, undefined); + }); + + it("preserves shortcuts open_url actions for notifications.send", async () => { + const ActivitySmith = require("../dist/src/index.js"); + const generated = require("../dist/generated/index.js"); + + const sendSpy = vi + .spyOn(generated.PushNotificationsApi.prototype, "sendPushNotification") + .mockResolvedValue({ success: true }); + + const client = new ActivitySmith({ apiKey: "test" }); + const payload = { + title: "Task finished", + actions: [ + { + title: "Run Shortcut", + type: "open_url", + url: "shortcuts://run-shortcut?name=Jarvis", + }, + ], + }; + + await client.notifications.send(payload); + + expect(sendSpy).toHaveBeenCalledWith({ pushNotificationRequest: payload }, undefined); + }); + it("rejects media with actions for notifications.send", async () => { const ActivitySmith = require("../dist/src/index.js"); @@ -295,6 +339,68 @@ describe("resource wrappers", () => { ); }); + it("passes through icon and badge on non-alert live activity types", async () => { + const ActivitySmith = require("../dist/src/index.js"); + const generated = require("../dist/generated/index.js"); + + const streamSpy = vi + .spyOn(generated.LiveActivitiesApi.prototype, "reconcileLiveActivityStream") + .mockResolvedValue({ operation: "started", stream_key: "prod-web-1" }); + + const client = new ActivitySmith({ apiKey: "test" }); + await client.liveActivities.stream("prod-web-1", { + content_state: ActivitySmith.contentState({ + title: "Server Health", + subtitle: "prod-web-1", + type: ActivitySmith.liveActivityTypes.metrics, + icon: ActivitySmith.alertIcon("server.rack", { color: "blue" }), + metrics: [{ label: "CPU", value: 18, unit: "%" }], + }), + }); + await client.liveActivities.stream("nightly-database-backup", { + content_state: ActivitySmith.contentState({ + title: "Nightly Database Backup", + subtitle: "verify restore", + type: ActivitySmith.liveActivityTypes.progress, + badge: ActivitySmith.alertBadge("S3", { color: "cyan" }), + percentage: 62, + }), + }); + + expect(streamSpy).toHaveBeenNthCalledWith( + 1, + { + streamKey: "prod-web-1", + liveActivityStreamRequest: { + content_state: { + title: "Server Health", + subtitle: "prod-web-1", + type: ActivitySmith.liveActivityTypes.metrics, + icon: { symbol: "server.rack", color: "blue" }, + metrics: [{ label: "CPU", value: 18, unit: "%" }], + }, + }, + }, + undefined, + ); + expect(streamSpy).toHaveBeenNthCalledWith( + 2, + { + streamKey: "nightly-database-backup", + liveActivityStreamRequest: { + content_state: { + title: "Nightly Database Backup", + subtitle: "verify restore", + type: ActivitySmith.liveActivityTypes.progress, + badge: { title: "S3", color: "cyan" }, + percentage: 62, + }, + }, + }, + undefined, + ); + }); + it("wraps live activity stream payloads for short methods", async () => { const ActivitySmith = require("../dist/src/index.js"); const generated = require("../dist/generated/index.js"); @@ -394,7 +500,7 @@ describe("resource wrappers", () => { action: { title: "Open Workflow", type: "open_url", - url: "https://github.com/acme/payments-api/actions/runs/1234567890", + url: "shortcuts://run-shortcut?name=Deploy%20Status", }, }; @@ -426,7 +532,7 @@ describe("resource wrappers", () => { action: { title: "Open Workflow", type: "open_url", - url: "https://github.com/acme/payments-api/actions/runs/1234567890", + url: "shortcuts://run-shortcut?name=Deploy%20Status", }, };