@syra.fm/sdk 0.13.1 → 0.14.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 (92) hide show
  1. package/README.md +3 -0
  2. package/lib/commonjs/client.js +24 -0
  3. package/lib/commonjs/client.js.map +1 -1
  4. package/lib/commonjs/index.js +6 -0
  5. package/lib/commonjs/index.js.map +1 -1
  6. package/lib/commonjs/live/components/CreateRoomSheet.js +73 -15
  7. package/lib/commonjs/live/components/CreateRoomSheet.js.map +1 -1
  8. package/lib/commonjs/live/components/InsightsPanel.js +1 -1
  9. package/lib/commonjs/live/components/LiveRoomSheet.js +1 -1
  10. package/lib/commonjs/live/components/MiniRoomBar.js +1 -1
  11. package/lib/commonjs/live/components/PanelHeader.js +1 -1
  12. package/lib/commonjs/live/components/PanelHeader.js.map +1 -1
  13. package/lib/commonjs/live/components/PodcastStreamPicker.js +1 -1
  14. package/lib/commonjs/live/components/RecordingCard.js +1 -1
  15. package/lib/commonjs/live/components/RecordingsPanel.js +1 -1
  16. package/lib/commonjs/live/components/RoomCard.js +1 -1
  17. package/lib/commonjs/live/components/StreamConfigModal.js +1 -1
  18. package/lib/commonjs/live/components/StreamConfigPanel.js +1 -1
  19. package/lib/commonjs/live/icons/MaterialCommunityIcons.js +43 -0
  20. package/lib/commonjs/live/icons/MaterialCommunityIcons.js.map +1 -0
  21. package/lib/commonjs/schema.js +10 -1
  22. package/lib/commonjs/schema.js.map +1 -1
  23. package/lib/module/client.js +25 -1
  24. package/lib/module/client.js.map +1 -1
  25. package/lib/module/index.js +1 -1
  26. package/lib/module/index.js.map +1 -1
  27. package/lib/module/live/components/CreateRoomSheet.js +74 -15
  28. package/lib/module/live/components/CreateRoomSheet.js.map +1 -1
  29. package/lib/module/live/components/InsightsPanel.js +1 -1
  30. package/lib/module/live/components/InsightsPanel.js.map +1 -1
  31. package/lib/module/live/components/LiveRoomSheet.js +1 -1
  32. package/lib/module/live/components/LiveRoomSheet.js.map +1 -1
  33. package/lib/module/live/components/MiniRoomBar.js +1 -1
  34. package/lib/module/live/components/MiniRoomBar.js.map +1 -1
  35. package/lib/module/live/components/PanelHeader.js +1 -1
  36. package/lib/module/live/components/PanelHeader.js.map +1 -1
  37. package/lib/module/live/components/PodcastStreamPicker.js +1 -1
  38. package/lib/module/live/components/PodcastStreamPicker.js.map +1 -1
  39. package/lib/module/live/components/RecordingCard.js +1 -1
  40. package/lib/module/live/components/RecordingCard.js.map +1 -1
  41. package/lib/module/live/components/RecordingsPanel.js +1 -1
  42. package/lib/module/live/components/RecordingsPanel.js.map +1 -1
  43. package/lib/module/live/components/RoomCard.js +1 -1
  44. package/lib/module/live/components/RoomCard.js.map +1 -1
  45. package/lib/module/live/components/StreamConfigModal.js +1 -1
  46. package/lib/module/live/components/StreamConfigModal.js.map +1 -1
  47. package/lib/module/live/components/StreamConfigPanel.js +1 -1
  48. package/lib/module/live/components/StreamConfigPanel.js.map +1 -1
  49. package/lib/module/live/icons/MaterialCommunityIcons.js +36 -0
  50. package/lib/module/live/icons/MaterialCommunityIcons.js.map +1 -0
  51. package/lib/module/schema.js +9 -0
  52. package/lib/module/schema.js.map +1 -1
  53. package/lib/typescript/commonjs/client.d.ts +18 -1
  54. package/lib/typescript/commonjs/client.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/index.d.ts +2 -2
  56. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  57. package/lib/typescript/commonjs/live/components/CreateRoomSheet.d.ts +10 -0
  58. package/lib/typescript/commonjs/live/components/CreateRoomSheet.d.ts.map +1 -1
  59. package/lib/typescript/commonjs/live/icons/MaterialCommunityIcons.d.ts +26 -0
  60. package/lib/typescript/commonjs/live/icons/MaterialCommunityIcons.d.ts.map +1 -0
  61. package/lib/typescript/commonjs/live/validation.d.ts +4 -4
  62. package/lib/typescript/commonjs/live/validation.d.ts.map +1 -1
  63. package/lib/typescript/commonjs/schema.d.ts +61 -0
  64. package/lib/typescript/commonjs/schema.d.ts.map +1 -1
  65. package/lib/typescript/module/client.d.ts +18 -1
  66. package/lib/typescript/module/client.d.ts.map +1 -1
  67. package/lib/typescript/module/index.d.ts +2 -2
  68. package/lib/typescript/module/index.d.ts.map +1 -1
  69. package/lib/typescript/module/live/components/CreateRoomSheet.d.ts +10 -0
  70. package/lib/typescript/module/live/components/CreateRoomSheet.d.ts.map +1 -1
  71. package/lib/typescript/module/live/icons/MaterialCommunityIcons.d.ts +26 -0
  72. package/lib/typescript/module/live/icons/MaterialCommunityIcons.d.ts.map +1 -0
  73. package/lib/typescript/module/live/validation.d.ts +4 -4
  74. package/lib/typescript/module/live/validation.d.ts.map +1 -1
  75. package/lib/typescript/module/schema.d.ts +61 -0
  76. package/lib/typescript/module/schema.d.ts.map +1 -1
  77. package/package.json +5 -4
  78. package/src/client.ts +47 -0
  79. package/src/index.ts +2 -0
  80. package/src/live/components/CreateRoomSheet.tsx +68 -15
  81. package/src/live/components/InsightsPanel.tsx +1 -1
  82. package/src/live/components/LiveRoomSheet.tsx +1 -1
  83. package/src/live/components/MiniRoomBar.tsx +1 -1
  84. package/src/live/components/PanelHeader.tsx +1 -1
  85. package/src/live/components/PodcastStreamPicker.tsx +1 -1
  86. package/src/live/components/RecordingCard.tsx +1 -1
  87. package/src/live/components/RecordingsPanel.tsx +1 -1
  88. package/src/live/components/RoomCard.tsx +1 -1
  89. package/src/live/components/StreamConfigModal.tsx +1 -1
  90. package/src/live/components/StreamConfigPanel.tsx +1 -1
  91. package/src/live/icons/MaterialCommunityIcons.tsx +33 -0
  92. package/src/schema.ts +11 -0
