@glivion/square-screen-js-sdk 0.1.0 → 1.0.1

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.
Files changed (49) hide show
  1. package/dist/index.cjs +874 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.d.cts +522 -0
  4. package/dist/index.d.mts +522 -0
  5. package/dist/index.mjs +870 -0
  6. package/dist/index.mjs.map +1 -0
  7. package/package.json +8 -1
  8. package/.github/workflows/build-js-sdk.yml +0 -70
  9. package/eslint.config.js +0 -3
  10. package/examples/react-app/README.md +0 -73
  11. package/examples/react-app/eslint.config.js +0 -22
  12. package/examples/react-app/index.html +0 -13
  13. package/examples/react-app/package-lock.json +0 -2239
  14. package/examples/react-app/package.json +0 -31
  15. package/examples/react-app/public/favicon.svg +0 -1
  16. package/examples/react-app/public/icons.svg +0 -24
  17. package/examples/react-app/src/App.css +0 -184
  18. package/examples/react-app/src/App.tsx +0 -157
  19. package/examples/react-app/src/EmergencyTicker.tsx +0 -25
  20. package/examples/react-app/src/HeadlessExample.tsx +0 -66
  21. package/examples/react-app/src/RendererExample.tsx +0 -70
  22. package/examples/react-app/src/assets/hero.png +0 -0
  23. package/examples/react-app/src/assets/react.svg +0 -1
  24. package/examples/react-app/src/assets/vite.svg +0 -1
  25. package/examples/react-app/src/index.css +0 -183
  26. package/examples/react-app/src/main.tsx +0 -10
  27. package/examples/react-app/src/mockNetworkDataSource.ts +0 -116
  28. package/examples/react-app/src/usePlayer.ts +0 -71
  29. package/examples/react-app/tsconfig.app.json +0 -25
  30. package/examples/react-app/tsconfig.json +0 -7
  31. package/examples/react-app/tsconfig.node.json +0 -24
  32. package/examples/react-app/vite.config.ts +0 -7
  33. package/examples/react-app/yarn.lock +0 -1089
  34. package/src/__tests__/cache/SquareScreenCache.test.ts +0 -375
  35. package/src/__tests__/network/NetworkClient.test.ts +0 -217
  36. package/src/__tests__/network/mappers.test.ts +0 -163
  37. package/src/__tests__/player/SquareScreenPlayer.test.ts +0 -840
  38. package/src/cache/SquareScreenCache.ts +0 -154
  39. package/src/constants.ts +0 -9
  40. package/src/core/types.ts +0 -251
  41. package/src/env.d.ts +0 -4
  42. package/src/index.ts +0 -34
  43. package/src/network/NetworkClient.ts +0 -234
  44. package/src/network/apiTypes.ts +0 -89
  45. package/src/network/mappers.ts +0 -106
  46. package/src/player/SquareScreenPlayer.ts +0 -414
  47. package/src/renderer/SquareScreenRenderer.ts +0 -282
  48. package/tsconfig.json +0 -12
  49. package/tsdown.config.ts +0 -23
