@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/constants.ts","../src/network/mappers.ts","../src/network/NetworkClient.ts","../src/cache/SquareScreenCache.ts","../src/player/SquareScreenPlayer.ts","../src/renderer/SquareScreenRenderer.ts"],"sourcesContent":["/**\n * Production root URL for the SquareScreen REST API.\n *\n * {@link SquareScreenPlayer} uses this for all network requests. Integrators cannot\n * override it via player configuration; supply a custom {@link NetworkDataSource}\n * only if you must talk to a different host (e.g. tests or a private gateway).\n */\nexport const SQUARESCREEN_API_BASE_URL =\n \"https://square-screen-api-development-f7zuxa.laravel.cloud/api/v1\";\n","import {\n EmergencyAlert,\n HeartbeatAck,\n MediaType,\n Playlist,\n PlaylistItem,\n PlaybackStrategy,\n HeartbeatPayload,\n TransitionType,\n} from \"../core/types\";\nimport {\n ApiEmergencyResponse,\n ApiHeartbeatAck,\n ApiHeartbeatPayload,\n ApiPlaybackStrategy,\n ApiPlaylistItem,\n ApiPlaylistResponse,\n} from \"./apiTypes\";\n\n/** Maps a raw playlist item to the core PlaylistItem type. */\nexport function mapPlaylistItem(raw: ApiPlaylistItem): PlaylistItem {\n return {\n uuid: raw.uuid,\n name: raw.name,\n type: raw.type as MediaType,\n url: raw.url,\n duration: raw.duration_seconds,\n ...(raw.transition !== undefined && {\n transition: raw.transition as TransitionType,\n }),\n ...(raw.width !== undefined && { width: raw.width }),\n ...(raw.height !== undefined && { height: raw.height }),\n ...(raw.title !== undefined && { title: raw.title }),\n ...(raw.thumbnail !== undefined && { thumbnail: raw.thumbnail }),\n };\n}\n\n/**\n * Maps the raw strategy field to a PlaybackStrategy.\n * Returns undefined when the API sends an empty array instead of a real object.\n */\nexport function mapPlaybackStrategy(\n raw: ApiPlaybackStrategy | [],\n): PlaybackStrategy | undefined {\n if (Array.isArray(raw)) return undefined;\n return {\n loop: raw.loop,\n shuffle: raw.shuffle,\n preloadCount: raw.preload_count ?? 0,\n };\n}\n\n/** Maps a raw playlist response to the core Playlist type. */\nexport function mapPlaylistResponse(raw: ApiPlaylistResponse): Playlist {\n return {\n uuid: raw.playlist!.uuid,\n items: raw.items.map(mapPlaylistItem),\n strategy: mapPlaybackStrategy(raw.strategy),\n schedule: raw.schedule,\n playlistMeta: raw.playlist,\n cachedAt: Date.now(),\n };\n}\n\n/**\n * Maps a raw emergency response to an EmergencyAlert.\n * Returns null when no emergency is active.\n */\nexport function mapEmergencyResponse(\n raw: ApiEmergencyResponse,\n): EmergencyAlert | null {\n if (raw.emergency === null) return null;\n return {\n id: raw.emergency.id,\n uuid: raw.emergency.uuid,\n companyId: raw.emergency.company_id,\n title: raw.emergency.title,\n message: raw.emergency.message,\n backgroundColor: raw.emergency.background_color,\n textColor: raw.emergency.text_color,\n targetScope: raw.emergency.target_scope,\n isActive: raw.emergency.is_active,\n startedAt: raw.emergency.started_at,\n ...(raw.emergency.ended_at !== undefined && {\n endedAt: raw.emergency.ended_at,\n }),\n };\n}\n\n/** Maps a raw heartbeat acknowledgement to the core HeartbeatAck type. */\nexport function mapHeartbeatAck(raw: ApiHeartbeatAck): HeartbeatAck {\n return { received: raw.received };\n}\n\nexport function mapHeartbeatPayload(\n raw: HeartbeatPayload,\n): ApiHeartbeatPayload {\n return {\n cpu_usage: raw.cpuUsage,\n memory_usage: raw.memoryUsage,\n disk_usage: raw.diskUsage,\n temperature: raw.temperature,\n os_version: raw.osVersion,\n app_version: raw.playerVersion,\n };\n}\n","import {\n EmergencyAlert,\n HeartbeatAck,\n HeartbeatPayload,\n ImagePlaylistParams,\n NetworkDataSource,\n PlaybackEvent,\n Playlist,\n SquareScreenResult,\n VideoPlaylistParams,\n} from \"../core/types\";\nimport {\n ApiEmergencyResponse,\n ApiHeartbeatAck,\n ApiPlaylistResponse,\n} from \"./apiTypes\";\nimport {\n mapEmergencyResponse,\n mapHeartbeatAck,\n mapPlaylistResponse,\n mapHeartbeatPayload,\n} from \"./mappers\";\n\n/**\n * Handles all HTTP communication with the SquareScreen API.\n *\n * Implements {@link NetworkDataSource} and is the only class in the SDK\n * that calls `fetch` directly. Auth headers are injected automatically on\n * every request — callers never set them manually.\n *\n * This class is internal to the SDK. Consumers interact with it only through\n * the `NetworkDataSource` interface via the player module.\n */\nexport class NetworkClient implements NetworkDataSource {\n private readonly baseUrl: string;\n private readonly deviceId: string;\n private readonly deviceToken: string;\n\n /**\n * @param baseUrl - Root URL of the SquareScreen API, e.g. `\"https://api.squarescreen.io/api/v1\"`.\n * @param deviceId - Unique identifier for this device.\n * @param deviceToken - Secret token used to authenticate this device. Never log or expose this value.\n */\n constructor(baseUrl: string, deviceId: string, deviceToken: string) {\n this.baseUrl = baseUrl;\n this.deviceId = deviceId;\n this.deviceToken = deviceToken;\n }\n\n /**\n * Central fetch wrapper used by all public methods.\n *\n * Injects auth headers, separates HTTP errors from network failures,\n * and isolates JSON parse errors — so callers always receive a\n * {@link SquareScreenResult} and never an uncaught exception.\n *\n * Error mapping:\n * - 401/403 → `AuthError`\n * - Other non-2xx → `NetworkError`\n * - JSON parse failure → `ParseError`\n * - `fetch` throw (no internet, CORS, timeout) → `NetworkError` with code `0`\n */\n private async fetchWrapper<T>(\n endpoint: string,\n options: RequestInit,\n ): Promise<SquareScreenResult<T>> {\n try {\n const response = await fetch(`${this.baseUrl}${endpoint}`, {\n ...options,\n headers: {\n ...(options?.headers ?? {}),\n \"Content-Type\": \"application/json\",\n \"X-Device-Id\": this.deviceId,\n \"X-Device-Token\": this.deviceToken,\n },\n });\n\n if (!response.ok) {\n if (response.status === 401) {\n return {\n success: false,\n error: {\n kind: \"auth\",\n message: \"Unauthorized\",\n },\n };\n }\n if (response.status === 403) {\n return {\n success: false,\n error: {\n kind: \"auth\",\n message: \"Forbidden\",\n },\n };\n }\n return {\n success: false,\n error: {\n kind: \"network\",\n code: response.status,\n message: response.statusText || `HTTP error ${response.status}`,\n },\n };\n }\n\n try {\n const data = (await response.json()) as T;\n return { success: true, data };\n } catch {\n return {\n success: false,\n error: {\n kind: \"parse\",\n message: \"Response could not be parsed as JSON\",\n },\n };\n }\n } catch (err) {\n return {\n success: false,\n error: {\n kind: \"network\",\n code: 0,\n message:\n err instanceof Error ? err.message : \"Network request failed\",\n },\n };\n }\n }\n\n /** Fetches the full active playlist for this device. */\n async fetchPlaylist(): Promise<SquareScreenResult<Playlist>> {\n const result = await this.fetchWrapper<ApiPlaylistResponse>(\n \"/screen/now-playing\",\n { method: \"GET\" },\n );\n if (!result.success) return result;\n return { success: true, data: mapPlaylistResponse(result.data) };\n }\n\n /** Fetches a playlist filtered to video items only. */\n async fetchVideoPlaylist(\n params: VideoPlaylistParams,\n ): Promise<SquareScreenResult<Playlist>> {\n const query = new URLSearchParams({\n type: \"video\",\n ...(params.category && { category: params.category }),\n ...(params.quality && { quality: params.quality }),\n ...(params.limit && { limit: String(params.limit) }),\n ...(params.tags && { tags: params.tags.join(\",\") }),\n });\n const result = await this.fetchWrapper<ApiPlaylistResponse>(\n `/screen/now-playing?${query}`,\n { method: \"GET\" },\n );\n if (!result.success) return result;\n return { success: true, data: mapPlaylistResponse(result.data) };\n }\n\n /** Fetches a playlist filtered to image items only. */\n async fetchImagePlaylist(\n params: ImagePlaylistParams,\n ): Promise<SquareScreenResult<Playlist>> {\n const query = new URLSearchParams({\n type: \"image\",\n ...(params.category && { category: params.category }),\n ...(params.limit && { limit: String(params.limit) }),\n ...(params.tags && { tags: params.tags.join(\",\") }),\n });\n const result = await this.fetchWrapper<ApiPlaylistResponse>(\n `/screen/now-playing?${query}`,\n { method: \"GET\" },\n );\n if (!result.success) return result;\n return { success: true, data: mapPlaylistResponse(result.data) };\n }\n\n /**\n * Checks whether an emergency broadcast is currently active for this device.\n * Resolves to `null` when no emergency is active.\n */\n async checkForEmergencyAlert(): Promise<\n SquareScreenResult<EmergencyAlert | null>\n > {\n const result = await this.fetchWrapper<ApiEmergencyResponse>(\n \"/screen/emergency\",\n { method: \"GET\" },\n );\n if (!result.success) return result;\n return { success: true, data: mapEmergencyResponse(result.data) };\n }\n\n /**\n * Posts device health metrics to the server.\n * The payload is mapped to snake_case before sending to match the API's expected format.\n *\n * @param payload - Collected device metrics. Hardware fields are optional;\n * `playerVersion` is always required.\n */\n async healthCheck(\n payload: HeartbeatPayload,\n ): Promise<SquareScreenResult<HeartbeatAck>> {\n const result = await this.fetchWrapper<ApiHeartbeatAck>(\n \"/screen/heartbeat\",\n { method: \"POST\", body: JSON.stringify(mapHeartbeatPayload(payload)) },\n );\n if (!result.success) return result;\n return { success: true, data: mapHeartbeatAck(result.data) };\n }\n\n /** Reports a playback event to the server.\n * @param event - The playback event to report.\n * @returns A result indicating whether the event was recorded successfully.\n * @example\n * const result = await networkClient.reportPlaybackEvent({\n * media_uuid: \"mf-00000001-0000-0000-0000-000000000001\",\n * playlist_uuid: \"pl-00000001-0000-0000-0000-000000000001\",\n * schedule_uuid: \"sc-00000001-0000-0000-0000-000000000001\",\n * started_at: \"2026-04-28T07:05:00Z\",\n * ended_at: \"2026-04-28T07:05:10Z\",\n * });\n */\n async reportPlaybackEvent(\n event: PlaybackEvent,\n ): Promise<SquareScreenResult<{ recorded: boolean }>> {\n const result = await this.fetchWrapper<{ recorded: boolean }>(\n \"/screen/playback\",\n { method: \"POST\", body: JSON.stringify(event) },\n );\n if (!result.success) return result;\n return { success: true, data: { recorded: result.data.recorded } };\n }\n}\n","import { openDB, deleteDB } from \"idb\";\nimport { Playlist, SquareScreenCacheProvider } from \"../core/types\";\n\nconst DEFAULT_DB_NAME = \"square-screen-cache\";\nconst DEFAULT_CACHE_NAME = \"square-screen-cache\";\n\n/**\n * Default {@link SquareScreenCacheProvider} backed by IndexedDB (playlist metadata)\n * and the Cache API (media blobs).\n *\n * **IndexedDB** — a single `playlists` object store holds one document per\n * playlist UUID. A private `_ttl` field (milliseconds) is stored alongside the\n * record and stripped before returning data to callers.\n *\n * **Cache API** — media files are stored under their original URL as the cache\n * key, matching the same URL-keyed approach used by the Android SDK's\n * `MediaFileCache`. Once a blob is retrieved from the Cache API it is wrapped\n * in a `URL.createObjectURL` handle that is kept in an in-memory map and reused\n * on subsequent calls to avoid redundant allocations. All handles are revoked\n * when {@link clear} is called.\n *\n * Integrators who need different storage behaviour (a service-worker cache,\n * an in-memory store for tests, a custom database) should implement\n * {@link SquareScreenCacheProvider} and pass it via\n * `SquareScreenPlayerConfig.cacheProvider`.\n */\nexport class SquareScreenCache implements SquareScreenCacheProvider {\n private readonly dbName: string;\n private readonly cacheName: string;\n /** Tracks the blob URL created for each media URL so it is reused across calls. */\n private readonly objectUrls = new Map<string, string>();\n\n /**\n * @param dbName Name of the IndexedDB database. Override in tests to isolate state.\n * @param cacheName Name of the Cache API bucket. Override in tests to isolate state.\n */\n constructor({\n dbName = DEFAULT_DB_NAME,\n cacheName = DEFAULT_CACHE_NAME,\n }: { dbName?: string; cacheName?: string } = {}) {\n this.dbName = dbName;\n this.cacheName = cacheName;\n }\n\n /** Opens (or lazily creates) the IndexedDB database with the `playlists` object store. */\n private openDB() {\n return openDB(this.dbName, 1, {\n upgrade(db) {\n if (!db.objectStoreNames.contains(\"playlists\")) {\n db.createObjectStore(\"playlists\", { keyPath: \"uuid\" });\n }\n },\n });\n }\n\n /**\n * Returns the cached playlist for the given UUID, or `null` if:\n * - nothing has been stored for that UUID, or\n * - the record's age exceeds its TTL and `allowStale` is `false`.\n *\n * @param uuid The playlist UUID to look up.\n * @param allowStale When `true`, expired records are returned as-is (useful\n * for serving a fallback when the network is unreachable).\n */\n async getPlaylist(uuid: string, allowStale = false): Promise<Playlist | null> {\n const db = await this.openDB();\n const record = await db.get(\"playlists\", uuid);\n if (!record) return null;\n if (!allowStale && Date.now() - record.cachedAt > record._ttl) return null;\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- strip cache metadata before return\n const { _ttl, ...playlist } = record;\n return playlist as Playlist;\n }\n\n /**\n * Persists a playlist to IndexedDB, overwriting any previous record with the\n * same UUID. `cachedAt` is set to the current wall-clock time so that TTL\n * checks in {@link getPlaylist} have an accurate baseline.\n *\n * @param playlist The playlist to store. Items are persisted inline as part\n * of the same document — no separate per-item records.\n * @param ttlMs How long (in milliseconds) the record is considered fresh.\n * Passed through `SquareScreenPlayerConfig.ttl` by the player.\n */\n async savePlaylist(playlist: Playlist, ttlMs: number): Promise<void> {\n const db = await this.openDB();\n await db.put(\"playlists\", { ...playlist, cachedAt: Date.now(), _ttl: ttlMs });\n }\n\n /**\n * Returns a blob URL for the locally cached copy of `url`, or `null` if the\n * media has not been downloaded yet.\n *\n * The blob URL is created once and stored in an in-memory map. Subsequent\n * calls for the same `url` return the same handle without re-reading the\n * Cache API, which avoids both I/O and unnecessary `URL.createObjectURL`\n * allocations on long-running signage devices.\n *\n * @param url The original remote URL used as the Cache API key.\n */\n async getMediaUrl(url: string): Promise<string | null> {\n const existing = this.objectUrls.get(url);\n if (existing) return existing;\n\n const cache = await caches.open(this.cacheName);\n const response = await cache.match(url);\n if (!response) return null;\n\n const blob = await response.blob();\n const objectUrl = URL.createObjectURL(blob);\n this.objectUrls.set(url, objectUrl);\n return objectUrl;\n }\n\n /**\n * Stores `blob` in the Cache API under `url` and returns a blob URL for\n * immediate use by the renderer.\n *\n * Called by the player after a successful `fetch()` so that subsequent\n * {@link getMediaUrl} calls for the same URL skip the network entirely.\n *\n * @param url The original remote URL — used as the Cache API key so that\n * {@link getMediaUrl} can retrieve the entry with a simple `match`.\n * @param blob The downloaded media blob to persist.\n * @returns A blob URL that the renderer can set as `src` on an\n * `<img>` or `<video>` element.\n */\n async saveMedia(url: string, blob: Blob): Promise<string> {\n const cache = await caches.open(this.cacheName);\n await cache.put(url, new Response(blob));\n const objectUrl = URL.createObjectURL(blob);\n this.objectUrls.set(url, objectUrl);\n return objectUrl;\n }\n\n /**\n * Wipes all cached data:\n * - Revokes every outstanding blob URL to release the underlying Blob references.\n * - Deletes the IndexedDB database entirely (recreated on next access).\n * - Removes all entries from the Cache API bucket.\n *\n * Useful for a \"factory reset\" flow or in tests to guarantee a clean slate.\n */\n async clear(): Promise<void> {\n for (const url of this.objectUrls.values()) URL.revokeObjectURL(url);\n this.objectUrls.clear();\n const db = await this.openDB();\n db.close();\n await deleteDB(this.dbName);\n const cache = await caches.open(this.cacheName);\n const keys = await cache.keys();\n await Promise.all(keys.map((req) => cache.delete(req)));\n }\n}\n","import { SQUARESCREEN_API_BASE_URL } from \"../constants\";\nimport { NetworkClient } from \"../network/NetworkClient\";\nimport {\n DeviceStatus,\n EmergencyAlert,\n NetworkDataSource,\n PlaybackEvent,\n Playlist,\n PlaylistItem,\n SquareScreenCacheProvider,\n} from \"../core/types\";\nimport { SquareScreenCache } from \"../cache/SquareScreenCache\";\n\nconst LAST_PLAYLIST_KEY = \"square-screen:last-playlist-uuid\";\n\nexport interface SquareScreenPlayerConfig {\n /** Unique identifier for this device. */\n deviceId: string;\n /** Secret token used to authenticate this device. Never log or expose this value. */\n deviceToken: string;\n /** SDK version string sent on every heartbeat. */\n version: string;\n /** How long a cached playlist is considered fresh, in ms. Defaults to 5 minutes. */\n ttl?: number;\n /** How often to poll the API for playlist updates, in ms. Defaults to 30 seconds. */\n pollInterval?: number;\n /** How often to poll the API for emergency alerts, in ms. Defaults to 15 seconds. */\n emergencyPollInterval?: number;\n /** How often to send a heartbeat to the API, in ms. Defaults to 60 seconds. */\n heartbeatInterval?: number;\n /**\n * Override the network layer with a custom implementation — useful for testing\n * or mocking without a real API. When provided, `deviceId` and `deviceToken`\n * are ignored for network calls. Otherwise requests go to the production API\n * URL defined by {@link SQUARESCREEN_API_BASE_URL}.\n */\n networkDataSource?: NetworkDataSource;\n /**\n * Override the cache layer with a custom implementation. When omitted the default\n * `SquareScreenCache` (IndexedDB + Cache API) is used. Pass your own implementation\n * to integrate with an existing media storage layer.\n */\n cacheProvider?: SquareScreenCacheProvider;\n}\n\n/** Typed detail payloads for each player event. */\nexport type PlayerEventMap = {\n /** Fires when the current playlist item changes. */\n itemchange: CustomEvent<{ item: PlaylistItem; index: number; total: number }>;\n /** Fires when the device connectivity/sync status changes. */\n statuschange: CustomEvent<{ status: DeviceStatus }>;\n /** Fires when an emergency alert becomes active or is cleared. */\n emergencyalert: CustomEvent<{ alert: EmergencyAlert | null }>;\n /** Fires whenever the playlist is refreshed from the network or cache. */\n playlistupdate: CustomEvent<{ playlist: Playlist }>;\n};\n\n/**\n * Headless, framework-agnostic player that manages playlist fetching, caching,\n * item advancement, preloading, polling, and emergency alerts.\n *\n * Extends `EventTarget` — use `addEventListener` / `removeEventListener` to\n * react to player events. No DOM or rendering logic lives here.\n *\n * @example\n * const player = new SquareScreenPlayer({ deviceId, deviceToken, version: \"1.0.0\" });\n * player.addEventListener(\"itemchange\", ({ detail }) => render(detail.item));\n * player.addEventListener(\"emergencyalert\", ({ detail }) => showAlert(detail.alert));\n * await player.start();\n */\nexport class SquareScreenPlayer extends EventTarget {\n private readonly network: NetworkDataSource;\n private readonly cache: SquareScreenCacheProvider;\n private readonly config: Required<\n Omit<SquareScreenPlayerConfig, \"networkDataSource\" | \"cacheProvider\">\n >;\n\n private playlist: Playlist | null = null;\n private stopped = false;\n private currentIndex = 0;\n private status: DeviceStatus = \"connecting\";\n private activeAlert: EmergencyAlert | null = null;\n private currentItemStartTime: number | null = null;\n\n private itemTimer: ReturnType<typeof setTimeout> | null = null;\n private playlistPollTimer: ReturnType<typeof setInterval> | null = null;\n private emergencyPollTimer: ReturnType<typeof setInterval> | null = null;\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(config: SquareScreenPlayerConfig) {\n super();\n\n if (!config.networkDataSource) {\n if (!config.deviceId) throw new Error(\"SquareScreenPlayer: deviceId is required.\");\n if (!config.deviceToken) throw new Error(\"SquareScreenPlayer: deviceToken is required.\");\n }\n\n this.network =\n config.networkDataSource ??\n new NetworkClient(SQUARESCREEN_API_BASE_URL, config.deviceId, config.deviceToken);\n\n this.cache = config.cacheProvider ?? new SquareScreenCache();\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- omit injected deps from persisted config snapshot\n const { networkDataSource: _n, cacheProvider: _c, ...rest } = config;\n this.config = {\n ttl: 5 * 60 * 1000,\n pollInterval: 30_000,\n emergencyPollInterval: 15_000,\n heartbeatInterval: 60_000,\n ...rest,\n };\n }\n\n /** The playlist item currently being displayed, or null if no playlist is loaded. */\n get currentItem(): PlaylistItem | null {\n return this.playlist?.items[this.currentIndex] ?? null;\n }\n\n /**\n * Starts the player: fetches the playlist, begins playback, and starts polling\n * and heartbeat intervals. Safe to await — resolves once the first playlist load\n * attempt completes (whether from network or cache fallback).\n */\n async start(): Promise<void> {\n this.stopped = false;\n this.setStatus(\"connecting\");\n await this.loadPlaylist();\n await this.checkEmergencyAlert();\n this.startPolling();\n this.startHeartbeat();\n }\n\n /** Stops all timers and clears internal state. Call this when tearing down the player. */\n stop(): void {\n if (this.itemTimer) clearTimeout(this.itemTimer);\n if (this.playlistPollTimer) clearInterval(this.playlistPollTimer);\n if (this.emergencyPollTimer) clearInterval(this.emergencyPollTimer);\n if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);\n this.itemTimer = null;\n this.playlistPollTimer = null;\n this.emergencyPollTimer = null;\n this.heartbeatTimer = null;\n // Prevent in-flight loadPlaylist/applyPlaylist/scheduleItem continuations\n // from dispatching events or setting new timers after teardown.\n this.stopped = true;\n this.playlist = null;\n }\n\n // ---------------------------------------------------------------------------\n // Playlist loading\n // ---------------------------------------------------------------------------\n\n /**\n * Network-first playlist load. On success the playlist is saved to cache and\n * applied if the UUID changed. On failure the player falls back to the last\n * known playlist UUID stored in `localStorage`, loading it from cache with\n * `allowStale = true` so it is served even after its TTL has expired.\n */\n private async loadPlaylist(): Promise<void> {\n const result = await this.network.fetchPlaylist();\n\n if (result.success) {\n this.setStatus(\"online\");\n const playlist = result.data;\n localStorage.setItem(LAST_PLAYLIST_KEY, playlist.uuid);\n await this.cache.savePlaylist(playlist, this.config.ttl);\n\n const isNewPlaylist = !this.playlist || this.playlist.uuid !== playlist.uuid;\n if (isNewPlaylist) {\n await this.applyPlaylist(playlist);\n } else {\n this.dispatch(\"playlistupdate\", { playlist });\n }\n } else {\n this.setStatus(\"offline\");\n if (!this.playlist) {\n const lastUuid = localStorage.getItem(LAST_PLAYLIST_KEY);\n if (lastUuid) {\n const cached = await this.cache.getPlaylist(lastUuid, true);\n if (cached) await this.applyPlaylist(cached);\n }\n }\n }\n }\n\n /**\n * Activates a playlist: optionally shuffles the items, resets the index to 0,\n * emits `playlistupdate`, and kicks off the first `scheduleItem` call.\n * Bails out immediately if `stop()` has been called.\n */\n private async applyPlaylist(playlist: Playlist): Promise<void> {\n if (this.stopped) return;\n const items = playlist.strategy?.shuffle\n ? [...playlist.items].sort(() => Math.random() - 0.5)\n : playlist.items;\n\n this.playlist = { ...playlist, items };\n this.currentIndex = 0;\n this.dispatch(\"playlistupdate\", { playlist: this.playlist });\n this.scheduleItem();\n }\n\n refreshPlaylist(): void {\n this.loadPlaylist();\n }\n\n // ---------------------------------------------------------------------------\n // Playback\n // ---------------------------------------------------------------------------\n\n /**\n * Emits `itemchange` immediately with either the cached blob URL or the raw\n * HTTPS URL, then arms the advancement timer. Never blocks on a network fetch —\n * if the media isn't cached yet the browser loads it directly while\n * `preloadNext` / `downloadInBackground` cache it for the next cycle.\n *\n * Guards against `stop()` being called while awaiting the cache lookup.\n */\n private async scheduleItem(): Promise<void> {\n if (this.itemTimer) clearTimeout(this.itemTimer);\n\n const item = this.currentItem;\n if (!item || !this.playlist) return;\n\n this.currentItemStartTime = Date.now();\n\n // Cache lookup only — never fetch inline so the timer isn't delayed.\n const url = (await this.cache.getMediaUrl(item.url)) ?? item.url;\n if (!this.playlist) return; // stopped while awaiting\n\n this.dispatch(\"itemchange\", {\n item: { ...item, url },\n index: this.currentIndex,\n total: this.playlist.items.length,\n });\n\n // If the current item wasn't in cache, download it now for the next cycle.\n if (url === item.url) this.downloadInBackground(item.url);\n this.preloadNext();\n const elapsed = Date.now() - this.currentItemStartTime!;\n const remaining = Math.max(0, item.duration * 1000 - elapsed);\n this.itemTimer = setTimeout(() => this.advance(), remaining);\n }\n\n /**\n * Schedules background downloads for upcoming items while the current one\n * is playing, so subsequent transitions can serve blob URLs immediately.\n *\n * - `preloadCount: 0` — disabled; nothing is downloaded.\n * - `preloadCount: N` — downloads the next N items ahead (wraps at end).\n * - `preloadCount` absent — downloads all items in the playlist up front.\n */\n private preloadNext(): void {\n if (!this.playlist) return;\n const preloadCount = this.playlist.strategy?.preloadCount;\n if (preloadCount === 0) return;\n const count = preloadCount ?? this.playlist.items.length;\n\n for (let i = 1; i <= count; i++) {\n const idx = (this.currentIndex + i) % this.playlist.items.length;\n this.downloadInBackground(this.playlist.items[idx].url);\n }\n }\n\n /**\n * Fire-and-forget download. Checks the cache first; if the media is already\n * present the call is a no-op. Errors are silently swallowed — a failed\n * download is not fatal; the raw URL will be used until the next successful download.\n */\n private downloadInBackground(url: string): void {\n this.cache\n .getMediaUrl(url)\n .then((cached) => {\n if (cached || !this.playlist) return;\n return fetch(url, { mode: \"cors\" })\n .then((r) => (r.ok ? r.blob() : Promise.reject(new Error(`HTTP ${r.status}`))))\n .then((blob) => this.cache.saveMedia(url, blob));\n })\n .catch(() => {});\n }\n\n /**\n * Moves to the next item. Reports the completed playback event, then either\n * wraps back to index 0 (when `loop` is true or absent) or halts when\n * `loop: false` and the last item has finished.\n */\n private advance(): void {\n if (!this.playlist) return;\n const total = this.playlist.items.length;\n const next = this.currentIndex + 1;\n\n this.reportPlaybackEvent();\n\n if (next >= total) {\n if (this.playlist.strategy?.loop ?? true) {\n this.currentIndex = 0;\n } else {\n return;\n }\n } else {\n this.currentIndex = next;\n }\n\n this.scheduleItem();\n }\n\n /** Sends a proof-of-play event to the server. Fire-and-forget; failures are not surfaced. */\n private async reportPlaybackEvent(): Promise<void> {\n if (!this.currentItemStartTime) return;\n if (!this.currentItem || !this.playlist) return;\n const endedAt = Date.now();\n const event: PlaybackEvent = {\n media_uuid: this.currentItem.uuid,\n playlist_uuid: this.playlist.uuid,\n schedule_uuid: this.playlist.schedule?.uuid ?? \"\",\n started_at: new Date(this.currentItemStartTime).toISOString(),\n ended_at: new Date(endedAt).toISOString(),\n duration_seconds: Math.round((endedAt - this.currentItemStartTime) / 1000),\n completed: true,\n };\n this.network.reportPlaybackEvent(event);\n }\n\n // ---------------------------------------------------------------------------\n // Polling and heartbeat\n // ---------------------------------------------------------------------------\n\n /** Arms the playlist-refresh and emergency-alert polling intervals. */\n private startPolling(): void {\n this.playlistPollTimer = setInterval(\n () => this.loadPlaylist(),\n this.config.pollInterval,\n );\n this.emergencyPollTimer = setInterval(\n () => this.checkEmergencyAlert(),\n this.config.emergencyPollInterval,\n );\n }\n\n /** Arms the periodic heartbeat that keeps the device registration alive. */\n private startHeartbeat(): void {\n this.heartbeatTimer = setInterval(async () => {\n await this.network.healthCheck({ playerVersion: this.config.version });\n }, this.config.heartbeatInterval);\n }\n\n /**\n * Polls the server for an active emergency alert. Emits `emergencyalert` only\n * when the state actually changes (alert activated, cleared, or replaced by a\n * different UUID) to avoid redundant re-renders on the consumer side.\n */\n private async checkEmergencyAlert(): Promise<void> {\n const result = await this.network.checkForEmergencyAlert();\n if (!result.success) return;\n\n const incoming = result.data;\n const wasActive = this.activeAlert !== null;\n const isActive = incoming !== null;\n const alertChanged = isActive && incoming!.uuid !== this.activeAlert?.uuid;\n\n if (wasActive !== isActive || alertChanged) {\n this.activeAlert = incoming;\n this.dispatch(\"emergencyalert\", { alert: incoming });\n }\n }\n\n // ---------------------------------------------------------------------------\n // Helpers\n // ---------------------------------------------------------------------------\n\n private setStatus(status: DeviceStatus): void {\n if (this.status === status) return;\n this.status = status;\n this.dispatch(\"statuschange\", { status });\n }\n\n private dispatch<K extends keyof PlayerEventMap>(\n type: K,\n detail: PlayerEventMap[K] extends CustomEvent<infer D> ? D : never,\n ): void {\n this.dispatchEvent(new CustomEvent(type, { detail }));\n }\n\n addEventListener<K extends keyof PlayerEventMap>(\n type: K,\n listener: (event: PlayerEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n addEventListener(type: string, listener: any, options?: any): void {\n super.addEventListener(type, listener, options);\n }\n\n removeEventListener<K extends keyof PlayerEventMap>(\n type: K,\n listener: (event: PlayerEventMap[K]) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n removeEventListener(type: string, listener: any, options?: any): void {\n super.removeEventListener(type, listener, options);\n }\n}\n","import { EmergencyAlert, PlaylistItem, TransitionType } from \"../core/types\";\nimport { SquareScreenPlayer, PlayerEventMap } from \"../player/SquareScreenPlayer\";\n\nexport interface SquareScreenRendererConfig {\n /** Transition to use when an item doesn't specify one. Defaults to `\"none\"`. */\n defaultTransition?: TransitionType;\n /** Duration of transition animations in ms. Defaults to `500`. */\n transitionDuration?: number;\n /**\n * Maximum ms to wait for a video to reach `canplay` before transitioning anyway.\n * Keeps transitions on-time when media loads from a slow network.\n * Defaults to `3000`. Set to `0` to transition immediately without waiting.\n */\n canPlayTimeout?: number;\n}\n\n/**\n * Optional vanilla JS renderer that wires a {@link SquareScreenPlayer} to a DOM container.\n * Handles `<img>` / `<video>` element lifecycle, autoplay, transitions, and emergency alerts.\n *\n * When an emergency alert is active it renders a full-screen overlay on top of all content.\n * Normal playback resumes automatically once the alert is cleared.\n *\n * @example\n * const player = new SquareScreenPlayer({ ... });\n * const renderer = new SquareScreenRenderer(document.getElementById(\"screen\"), player);\n * renderer.mount();\n * await player.start();\n *\n * // Tear down\n * player.stop();\n * renderer.unmount();\n */\nexport class SquareScreenRenderer {\n private readonly container: HTMLElement;\n private readonly player: SquareScreenPlayer;\n private readonly config: Required<SquareScreenRendererConfig>;\n\n private wrapper: HTMLDivElement | null = null;\n /** Two slots that alternate as current/next during transitions. */\n private slots: [HTMLDivElement, HTMLDivElement] | null = null;\n private activeSlot = 0;\n private alertOverlay: HTMLDivElement | null = null;\n\n private readonly onItemChange: (event: PlayerEventMap[\"itemchange\"]) => void;\n private readonly onEmergencyAlert: (event: PlayerEventMap[\"emergencyalert\"]) => void;\n\n constructor(\n container: HTMLElement,\n player: SquareScreenPlayer,\n config: SquareScreenRendererConfig = {},\n ) {\n this.container = container;\n this.player = player;\n this.config = {\n defaultTransition: \"none\",\n transitionDuration: 500,\n canPlayTimeout: 3000,\n ...config,\n };\n this.onItemChange = (e) => this.handleItemChange(e.detail.item);\n this.onEmergencyAlert = (e) => this.handleEmergencyAlert(e.detail.alert);\n }\n\n /** Injects the renderer DOM into the container and begins listening to the player. */\n mount(): void {\n this.buildDOM();\n this.player.addEventListener(\"itemchange\", this.onItemChange);\n this.player.addEventListener(\"emergencyalert\", this.onEmergencyAlert);\n }\n\n /** Removes the renderer DOM and stops listening to the player. */\n unmount(): void {\n this.player.removeEventListener(\"itemchange\", this.onItemChange);\n this.player.removeEventListener(\"emergencyalert\", this.onEmergencyAlert);\n if (this.wrapper && this.container.contains(this.wrapper)) {\n this.container.removeChild(this.wrapper);\n }\n this.wrapper = null;\n this.slots = null;\n }\n\n // ---------------------------------------------------------------------------\n // DOM setup\n // ---------------------------------------------------------------------------\n\n private buildDOM(): void {\n this.wrapper = document.createElement(\"div\");\n Object.assign(this.wrapper.style, {\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n overflow: \"hidden\",\n backgroundColor: \"#000\",\n });\n\n const slotA = this.createSlot(true);\n const slotB = this.createSlot(false);\n this.wrapper.appendChild(slotA);\n this.wrapper.appendChild(slotB);\n this.slots = [slotA, slotB];\n\n this.alertOverlay = document.createElement(\"div\");\n Object.assign(this.alertOverlay.style, {\n display: \"none\",\n position: \"absolute\",\n top: \"0\",\n right: \"0\",\n bottom: \"0\",\n left: \"0\",\n zIndex: \"9999\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n padding: \"2rem\",\n textAlign: \"center\",\n boxSizing: \"border-box\",\n });\n this.wrapper.appendChild(this.alertOverlay);\n\n this.container.appendChild(this.wrapper);\n }\n\n private createSlot(visible: boolean): HTMLDivElement {\n const slot = document.createElement(\"div\");\n Object.assign(slot.style, {\n position: \"absolute\",\n top: \"0\",\n right: \"0\",\n bottom: \"0\",\n left: \"0\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n opacity: visible ? \"1\" : \"0\",\n transition: `opacity ${this.config.transitionDuration}ms ease`,\n });\n return slot;\n }\n\n // ---------------------------------------------------------------------------\n // Item rendering\n // ---------------------------------------------------------------------------\n\n private async handleItemChange(item: PlaylistItem): Promise<void> {\n if (!this.slots) return;\n\n const nextIndex = (this.activeSlot + 1) % 2;\n const currentSlot = this.slots[this.activeSlot];\n const nextSlot = this.slots[nextIndex];\n\n nextSlot.innerHTML = \"\";\n const media =\n item.type === \"video\"\n ? this.createVideo(item.url)\n : this.createImage(item.url);\n nextSlot.appendChild(media);\n\n if (item.type === \"video\") {\n await this.waitForCanPlay(media as HTMLVideoElement);\n }\n\n const transitionType = item.transition ?? this.config.defaultTransition;\n await this.applyTransition(currentSlot, nextSlot, transitionType);\n\n this.activeSlot = nextIndex;\n }\n\n private handleEmergencyAlert(alert: EmergencyAlert | null): void {\n if (!this.alertOverlay) return;\n\n if (!alert) {\n this.alertOverlay.style.display = \"none\";\n this.alertOverlay.innerHTML = \"\";\n return;\n }\n\n this.alertOverlay.style.display = \"flex\";\n this.alertOverlay.style.backgroundColor = alert.backgroundColor;\n this.alertOverlay.style.color = alert.textColor;\n\n const title = document.createElement(\"h1\");\n title.textContent = alert.title;\n Object.assign(title.style, { margin: \"0 0 1rem\", fontSize: \"2.5rem\", fontWeight: \"bold\" });\n\n const message = document.createElement(\"p\");\n message.textContent = alert.message;\n Object.assign(message.style, { margin: \"0\", fontSize: \"1.5rem\" });\n\n this.alertOverlay.innerHTML = \"\";\n this.alertOverlay.appendChild(title);\n this.alertOverlay.appendChild(message);\n }\n\n private createImage(src: string): HTMLImageElement {\n const img = document.createElement(\"img\");\n Object.assign(img.style, {\n maxWidth: \"100%\",\n maxHeight: \"100%\",\n objectFit: \"contain\",\n });\n img.src = src;\n return img;\n }\n\n private createVideo(src: string): HTMLVideoElement {\n const video = document.createElement(\"video\");\n Object.assign(video.style, {\n width: \"100%\",\n height: \"100%\",\n objectFit: \"contain\",\n });\n video.src = src;\n video.autoplay = true;\n video.muted = true; // required for autoplay in most browsers\n video.playsInline = true; // required on iOS\n video.loop = true; // loop within the item's duration window\n return video;\n }\n\n private waitForCanPlay(video: HTMLVideoElement): Promise<void> {\n return new Promise((resolve) => {\n if (video.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA) {\n resolve();\n return;\n }\n const timeout = this.config.canPlayTimeout;\n const timer = timeout > 0 ? setTimeout(resolve, timeout) : null;\n const handler = () => {\n if (timer) clearTimeout(timer);\n video.removeEventListener(\"canplay\", handler);\n resolve();\n };\n video.addEventListener(\"canplay\", handler);\n });\n }\n\n // ---------------------------------------------------------------------------\n // Transitions\n // ---------------------------------------------------------------------------\n\n private async applyTransition(\n from: HTMLDivElement,\n to: HTMLDivElement,\n type: TransitionType,\n ): Promise<void> {\n const duration = this.config.transitionDuration;\n\n if (type === \"none\" || duration === 0) {\n from.style.opacity = \"0\";\n to.style.opacity = \"1\";\n } else if (type === \"fade\") {\n to.style.opacity = \"1\";\n from.style.opacity = \"0\";\n await this.wait(duration);\n } else if (type === \"slide\") {\n // Snap next slot into position off-screen (no animation), then slide both\n to.style.transition = \"none\";\n to.style.transform = \"translateX(100%)\";\n to.style.opacity = \"1\";\n void to.offsetWidth; // force reflow so the snap takes effect before animating\n\n to.style.transition = `transform ${duration}ms ease`;\n from.style.transition = `transform ${duration}ms ease`;\n to.style.transform = \"translateX(0)\";\n from.style.transform = \"translateX(-100%)\";\n await this.wait(duration);\n }\n\n // Reset both slots to a clean opacity-only state for the next transition\n from.style.transition = `opacity ${duration}ms ease`;\n from.style.transform = \"\";\n from.style.opacity = \"0\";\n to.style.transition = `opacity ${duration}ms ease`;\n to.style.transform = \"\";\n to.style.opacity = \"1\";\n }\n\n private wait(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glivion/square-screen-js-sdk",
3
- "version": "0.1.0",
3
+ "version": "1.0.1",
4
4
  "description": "",