@@ -7,12 +7,25 @@ import {
7
7
  TextInput,
8
8
  ScrollView,
9
9
  FlatList,
10
+ AccessibilityInfo,
10
11
  } from 'react-native';
11
- import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
12
+ import MaterialCommunityIcons from '../icons/MaterialCommunityIcons';
12
13
 
13
14
  import { useLiveConfig } from '../context/LiveConfigContext';
14
15
  import { useLiveRoom } from '../context/LiveRoomContext';
15
16
  import type { Room, House } from '../types';
17
+ import { LIVE_COLOR, LIVE_TINT_COLOR } from '../colors';
18
+
19
+ /**
20
+ * Failure copy the sheet shows INLINE. It used to go to the host `toast`, but
21
+ * the sheet is usually presented in a native modal window, and a toast renders
22
+ * in the main window underneath it: a failed create (a 401 from an expired
23
+ * session, a network drop) looked like a button that did nothing.
24
+ */
25
+ export const CREATE_ROOM_ERRORS = {
26
+ createFailed: "Couldn't create the room. Check your connection and that you're signed in, then try again.",
27
+ scheduleMissing: 'Enter a scheduled start time to schedule the room.',
28
+ } as const;
16
29
 
17
30
  const TOPICS = [
18
31
  'Technology',
@@ -82,6 +95,7 @@ export const CreateRoomSheet = forwardRef<CreateRoomSheetRef, CreateRoomSheetPro
82
95
  const [selectedHouse, setSelectedHouse] = useState<House | null>(null);
83
96
  const [recordingEnabled, setRecordingEnabled] = useState(true);
84
97
  const [loading, setLoading] = useState(false);
98
+ const [error, setError] = useState<string | null>(null);
85
99
 
86
100
  const isValid = title.trim().length > 0;
87
101
  const isBroadcast = roomType === 'broadcast';
@@ -101,9 +115,17 @@ export const CreateRoomSheet = forwardRef<CreateRoomSheetRef, CreateRoomSheetPro
101
115
  recordingEnabled,
102
116
  });
