@rivi-software/radiotools-sdk 0.0.0-stage → 0.2.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/package.json +31 -4
- package/src/client.ts +587 -0
- package/src/env.ts +38 -0
- package/src/graphql.ts +297 -0
- package/src/http.ts +167 -0
- package/src/index.ts +44 -0
- package/src/media.ts +14 -0
- package/src/playback.ts +58 -0
- package/src/preview.ts +65 -0
- package/src/realtime.ts +120 -0
- package/src/schemas.ts +725 -0
- package/README.md +0 -3
package/src/realtime.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PUBLIC_SOCKET_PROTOCOL,
|
|
3
|
+
type SdkPublicNowPlaying,
|
|
4
|
+
type SdkPublicOnAir,
|
|
5
|
+
type SdkPublicSchedule,
|
|
6
|
+
type SdkPublicSocketEvent,
|
|
7
|
+
type SdkPublishedRecord,
|
|
8
|
+
sdkSchemas,
|
|
9
|
+
} from "./schemas";
|
|
10
|
+
|
|
11
|
+
export type PublicSocketHandlers = {
|
|
12
|
+
onSchedule?: (data: SdkPublicSchedule) => void;
|
|
13
|
+
onAir?: (data: SdkPublicOnAir | null) => void;
|
|
14
|
+
onNowPlaying?: (data: SdkPublicNowPlaying) => void;
|
|
15
|
+
onContent?: (recordId: string, data: SdkPublishedRecord | null) => void;
|
|
16
|
+
onError?: (error: unknown) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type SubscribePublicSocketOptions = {
|
|
20
|
+
/** Absolute or relative socket URL from `station.resources.socket`. */
|
|
21
|
+
socketUrl: string;
|
|
22
|
+
radioToolsOrigin: string;
|
|
23
|
+
fromDate?: string;
|
|
24
|
+
throughDate?: string;
|
|
25
|
+
handlers: PublicSocketHandlers;
|
|
26
|
+
apiToken: string;
|
|
27
|
+
/** Server adapter that sets Authorization on the WebSocket upgrade. */
|
|
28
|
+
createSocket?: (url: string, headers: Record<string, string>) => WebSocket;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type PublicSocketSubscription = {
|
|
32
|
+
close: () => void;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function defaultScheduleWindow(): { fromDate: string; throughDate: string } {
|
|
36
|
+
const from = new Date();
|
|
37
|
+
const fromDate = from.toISOString().slice(0, 10);
|
|
38
|
+
const through = new Date(from);
|
|
39
|
+
through.setUTCDate(through.getUTCDate() + 7);
|
|
40
|
+
return { fromDate, throughDate: through.toISOString().slice(0, 10) };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function parsePublicSocketMessage(raw: string): SdkPublicSocketEvent | null {
|
|
44
|
+
let json: unknown;
|
|
45
|
+
try {
|
|
46
|
+
json = JSON.parse(raw);
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const parsed = sdkSchemas.PublicSocketEvent.safeParse(json);
|
|
51
|
+
if (!parsed.success || parsed.data.protocol !== PUBLIC_SOCKET_PROTOCOL) return null;
|
|
52
|
+
return parsed.data;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function dispatchSocketMessage(message: SdkPublicSocketEvent, handlers: PublicSocketHandlers) {
|
|
56
|
+
switch (message.type) {
|
|
57
|
+
case "schedule":
|
|
58
|
+
handlers.onSchedule?.(message.data);
|
|
59
|
+
return;
|
|
60
|
+
case "on-air":
|
|
61
|
+
handlers.onAir?.(message.data);
|
|
62
|
+
return;
|
|
63
|
+
case "now-playing":
|
|
64
|
+
handlers.onNowPlaying?.(message.data);
|
|
65
|
+
return;
|
|
66
|
+
case "content":
|
|
67
|
+
handlers.onContent?.(message.recordId, message.data);
|
|
68
|
+
return;
|
|
69
|
+
case "preview":
|
|
70
|
+
return;
|
|
71
|
+
default: {
|
|
72
|
+
const unreachable: never = message;
|
|
73
|
+
return unreachable;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Subscribe to the public station socket.
|
|
80
|
+
* Requires a server WebSocket adapter. Browser pages use a server-side bridge.
|
|
81
|
+
* The listener token is sent as an Authorization header, never in the URL.
|
|
82
|
+
*/
|
|
83
|
+
export function subscribePublicSocket(
|
|
84
|
+
options: SubscribePublicSocketOptions,
|
|
85
|
+
): PublicSocketSubscription {
|
|
86
|
+
if (!options.apiToken?.trim()) throw new Error("Listener API token is required");
|
|
87
|
+
if (!options.createSocket) {
|
|
88
|
+
throw new Error("An authenticated server WebSocket adapter is required");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const url = new URL(options.socketUrl, options.radioToolsOrigin);
|
|
92
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
93
|
+
const window =
|
|
94
|
+
options.fromDate && options.throughDate
|
|
95
|
+
? { fromDate: options.fromDate, throughDate: options.throughDate }
|
|
96
|
+
: defaultScheduleWindow();
|
|
97
|
+
url.searchParams.set("fromDate", window.fromDate);
|
|
98
|
+
url.searchParams.set("throughDate", window.throughDate);
|
|
99
|
+
|
|
100
|
+
const socket = options.createSocket(url.href, {
|
|
101
|
+
authorization: `Bearer ${options.apiToken}`,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
socket.addEventListener("message", (event) => {
|
|
105
|
+
const data = typeof event.data === "string" ? event.data : String(event.data);
|
|
106
|
+
const message = parsePublicSocketMessage(data);
|
|
107
|
+
if (!message) return;
|
|
108
|
+
dispatchSocketMessage(message, options.handlers);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
socket.addEventListener("error", (error) => {
|
|
112
|
+
options.handlers.onError?.(error);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
close: () => {
|
|
117
|
+
socket.close();
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|