diff --git a/README.md b/README.md
index 99b3147..b217411 100644
--- a/README.md
+++ b/README.md
@@ -245,6 +245,59 @@ $activitysmith->liveActivities->end([
]);
```
+### Live Activity Action
+
+Just like Actionable Push Notifications, Live Activities can have a button that opens provided URL in a browser or triggers a webhook. Webhooks are executed by the ActivitySmith backend.
+
+
+
+
+
+#### Open URL action
+
+```php
+$start = $activitysmith->liveActivities->start([
+ 'content_state' => [
+ 'title' => 'Deploying payments-api',
+ 'subtitle' => 'Running database migrations',
+ 'number_of_steps' => 5,
+ 'current_step' => 3,
+ 'type' => 'segmented_progress',
+ ],
+ 'action' => [
+ 'title' => 'Open Workflow',
+ 'type' => 'open_url',
+ 'url' => 'https://github.com/acme/payments-api/actions/runs/1234567890',
+ ],
+]);
+
+$activityId = $start->getActivityId();
+```
+
+#### Webhook action
+
+```php
+$activitysmith->liveActivities->update([
+ 'activity_id' => $activityId,
+ 'content_state' => [
+ 'title' => 'Reindexing product search',
+ 'subtitle' => 'Shard 7 of 12',
+ 'number_of_steps' => 12,
+ 'current_step' => 7,
+ ],
+ 'action' => [
+ 'title' => 'Pause Reindex',
+ 'type' => 'webhook',
+ 'url' => 'https://ops.example.com/hooks/search/reindex/pause',
+ 'method' => 'POST',
+ 'body' => [
+ 'job_id' => 'reindex-2026-03-19',
+ 'requested_by' => 'activitysmith-php',
+ ],
+ ],
+]);
+```
+
## 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/Configuration.php b/generated/Configuration.php
index c49b238..ef29e0f 100644
--- a/generated/Configuration.php
+++ b/generated/Configuration.php
@@ -100,7 +100,7 @@ class Configuration
*
* @var string
*/
- protected $userAgent = 'OpenAPI-Generator/0.1.5/PHP';
+ protected $userAgent = 'OpenAPI-Generator/1.0.0/PHP';
/**
* Debug switch (default set to false)
@@ -433,7 +433,7 @@ public static function toDebugReport()
$report .= ' OS: ' . php_uname() . PHP_EOL;
$report .= ' PHP Version: ' . PHP_VERSION . PHP_EOL;
$report .= ' The version of the OpenAPI document: 1.0.0' . PHP_EOL;
- $report .= ' SDK Package Version: 0.1.5' . PHP_EOL;
+ $report .= ' SDK Package Version: 1.0.0' . PHP_EOL;
$report .= ' Temp Folder Path: ' . self::getDefaultConfiguration()->getTempFolderPath() . PHP_EOL;
return $report;
diff --git a/generated/Model/LiveActivityAction.php b/generated/Model/LiveActivityAction.php
new file mode 100644
index 0000000..f1030bc
--- /dev/null
+++ b/generated/Model/LiveActivityAction.php
@@ -0,0 +1,564 @@
+
+ */
+class LiveActivityAction implements ModelInterface, ArrayAccess, \JsonSerializable
+{
+ public const DISCRIMINATOR = null;
+
+ /**
+ * The original name of the model.
+ *
+ * @var string
+ */
+ protected static $openAPIModelName = 'LiveActivityAction';
+
+ /**
+ * Array of property to type mappings. Used for (de)serialization
+ *
+ * @var string[]
+ */
+ protected static $openAPITypes = [
+ 'title' => 'string',
+ 'type' => '\ActivitySmith\Generated\Model\LiveActivityActionType',
+ 'url' => 'string',
+ 'method' => '\ActivitySmith\Generated\Model\LiveActivityWebhookMethod',
+ 'body' => 'array'
+ ];
+
+ /**
+ * Array of property to format mappings. Used for (de)serialization
+ *
+ * @var string[]
+ * @phpstan-var array
+ * @psalm-var array
+ */
+ protected static $openAPIFormats = [
+ 'title' => null,
+ 'type' => null,
+ 'url' => 'uri',
+ 'method' => null,
+ 'body' => null
+ ];
+
+ /**
+ * Array of nullable properties. Used for (de)serialization
+ *
+ * @var boolean[]
+ */
+ protected static array $openAPINullables = [
+ 'title' => false,
+ 'type' => false,
+ 'url' => false,
+ 'method' => false,
+ 'body' => false
+ ];
+
+ /**
+ * If a nullable field gets set to null, insert it here
+ *
+ * @var boolean[]
+ */
+ protected array $openAPINullablesSetToNull = [];
+
+ /**
+ * Array of property to type mappings. Used for (de)serialization
+ *
+ * @return array
+ */
+ public static function openAPITypes()
+ {
+ return self::$openAPITypes;
+ }
+
+ /**
+ * Array of property to format mappings. Used for (de)serialization
+ *
+ * @return array
+ */
+ public static function openAPIFormats()
+ {
+ return self::$openAPIFormats;
+ }
+
+ /**
+ * Array of nullable properties
+ *
+ * @return array
+ */
+ protected static function openAPINullables(): array
+ {
+ return self::$openAPINullables;
+ }
+
+ /**
+ * Array of nullable field names deliberately set to null
+ *
+ * @return boolean[]
+ */
+ private function getOpenAPINullablesSetToNull(): array
+ {
+ return $this->openAPINullablesSetToNull;
+ }
+
+ /**
+ * Setter - Array of nullable field names deliberately set to null
+ *
+ * @param boolean[] $openAPINullablesSetToNull
+ */
+ private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void
+ {
+ $this->openAPINullablesSetToNull = $openAPINullablesSetToNull;
+ }
+
+ /**
+ * Checks if a property is nullable
+ *
+ * @param string $property
+ * @return bool
+ */
+ public static function isNullable(string $property): bool
+ {
+ return self::openAPINullables()[$property] ?? false;
+ }
+
+ /**
+ * Checks if a nullable property is set to null.
+ *
+ * @param string $property
+ * @return bool
+ */
+ public function isNullableSetToNull(string $property): bool
+ {
+ return in_array($property, $this->getOpenAPINullablesSetToNull(), true);
+ }
+
+ /**
+ * Array of attributes where the key is the local name,
+ * and the value is the original name
+ *
+ * @var string[]
+ */
+ protected static $attributeMap = [
+ 'title' => 'title',
+ 'type' => 'type',
+ 'url' => 'url',
+ 'method' => 'method',
+ 'body' => 'body'
+ ];
+
+ /**
+ * Array of attributes to setter functions (for deserialization of responses)
+ *
+ * @var string[]
+ */
+ protected static $setters = [
+ 'title' => 'setTitle',
+ 'type' => 'setType',
+ 'url' => 'setUrl',
+ 'method' => 'setMethod',
+ 'body' => 'setBody'
+ ];
+
+ /**
+ * Array of attributes to getter functions (for serialization of requests)
+ *
+ * @var string[]
+ */
+ protected static $getters = [
+ 'title' => 'getTitle',
+ 'type' => 'getType',
+ 'url' => 'getUrl',
+ 'method' => 'getMethod',
+ 'body' => 'getBody'
+ ];
+
+ /**
+ * Array of attributes where the key is the local name,
+ * and the value is the original name
+ *
+ * @return array
+ */
+ public static function attributeMap()
+ {
+ return self::$attributeMap;
+ }
+
+ /**
+ * Array of attributes to setter functions (for deserialization of responses)
+ *
+ * @return array
+ */
+ public static function setters()
+ {
+ return self::$setters;
+ }
+
+ /**
+ * Array of attributes to getter functions (for serialization of requests)
+ *
+ * @return array
+ */
+ public static function getters()
+ {
+ return self::$getters;
+ }
+
+ /**
+ * The original name of the model.
+ *
+ * @return string
+ */
+ public function getModelName()
+ {
+ return self::$openAPIModelName;
+ }
+
+
+ /**
+ * Associative array for storing property values
+ *
+ * @var mixed[]
+ */
+ protected $container = [];
+
+ /**
+ * Constructor
+ *
+ * @param mixed[] $data Associated array of property values
+ * initializing the model
+ */
+ public function __construct(array $data = null)
+ {
+ $this->setIfExists('title', $data ?? [], null);
+ $this->setIfExists('type', $data ?? [], null);
+ $this->setIfExists('url', $data ?? [], null);
+ $this->setIfExists('method', $data ?? [], null);
+ $this->setIfExists('body', $data ?? [], null);
+ }
+
+ /**
+ * Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName
+ * is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the
+ * $this->openAPINullablesSetToNull array
+ *
+ * @param string $variableName
+ * @param array $fields
+ * @param mixed $defaultValue
+ */
+ private function setIfExists(string $variableName, array $fields, $defaultValue): void
+ {
+ if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) {
+ $this->openAPINullablesSetToNull[] = $variableName;
+ }
+
+ $this->container[$variableName] = $fields[$variableName] ?? $defaultValue;
+ }
+
+ /**
+ * Show all the invalid properties with reasons.
+ *
+ * @return array invalid properties with reasons
+ */
+ public function listInvalidProperties()
+ {
+ $invalidProperties = [];
+
+ if ($this->container['title'] === null) {
+ $invalidProperties[] = "'title' can't be null";
+ }
+ if ($this->container['type'] === null) {
+ $invalidProperties[] = "'type' can't be null";
+ }
+ if ($this->container['url'] === null) {
+ $invalidProperties[] = "'url' can't be null";
+ }
+ if (!preg_match("/^https:\/\//", $this->container['url'])) {
+ $invalidProperties[] = "invalid value for 'url', must be conform to the pattern /^https:\/\//.";
+ }
+
+ return $invalidProperties;
+ }
+
+ /**
+ * Validate all the properties in the model
+ * return true if all passed
+ *
+ * @return bool True if all properties are valid
+ */
+ public function valid()
+ {
+ return count($this->listInvalidProperties()) === 0;
+ }
+
+
+ /**
+ * Gets title
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ return $this->container['title'];
+ }
+
+ /**
+ * Sets title
+ *
+ * @param string $title Button title displayed in the Live Activity UI.
+ *
+ * @return self
+ */
+ public function setTitle($title)
+ {
+ if (is_null($title)) {
+ throw new \InvalidArgumentException('non-nullable title cannot be null');
+ }
+ $this->container['title'] = $title;
+
+ return $this;
+ }
+
+ /**
+ * Gets type
+ *
+ * @return \ActivitySmith\Generated\Model\LiveActivityActionType
+ */
+ public function getType()
+ {
+ return $this->container['type'];
+ }
+
+ /**
+ * Sets type
+ *
+ * @param \ActivitySmith\Generated\Model\LiveActivityActionType $type type
+ *
+ * @return self
+ */
+ public function setType($type)
+ {
+ if (is_null($type)) {
+ throw new \InvalidArgumentException('non-nullable type cannot be null');
+ }
+ $this->container['type'] = $type;
+
+ return $this;
+ }
+
+ /**
+ * Gets url
+ *
+ * @return string
+ */
+ public function getUrl()
+ {
+ return $this->container['url'];
+ }
+
+ /**
+ * Sets url
+ *
+ * @param string $url HTTPS URL. For open_url it is opened in browser. For webhook it is called by ActivitySmith backend.
+ *
+ * @return self
+ */
+ public function setUrl($url)
+ {
+ if (is_null($url)) {
+ throw new \InvalidArgumentException('non-nullable url cannot be null');
+ }
+
+ if ((!preg_match("/^https:\/\//", ObjectSerializer::toString($url)))) {
+ throw new \InvalidArgumentException("invalid value for \$url when calling LiveActivityAction., must conform to the pattern /^https:\/\//.");
+ }
+
+ $this->container['url'] = $url;
+
+ return $this;
+ }
+
+ /**
+ * Gets method
+ *
+ * @return \ActivitySmith\Generated\Model\LiveActivityWebhookMethod|null
+ */
+ public function getMethod()
+ {
+ return $this->container['method'];
+ }
+
+ /**
+ * Sets method
+ *
+ * @param \ActivitySmith\Generated\Model\LiveActivityWebhookMethod|null $method Webhook HTTP method. Used only when type=webhook.
+ *
+ * @return self
+ */
+ public function setMethod($method)
+ {
+ if (is_null($method)) {
+ throw new \InvalidArgumentException('non-nullable method cannot be null');
+ }
+ $this->container['method'] = $method;
+
+ return $this;
+ }
+
+ /**
+ * Gets body
+ *
+ * @return array|null
+ */
+ public function getBody()
+ {
+ return $this->container['body'];
+ }
+
+ /**
+ * Sets body
+ *
+ * @param array|null $body Optional webhook payload body. Used only when type=webhook.
+ *
+ * @return self
+ */
+ public function setBody($body)
+ {
+ if (is_null($body)) {
+ throw new \InvalidArgumentException('non-nullable body cannot be null');
+ }
+ $this->container['body'] = $body;
+
+ return $this;
+ }
+ /**
+ * Returns true if offset exists. False otherwise.
+ *
+ * @param integer $offset Offset
+ *
+ * @return boolean
+ */
+ public function offsetExists($offset): bool
+ {
+ return isset($this->container[$offset]);
+ }
+
+ /**
+ * Gets offset.
+ *
+ * @param integer $offset Offset
+ *
+ * @return mixed|null
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($offset)
+ {
+ return $this->container[$offset] ?? null;
+ }
+
+ /**
+ * Sets value based on offset.
+ *
+ * @param int|null $offset Offset
+ * @param mixed $value Value to be set
+ *
+ * @return void
+ */
+ public function offsetSet($offset, $value): void
+ {
+ if (is_null($offset)) {
+ $this->container[] = $value;
+ } else {
+ $this->container[$offset] = $value;
+ }
+ }
+
+ /**
+ * Unsets offset.
+ *
+ * @param integer $offset Offset
+ *
+ * @return void
+ */
+ public function offsetUnset($offset): void
+ {
+ unset($this->container[$offset]);
+ }
+
+ /**
+ * Serializes the object to a value that can be serialized natively by json_encode().
+ * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php
+ *
+ * @return mixed Returns data which can be serialized by json_encode(), which is a value
+ * of any type other than a resource.
+ */
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ return ObjectSerializer::sanitizeForSerialization($this);
+ }
+
+ /**
+ * Gets the string presentation of the object
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return json_encode(
+ ObjectSerializer::sanitizeForSerialization($this),
+ JSON_PRETTY_PRINT
+ );
+ }
+
+ /**
+ * Gets a header-safe presentation of the object
+ *
+ * @return string
+ */
+ public function toHeaderValue()
+ {
+ return json_encode(ObjectSerializer::sanitizeForSerialization($this));
+ }
+}
+
+
diff --git a/generated/Model/LiveActivityActionType.php b/generated/Model/LiveActivityActionType.php
new file mode 100644
index 0000000..64f837c
--- /dev/null
+++ b/generated/Model/LiveActivityActionType.php
@@ -0,0 +1,62 @@
+ 'string',
- 'contentState' => '\ActivitySmith\Generated\Model\ContentStateEnd'
+ 'contentState' => '\ActivitySmith\Generated\Model\ContentStateEnd',
+ 'action' => '\ActivitySmith\Generated\Model\LiveActivityAction'
];
/**
@@ -71,7 +72,8 @@ class LiveActivityEndRequest implements ModelInterface, ArrayAccess, \JsonSerial
*/
protected static $openAPIFormats = [
'activityId' => null,
- 'contentState' => null
+ 'contentState' => null,
+ 'action' => null
];
/**
@@ -81,7 +83,8 @@ class LiveActivityEndRequest implements ModelInterface, ArrayAccess, \JsonSerial
*/
protected static array $openAPINullables = [
'activityId' => false,
- 'contentState' => false
+ 'contentState' => false,
+ 'action' => false
];
/**
@@ -171,7 +174,8 @@ public function isNullableSetToNull(string $property): bool
*/
protected static $attributeMap = [
'activityId' => 'activity_id',
- 'contentState' => 'content_state'
+ 'contentState' => 'content_state',
+ 'action' => 'action'
];
/**
@@ -181,7 +185,8 @@ public function isNullableSetToNull(string $property): bool
*/
protected static $setters = [
'activityId' => 'setActivityId',
- 'contentState' => 'setContentState'
+ 'contentState' => 'setContentState',
+ 'action' => 'setAction'
];
/**
@@ -191,7 +196,8 @@ public function isNullableSetToNull(string $property): bool
*/
protected static $getters = [
'activityId' => 'getActivityId',
- 'contentState' => 'getContentState'
+ 'contentState' => 'getContentState',
+ 'action' => 'getAction'
];
/**
@@ -253,6 +259,7 @@ public function __construct(array $data = null)
{
$this->setIfExists('activityId', $data ?? [], null);
$this->setIfExists('contentState', $data ?? [], null);
+ $this->setIfExists('action', $data ?? [], null);
}
/**
@@ -356,6 +363,33 @@ public function setContentState($contentState)
return $this;
}
+
+ /**
+ * Gets action
+ *
+ * @return \ActivitySmith\Generated\Model\LiveActivityAction|null
+ */
+ public function getAction()
+ {
+ return $this->container['action'];
+ }
+
+ /**
+ * Sets action
+ *
+ * @param \ActivitySmith\Generated\Model\LiveActivityAction|null $action action
+ *
+ * @return self
+ */
+ public function setAction($action)
+ {
+ if (is_null($action)) {
+ throw new \InvalidArgumentException('non-nullable action cannot be null');
+ }
+ $this->container['action'] = $action;
+
+ return $this;
+ }
/**
* Returns true if offset exists. False otherwise.
*
diff --git a/generated/Model/LiveActivityStartRequest.php b/generated/Model/LiveActivityStartRequest.php
index 049d386..96ce673 100644
--- a/generated/Model/LiveActivityStartRequest.php
+++ b/generated/Model/LiveActivityStartRequest.php
@@ -59,6 +59,7 @@ class LiveActivityStartRequest implements ModelInterface, ArrayAccess, \JsonSeri
*/
protected static $openAPITypes = [
'contentState' => '\ActivitySmith\Generated\Model\ContentStateStart',
+ 'action' => '\ActivitySmith\Generated\Model\LiveActivityAction',
'alert' => '\ActivitySmith\Generated\Model\AlertPayload',
'target' => '\ActivitySmith\Generated\Model\ChannelTarget'
];
@@ -72,6 +73,7 @@ class LiveActivityStartRequest implements ModelInterface, ArrayAccess, \JsonSeri
*/
protected static $openAPIFormats = [
'contentState' => null,
+ 'action' => null,
'alert' => null,
'target' => null
];
@@ -83,6 +85,7 @@ class LiveActivityStartRequest implements ModelInterface, ArrayAccess, \JsonSeri
*/
protected static array $openAPINullables = [
'contentState' => false,
+ 'action' => false,
'alert' => false,
'target' => false
];
@@ -174,6 +177,7 @@ public function isNullableSetToNull(string $property): bool
*/
protected static $attributeMap = [
'contentState' => 'content_state',
+ 'action' => 'action',
'alert' => 'alert',
'target' => 'target'
];
@@ -185,6 +189,7 @@ public function isNullableSetToNull(string $property): bool
*/
protected static $setters = [
'contentState' => 'setContentState',
+ 'action' => 'setAction',
'alert' => 'setAlert',
'target' => 'setTarget'
];
@@ -196,6 +201,7 @@ public function isNullableSetToNull(string $property): bool
*/
protected static $getters = [
'contentState' => 'getContentState',
+ 'action' => 'getAction',
'alert' => 'getAlert',
'target' => 'getTarget'
];
@@ -258,6 +264,7 @@ public function getModelName()
public function __construct(array $data = null)
{
$this->setIfExists('contentState', $data ?? [], null);
+ $this->setIfExists('action', $data ?? [], null);
$this->setIfExists('alert', $data ?? [], null);
$this->setIfExists('target', $data ?? [], null);
}
@@ -334,6 +341,33 @@ public function setContentState($contentState)
return $this;
}
+ /**
+ * Gets action
+ *
+ * @return \ActivitySmith\Generated\Model\LiveActivityAction|null
+ */
+ public function getAction()
+ {
+ return $this->container['action'];
+ }
+
+ /**
+ * Sets action
+ *
+ * @param \ActivitySmith\Generated\Model\LiveActivityAction|null $action action
+ *
+ * @return self
+ */
+ public function setAction($action)
+ {
+ if (is_null($action)) {
+ throw new \InvalidArgumentException('non-nullable action cannot be null');
+ }
+ $this->container['action'] = $action;
+
+ return $this;
+ }
+
/**
* Gets alert
*
diff --git a/generated/Model/LiveActivityUpdateRequest.php b/generated/Model/LiveActivityUpdateRequest.php
index 5c3e280..4ba916f 100644
--- a/generated/Model/LiveActivityUpdateRequest.php
+++ b/generated/Model/LiveActivityUpdateRequest.php
@@ -59,7 +59,8 @@ class LiveActivityUpdateRequest implements ModelInterface, ArrayAccess, \JsonSer
*/
protected static $openAPITypes = [
'activityId' => 'string',
- 'contentState' => '\ActivitySmith\Generated\Model\ContentStateUpdate'
+ 'contentState' => '\ActivitySmith\Generated\Model\ContentStateUpdate',
+ 'action' => '\ActivitySmith\Generated\Model\LiveActivityAction'
];
/**
@@ -71,7 +72,8 @@ class LiveActivityUpdateRequest implements ModelInterface, ArrayAccess, \JsonSer
*/
protected static $openAPIFormats = [
'activityId' => null,
- 'contentState' => null
+ 'contentState' => null,
+ 'action' => null
];
/**
@@ -81,7 +83,8 @@ class LiveActivityUpdateRequest implements ModelInterface, ArrayAccess, \JsonSer
*/
protected static array $openAPINullables = [
'activityId' => false,
- 'contentState' => false
+ 'contentState' => false,
+ 'action' => false
];
/**
@@ -171,7 +174,8 @@ public function isNullableSetToNull(string $property): bool
*/
protected static $attributeMap = [
'activityId' => 'activity_id',
- 'contentState' => 'content_state'
+ 'contentState' => 'content_state',
+ 'action' => 'action'
];
/**
@@ -181,7 +185,8 @@ public function isNullableSetToNull(string $property): bool
*/
protected static $setters = [
'activityId' => 'setActivityId',
- 'contentState' => 'setContentState'
+ 'contentState' => 'setContentState',
+ 'action' => 'setAction'
];
/**
@@ -191,7 +196,8 @@ public function isNullableSetToNull(string $property): bool
*/
protected static $getters = [
'activityId' => 'getActivityId',
- 'contentState' => 'getContentState'
+ 'contentState' => 'getContentState',
+ 'action' => 'getAction'
];
/**
@@ -253,6 +259,7 @@ public function __construct(array $data = null)
{
$this->setIfExists('activityId', $data ?? [], null);
$this->setIfExists('contentState', $data ?? [], null);
+ $this->setIfExists('action', $data ?? [], null);
}
/**
@@ -356,6 +363,33 @@ public function setContentState($contentState)
return $this;
}
+
+ /**
+ * Gets action
+ *
+ * @return \ActivitySmith\Generated\Model\LiveActivityAction|null
+ */
+ public function getAction()
+ {
+ return $this->container['action'];
+ }
+
+ /**
+ * Sets action
+ *
+ * @param \ActivitySmith\Generated\Model\LiveActivityAction|null $action action
+ *
+ * @return self
+ */
+ public function setAction($action)
+ {
+ if (is_null($action)) {
+ throw new \InvalidArgumentException('non-nullable action cannot be null');
+ }
+ $this->container['action'] = $action;
+
+ return $this;
+ }
/**
* Returns true if offset exists. False otherwise.
*
diff --git a/generated/Model/LiveActivityWebhookMethod.php b/generated/Model/LiveActivityWebhookMethod.php
new file mode 100644
index 0000000..cca53a8
--- /dev/null
+++ b/generated/Model/LiveActivityWebhookMethod.php
@@ -0,0 +1,62 @@
+ true];
+ $captured = [
+ 'start' => [],
+ 'update' => [],
+ 'end' => [],
+ ];
+
+ $api = $this->getMockBuilder(LiveActivitiesApi::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods(['startLiveActivity', 'updateLiveActivity', 'endLiveActivity'])
+ ->getMock();
+
+ $api->expects($this->once())
+ ->method('startLiveActivity')
+ ->willReturnCallback(function (...$args) use (&$captured, $response) {
+ $captured['start'][] = $args;
+ return $response;
+ });
+
+ $api->expects($this->once())
+ ->method('updateLiveActivity')
+ ->willReturnCallback(function (...$args) use (&$captured, $response) {
+ $captured['update'][] = $args;
+ return $response;
+ });
+
+ $api->expects($this->once())
+ ->method('endLiveActivity')
+ ->willReturnCallback(function (...$args) use (&$captured, $response) {
+ $captured['end'][] = $args;
+ return $response;
+ });
+
+ $resource = new LiveActivities($api);
+
+ $startPayload = [
+ 'content_state' => [
+ 'title' => 'Deploying payments-api',
+ 'subtitle' => 'Running database migrations',
+ 'number_of_steps' => 5,
+ 'current_step' => 3,
+ 'type' => 'segmented_progress',
+ ],
+ 'action' => [
+ 'title' => 'Open Workflow',
+ 'type' => 'open_url',
+ 'url' => 'https://github.com/acme/payments-api/actions/runs/1234567890',
+ ],
+ ];
+
+ $updatePayload = [
+ 'activity_id' => 'act-1',
+ 'content_state' => [
+ 'title' => 'Reindexing product search',
+ 'subtitle' => 'Shard 7 of 12',
+ 'number_of_steps' => 12,
+ 'current_step' => 7,
+ ],
+ 'action' => [
+ 'title' => 'Pause Reindex',
+ 'type' => 'webhook',
+ 'url' => 'https://ops.example.com/hooks/search/reindex/pause',
+ 'method' => 'POST',
+ 'body' => [
+ 'job_id' => 'reindex-2026-03-19',
+ ],
+ ],
+ ];
+
+ $endPayload = [
+ 'activity_id' => 'act-1',
+ 'content_state' => [
+ 'title' => 'Deploying payments-api',
+ 'subtitle' => 'Production rollout complete',
+ 'number_of_steps' => 5,
+ 'current_step' => 5,
+ ],
+ 'action' => [
+ 'title' => 'Open Workflow',
+ 'type' => 'open_url',
+ 'url' => 'https://github.com/acme/payments-api/actions/runs/1234567890',
+ ],
+ ];
+
+ $resource->start($startPayload);
+ $resource->update($updatePayload);
+ $resource->end($endPayload);
+
+ $this->assertSame(
+ [[$startPayload, LiveActivitiesApi::contentTypes['startLiveActivity'][0]]],
+ $captured['start']
+ );
+ $this->assertSame(
+ [[$updatePayload, LiveActivitiesApi::contentTypes['updateLiveActivity'][0]]],
+ $captured['update']
+ );
+ $this->assertSame(
+ [[$endPayload, LiveActivitiesApi::contentTypes['endLiveActivity'][0]]],
+ $captured['end']
+ );
+ }
+
public function testLiveActivitiesSupportProgressPayloads(): void
{
$captured = [];