@@ -1,183 +0,0 @@
1
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
2
-
3
- body {
4
- font-family: system-ui, sans-serif;
5
- background: #0f0f0f;
6
- color: #f0f0f0;
7
- min-height: 100dvh;
8
- }
9
-
10
- .app {
11
- max-width: 960px;
12
- margin: 0 auto;
13
- padding: 2rem;
14
- }
15
-
16
- h1 { font-size: 1.5rem; margin-bottom: 0.25rem; }
17
- .subtitle { color: #888; font-size: 0.9rem; margin-bottom: 2rem; }
18
-
19
- .tabs {
20
- display: flex;
21
- gap: 0.5rem;
22
- margin-bottom: 2rem;
23
- border-bottom: 1px solid #333;
24
- padding-bottom: 0.5rem;
25
- }
26
-
27
- .tabs button {
28
- background: none;
29
- border: none;
30
- color: #888;
31
- font-size: 0.95rem;
32
- padding: 0.4rem 0.8rem;
33
- cursor: pointer;
34
- border-radius: 4px;
35
- }
36
-
37
- .tabs button.active {
38
- background: #1e1e1e;
39
- color: #fff;
40
- }
41
-
42
- .config-form {
43
- display: grid;
44
- gap: 0.75rem;
45
- margin-bottom: 2rem;
46
- background: #1a1a1a;
47
- padding: 1.25rem;
48
- border-radius: 8px;
49
- }
50
-
51
- .config-form label { font-size: 0.8rem; color: #aaa; display: block; margin-bottom: 0.25rem; }
52
-
53
- .config-form input {
54
- width: 100%;
55
- background: #111;
56
- border: 1px solid #333;
57
- border-radius: 4px;
58
- color: #f0f0f0;
59
- font-size: 0.9rem;
60
- padding: 0.5rem 0.75rem;
61
- }
62
-
63
- .config-form input:focus { outline: none; border-color: #555; }
64
-
65
- .actions { display: flex; gap: 0.5rem; }
66
-
67
- button.primary {
68
- background: #fff;
69
- color: #000;
70
- border: none;
71
- border-radius: 4px;
72
- padding: 0.5rem 1.25rem;
73
- font-size: 0.9rem;
74
- cursor: pointer;
75
- }
76
-
77
- button.secondary {
78
- background: #2a2a2a;
79
- color: #f0f0f0;
80
- border: none;
81
- border-radius: 4px;
82
- padding: 0.5rem 1.25rem;
83
- font-size: 0.9rem;
84
- cursor: pointer;
85
- }
86
-
87
- .screen-wrap {
88
- position: relative;
89
- width: 100%;
90
- aspect-ratio: 16 / 9;
91
- background: #000;
92
- border-radius: 8px;
93
- overflow: hidden;
94
- margin-bottom: 1rem;
95
- }
96
-
97
- .status-bar {
98
- display: flex;
99
- gap: 1rem;
100
- align-items: center;
101
- font-size: 0.8rem;
102
- color: #888;
103
- margin-bottom: 1rem;
104
- }
105
-
106
- .dot {
107
- width: 8px;
108
- height: 8px;
109
- border-radius: 50%;
110
- background: #555;
111
- display: inline-block;
112
- margin-right: 0.4rem;
113
- }
114
- .dot.online { background: #4caf50; }
115
- .dot.offline { background: #f44336; }
116
- .dot.connecting { background: #ff9800; }
117
- .dot.syncing { background: #2196f3; }
118
-
119
- .item-info {
120
- background: #1a1a1a;
121
- border-radius: 6px;
122
- padding: 0.75rem 1rem;
123
- font-size: 0.85rem;
124
- color: #aaa;
125
- }
126
-
127
- .item-info strong { color: #fff; }
128
-
129
- .emergency-ticker {
130
- position: absolute;
131
- bottom: 0;
132
- left: 0;
133
- right: 0;
134
- height: 72px;
135
- z-index: 10;
136
- display: flex;
137
- align-items: center;
138
- overflow: hidden;
139
- }
140
-
141
- .emergency-ticker-track {
142
- display: flex;
143
- width: max-content;
144
- animation: emergency-scroll 18s linear infinite;
145
- }
146
-
147
- .emergency-ticker-text {
148
- white-space: nowrap;
149
- font-size: 1.1rem;
150
- font-weight: 600;
151
- padding-right: 6rem;
152
- }
153
-
154
- @keyframes emergency-scroll {
155
- from { transform: translateX(0); }
156
- to { transform: translateX(-50%); }
157
- }
158
-
159
- .placeholder {
160
- position: absolute;
161
- inset: 0;
162
- display: flex;
163
- align-items: center;
164
- justify-content: center;
165
- color: #555;
166
- font-size: 0.9rem;
167
- }
168
-
169
- .headless-media {
170
- position: absolute;
171
- inset: 0;
172
- display: flex;
173
- align-items: center;
174
- justify-content: center;
175
- background: #000;
176
- }
177
-
178
- .headless-media img,
179
- .headless-media video {
180
- max-width: 100%;
181
- max-height: 100%;
182
- object-fit: contain;
183
- }
@@ -1,10 +0,0 @@
1
- import { StrictMode } from 'react'
2
- import { createRoot } from 'react-dom/client'
3
- import './index.css'
4
- import App from './App.tsx'
5
-
6
- createRoot(document.getElementById('root')!).render(
7
- <StrictMode>
8
- <App />
9
- </StrictMode>,
10
- )
@@ -1,116 +0,0 @@
1
- import type {
2
- NetworkDataSource,
3
- Playlist,
4
- SquareScreenResult,
5
- EmergencyAlert,
6
- HeartbeatPayload,
7
- HeartbeatAck,
8
- VideoPlaylistParams,
9
- ImagePlaylistParams,
10
- PlaybackEvent,
11
- } from "square-screen-js-sdk";
12
-
13
- const MOCK_PLAYLIST: Playlist = {
14
- uuid: "mock-playlist-001",
15
- cachedAt: Date.now(),
16
- playlistMeta: { uuid: "mock-playlist-001", name: "Demo Playlist" },
17
- strategy: { loop: true, shuffle: false, preloadCount: 1 },
18
- items: [
19
- {
20
- uuid: "item-001",
21
- name: "Mountain landscape",
22
- type: "image",
23
- url: "https://fastly.picsum.photos/id/10/2500/1667.jpg?hmac=J04WWC_ebchx3WwzbM-Z4_KC_LeLBWr5LZMaAkWkF68",
24
- duration: 5,
25
- transition: "fade",
26
- },
27
- {
28
- uuid: "item-002",
29
- name: "Nature video",
30
- type: "video",
31
- url: "https://lorem.video/480p",
32
- duration: 10,
33
- transition: "slide",
34
- },
35
- {
36
- uuid: "item-004",
37
- name: "Cat video",
38
- type: "video",
39
- url: "https://lorem.video/cat",
40
- duration: 12,
41
- transition: "slide",
42
- },
43
- {
44
- uuid: "item-003",
45
- name: "Forest path",
46
- type: "image",
47
- url: "https://fastly.picsum.photos/id/10/2500/1667.jpg?hmac=J04WWC_ebchx3WwzbM-Z4_KC_LeLBWr5LZMaAkWkF68",
48
- duration: 5,
49
- transition: "fade",
50
- },
51
- ],
52
- };
53
- const MOCK_EMERGENCY: EmergencyAlert = {
54
- uuid: "emergency-001",
55
- title: "Emergency Broadcast",
56
- message:
57
- "This is a test emergency alert. Normal playback will resume shortly.",
58
- backgroundColor: "#cc0000",
59
- textColor: "#ffffff",
60
- targetScope: "all",
61
- };
62
-
63
- let emergencyActive = false;
64
-
65
- export function setMockEmergency(active: boolean) {
66
- emergencyActive = active;
67
- }
68
-
69
- export const mockNetworkDataSource: NetworkDataSource = {
70
- fetchPlaylist: async (): Promise<SquareScreenResult<Playlist>> => ({
71
- success: true,
72
- data: { ...MOCK_PLAYLIST, cachedAt: Date.now() },
73
- }),
74
-
75
- fetchVideoPlaylist: async (
76
- _params: VideoPlaylistParams,
77
- ): Promise<SquareScreenResult<Playlist>> => ({
78
- success: true,
79
- data: {
80
- ...MOCK_PLAYLIST,
81
- items: MOCK_PLAYLIST.items.filter((i) => i.type === "video"),
82
- cachedAt: Date.now(),
83
- },
84
- }),
85
-
86
- fetchImagePlaylist: async (
87
- _params: ImagePlaylistParams,
88
- ): Promise<SquareScreenResult<Playlist>> => ({
89
- success: true,
90
- data: {
91
- ...MOCK_PLAYLIST,
92
- items: MOCK_PLAYLIST.items.filter((i) => i.type === "image"),
93
- cachedAt: Date.now(),
94
- },
95
- }),
96
-
97
- checkForEmergencyAlert: async (): Promise<
98
- SquareScreenResult<EmergencyAlert | null>
99
- > => ({
100
- success: true,
101
- data: emergencyActive ? MOCK_EMERGENCY : null,
102
- }),
103
-
104
- healthCheck: async (
105
- _payload: HeartbeatPayload,
106
- ): Promise<SquareScreenResult<HeartbeatAck>> => ({
107
- success: true,
108
- data: { received: true },
109
- }),
110
- reportPlaybackEvent: async (
111
- _event: PlaybackEvent,
112
- ): Promise<SquareScreenResult<{ recorded: boolean }>> => ({
113
- success: true,
114
- data: { recorded: true },
115
- }),
116
- };
@@ -1,71 +0,0 @@
1
- import { useEffect, useRef, useState } from "react";
2
- import {
3
- SquareScreenPlayer,
4
- type SquareScreenPlayerConfig,
5
- type DeviceStatus,
6
- type EmergencyAlert,
7
- type PlaylistItem,
8
- } from "square-screen-js-sdk";
9
-
10
- export interface PlayerState {
11
- status: DeviceStatus;
12
- currentItem: PlaylistItem | null;
13
- currentIndex: number;
14
- total: number;
15
- alert: EmergencyAlert | null;
16
- }
17
-
18
- /**
19
- * Creates and manages a SquareScreenPlayer instance for the lifetime of the component.
20
- * Returns the player instance and reactive state derived from player events.
21
- */
22
- export function usePlayer(config: SquareScreenPlayerConfig | null): {
23
- player: SquareScreenPlayer | null;
24
- state: PlayerState;
25
- } {
26
- const playerRef = useRef<SquareScreenPlayer | null>(null);
27
- const [state, setState] = useState<PlayerState>({
28
- status: "connecting",
29
- currentItem: null,
30
- currentIndex: 0,
31
- total: 0,
32
- alert: null,
33
- });
34
-
35
- useEffect(() => {
36
- if (!config) return;
37
-
38
- const player = new SquareScreenPlayer(config);
39
- playerRef.current = player;
40
-
41
- const onStatus: Parameters<typeof player.addEventListener<"statuschange">>[1] = (e) =>
42
- setState((s) => ({ ...s, status: e.detail.status }));
43
-
44
- const onItem: Parameters<typeof player.addEventListener<"itemchange">>[1] = (e) =>
45
- setState((s) => ({
46
- ...s,
47
- currentItem: e.detail.item,
48
- currentIndex: e.detail.index,
49
- total: e.detail.total,
50
- }));
51
-
52
- const onAlert: Parameters<typeof player.addEventListener<"emergencyalert">>[1] = (e) =>
53
- setState((s) => ({ ...s, alert: e.detail.alert }));
54
-
55
- player.addEventListener("statuschange", onStatus);
56
- player.addEventListener("itemchange", onItem);
57
- player.addEventListener("emergencyalert", onAlert);
58
-
59
- player.start();
60
-
61
- return () => {
62
- player.removeEventListener("statuschange", onStatus);
63
- player.removeEventListener("itemchange", onItem);
64
- player.removeEventListener("emergencyalert", onAlert);
65
- player.stop();
66
- playerRef.current = null;
67
- };
68
- }, [config]);
69
-
70
- return { player: playerRef.current, state };
71
- }
@@ -1,25 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
- "target": "es2023",
5
- "lib": ["ES2023", "DOM"],
6
- "module": "esnext",
7
- "types": ["vite/client"],
8
- "skipLibCheck": true,
9
-
10
- /* Bundler mode */
11
- "moduleResolution": "bundler",
12
- "allowImportingTsExtensions": true,
13
- "verbatimModuleSyntax": true,
14
- "moduleDetection": "force",
15
- "noEmit": true,
16
- "jsx": "react-jsx",
17
-
18
- /* Linting */
19
- "noUnusedLocals": true,
20
- "noUnusedParameters": true,
21
- "erasableSyntaxOnly": true,
22
- "noFallthroughCasesInSwitch": true
23
- },
24
- "include": ["src"]
25
- }
@@ -1,7 +0,0 @@
1
- {
2
- "files": [],
3
- "references": [
4
- { "path": "./tsconfig.app.json" },
5
- { "path": "./tsconfig.node.json" }
6
- ]
7
- }
@@ -1,24 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
- "target": "es2023",
5
- "lib": ["ES2023"],
6
- "module": "esnext",
7
- "types": ["node"],
8
- "skipLibCheck": true,
9
-
10
- /* Bundler mode */
11
- "moduleResolution": "bundler",
12
- "allowImportingTsExtensions": true,
13
- "verbatimModuleSyntax": true,
14
- "moduleDetection": "force",
15
- "noEmit": true,
16
-
17
- /* Linting */
18
- "noUnusedLocals": true,
19
- "noUnusedParameters": true,
20
- "erasableSyntaxOnly": true,
21
- "noFallthroughCasesInSwitch": true
22
- },
23
- "include": ["vite.config.ts"]
24
- }
@@ -1,7 +0,0 @@
1
- import { defineConfig } from 'vite'
2
- import react from '@vitejs/plugin-react'
3
-
4
- // https://vite.dev/config/
5
- export default defineConfig({
6
- plugins: [react()],
7
- })