1/**
2 * The channel id is defined as:
3 *
4 *   ${scope}/${namespace}/${path}
5 *
6 * The scope drives how the namespace is used and controlled
7 *
8 * @alpha
9 */
10export enum LiveChannelScope {
11  DataSource = 'ds', // namespace = data source ID
12  Plugin = 'plugin', // namespace = plugin name (singleton works for apps too)
13  Grafana = 'grafana', // namespace = feature
14  Stream = 'stream', // namespace = id for the managed data stream
15}
16
17/**
18 * The type of data to expect in a given channel
19 *
20 * @alpha
21 */
22export enum LiveChannelType {
23  DataStream = 'stream', // each message contains a batch of rows that will be appened to previous values
24  DataFrame = 'frame', // each message is an entire data frame and should *replace* previous content
25  JSON = 'json', // arbitray json message
26}
27
28export enum LiveChannelConnectionState {
29  /** The connection is not yet established */
30  Pending = 'pending',
31  /** Connected to the channel */
32  Connected = 'connected',
33  /** Disconnected from the channel.  The channel will reconnect when possible */
34  Disconnected = 'disconnected',
35  /** Was at some point connected, and will not try to reconnect */
36  Shutdown = 'shutdown',
37  /** Channel configuraiton was invalid and will not connect */
38  Invalid = 'invalid',
39}
40
41export enum LiveChannelEventType {
42  Status = 'status',
43  Join = 'join',
44  Leave = 'leave',
45  Message = 'message',
46}
47
48/**
49 * @alpha -- experimental
50 */
51export interface LiveChannelStatusEvent {
52  type: LiveChannelEventType.Status;
53
54  /**
55   * {scope}/{namespace}/{path}
56   */
57  id: string;
58
59  /**
60   * unix millies timestamp for the last status change
61   */
62  timestamp: number;
63
64  /**
65   * flag if the channel is actively connected to the channel.
66   * This may be false while the connections get established or if the network is lost
67   * As long as the `shutdown` flag is not set, the connection will try to reestablish
68   */
69  state: LiveChannelConnectionState;
70
71  /**
72   * When joining a channel, there may be an initial packet in the subscribe method
73   */
74  message?: any;
75
76  /**
77   * The last error.
78   *
79   * This will remain in the status until a new message is successfully received from the channel
80   */
81  error?: any;
82}
83
84export interface LiveChannelJoinEvent {
85  type: LiveChannelEventType.Join;
86  user: any; // @alpha -- experimental -- will be filled in when we improve the UI
87}
88
89export interface LiveChannelLeaveEvent {
90  type: LiveChannelEventType.Leave;
91  user: any; // @alpha -- experimental -- will be filled in when we improve the UI
92}
93
94export interface LiveChannelMessageEvent<T> {
95  type: LiveChannelEventType.Message;
96  message: T;
97}
98
99export type LiveChannelEvent<T = any> =
100  | LiveChannelStatusEvent
101  | LiveChannelJoinEvent
102  | LiveChannelLeaveEvent
103  | LiveChannelMessageEvent<T>;
104
105export function isLiveChannelStatusEvent<T>(evt: LiveChannelEvent<T>): evt is LiveChannelStatusEvent {
106  return evt.type === LiveChannelEventType.Status;
107}
108
109export function isLiveChannelJoinEvent<T>(evt: LiveChannelEvent<T>): evt is LiveChannelJoinEvent {
110  return evt.type === LiveChannelEventType.Join;
111}
112
113export function isLiveChannelLeaveEvent<T>(evt: LiveChannelEvent<T>): evt is LiveChannelLeaveEvent {
114  return evt.type === LiveChannelEventType.Leave;
115}
116
117export function isLiveChannelMessageEvent<T>(evt: LiveChannelEvent<T>): evt is LiveChannelMessageEvent<T> {
118  return evt.type === LiveChannelEventType.Message;
119}
120
121/**
122 * @alpha -- experimental
123 */
124export interface LiveChannelPresenceStatus {
125  users: any; // @alpha -- experimental -- will be filled in when we improve the UI
126}
127
128/**
129 * @alpha -- experimental
130 */
131export interface LiveChannelAddress {
132  scope: LiveChannelScope;
133  namespace: string; // depends on the scope
134  path: string;
135}
136
137/**
138 * Return an address from a string
139 *
140 * @alpha -- experimental
141 */
142export function parseLiveChannelAddress(id?: string): LiveChannelAddress | undefined {
143  if (id?.length) {
144    let parts = id.trim().split('/');
145    if (parts.length >= 3) {
146      return {
147        scope: parts[0] as LiveChannelScope,
148        namespace: parts[1],
149        path: parts.slice(2).join('/'),
150      };
151    }
152  }
153  return undefined;
154}
155
156/**
157 * Check if the address has a scope, namespace, and path
158 *
159 * @alpha -- experimental
160 */
161export function isValidLiveChannelAddress(addr?: LiveChannelAddress): addr is LiveChannelAddress {
162  return !!(addr?.path && addr.namespace && addr.scope);
163}
164
165/**
166 * Convert the address to an explicit channel path
167 *
168 * @alpha -- experimental
169 */
170export function toLiveChannelId(addr: LiveChannelAddress): string {
171  if (!addr.scope) {
172    return '';
173  }
174  let id = addr.scope as string;
175  if (!addr.namespace) {
176    return id;
177  }
178  id += '/' + addr.namespace;
179  if (!addr.path) {
180    return id;
181  }
182  return id + '/' + addr.path;
183}
184