feat(companion): add publishHeartbeat helper across platform clients
This commit is contained in:
@@ -1196,6 +1196,7 @@ Companion runtime helper:
|
|||||||
- `IOSCompanionClient` (`platform: "ios"`, APNs push registration)
|
- `IOSCompanionClient` (`platform: "ios"`, APNs push registration)
|
||||||
- `AndroidCompanionClient` (`platform: "android"`, FCM push registration)
|
- `AndroidCompanionClient` (`platform: "android"`, FCM push registration)
|
||||||
- shared `bootstrap()` helper (`register` + `getCapabilities`) for startup handshakes
|
- shared `bootstrap()` helper (`register` + `getCapabilities`) for startup handshakes
|
||||||
|
- shared `publishHeartbeat()` helper for periodic `node.status.set` updates with safe defaults
|
||||||
|
|
||||||
## Canvas / A2UI Foundation
|
## Canvas / A2UI Foundation
|
||||||
|
|
||||||
|
|||||||
@@ -217,6 +217,21 @@
|
|||||||
],
|
],
|
||||||
"test_status": "pnpm test:run src/companion/platformClients.test.ts src/companion/platformClients.integration.test.ts + pnpm typecheck passing"
|
"test_status": "pnpm test:run src/companion/platformClients.test.ts src/companion/platformClients.integration.test.ts + pnpm typecheck passing"
|
||||||
},
|
},
|
||||||
|
"companion-platform-heartbeat-helper": {
|
||||||
|
"status": "completed",
|
||||||
|
"date": "2026-02-17",
|
||||||
|
"updated": "2026-02-17",
|
||||||
|
"summary": "Added `publishHeartbeat()` convenience methods across platform companion clients to standardize periodic status reporting with safe defaults (`statusText: heartbeat`, `powerSource: unknown`), plus test and docs updates.",
|
||||||
|
"files_modified": [
|
||||||
|
"src/companion/platformClients.ts",
|
||||||
|
"src/companion/platformClients.test.ts",
|
||||||
|
"src/companion/platformClients.integration.test.ts",
|
||||||
|
"src/companion/index.ts",
|
||||||
|
"README.md",
|
||||||
|
"docs/plans/state.json"
|
||||||
|
],
|
||||||
|
"test_status": "pnpm test:run src/companion/platformClients.test.ts src/companion/platformClients.integration.test.ts + pnpm typecheck passing"
|
||||||
|
},
|
||||||
"browser-tools-activation-clarity": {
|
"browser-tools-activation-clarity": {
|
||||||
"status": "completed",
|
"status": "completed",
|
||||||
"date": "2026-02-17",
|
"date": "2026-02-17",
|
||||||
|
|||||||
@@ -41,5 +41,6 @@ export type {
|
|||||||
PlatformClientOptions,
|
PlatformClientOptions,
|
||||||
RegisterPushTokenInput,
|
RegisterPushTokenInput,
|
||||||
SharedStatusInput,
|
SharedStatusInput,
|
||||||
|
HeartbeatStatusInput,
|
||||||
PlatformBootstrapResult,
|
PlatformBootstrapResult,
|
||||||
} from './platformClients.js';
|
} from './platformClients.js';
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ describe('platform clients integration', () => {
|
|||||||
const boot = await client.bootstrap();
|
const boot = await client.bootstrap();
|
||||||
expect(boot.register.registered).toBe(true);
|
expect(boot.register.registered).toBe(true);
|
||||||
expect(boot.capabilities.node.id).toBe('macos-e2e');
|
expect(boot.capabilities.node.id).toBe('macos-e2e');
|
||||||
const status = await client.setStatus({
|
const status = await client.publishHeartbeat({
|
||||||
appVersion: '1.0.0',
|
appVersion: '1.0.0',
|
||||||
statusText: 'menu-bar-active',
|
statusText: 'menu-bar-active',
|
||||||
powerSource: 'ac',
|
powerSource: 'ac',
|
||||||
|
|||||||
@@ -168,4 +168,19 @@ describe('platform companion clients', () => {
|
|||||||
capabilities: expect.any(Object),
|
capabilities: expect.any(Object),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('publishHeartbeat uses safe defaults for status payload', async () => {
|
||||||
|
const mock = createRuntimeMock();
|
||||||
|
const client = new AndroidCompanionClient({ runtime: mock.runtime, nodeId: 'android-node' });
|
||||||
|
|
||||||
|
await client.publishHeartbeat();
|
||||||
|
|
||||||
|
expect(mock.setNodeStatus).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
platform: 'android',
|
||||||
|
statusText: 'heartbeat',
|
||||||
|
powerSource: 'unknown',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,6 +38,14 @@ export type SharedStatusInput = Omit<
|
|||||||
'platform'
|
'platform'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export interface HeartbeatStatusInput {
|
||||||
|
appVersion?: SharedStatusInput['appVersion'];
|
||||||
|
deviceName?: SharedStatusInput['deviceName'];
|
||||||
|
statusText?: SharedStatusInput['statusText'];
|
||||||
|
batteryPct?: SharedStatusInput['batteryPct'];
|
||||||
|
powerSource?: SharedStatusInput['powerSource'];
|
||||||
|
}
|
||||||
|
|
||||||
export interface PlatformBootstrapResult {
|
export interface PlatformBootstrapResult {
|
||||||
register: NodeRegisterResult;
|
register: NodeRegisterResult;
|
||||||
capabilities: NodeCapabilitiesResult;
|
capabilities: NodeCapabilitiesResult;
|
||||||
@@ -96,6 +104,16 @@ export class MacOSCompanionClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publishHeartbeat(input: HeartbeatStatusInput = {}): Promise<NodeStatusSetResult> {
|
||||||
|
return this.setStatus({
|
||||||
|
appVersion: input.appVersion,
|
||||||
|
deviceName: input.deviceName,
|
||||||
|
statusText: input.statusText ?? 'heartbeat',
|
||||||
|
batteryPct: input.batteryPct,
|
||||||
|
powerSource: input.powerSource ?? 'unknown',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setLocation(location: SetNodeLocationInput): Promise<NodeLocationSetResult> {
|
setLocation(location: SetNodeLocationInput): Promise<NodeLocationSetResult> {
|
||||||
return this.runtime.setNodeLocation(location);
|
return this.runtime.setNodeLocation(location);
|
||||||
}
|
}
|
||||||
@@ -195,6 +213,16 @@ export class IOSCompanionClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publishHeartbeat(input: HeartbeatStatusInput = {}): Promise<NodeStatusSetResult> {
|
||||||
|
return this.setStatus({
|
||||||
|
appVersion: input.appVersion,
|
||||||
|
deviceName: input.deviceName,
|
||||||
|
statusText: input.statusText ?? 'heartbeat',
|
||||||
|
batteryPct: input.batteryPct,
|
||||||
|
powerSource: input.powerSource ?? 'unknown',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setLocation(location: SetNodeLocationInput): Promise<NodeLocationSetResult> {
|
setLocation(location: SetNodeLocationInput): Promise<NodeLocationSetResult> {
|
||||||
return this.runtime.setNodeLocation(location);
|
return this.runtime.setNodeLocation(location);
|
||||||
}
|
}
|
||||||
@@ -294,6 +322,16 @@ export class AndroidCompanionClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publishHeartbeat(input: HeartbeatStatusInput = {}): Promise<NodeStatusSetResult> {
|
||||||
|
return this.setStatus({
|
||||||
|
appVersion: input.appVersion,
|
||||||
|
deviceName: input.deviceName,
|
||||||
|
statusText: input.statusText ?? 'heartbeat',
|
||||||
|
batteryPct: input.batteryPct,
|
||||||
|
powerSource: input.powerSource ?? 'unknown',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setLocation(location: SetNodeLocationInput): Promise<NodeLocationSetResult> {
|
setLocation(location: SetNodeLocationInput): Promise<NodeLocationSetResult> {
|
||||||
return this.runtime.setNodeLocation(location);
|
return this.runtime.setNodeLocation(location);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user