5
5
  "homepage": "https://github.com/Perpectiv-Global/square-screen-js-sdk#readme",
6
6
  "bugs": {
@@ -13,6 +13,13 @@
13
13
  "license": "ISC",
14
14
  "author": "",
15
15
  "type": "module",
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
16
23
  "main": "./dist/index.cjs",
17
24
  "module": "./dist/index.mjs",
18
25
  "types": "./dist/index.d.cts",
@@ -1,70 +0,0 @@
1
- name: Build JS SDK
2
-
3
- on:
4
- push:
5
- branches: [ develop ]
6
- pull_request:
7
- branches: [ develop ]
8
-
9
- jobs:
10
- lint:
11
- name: Lint
12
- runs-on: ubuntu-latest
13
- steps:
14
- - uses: actions/checkout@v4
15
-
16
- - uses: actions/setup-node@v4
17
- with:
18
- node-version: 20
19
- cache: yarn
20
-
21
- - name: Install dependencies
22
- run: yarn install --frozen-lockfile
23
-
24
- - name: ESLint
25
- run: yarn lint
26
-
27
- - name: Type check
28
- run: yarn typecheck
29
-
30
- test:
31
- name: Test
32
- needs: lint
33
- runs-on: ubuntu-latest
34
- steps:
35
- - uses: actions/checkout@v4
36
-
37
- - uses: actions/setup-node@v4
38
- with:
39
- node-version: 20
40
- cache: yarn
41
-
42
- - name: Install dependencies
43
- run: yarn install --frozen-lockfile
44
-
45
- - name: Run tests
46
- run: yarn test
47
-
48
- build:
49
- name: Build
50
- needs: test
51
- runs-on: ubuntu-latest
52
- steps:
53
- - uses: actions/checkout@v4
54
-
55
- - uses: actions/setup-node@v4
56
- with:
57
- node-version: 20
58
- cache: yarn
59
-
60
- - name: Install dependencies
61
- run: yarn install --frozen-lockfile
62
-
63
- - name: Build
64
- run: yarn build
65
-
66
- - name: Upload dist artifact
67
- uses: actions/upload-artifact@v4
68
- with:
69
- name: dist
70
- path: dist/
package/eslint.config.js DELETED
@@ -1,3 +0,0 @@
1
- import tseslint from 'typescript-eslint';
2
-
3
- export default tseslint.config(tseslint.configs.recommended);
@@ -1,73 +0,0 @@
1
- # React + TypeScript + Vite
2
-
3
- This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
-
5
- Currently, two official plugins are available:
6
-
7
- - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
8
- - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
9
-
10
- ## React Compiler
11
-
12
- The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
13
-
14
- ## Expanding the ESLint configuration
15
-
16
- If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
17
-
18
- ```js
19
- export default defineConfig([
20
- globalIgnores(['dist']),
21
- {
22
- files: ['**/*.{ts,tsx}'],
23
- extends: [
24
- // Other configs...
25
-
26
- // Remove tseslint.configs.recommended and replace with this
27
- tseslint.configs.recommendedTypeChecked,
28
- // Alternatively, use this for stricter rules
29
- tseslint.configs.strictTypeChecked,
30
- // Optionally, add this for stylistic rules
31
- tseslint.configs.stylisticTypeChecked,
32
-
33
- // Other configs...
34
- ],
35
- languageOptions: {
36
- parserOptions: {
37
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
38
- tsconfigRootDir: import.meta.dirname,
39
- },
40
- // other options...
41
- },
42
- },
43
- ])
44
- ```
45
-
46
- You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
47
-
48
- ```js
49
- // eslint.config.js
50
- import reactX from 'eslint-plugin-react-x'
51
- import reactDom from 'eslint-plugin-react-dom'
52
-
53
- export default defineConfig([
54
- globalIgnores(['dist']),
55
- {
56
- files: ['**/*.{ts,tsx}'],
57
- extends: [
58
- // Other configs...
59
- // Enable lint rules for React
60
- reactX.configs['recommended-typescript'],
61
- // Enable lint rules for React DOM
62
- reactDom.configs.recommended,
63
- ],
64
- languageOptions: {
65
- parserOptions: {
66
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
67
- tsconfigRootDir: import.meta.dirname,
68
- },
69
- // other options...
70
- },
71
- },
72
- ])
73
- ```
@@ -1,22 +0,0 @@
1
- import js from '@eslint/js'
2
- import globals from 'globals'
3
- import reactHooks from 'eslint-plugin-react-hooks'
4
- import reactRefresh from 'eslint-plugin-react-refresh'
5
- import tseslint from 'typescript-eslint'
6
- import { defineConfig, globalIgnores } from 'eslint/config'
7
-
8
- export default defineConfig([
9
- globalIgnores(['dist']),
10
- {
11
- files: ['**/*.{ts,tsx}'],
12
- extends: [
13
- js.configs.recommended,
14
- tseslint.configs.recommended,
15
- reactHooks.configs.flat.recommended,
16
- reactRefresh.configs.vite,
17
- ],
18
- languageOptions: {
19
- globals: globals.browser,
20
- },
21
- },
22
- ])
@@ -1,13 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>react-app</title>
8
- </head>
9
- <body>
10
- <div id="root"></div>
11
- <script type="module" src="/src/main.tsx"></script>
12
- </body>
13
- </html>