103
117
 
118
+ // Keep the sheet open and say what went wrong where the user is looking, so
119
+ // they can fix it and retry. Screen readers get it announced too.
120
+ const showError = (message: string) => {
121
+ setError(message);
122
+ AccessibilityInfo.announceForAccessibility?.(message);
123
+ };
124
+
104
125
  const handleCreateAndStart = async () => {
105
126
  if (!isValid || loading) return;
106
127
 
128
+ setError(null);
107
129
  setLoading(true);
108
130
  try {
109
131
  const room = await roomsService.createRoom(buildCreatePayload());
@@ -118,11 +140,11 @@ export const CreateRoomSheet = forwardRef<CreateRoomSheetRef, CreateRoomSheetPro
118
140
  }
119
141
  onRoomCreated?.(room);
120
142
  } else {
121
- toast.error('Failed to create room');
143
+ showError(CREATE_ROOM_ERRORS.createFailed);
122
144
  }
123
- } catch (error) {
124
- console.error('Error creating room:', error);
125
- toast.error('Failed to create room');
145
+ } catch (err) {
146
+ console.error('Error creating room:', err);
147
+ showError(CREATE_ROOM_ERRORS.createFailed);
126
148
  } finally {
127
149
  setLoading(false);
128
150
  }
@@ -132,10 +154,11 @@ export const CreateRoomSheet = forwardRef<CreateRoomSheetRef, CreateRoomSheetPro
132
154
  if (!isValid || loading) return;
133
155
 
134
156
  if (!scheduledStart.trim()) {
135
- toast.error('Please enter a scheduled start time');
157
+ showError(CREATE_ROOM_ERRORS.scheduleMissing);
136
158
  return;
137
159
  }
138
160
 
161
+ setError(null);
139
162
  setLoading(true);
