@appstrata/player-lib 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -0
- package/dist/app-host.d.ts +161 -0
- package/dist/app-host.d.ts.map +1 -0
- package/dist/app-host.js +219 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# @appstrata/player-lib
|
|
2
|
+
|
|
3
|
+
Player implementation library for AppStrata digital signage platform.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This package provides helpers for platform vendors implementing AppStrata-compliant players. It handles the host side of the message protocol and provides a high-level API for hosting apps.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
> **Note:** This is a private npm package. Requires an npm access token — contact the AppStrata team to get one, then add it to your `~/.npmrc`:
|
|
12
|
+
>
|
|
13
|
+
> ```
|
|
14
|
+
> //registry.npmjs.org/:_authToken=YOUR_TOKEN
|
|
15
|
+
> ```
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @appstrata/player-lib
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### Session scope
|
|
24
|
+
|
|
25
|
+
`createAppHost` (and the underlying `MessageBridge`) are **single-session**: one instance covers one App page load, from the initial handshake through `fireStop()` / `destroy()`. When the App reloads (e.g. due to a config change), you **must** destroy the old host and create a new one:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// App reloads → tear down old, create fresh
|
|
29
|
+
host.destroy();
|
|
30
|
+
host = createAppHost({ ... });
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Reusing a host across reloads is not supported. The handshake state is not reset between sessions.
|
|
34
|
+
|
|
35
|
+
### Using createAppHost (Recommended)
|
|
36
|
+
|
|
37
|
+
The `createAppHost` helper is the easiest way to host an AppStrata app. It automatically handles:
|
|
38
|
+
- Protocol handshake (HELLO/READY/READY_ACK)
|
|
39
|
+
- Lifecycle event routing via the message bridge
|
|
40
|
+
- RPC operations for capability APIs
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { createAppHost } from "@appstrata/player-lib";
|
|
44
|
+
|
|
45
|
+
// Create app host
|
|
46
|
+
const host = createAppHost({
|
|
47
|
+
services: { storage, proxy },
|
|
48
|
+
target: iframe.contentWindow!,
|
|
49
|
+
targetOrigin: "*",
|
|
50
|
+
notifyComplete: () => { /* handle playback complete */ },
|
|
51
|
+
notifyNoContent: (options) => { /* handle no content */ },
|
|
52
|
+
notifyEstimatedEnd: (expectedFinishTime) => { /* handle estimation */ },
|
|
53
|
+
log: (level, msg, data) => console.log(`[${level}]`, msg, data),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Fire lifecycle events as needed
|
|
57
|
+
const context = buildAppContext();
|
|
58
|
+
host.fireInit(context);
|
|
59
|
+
host.fireShow();
|
|
60
|
+
host.fireStart();
|
|
61
|
+
// ... later ...
|
|
62
|
+
host.fireHide();
|
|
63
|
+
host.fireStop();
|
|
64
|
+
|
|
65
|
+
// Update context
|
|
66
|
+
host.updateContext(newContext);
|
|
67
|
+
|
|
68
|
+
// Cleanup when done
|
|
69
|
+
host.destroy();
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Using MessageBridge (Advanced)
|
|
73
|
+
|
|
74
|
+
For advanced use cases, you can use `MessageBridge` directly from `@appstrata/protocol`:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { MessageBridge, createPostMessageTransport } from "@appstrata/protocol";
|
|
78
|
+
|
|
79
|
+
const transport = createPostMessageTransport({
|
|
80
|
+
targetWindow: iframe.contentWindow!,
|
|
81
|
+
targetOrigin: "*",
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const bridge = new MessageBridge({
|
|
85
|
+
transport,
|
|
86
|
+
player: myPlayer,
|
|
87
|
+
getContext: async () => buildAppContext(),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
bridge.start();
|
|
91
|
+
|
|
92
|
+
// Fire lifecycle events
|
|
93
|
+
bridge.fireShow();
|
|
94
|
+
bridge.fireStart();
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## API Reference
|
|
98
|
+
|
|
99
|
+
### createAppHost
|
|
100
|
+
|
|
101
|
+
High-level helper for hosting an AppStrata app.
|
|
102
|
+
|
|
103
|
+
**Options:**
|
|
104
|
+
- `services: CapabilityServices` - Capability service implementations (storage, proxy, etc.)
|
|
105
|
+
- `target: Window` - Target window (typically `iframe.contentWindow`)
|
|
106
|
+
- `transport?: Transport` - Optional transport (auto-created from target/targetOrigin if not provided)
|
|
107
|
+
- `targetOrigin?: string` - Target origin for the default transport (default: `"*"`)
|
|
108
|
+
- `notifyComplete: () => void` - Callback when app signals playback complete
|
|
109
|
+
- `notifyEstimatedEnd: (expectedFinishTime: number) => void` - Callback when app provides estimated finish time (Unix ms)
|
|
110
|
+
- `notifyNoContent: (options?) => void` - Callback when app signals no content (`{ retryNotBefore?: number; reason?: string }`)
|
|
111
|
+
- `log: (level, message, data?) => void` - Logging callback
|
|
112
|
+
- `debug?: boolean` - Enable debug logging (default: `false`)
|
|
113
|
+
- `retryInterval?: number` - READY retry interval in ms (default: `1000`)
|
|
114
|
+
- `maxRetries?: number` - Max READY retries (default: `10`)
|
|
115
|
+
|
|
116
|
+
**Returns:** `AppHost` with methods:
|
|
117
|
+
- `fireInit(context)`: Initialize with AppContext
|
|
118
|
+
- `fireShow()`: Fire show event
|
|
119
|
+
- `fireStart()`: Fire start event
|
|
120
|
+
- `fireHide()`: Fire hide event
|
|
121
|
+
- `fireStop()`: Fire stop event
|
|
122
|
+
- `updateContext(context)`: Update context
|
|
123
|
+
- `destroy()`: Cleanup resources
|
|
124
|
+
- `player`: Direct access to the underlying SignagePlayer
|
|
125
|
+
|
|
126
|
+
## Protocol Details
|
|
127
|
+
|
|
128
|
+
The library implements the AppStrata message protocol:
|
|
129
|
+
|
|
130
|
+
1. **Handshake:**
|
|
131
|
+
- App sends HELLO (optional)
|
|
132
|
+
- Host sends READY with AppContext
|
|
133
|
+
- If no HELLO received, host retries READY until READY_ACK
|
|
134
|
+
- App sends READY_ACK
|
|
135
|
+
|
|
136
|
+
2. **RPC:**
|
|
137
|
+
- App sends REQUEST
|
|
138
|
+
- Host routes to SignagePlayer capability methods
|
|
139
|
+
- Host sends RESPONSE
|
|
140
|
+
|
|
141
|
+
3. **Events:**
|
|
142
|
+
- Host sends EVENT (show, start, hide, stop, contextChange)
|
|
143
|
+
- Events are queued if sent before READY_ACK, then flushed
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppHost - Host-side player controller.
|
|
3
|
+
*
|
|
4
|
+
* Encapsulates the SignagePlayer and player-side state such as the current AppContext and
|
|
5
|
+
* registeredlifecycle handlers.
|
|
6
|
+
* Provides fire methods for Players to trigger lifecycle events.
|
|
7
|
+
*/
|
|
8
|
+
import { type SignagePlayer, type AppContext, type LogLevel, type StorageApi, type ProxyApi, type MediaCacheApi, type StaticApi } from "@appstrata/core";
|
|
9
|
+
import type { Transport } from "@appstrata/protocol";
|
|
10
|
+
/**
|
|
11
|
+
* Capability services that can be provided to the host.
|
|
12
|
+
*/
|
|
13
|
+
export interface CapabilityServices {
|
|
14
|
+
storage?: StorageApi;
|
|
15
|
+
proxy?: ProxyApi;
|
|
16
|
+
mediaCache?: MediaCacheApi;
|
|
17
|
+
static?: StaticApi;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* AppHost interface - Host-side player controller.
|
|
21
|
+
*
|
|
22
|
+
* **Single-session:** one `AppHost` instance covers one App page load. The
|
|
23
|
+
* handshake state is not reset between sessions. When the App reloads (e.g.
|
|
24
|
+
* the iframe navigates), call `destroy()` on the old host and create a new
|
|
25
|
+
* instance for the incoming session. Reusing an `AppHost` across sessions is
|
|
26
|
+
* not supported and will result in protocol errors (duplicate / stale READY).
|
|
27
|
+
*
|
|
28
|
+
* > Future versions may introduce multi-session support.
|
|
29
|
+
*/
|
|
30
|
+
export interface AppHost {
|
|
31
|
+
/**
|
|
32
|
+
* Direct access to the underlying SignagePlayer interface.
|
|
33
|
+
*
|
|
34
|
+
* This property is primarily useful for testing and debugging.
|
|
35
|
+
*/
|
|
36
|
+
readonly player: SignagePlayer;
|
|
37
|
+
/**
|
|
38
|
+
* Fire init lifecycle event with the given AppContext.
|
|
39
|
+
*/
|
|
40
|
+
fireInit(context: AppContext): void;
|
|
41
|
+
/**
|
|
42
|
+
* Fire show lifecycle event (app is about to become visible).
|
|
43
|
+
*/
|
|
44
|
+
fireShow(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Fire start lifecycle event (app is visible and should start playback).
|
|
47
|
+
*/
|
|
48
|
+
fireStart(): void;
|
|
49
|
+
/**
|
|
50
|
+
* Fire hide lifecycle event (app is about to be hidden).
|
|
51
|
+
*/
|
|
52
|
+
fireHide(): void;
|
|
53
|
+
/**
|
|
54
|
+
* Fire stop lifecycle event (app is hidden and should stop playback).
|
|
55
|
+
*/
|
|
56
|
+
fireStop(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Fire context change event with the given context.
|
|
59
|
+
* Should only be called after fireInit() has been called.
|
|
60
|
+
*/
|
|
61
|
+
updateContext(context: AppContext): void;
|
|
62
|
+
/**
|
|
63
|
+
* Hot-swap capability services at runtime.
|
|
64
|
+
* Updates the player's service references and rebuilds the internal
|
|
65
|
+
* capabilities array so that subsequent RPC calls route correctly.
|
|
66
|
+
* This is useful for development players that need to hot-swap services at runtime.
|
|
67
|
+
*/
|
|
68
|
+
updateServices(services: CapabilityServices): void;
|
|
69
|
+
/**
|
|
70
|
+
* Cleanup resources.
|
|
71
|
+
*/
|
|
72
|
+
destroy(): void;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Options for creating an AppHost.
|
|
76
|
+
*/
|
|
77
|
+
export interface CreateAppHostOptions {
|
|
78
|
+
/**
|
|
79
|
+
* Capability service implementations.
|
|
80
|
+
*/
|
|
81
|
+
services: CapabilityServices;
|
|
82
|
+
/**
|
|
83
|
+
* Transport to use. If not provided, a postMessage transport
|
|
84
|
+
* will be created by default based on the target window and target origin.
|
|
85
|
+
*/
|
|
86
|
+
transport?: Transport;
|
|
87
|
+
/**
|
|
88
|
+
* Target window to use when creating the default transport.
|
|
89
|
+
*/
|
|
90
|
+
target: Window;
|
|
91
|
+
/**
|
|
92
|
+
* Target origin to use when creating the default transport.
|
|
93
|
+
* @default "*"
|
|
94
|
+
*/
|
|
95
|
+
targetOrigin?: string;
|
|
96
|
+
/**
|
|
97
|
+
* Retry interval in milliseconds for sending READY messages.
|
|
98
|
+
* @default 1000 (1 second)
|
|
99
|
+
*/
|
|
100
|
+
retryInterval?: number;
|
|
101
|
+
/**
|
|
102
|
+
* Maximum number of retries for sending READY messages.
|
|
103
|
+
* @default 10
|
|
104
|
+
*/
|
|
105
|
+
maxRetries?: number;
|
|
106
|
+
/**
|
|
107
|
+
* Called when the app signals it has finished playback.
|
|
108
|
+
* Vendors MUST provide their own implementation.
|
|
109
|
+
*/
|
|
110
|
+
notifyComplete: () => void;
|
|
111
|
+
/**
|
|
112
|
+
* Called when the app provides an estimated finish time.
|
|
113
|
+
* Vendors MUST provide their own implementation.
|
|
114
|
+
*
|
|
115
|
+
* @param expectedFinishTime - Absolute epoch timestamp in ms (already
|
|
116
|
+
* converted from the app-facing totalDuration/finishTime by the SDK)
|
|
117
|
+
*/
|
|
118
|
+
notifyEstimatedEnd: (expectedFinishTime: number) => void;
|
|
119
|
+
/**
|
|
120
|
+
* Called when the app signals it has no content to display.
|
|
121
|
+
* Vendors MUST provide their own implementation.
|
|
122
|
+
*
|
|
123
|
+
* @param options - Optional retry and reason info
|
|
124
|
+
* @param options.retryNotBefore - Earliest time to retry (epoch timestamp in ms)
|
|
125
|
+
* @param options.reason - Human-readable reason
|
|
126
|
+
*/
|
|
127
|
+
notifyNoContent: (options?: {
|
|
128
|
+
retryNotBefore?: number;
|
|
129
|
+
reason?: string;
|
|
130
|
+
}) => void;
|
|
131
|
+
/**
|
|
132
|
+
* Implementation for logging.
|
|
133
|
+
* Vendors MUST provide their own implementation.
|
|
134
|
+
*
|
|
135
|
+
* @param level - Log level
|
|
136
|
+
* @param message - Log message
|
|
137
|
+
* @param data - Optional data to log
|
|
138
|
+
*/
|
|
139
|
+
log: (level: LogLevel, message: string, data?: unknown) => void;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Create an AppHost for managing an app instance.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```typescript
|
|
146
|
+
* const host = createAppHost({
|
|
147
|
+
* services: { storage, proxy },
|
|
148
|
+
* target: iframe.contentWindow!,
|
|
149
|
+
* notifyComplete: () => { ... },
|
|
150
|
+
* log: (level, msg) => console.log(msg),
|
|
151
|
+
* });
|
|
152
|
+
*
|
|
153
|
+
* // Fire lifecycle events with AppContext
|
|
154
|
+
* const appContext = buildContextFromVendor(vendorContext);
|
|
155
|
+
* host.fireInit(appContext);
|
|
156
|
+
* host.fireShow();
|
|
157
|
+
* host.fireStart();
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
export declare function createAppHost(options: CreateAppHostOptions): AppHost;
|
|
161
|
+
//# sourceMappingURL=app-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-host.d.ts","sourceRoot":"","sources":["../src/app-host.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,UAAU,EAEf,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,iBAAiB,CAAC;AAGzB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAUrD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,OAAO;IACtB;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAE/B;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IAEpC;;OAEG;IACH,QAAQ,IAAI,IAAI,CAAC;IAEjB;;OAEG;IACH,SAAS,IAAI,IAAI,CAAC;IAElB;;OAEG;IACH,QAAQ,IAAI,IAAI,CAAC;IAEjB;;OAEG;IACH,QAAQ,IAAI,IAAI,CAAC;IAEjB;;;OAGG;IACH,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IAEzC;;;;;OAKG;IACH,cAAc,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAEnD;;OAEG;IACH,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,QAAQ,EAAE,kBAAkB,CAAC;IAE7B;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IAEtB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,cAAc,EAAE,MAAM,IAAI,CAAC;IAE3B;;;;;;OAMG;IACH,kBAAkB,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,IAAI,CAAC;IAEzD;;;;;;;OAOG;IACH,eAAe,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAElF;;;;;;;OAOG;IACH,GAAG,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CACjE;AAMD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CA6NT"}
|
package/dist/app-host.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppHost - Host-side player controller.
|
|
3
|
+
*
|
|
4
|
+
* Encapsulates the SignagePlayer and player-side state such as the current AppContext and
|
|
5
|
+
* registeredlifecycle handlers.
|
|
6
|
+
* Provides fire methods for Players to trigger lifecycle events.
|
|
7
|
+
*/
|
|
8
|
+
import { LifecyclePhase, createLogger, } from "@appstrata/core";
|
|
9
|
+
const logger = createLogger("AppHost");
|
|
10
|
+
import { MessageBridge, createPostMessageTransport, } from "@appstrata/protocol";
|
|
11
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
12
|
+
// IMPLEMENTATION
|
|
13
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
14
|
+
/**
|
|
15
|
+
* Create an AppHost for managing an app instance.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const host = createAppHost({
|
|
20
|
+
* services: { storage, proxy },
|
|
21
|
+
* target: iframe.contentWindow!,
|
|
22
|
+
* notifyComplete: () => { ... },
|
|
23
|
+
* log: (level, msg) => console.log(msg),
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // Fire lifecycle events with AppContext
|
|
27
|
+
* const appContext = buildContextFromVendor(vendorContext);
|
|
28
|
+
* host.fireInit(appContext);
|
|
29
|
+
* host.fireShow();
|
|
30
|
+
* host.fireStart();
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function createAppHost(options) {
|
|
34
|
+
const { services, transport, target, targetOrigin = "*", retryInterval, maxRetries, notifyComplete, notifyEstimatedEnd, notifyNoContent, log, } = options;
|
|
35
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
36
|
+
// Internal state
|
|
37
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
38
|
+
let context = null;
|
|
39
|
+
let resolveReady;
|
|
40
|
+
const readyPromise = new Promise((r) => { resolveReady = r; });
|
|
41
|
+
// Lifecycle handlers (arrays for predictable ordering)
|
|
42
|
+
const handlers = {
|
|
43
|
+
onInit: [],
|
|
44
|
+
onShow: [],
|
|
45
|
+
onStart: [],
|
|
46
|
+
onHide: [],
|
|
47
|
+
onStop: [],
|
|
48
|
+
onContextChange: [],
|
|
49
|
+
};
|
|
50
|
+
// Determine capabilities from services
|
|
51
|
+
const capabilities = [];
|
|
52
|
+
if (services.storage)
|
|
53
|
+
capabilities.push("storage");
|
|
54
|
+
if (services.proxy)
|
|
55
|
+
capabilities.push("proxy");
|
|
56
|
+
if (services.mediaCache)
|
|
57
|
+
capabilities.push("mediaCache");
|
|
58
|
+
if (services.static)
|
|
59
|
+
capabilities.push("static");
|
|
60
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
61
|
+
// SignagePlayer (apps interface) - used by MessageBridge for RPC routing
|
|
62
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
63
|
+
const player = {
|
|
64
|
+
apiVersion: "1.0.0",
|
|
65
|
+
lifecyclePhase: LifecyclePhase.None,
|
|
66
|
+
async getContext() {
|
|
67
|
+
await readyPromise;
|
|
68
|
+
return context;
|
|
69
|
+
},
|
|
70
|
+
async hasCapability(cap) {
|
|
71
|
+
return capabilities.includes(cap);
|
|
72
|
+
},
|
|
73
|
+
onInit(handler) {
|
|
74
|
+
if (context)
|
|
75
|
+
handler(context); // Late subscriber
|
|
76
|
+
handlers.onInit.push(handler);
|
|
77
|
+
},
|
|
78
|
+
onShow(handler) { handlers.onShow.push(handler); },
|
|
79
|
+
onStart(handler) { handlers.onStart.push(handler); },
|
|
80
|
+
onHide(handler) { handlers.onHide.push(handler); },
|
|
81
|
+
onStop(handler) { handlers.onStop.push(handler); },
|
|
82
|
+
onContextChange(handler) { handlers.onContextChange.push(handler); },
|
|
83
|
+
notifyComplete() {
|
|
84
|
+
notifyComplete();
|
|
85
|
+
},
|
|
86
|
+
notifyEstimatedEnd(options) {
|
|
87
|
+
notifyEstimatedEnd(options?.expectedFinishTime ?? options);
|
|
88
|
+
},
|
|
89
|
+
notifyNoContent(options) {
|
|
90
|
+
notifyNoContent(options);
|
|
91
|
+
},
|
|
92
|
+
storage: services.storage,
|
|
93
|
+
proxy: services.proxy,
|
|
94
|
+
mediaCache: services.mediaCache,
|
|
95
|
+
static: services.static,
|
|
96
|
+
log(level, message, data) {
|
|
97
|
+
log(level, message, data);
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
101
|
+
// Message bridge setup
|
|
102
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
103
|
+
const ownsTransport = !transport;
|
|
104
|
+
const transportToUse = transport ?? createPostMessageTransport({
|
|
105
|
+
targetWindow: target,
|
|
106
|
+
targetOrigin: targetOrigin,
|
|
107
|
+
});
|
|
108
|
+
const bridge = new MessageBridge({
|
|
109
|
+
transport: transportToUse,
|
|
110
|
+
getContext: player.getContext, // we could probably drop this, since we are passing the player to the bridge
|
|
111
|
+
player,
|
|
112
|
+
...(retryInterval !== undefined && { retryInterval }), // keep default values if not provided
|
|
113
|
+
...(maxRetries !== undefined && { maxRetries }), // keep default values if not provided
|
|
114
|
+
});
|
|
115
|
+
// Forward lifecycle events through the message bridge
|
|
116
|
+
handlers.onShow.push(() => bridge.fireShow());
|
|
117
|
+
handlers.onStart.push(() => bridge.fireStart());
|
|
118
|
+
handlers.onHide.push(() => bridge.fireHide());
|
|
119
|
+
handlers.onStop.push(() => bridge.fireStop());
|
|
120
|
+
handlers.onContextChange.push((ctx) => bridge.fireContextChange(ctx));
|
|
121
|
+
// This will send a proactive READY
|
|
122
|
+
// as soon as the context is available.
|
|
123
|
+
bridge.start();
|
|
124
|
+
logger.info("AppHost created, message bridge started");
|
|
125
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
126
|
+
// Helper functions for safe handler execution
|
|
127
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
128
|
+
/**
|
|
129
|
+
* Safely execute lifecycle handlers.
|
|
130
|
+
* Catches and logs errors to prevent one handler from breaking the chain.
|
|
131
|
+
*/
|
|
132
|
+
function fireLifecycleHandlers(handlerArray) {
|
|
133
|
+
for (const handler of handlerArray) {
|
|
134
|
+
try {
|
|
135
|
+
handler();
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
logger.error("Error in lifecycle handler", error);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function setPhase(phase) {
|
|
143
|
+
player.lifecyclePhase = phase;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Safely execute context handlers.
|
|
147
|
+
* Catches and logs errors to prevent one handler from breaking the chain.
|
|
148
|
+
*/
|
|
149
|
+
function fireContextHandlers(handlerArray, ctx) {
|
|
150
|
+
for (const handler of handlerArray) {
|
|
151
|
+
try {
|
|
152
|
+
handler(ctx);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
logger.error("Error in context handler", error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
160
|
+
// AppHost interface
|
|
161
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
162
|
+
return {
|
|
163
|
+
player,
|
|
164
|
+
fireInit(appContext) {
|
|
165
|
+
context = appContext;
|
|
166
|
+
setPhase(LifecyclePhase.Init);
|
|
167
|
+
resolveReady();
|
|
168
|
+
fireContextHandlers(handlers.onInit, context);
|
|
169
|
+
},
|
|
170
|
+
fireShow() {
|
|
171
|
+
setPhase(LifecyclePhase.Show);
|
|
172
|
+
fireLifecycleHandlers(handlers.onShow);
|
|
173
|
+
},
|
|
174
|
+
fireStart() {
|
|
175
|
+
setPhase(LifecyclePhase.Start);
|
|
176
|
+
fireLifecycleHandlers(handlers.onStart);
|
|
177
|
+
},
|
|
178
|
+
fireHide() {
|
|
179
|
+
setPhase(LifecyclePhase.Hide);
|
|
180
|
+
fireLifecycleHandlers(handlers.onHide);
|
|
181
|
+
},
|
|
182
|
+
fireStop() {
|
|
183
|
+
setPhase(LifecyclePhase.Stop);
|
|
184
|
+
fireLifecycleHandlers(handlers.onStop);
|
|
185
|
+
},
|
|
186
|
+
updateContext(appContext) {
|
|
187
|
+
if (!context) {
|
|
188
|
+
throw new Error("Cannot update context before context is initialized");
|
|
189
|
+
}
|
|
190
|
+
context = appContext;
|
|
191
|
+
fireContextHandlers(handlers.onContextChange, context);
|
|
192
|
+
},
|
|
193
|
+
updateServices(newServices) {
|
|
194
|
+
player.storage = newServices.storage;
|
|
195
|
+
player.proxy = newServices.proxy;
|
|
196
|
+
player.mediaCache = newServices.mediaCache;
|
|
197
|
+
player.static = newServices.static;
|
|
198
|
+
capabilities.length = 0;
|
|
199
|
+
if (newServices.storage)
|
|
200
|
+
capabilities.push("storage");
|
|
201
|
+
if (newServices.proxy)
|
|
202
|
+
capabilities.push("proxy");
|
|
203
|
+
if (newServices.mediaCache)
|
|
204
|
+
capabilities.push("mediaCache");
|
|
205
|
+
if (newServices.static)
|
|
206
|
+
capabilities.push("static");
|
|
207
|
+
},
|
|
208
|
+
destroy() {
|
|
209
|
+
bridge.destroy();
|
|
210
|
+
// Close the transport only if we created it internally.
|
|
211
|
+
// When the caller passes their own transport, they own its lifecycle.
|
|
212
|
+
if (ownsTransport) {
|
|
213
|
+
transportToUse.close();
|
|
214
|
+
}
|
|
215
|
+
// remove all registered lifecycle handlers
|
|
216
|
+
Object.values(handlers).forEach((arr) => (arr.length = 0));
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @appstrata/player-lib - Player implementation library
|
|
3
|
+
*
|
|
4
|
+
* Provides the createAppHost() function for platform vendors implementing
|
|
5
|
+
* AppStrata-compliant players. Creates an AppHost instance.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
export * from "@appstrata/protocol";
|
|
10
|
+
export type { AppHost, CreateAppHostOptions, CapabilityServices } from "./app-host.js";
|
|
11
|
+
export { createAppHost } from "./app-host.js";
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,cAAc,qBAAqB,CAAC;AAGpC,YAAY,EAAE,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACvF,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @appstrata/player-lib - Player implementation library
|
|
3
|
+
*
|
|
4
|
+
* Provides the createAppHost() function for platform vendors implementing
|
|
5
|
+
* AppStrata-compliant players. Creates an AppHost instance.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
// Re-export everything from protocol for convenience
|
|
10
|
+
export * from "@appstrata/protocol";
|
|
11
|
+
export { createAppHost } from "./app-host.js";
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@appstrata/player-lib",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AppStrata Player Library - Helpers for platform vendors implementing compliant players",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"digital-signage",
|
|
16
|
+
"appstrata",
|
|
17
|
+
"player",
|
|
18
|
+
"cms"
|
|
19
|
+
],
|
|
20
|
+
"author": "AppStrata",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "restricted"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@appstrata/core": "0.1.0",
|
|
30
|
+
"@appstrata/protocol": "0.1.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@jest/globals": "^29.7.0",
|
|
34
|
+
"@types/jest": "^29.5.11",
|
|
35
|
+
"jest": "^29.7.0",
|
|
36
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
37
|
+
"ts-jest": "^29.1.1",
|
|
38
|
+
"typescript": "^5.3.3"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsc --build tsconfig.build.json",
|
|
42
|
+
"clean": "rm -rf dist *.tsbuildinfo",
|
|
43
|
+
"dev": "tsc --build --watch",
|
|
44
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
|
|
45
|
+
}
|
|
46
|
+
}
|