Live video on the AT Protocol
1/**
2 * Shared linking configuration for react-navigation
3 * Used both for URL parsing (inbound) and URL generation (outbound)
4 */
5
6import { LinkingOptions, getStateFromPath } from "@react-navigation/native";
7import * as ExpoLinking from "expo-linking";
8
9export const SCREEN_PATHS = {
10 // HomeTab screens
11 HomeMain: "",
12 About: "about",
13 Download: "download",
14 LiveDashboard: "live",
15 Login: "login",
16 Multi: "multi/:config",
17 Support: "support",
18 // Settings screens
19 MainSettings: "settings",
20 AboutCategory: "settings/about",
21 AccountCategory: "settings/account",
22 StreamingCategory: "settings/streaming",
23 WebhooksSettings: "settings/streaming/webhooks",
24 RecommendationsSettings: "settings/streaming/recommendations",
25 PrivacyCategory: "settings/privacy",
26 DanmuCategory: "settings/danmu",
27 AdvancedCategory: "settings/advanced",
28 DeveloperSettings: "settings/developer",
29 MultistreamCategory: "settings/streaming/multistream",
30 KeyManagement: "settings/streaming/key-management",
31 LanguagesCategory: "settings/languages",
32 BrandingAdmin: "settings/branding",
33 // Tabs
34 GoLiveTab: "go-live",
35 // Root stack screens
36 Stream: ":user",
37 MobileGoLive: "mobile-golive",
38 AVSync: "sync-test",
39 AppReturn: "app-return/:scheme",
40 PopoutChat: "chat-popout/:user",
41 Embed: "embed/:user",
42 InfoWidgetEmbed: "info-widget",
43 LegacyStream: "legacy/:user",
44 DanmuOBS: "widgets/:user/danmu",
45} as const;
46
47/**
48 * Convert screen path to absolute URL path
49 * Adds leading slash if not present
50 */
51export function getAbsolutePath(screenName: keyof typeof SCREEN_PATHS): string {
52 const path = SCREEN_PATHS[screenName];
53 return path.startsWith("/") ? path : `/${path}`;
54}
55
56/**
57 * Interpolate params into a path template
58 * Example: interpolateParams("/:user", { user: "alice" }) => "/alice"
59 */
60export function interpolateParams(
61 path: string,
62 params?: Record<string, any>,
63): string {
64 if (!params || typeof params !== "object") {
65 return path;
66 }
67
68 let result = path;
69 for (const [key, value] of Object.entries(params)) {
70 result = result.replace(`:${key}`, String(value));
71 }
72 return result;
73}
74
75/**
76 * Check if a screen name is valid
77 */
78export function isValidScreenName(
79 name: string,
80): name is keyof typeof SCREEN_PATHS {
81 return name in SCREEN_PATHS;
82}
83
84export const streamplaceLinkingOptions: LinkingOptions<ReactNavigation.RootParamList> =
85 {
86 prefixes: [ExpoLinking.createURL("")],
87 config: {
88 screens: {
89 // Main tabs (used on all platforms, tab bar hidden on web)
90 MainTabs: {
91 screens: {
92 HomeTab: {
93 screens: {
94 HomeMain: SCREEN_PATHS.HomeMain,
95 About: SCREEN_PATHS.About,
96 Download: SCREEN_PATHS.Download,
97 LiveDashboard: SCREEN_PATHS.LiveDashboard,
98 Login: SCREEN_PATHS.Login,
99 Multi: SCREEN_PATHS.Multi,
100 Support: SCREEN_PATHS.Support,
101 },
102 },
103 GoLiveTab: SCREEN_PATHS.GoLiveTab,
104 SettingsTab: {
105 screens: {
106 MainSettings: SCREEN_PATHS.MainSettings,
107 AboutCategory: SCREEN_PATHS.AboutCategory,
108 AccountCategory: SCREEN_PATHS.AccountCategory,
109 StreamingCategory: SCREEN_PATHS.StreamingCategory,
110 WebhooksSettings: SCREEN_PATHS.WebhooksSettings,
111 RecommendationsSettings: SCREEN_PATHS.RecommendationsSettings,
112 PrivacyCategory: SCREEN_PATHS.PrivacyCategory,
113 DanmuCategory: SCREEN_PATHS.DanmuCategory,
114 AdvancedCategory: SCREEN_PATHS.AdvancedCategory,
115 DeveloperSettings: SCREEN_PATHS.DeveloperSettings,
116 MultistreamCategory: SCREEN_PATHS.MultistreamCategory,
117 KeyManagement: SCREEN_PATHS.KeyManagement,
118 LanguagesCategory: SCREEN_PATHS.LanguagesCategory,
119 BrandingAdmin: SCREEN_PATHS.BrandingAdmin,
120 },
121 },
122 },
123 },
124 // Root stack screens (outside tabs - full-screen experiences)
125 Stream: {
126 path: SCREEN_PATHS.Stream,
127 },
128 MobileGoLive: SCREEN_PATHS.MobileGoLive,
129 AVSync: SCREEN_PATHS.AVSync,
130 AppReturn: SCREEN_PATHS.AppReturn,
131 PopoutChat: SCREEN_PATHS.PopoutChat,
132 Embed: SCREEN_PATHS.Embed,
133 InfoWidgetEmbed: SCREEN_PATHS.InfoWidgetEmbed,
134 LegacyStream: SCREEN_PATHS.LegacyStream,
135 DanmuOBS: SCREEN_PATHS.DanmuOBS,
136 },
137 },
138 };
139
140export function getStreamplaceStateFromPath(path: string) {
141 const ret = getStateFromPath(path, streamplaceLinkingOptions.config);
142 if (!ret) {
143 throw new Error(`Invalid path: ${path}`);
144 }
145 return ret;
146}