140
163
  try {
141
164
  const room = await roomsService.createRoom({
@@ -147,11 +170,11 @@ export const CreateRoomSheet = forwardRef<CreateRoomSheetRef, CreateRoomSheetPro
147
170
  onClose();
148
171
  onRoomCreated?.(room);
149
172
  } else {
150
- toast.error('Failed to create room');
173
+ showError(CREATE_ROOM_ERRORS.createFailed);
151
174
  }
152
- } catch (error) {
153
- console.error('Error creating room:', error);
154
- toast.error('Failed to create room');
175
+ } catch (err) {
176
+ console.error('Error creating room:', err);
177
+ showError(CREATE_ROOM_ERRORS.createFailed);
155
178
  } finally {
156
179
  setLoading(false);
157
180
  }
@@ -160,6 +183,7 @@ export const CreateRoomSheet = forwardRef<CreateRoomSheetRef, CreateRoomSheetPro
160
183
  const handleCreateForEmbed = async () => {
161
184
  if (!isValid || loading) return;
162
185
 
186
+ setError(null);
163
187
  setLoading(true);
164
188
  try {
165
189
  const room = await roomsService.createRoom(buildCreatePayload());
@@ -168,11 +192,11 @@ export const CreateRoomSheet = forwardRef<CreateRoomSheetRef, CreateRoomSheetPro
168
192
  onClose();
169
193
  onRoomCreated?.(room);
170
194
  } else {
171
- toast.error('Failed to create room');
195
+ showError(CREATE_ROOM_ERRORS.createFailed);
172
196
  }
173
- } catch (error) {
174
- console.error('Error creating room:', error);
175
- toast.error('Failed to create room');
197
+ } catch (err) {
198
+ console.error('Error creating room:', err);
199
+ showError(CREATE_ROOM_ERRORS.createFailed);
176
200
  } finally {
177
201
  setLoading(false);
178
202
  }
@@ -260,7 +284,12 @@ export const CreateRoomSheet = forwardRef<CreateRoomSheetRef, CreateRoomSheetPro
260
284
  return (
261
285
  <View style={[styles.container, { backgroundColor: theme.colors.background }]}>
262
286
  <View style={[styles.header, { borderBottomColor: theme.colors.border }]}>
263
- <TouchableOpacity onPress={onClose} style={styles.headerCloseBtn}>
287
+ <TouchableOpacity
288
+ onPress={onClose}
289
+ style={styles.headerCloseBtn}
290
+ accessibilityRole="button"
291
+ accessibilityLabel="Close"
292
+ >
264
293
  <MaterialCommunityIcons name="close" size={20} color={theme.colors.text} />
265
294
  </TouchableOpacity>
266
295
  <Text style={[styles.headerTitle, { color: theme.colors.text }]}>
@@ -269,6 +298,18 @@ export const CreateRoomSheet = forwardRef<CreateRoomSheetRef, CreateRoomSheetPro
269
298
  <View style={{ width: 28 }} />
270
299
  </View>
271
300
 
301
+ {error && (
302
+ <View
303
+ testID="create-room-error"
304
+ accessibilityRole="alert"
305
+ accessibilityLiveRegion="assertive"
306
+ style={[styles.errorBanner, { backgroundColor: LIVE_TINT_COLOR, borderColor: LIVE_COLOR }]}
307
+ >
308
+ <MaterialCommunityIcons name="alert-circle-outline" size={18} color={LIVE_COLOR} />
309
+ <Text style={[styles.errorText, { color: theme.colors.text }]}>{error}</Text>
310
+ </View>
311
+ )}
312
+
272
313
  <Scroll
273
314
  style={{ flex: 1 }}
274
315
  contentContainerStyle={[styles.scrollContent, hideFooter && { paddingBottom: 72 }]}
@@ -532,6 +573,18 @@ const styles = StyleSheet.create({
532
573
  },
533
574
  headerCloseBtn: { width: 28, height: 28, alignItems: 'center', justifyContent: 'center' },
534
575
  headerTitle: { fontSize: 16, fontWeight: '600' },
576
+ errorBanner: {
577
+ flexDirection: 'row',
578
+ alignItems: 'flex-start',
579
+ gap: 8,
580
+ marginHorizontal: 16,
581
+ marginTop: 12,
582
+ paddingHorizontal: 12,
583
+ paddingVertical: 10,
584
+ borderRadius: 12,
585
+ borderWidth: 1,
586
+ },
587
+ errorText: { flex: 1, fontSize: 13, lineHeight: 18 },
535
588
  scrollContent: { paddingVertical: 16, paddingBottom: 12 },
536
589
  sectionPadded: { paddingHorizontal: 16 },
537
590
  inputSection: { marginBottom: 16 },
@@ -1,6 +1,6 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { View, Text, StyleSheet, ScrollView } from 'react-native';
3
- import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
3
+ import MaterialCommunityIcons from '../icons/MaterialCommunityIcons';
4
4
 
5
5
  import { PanelHeader } from './PanelHeader';
6
6
  import type { Room, RoomParticipant, LiveTheme } from '../types';
@@ -8,7 +8,7 @@ import {
8
8
  ScrollView,
9
9
  type ViewStyle,
10
10
  } from 'react-native';
11
- import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
11
+ import MaterialCommunityIcons from '../icons/MaterialCommunityIcons';
12
12
  import { useAuth } from '@oxy.so/services';
13
13
 
14
14
  import { useLiveConfig } from '../context/LiveConfigContext';
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect } from 'react';
2
2
  import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
3
- import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
3
+ import MaterialCommunityIcons from '../icons/MaterialCommunityIcons';
4
4
  import Animated, {
5
5
  useSharedValue,
6
6
  useAnimatedStyle,
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
3
- import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
3
+ import MaterialCommunityIcons from '../icons/MaterialCommunityIcons';
4
4
 
5
5
  import type { LiveTheme } from '../types';
6
6
 
@@ -10,7 +10,7 @@ import {
10
10
  Linking,
11
11
  } from 'react-native';
12
12
  import type { ViewStyle } from 'react-native';
13
- import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
13
+ import MaterialCommunityIcons from '../icons/MaterialCommunityIcons';
14
14
 
15
15
  import { useLiveConfig, type PinnedPodcast } from '../context/LiveConfigContext';
16
16
  import type { LiveTheme } from '../types';
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { View, Text, StyleSheet, TouchableOpacity, StyleProp, ViewStyle } from 'react-native';
3
- import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
3
+ import MaterialCommunityIcons from '../icons/MaterialCommunityIcons';
4
4
  import { useAuth } from '@oxy.so/services';
5
5
 
6
6
  import { useLiveConfig } from '../context/LiveConfigContext';
@@ -1,6 +1,6 @@
1
1
  import React, { useState, useEffect } from 'react';
2
2
  import { View, Text, StyleSheet, ScrollView, TouchableOpacity, ActivityIndicator } from 'react-native';
3
- import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
3
+ import MaterialCommunityIcons from '../icons/MaterialCommunityIcons';
4
4
 
5
5
  import { PanelHeader } from './PanelHeader';
6
6
  import { useLiveConfig } from '../context/LiveConfigContext';
@@ -1,6 +1,6 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { View, Text, TouchableOpacity, StyleProp, ViewStyle } from 'react-native';
3
- import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
3
+ import MaterialCommunityIcons from '../icons/MaterialCommunityIcons';
4
4
  import { useAuth } from '@oxy.so/services';
5
5
 
6
6
  import { useLiveConfig } from '../context/LiveConfigContext';
@@ -12,7 +12,7 @@ import {
12
12
  ScrollView,
13
13
  KeyboardAvoidingView,
14
14
  } from 'react-native';
15
- import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
15
+ import MaterialCommunityIcons from '../icons/MaterialCommunityIcons';
16
16
  import { useAuth } from '@oxy.so/services';
17
17
  import * as ImagePicker from 'expo-image-picker';
18
18
 
@@ -10,7 +10,7 @@ import {
10
10
  Platform,
11
11
  ScrollView,
12
12
  } from 'react-native';
13
- import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
13
+ import MaterialCommunityIcons from '../icons/MaterialCommunityIcons';
14
14
  import * as ImagePicker from 'expo-image-picker';
15
15
 
16
16
  import { useLiveConfig } from '../context/LiveConfigContext';
@@ -0,0 +1,33 @@
1
+ import React, { type ComponentProps } from 'react';
2
+ // The SUBPATH, never the `@expo/vector-icons` barrel: the barrel re-exports every
3
+ // icon family, so one glyph through it drags every family's font into a web
4
+ // bundle.
5
+ import BaseMaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
6
+
7
+ export type MaterialCommunityIconsProps = ComponentProps<typeof BaseMaterialCommunityIcons>;
8
+
9
+ /**
10
+ * The live-rooms UI's one entry to the MaterialCommunityIcons glyph font. SDK
11
+ * components import this, never `@expo/vector-icons/MaterialCommunityIcons`
12
+ * directly — `src/live/icons/glyphImports.test.ts` fails the suite if one does.
13
+ *
14
+ * A vector-icons glyph is a `Text` node whose content is a private-use code
15
+ * point. A screen reader reads it like any other text, so the Create Room sheet
16
+ * announced its options as "\U000f036c, Talk, Open conversation" and its button
17
+ * as "\U000f040a, Start Now": on Android RN builds an unlabelled pressable's
18
+ * description from its children's text, code point included.
19
+ *
20
+ * Every glyph in the SDK is decorative — the control around it carries the
21
+ * words, or an `accessibilityLabel` — so the glyph is hidden from assistive
22
+ * technology here, once, rather than at each call site where the next icon
23
+ * would forget it. `aria-hidden` is `importantForAccessibility=
24
+ * "no-hide-descendants"` on Android, `accessibilityElementsHidden` on iOS and
25
+ * `aria-hidden` on web. It is applied AFTER the caller's props so a call site
26
+ * cannot switch it back on: a glyph that needs a name belongs inside a control
27
+ * that has one.
28
+ */
29
+ export function MaterialCommunityIcons(props: MaterialCommunityIconsProps) {
30
+ return <BaseMaterialCommunityIcons {...props} aria-hidden />;
31
+ }
32
+
33
+ export default MaterialCommunityIcons;
package/src/schema.ts CHANGED
@@ -82,6 +82,17 @@ export const podcastSummarySchema = z.object({
82
82
  });
83
83
  export type PodcastSummary = z.infer<typeof podcastSummarySchema>;
84
84
 
85
+ /**
86
+ * One show the caller is subscribed to, from `GET /api/podcasts/subscriptions`.
87
+ * `lastEpisodeAt` is the newest episode's publish time — what a client compares
88
+ * against its own last visit to badge a show with new episodes.
89
+ */
90
+ export const podcastSubscriptionSchema = z.object({
91
+ podcast: podcastSummarySchema,
92
+ lastEpisodeAt: z.string().optional(),
93
+ });
94
+ export type PodcastSubscription = z.infer<typeof podcastSubscriptionSchema>;
95
+
85
96
  /**
86
97
  * The summary view of a podcast EPISODE returned by the public podcast endpoints
87
98
  * (`GET /api/podcasts/:id/episodes`, `GET /api/episodes/:id`) — just enough to