openrtc 0.1.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.
Files changed (122) hide show
  1. package/README.md +106 -0
  2. package/dist/OpenRTC.d.ts +71 -0
  3. package/dist/OpenRTC.js +130 -0
  4. package/dist/api/MediaTransport.d.ts +7 -0
  5. package/dist/api/MediaTransport.js +262 -0
  6. package/dist/api/PlutoPeerConnection.d.ts +50 -0
  7. package/dist/api/PlutoPeerConnection.js +434 -0
  8. package/dist/api/PlutoWebSocket.d.ts +24 -0
  9. package/dist/api/PlutoWebSocket.js +113 -0
  10. package/dist/api/PlutoWebTransport.d.ts +29 -0
  11. package/dist/api/PlutoWebTransport.js +105 -0
  12. package/dist/auth/host.d.ts +73 -0
  13. package/dist/auth/host.js +513 -0
  14. package/dist/auth/index.d.ts +1 -0
  15. package/dist/auth/index.js +1 -0
  16. package/dist/auth/internal.d.ts +1 -0
  17. package/dist/auth/internal.js +1 -0
  18. package/dist/compat/index.d.ts +20 -0
  19. package/dist/compat/index.js +20 -0
  20. package/dist/core/Client.d.ts +352 -0
  21. package/dist/core/Client.js +3689 -0
  22. package/dist/core/Connection.d.ts +67 -0
  23. package/dist/core/Connection.js +404 -0
  24. package/dist/core/IEngineBridge.d.ts +18 -0
  25. package/dist/core/IEngineBridge.js +1 -0
  26. package/dist/core/ISignalingTransport.d.ts +13 -0
  27. package/dist/core/ISignalingTransport.js +1 -0
  28. package/dist/core/PeerLifecycleManager.d.ts +43 -0
  29. package/dist/core/PeerLifecycleManager.js +330 -0
  30. package/dist/core/PeerProjectionStore.d.ts +43 -0
  31. package/dist/core/PeerProjectionStore.js +327 -0
  32. package/dist/core/PlutoRTC.d.ts +13 -0
  33. package/dist/core/PlutoRTC.js +26 -0
  34. package/dist/core/Room.d.ts +47 -0
  35. package/dist/core/Room.js +376 -0
  36. package/dist/core/Signaling.d.ts +39 -0
  37. package/dist/core/Signaling.js +147 -0
  38. package/dist/core/bridges/BridgeCodecs.d.ts +19 -0
  39. package/dist/core/bridges/BridgeCodecs.js +317 -0
  40. package/dist/core/bridges/BrowserFirestoreRooms.d.ts +36 -0
  41. package/dist/core/bridges/BrowserFirestoreRooms.js +169 -0
  42. package/dist/core/bridges/BrowserFirestoreSignaling.d.ts +111 -0
  43. package/dist/core/bridges/BrowserFirestoreSignaling.js +1010 -0
  44. package/dist/core/bridges/NativeIpcBridge.d.ts +156 -0
  45. package/dist/core/bridges/NativeIpcBridge.js +1472 -0
  46. package/dist/core/bridges/RtdbPresence.d.ts +122 -0
  47. package/dist/core/bridges/RtdbPresence.js +201 -0
  48. package/dist/core/bridges/TauriBridge.d.ts +8 -0
  49. package/dist/core/bridges/TauriBridge.js +7 -0
  50. package/dist/core/bridges/WasmBridge.d.ts +233 -0
  51. package/dist/core/bridges/WasmBridge.js +1811 -0
  52. package/dist/core/config.d.ts +31 -0
  53. package/dist/core/config.js +138 -0
  54. package/dist/core/transports/ITransport.d.ts +12 -0
  55. package/dist/core/transports/ITransport.js +1 -0
  56. package/dist/core/transports/LocalDaemonBridge.d.ts +41 -0
  57. package/dist/core/transports/LocalDaemonBridge.js +227 -0
  58. package/dist/core/transports/TauriSignaling.d.ts +28 -0
  59. package/dist/core/transports/TauriSignaling.js +373 -0
  60. package/dist/core/transports/TransportFactory.d.ts +9 -0
  61. package/dist/core/transports/TransportFactory.js +59 -0
  62. package/dist/core/transports/WasmSignaling.d.ts +78 -0
  63. package/dist/core/transports/WasmSignaling.js +522 -0
  64. package/dist/core/transports/WebRTCTransport.d.ts +23 -0
  65. package/dist/core/transports/WebRTCTransport.js +132 -0
  66. package/dist/core/trust.d.ts +23 -0
  67. package/dist/core/trust.js +99 -0
  68. package/dist/core/types.d.ts +559 -0
  69. package/dist/core/types.js +19 -0
  70. package/dist/index.d.ts +3 -0
  71. package/dist/index.js +3 -0
  72. package/dist/moq/MoQMediaTransport.d.ts +6 -0
  73. package/dist/moq/MoQMediaTransport.js +106 -0
  74. package/dist/moq/MoQPublisher.d.ts +22 -0
  75. package/dist/moq/MoQPublisher.js +179 -0
  76. package/dist/moq/MoQSession.d.ts +35 -0
  77. package/dist/moq/MoQSession.js +166 -0
  78. package/dist/moq/MoQSubscriber.d.ts +19 -0
  79. package/dist/moq/MoQSubscriber.js +235 -0
  80. package/dist/moq/common.d.ts +47 -0
  81. package/dist/moq/common.js +304 -0
  82. package/dist/moq/messages.d.ts +105 -0
  83. package/dist/moq/messages.js +367 -0
  84. package/dist/presets/plutoHosted.d.ts +13 -0
  85. package/dist/presets/plutoHosted.js +18 -0
  86. package/dist/runtime/DelegatingRuntimeAdapter.d.ts +68 -0
  87. package/dist/runtime/DelegatingRuntimeAdapter.js +250 -0
  88. package/dist/runtime/IpcRuntimeAdapter.d.ts +9 -0
  89. package/dist/runtime/IpcRuntimeAdapter.js +13 -0
  90. package/dist/runtime/RuntimeClient.d.ts +257 -0
  91. package/dist/runtime/RuntimeClient.js +1484 -0
  92. package/dist/runtime/TauriRuntimeAdapter.d.ts +1 -0
  93. package/dist/runtime/TauriRuntimeAdapter.js +1 -0
  94. package/dist/runtime/WasmRuntimeAdapter.d.ts +6 -0
  95. package/dist/runtime/WasmRuntimeAdapter.js +11 -0
  96. package/dist/runtime/factories.d.ts +24 -0
  97. package/dist/runtime/factories.js +66 -0
  98. package/dist/runtime/index.d.ts +6 -0
  99. package/dist/runtime/index.js +5 -0
  100. package/dist/runtime/ipc.d.ts +8 -0
  101. package/dist/runtime/ipc.js +65 -0
  102. package/dist/runtime/ipcContract.d.ts +46 -0
  103. package/dist/runtime/ipcContract.js +1 -0
  104. package/dist/runtime/tauri.d.ts +16 -0
  105. package/dist/runtime/tauri.js +22 -0
  106. package/dist/runtime/types.d.ts +110 -0
  107. package/dist/runtime/types.js +1 -0
  108. package/dist/transports/TauriTransport.d.ts +17 -0
  109. package/dist/transports/TauriTransport.js +49 -0
  110. package/dist/transports/WebTransport.d.ts +23 -0
  111. package/dist/transports/WebTransport.js +71 -0
  112. package/dist/utils/debug.d.ts +4 -0
  113. package/dist/utils/debug.js +33 -0
  114. package/dist/utils/runtime.d.ts +1 -0
  115. package/dist/utils/runtime.js +10 -0
  116. package/package.json +97 -0
  117. package/wasm/README.md +64 -0
  118. package/wasm/openrtc.d.ts +284 -0
  119. package/wasm/openrtc.js +2132 -0
  120. package/wasm/openrtc_bg.wasm +0 -0
  121. package/wasm/openrtc_bg.wasm.d.ts +110 -0
  122. package/wasm/package.json +16 -0
@@ -0,0 +1,105 @@
1
+ import { TransportFactory } from '../core/transports/TransportFactory';
2
+ export class PlutoWebTransport {
3
+ constructor(url, options, clientInstance) {
4
+ this.connection = null;
5
+ this.remoteNodeId = null;
6
+ this.offIncomingStreams = null;
7
+ this.url = url;
8
+ TransportFactory.assertProtocolAvailable('webtransport');
9
+ if (clientInstance) {
10
+ this.client = clientInstance;
11
+ }
12
+ else {
13
+ throw new Error("PlutoWebTransport requires a 'Client' instance to be passed in constructor currently.");
14
+ }
15
+ this.ready = new Promise((resolve, reject) => {
16
+ this._readyResolve = resolve;
17
+ this._readyReject = reject;
18
+ });
19
+ this.closed = new Promise((resolve, reject) => {
20
+ this._closedResolve = resolve;
21
+ this._closedReject = reject;
22
+ });
23
+ this.datagrams = {
24
+ readable: new ReadableStream(),
25
+ writable: new WritableStream()
26
+ };
27
+ this.incomingBidirectionalStreams = new ReadableStream({
28
+ start: (controller) => { this.incomingBiController = controller; }
29
+ });
30
+ this.incomingUnidirectionalStreams = new ReadableStream({
31
+ start: (controller) => { this.incomingUniController = controller; }
32
+ });
33
+ // Use existing connection if provided
34
+ if (options && options.connection) {
35
+ this.connection = options.connection;
36
+ this.remoteNodeId = this.connection.remoteNodeId;
37
+ this.setupListeners();
38
+ this._readyResolve();
39
+ this.connection.onDisconnect(() => {
40
+ this._closedResolve();
41
+ });
42
+ }
43
+ else {
44
+ this.setupListeners();
45
+ this.connect();
46
+ }
47
+ }
48
+ setupListeners() {
49
+ if (!this.client || typeof this.client.onIncomingStream !== 'function')
50
+ return;
51
+ this.offIncomingStreams = this.client.onIncomingStream((incoming) => {
52
+ // Filter by remote Node ID if we are connected
53
+ if (!this.remoteNodeId || incoming.remoteNodeId !== this.remoteNodeId) {
54
+ return;
55
+ }
56
+ if (incoming.type === 'bi') {
57
+ const stream = incoming.stream; // BiStream
58
+ this.incomingBiController.enqueue({
59
+ readable: stream.recv,
60
+ writable: stream.send
61
+ });
62
+ }
63
+ else if (incoming.type === 'uni') {
64
+ const stream = incoming.stream; // ReadableStream
65
+ this.incomingUniController.enqueue(stream);
66
+ }
67
+ });
68
+ }
69
+ async connect() {
70
+ try {
71
+ let ticket = this.url.trim();
72
+ if (ticket.startsWith('pluto://')) {
73
+ ticket = ticket.replace('pluto://', '');
74
+ }
75
+ else if (ticket.startsWith('https://') || ticket.startsWith('http://')) {
76
+ throw new Error('PlutoWebTransport does not support direct native WebTransport URLs. Use a Pluto ticket (pluto://...) routed through the core Client.');
77
+ }
78
+ this.connection = await this.client.connect(ticket);
79
+ this.remoteNodeId = this.connection.remoteNodeId;
80
+ this.connection.onDisconnect(() => this._closedResolve());
81
+ this._readyResolve();
82
+ }
83
+ catch (e) {
84
+ this._readyReject(e);
85
+ this._closedReject(e);
86
+ }
87
+ }
88
+ async createBidirectionalStream() {
89
+ await this.ready;
90
+ if (!this.connection || !this.remoteNodeId)
91
+ throw new Error("Connection not established");
92
+ return await this.client.openBi(this.remoteNodeId);
93
+ }
94
+ async createUnidirectionalStream() {
95
+ await this.ready;
96
+ if (!this.connection || !this.remoteNodeId)
97
+ throw new Error("Connection not established");
98
+ return await this.client.openUni(this.remoteNodeId);
99
+ }
100
+ close() {
101
+ this.offIncomingStreams?.();
102
+ this.offIncomingStreams = null;
103
+ this.connection?.disconnect();
104
+ }
105
+ }
@@ -0,0 +1,73 @@
1
+ import { FirebaseApp } from 'firebase/app';
2
+ import { Auth, User } from 'firebase/auth';
3
+ type ProviderName = 'google' | 'apple';
4
+ interface SignInCredentialPayload {
5
+ provider: ProviderName;
6
+ idToken: string;
7
+ accessToken?: string;
8
+ nonce?: string;
9
+ isCustomToken?: boolean;
10
+ callbackId?: string;
11
+ email?: string;
12
+ displayName?: string;
13
+ photoURL?: string;
14
+ }
15
+ type DesktopAuthRelay = (payload: {
16
+ authToken: string | null;
17
+ refreshToken: string | null;
18
+ }) => Promise<void> | void;
19
+ export declare function registerDesktopAuthRelay(relay: DesktopAuthRelay | null): void;
20
+ declare class AuthHost {
21
+ private app;
22
+ private auth;
23
+ private functions;
24
+ private persistenceReady;
25
+ private redirectResultPromise;
26
+ private redirectResultConsumed;
27
+ private lastRelayedToken;
28
+ private lastRelayedRefreshToken;
29
+ private desktopTokenRefreshIntervalId;
30
+ private syncDesktopAuthToken;
31
+ constructor();
32
+ getApp(): FirebaseApp;
33
+ getCurrentUser(): User | null;
34
+ getAuth(): Auth;
35
+ onAuthStateChanged(callback: (user: User | null) => void): () => void;
36
+ onIdTokenChanged(callback: (user: User | null) => void): () => void;
37
+ getIdToken(options?: {
38
+ forceRefresh?: boolean;
39
+ }): Promise<string | null>;
40
+ checkForSSOToken(): Promise<void>;
41
+ signInWithCustomTokenValue(token: string): Promise<User>;
42
+ signInWithPluto(options?: {
43
+ redirectUri?: string;
44
+ authorizeBaseUrl?: string;
45
+ }): Promise<void>;
46
+ waitForAuth(): Promise<void>;
47
+ signInAnonymously(): Promise<void>;
48
+ signIn(email: string, password: string): Promise<User>;
49
+ signUp(email: string, password: string): Promise<User>;
50
+ signOut(): Promise<void>;
51
+ deleteAccount(): Promise<void>;
52
+ signInWithCredential(payload: SignInCredentialPayload): Promise<User>;
53
+ private mintSessionTokenFromProviderToken;
54
+ signInWithPopup(provider: ProviderName): Promise<{
55
+ idToken: string;
56
+ refreshToken?: string;
57
+ }>;
58
+ signInWithRedirect(provider: ProviderName, options?: {
59
+ nonce?: string;
60
+ }): Promise<void>;
61
+ consumeRedirectResult(): Promise<{
62
+ fromRedirect: boolean;
63
+ user: User | null;
64
+ providerId?: string | null;
65
+ idToken?: string | null;
66
+ } | null>;
67
+ startGoogleSignIn(): Promise<void>;
68
+ startAppleSignIn(): Promise<void>;
69
+ mintSessionToken(): Promise<string>;
70
+ }
71
+ export declare function getAuthHost(): AuthHost;
72
+ export declare const authHost: AuthHost;
73
+ export {};
@@ -0,0 +1,513 @@
1
+ import { getApps, initializeApp } from 'firebase/app';
2
+ import { getFunctions, httpsCallable } from 'firebase/functions';
3
+ import { GoogleAuthProvider, OAuthProvider, browserPopupRedirectResolver, inMemoryPersistence, browserLocalPersistence, createUserWithEmailAndPassword, deleteUser, getAuth, initializeAuth, getRedirectResult, getIdToken as firebaseGetIdToken, onIdTokenChanged, onAuthStateChanged, signInWithCustomToken, signInWithCredential, signInWithEmailAndPassword, signInAnonymously, signInWithPopup, signInWithRedirect, signOut, setPersistence, updateProfile, } from 'firebase/auth';
4
+ import { isTauriEnvironment as detectTauriEnvironment } from '../utils/runtime';
5
+ import { PLUTO_HOSTED_FIREBASE_CONFIG } from '../presets/plutoHosted';
6
+ const AUTH_APP_NAME = 'pluto-rtc-auth';
7
+ function sleep(ms) {
8
+ return new Promise((resolve) => setTimeout(resolve, ms));
9
+ }
10
+ let desktopAuthRelay = null;
11
+ function authDebugEnabled() {
12
+ if (typeof globalThis !== 'undefined' && globalThis.__OPENRTC_DEBUG__ === true) {
13
+ return true;
14
+ }
15
+ if (typeof localStorage !== 'undefined') {
16
+ try {
17
+ return localStorage.getItem('openrtc:debug') === '1';
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ return false;
24
+ }
25
+ function authDebugLog(message, data) {
26
+ if (!authDebugEnabled()) {
27
+ return;
28
+ }
29
+ if (typeof data === 'undefined') {
30
+ console.log(message);
31
+ return;
32
+ }
33
+ console.log(message, data);
34
+ }
35
+ export function registerDesktopAuthRelay(relay) {
36
+ desktopAuthRelay = relay;
37
+ }
38
+ function isTauri() {
39
+ return detectTauriEnvironment();
40
+ }
41
+ function isMobileUserAgent() {
42
+ if (typeof navigator === 'undefined') {
43
+ return false;
44
+ }
45
+ return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent || '');
46
+ }
47
+ function isTauriMobileEnvironment() {
48
+ return isTauri() && isMobileUserAgent();
49
+ }
50
+ function withTimeout(promise, timeoutMs, label) {
51
+ return Promise.race([
52
+ promise,
53
+ new Promise((_, reject) => {
54
+ setTimeout(() => reject(new Error(`${label} timeout after ${timeoutMs}ms`)), timeoutMs);
55
+ }),
56
+ ]);
57
+ }
58
+ function generateNonce(length) {
59
+ const charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
60
+ const randomValues = new Uint8Array(length);
61
+ window.crypto.getRandomValues(randomValues);
62
+ let result = '';
63
+ for (let index = 0; index < length; index += 1) {
64
+ result += charset[randomValues[index] % charset.length];
65
+ }
66
+ return result;
67
+ }
68
+ async function sha256(value) {
69
+ const encoder = new TextEncoder();
70
+ const data = encoder.encode(value);
71
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
72
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
73
+ return hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
74
+ }
75
+ class AuthHost {
76
+ async syncDesktopAuthToken(user, forceRefresh = false) {
77
+ if (!desktopAuthRelay) {
78
+ return;
79
+ }
80
+ const token = user ? await firebaseGetIdToken(user, forceRefresh).catch(() => null) : null;
81
+ const refreshToken = user ? (user.refreshToken ?? null) : null;
82
+ if (token === this.lastRelayedToken && refreshToken === this.lastRelayedRefreshToken) {
83
+ return;
84
+ }
85
+ this.lastRelayedToken = token;
86
+ this.lastRelayedRefreshToken = refreshToken;
87
+ const relayResult = await Promise.allSettled([
88
+ Promise.resolve(desktopAuthRelay({
89
+ authToken: token,
90
+ refreshToken,
91
+ })),
92
+ ]);
93
+ if (relayResult[0]?.status === 'rejected') {
94
+ console.warn('[PLUTO-RTC][AUTH-HOST] Failed to relay auth token to desktop backend', {
95
+ message: relayResult[0].reason?.message,
96
+ });
97
+ }
98
+ }
99
+ constructor() {
100
+ this.redirectResultPromise = null;
101
+ this.redirectResultConsumed = false;
102
+ this.lastRelayedToken = null;
103
+ this.lastRelayedRefreshToken = null;
104
+ this.desktopTokenRefreshIntervalId = null;
105
+ const existingAuthApp = getApps().find((app) => app.name === AUTH_APP_NAME);
106
+ this.app = existingAuthApp || initializeApp(PLUTO_HOSTED_FIREBASE_CONFIG, AUTH_APP_NAME);
107
+ const useMobileSafeAuth = isMobileUserAgent();
108
+ const useTauriMobileSafeAuth = isTauriMobileEnvironment();
109
+ if (useMobileSafeAuth) {
110
+ try {
111
+ const authOptions = useTauriMobileSafeAuth
112
+ ? {
113
+ persistence: [browserLocalPersistence, inMemoryPersistence],
114
+ }
115
+ : {
116
+ persistence: [browserLocalPersistence, inMemoryPersistence],
117
+ popupRedirectResolver: browserPopupRedirectResolver,
118
+ };
119
+ this.auth = initializeAuth(this.app, authOptions);
120
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] Using mobile-safe Firebase auth initialization', {
121
+ appName: this.app.name,
122
+ persistence: 'browserLocalPersistence->inMemoryPersistence',
123
+ redirectResolver: useTauriMobileSafeAuth ? 'disabled-for-tauri-mobile' : 'browserPopupRedirectResolver',
124
+ tauriMobile: useTauriMobileSafeAuth,
125
+ });
126
+ }
127
+ catch {
128
+ this.auth = getAuth(this.app);
129
+ console.warn('[PLUTO-RTC][AUTH-HOST] Mobile-safe auth initialization unavailable; falling back to default getAuth');
130
+ }
131
+ }
132
+ else {
133
+ this.auth = getAuth(this.app);
134
+ }
135
+ this.functions = getFunctions(this.app);
136
+ if (!useMobileSafeAuth) {
137
+ this.persistenceReady = setPersistence(this.auth, browserLocalPersistence).catch((error) => {
138
+ console.warn('[PLUTO-RTC][AUTH-HOST] Failed to set auth persistence', {
139
+ message: error?.message,
140
+ });
141
+ });
142
+ }
143
+ else {
144
+ this.persistenceReady = Promise.resolve();
145
+ }
146
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] Initialized auth host', {
147
+ appName: this.app.name,
148
+ mobileSafeAuth: useMobileSafeAuth,
149
+ });
150
+ onIdTokenChanged(this.auth, (user) => {
151
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] Firebase ID token changed', {
152
+ hasUser: !!user,
153
+ uid: user?.uid,
154
+ providerDataCount: user?.providerData?.length || 0,
155
+ });
156
+ void this.syncDesktopAuthToken(user);
157
+ });
158
+ if (!this.desktopTokenRefreshIntervalId) {
159
+ this.desktopTokenRefreshIntervalId = setInterval(() => {
160
+ void this.syncDesktopAuthToken(this.auth.currentUser, true);
161
+ }, 10 * 60 * 1000);
162
+ }
163
+ }
164
+ getApp() {
165
+ return this.app;
166
+ }
167
+ getCurrentUser() {
168
+ return this.auth.currentUser;
169
+ }
170
+ getAuth() {
171
+ return this.auth;
172
+ }
173
+ onAuthStateChanged(callback) {
174
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] onAuthStateChanged listener registered (via onIdTokenChanged)');
175
+ return onIdTokenChanged(this.auth, callback);
176
+ }
177
+ onIdTokenChanged(callback) {
178
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] onIdTokenChanged listener registered');
179
+ return onIdTokenChanged(this.auth, callback);
180
+ }
181
+ async getIdToken(options) {
182
+ if (!this.auth.currentUser) {
183
+ return null;
184
+ }
185
+ return firebaseGetIdToken(this.auth.currentUser, options?.forceRefresh ?? true);
186
+ }
187
+ async checkForSSOToken() {
188
+ if (typeof window === 'undefined')
189
+ return;
190
+ const urlParams = new URLSearchParams(window.location.search);
191
+ const hashParams = new URLSearchParams(window.location.hash.startsWith('#') ? window.location.hash.slice(1) : window.location.hash);
192
+ const token = urlParams.get('token')
193
+ || hashParams.get('token')
194
+ || ((urlParams.get('custom_token') === 'true' || hashParams.get('custom_token') === 'true')
195
+ ? (urlParams.get('id_token') || hashParams.get('id_token'))
196
+ : null);
197
+ if (!token)
198
+ return;
199
+ try {
200
+ await this.persistenceReady;
201
+ await signInWithCustomToken(this.auth, token);
202
+ window.history.replaceState({}, document.title, window.location.pathname);
203
+ }
204
+ catch (error) {
205
+ console.error('[PLUTO-RTC][AUTH-HOST] SSO sign-in failed:', error);
206
+ }
207
+ }
208
+ async signInWithCustomTokenValue(token) {
209
+ await this.persistenceReady;
210
+ const result = await signInWithCustomToken(this.auth, token);
211
+ return result.user;
212
+ }
213
+ async signInWithPluto(options) {
214
+ if (typeof window === 'undefined') {
215
+ throw new Error('Pluto SSO requires a browser environment.');
216
+ }
217
+ const redirectUri = options?.redirectUri || window.location.href;
218
+ const authorizeBaseUrl = options?.authorizeBaseUrl || 'https://pluto.openrtc.app/sso/authorize';
219
+ const url = new URL(authorizeBaseUrl);
220
+ url.searchParams.set('redirect_uri', redirectUri);
221
+ window.location.assign(url.toString());
222
+ }
223
+ async waitForAuth() {
224
+ await this.persistenceReady;
225
+ if (this.auth.currentUser) {
226
+ return;
227
+ }
228
+ return new Promise((resolve) => {
229
+ const unsubscribe = onAuthStateChanged(this.auth, (user) => {
230
+ if (user) {
231
+ unsubscribe();
232
+ resolve();
233
+ }
234
+ });
235
+ });
236
+ }
237
+ async signInAnonymously() {
238
+ await signInAnonymously(this.auth);
239
+ }
240
+ async signIn(email, password) {
241
+ const result = await signInWithEmailAndPassword(this.auth, email, password);
242
+ return result.user;
243
+ }
244
+ async signUp(email, password) {
245
+ const result = await createUserWithEmailAndPassword(this.auth, email, password);
246
+ return result.user;
247
+ }
248
+ async signOut() {
249
+ await signOut(this.auth);
250
+ }
251
+ async deleteAccount() {
252
+ if (!this.auth.currentUser) {
253
+ throw new Error('No user signed in');
254
+ }
255
+ await deleteUser(this.auth.currentUser);
256
+ }
257
+ async signInWithCredential(payload) {
258
+ const startedAt = Date.now();
259
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] signInWithCredential start', {
260
+ callbackId: payload.callbackId,
261
+ provider: payload.provider,
262
+ hasIdToken: !!payload.idToken,
263
+ hasAccessToken: !!payload.accessToken,
264
+ hasNonce: !!payload.nonce,
265
+ });
266
+ // Fast path: if we received a custom token (minted by the authenticated
267
+ // Web App via mintSessionToken), we can sign in directly without going
268
+ // through the IdP credential flow.
269
+ if (payload.isCustomToken) {
270
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] signInWithCredential: custom token detected, using signInWithCustomToken');
271
+ try {
272
+ const skipAuthStateReady = isTauriMobileEnvironment();
273
+ if (skipAuthStateReady) {
274
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] Skipping authStateReady before custom token sign-in on Tauri mobile', {
275
+ callbackId: payload.callbackId,
276
+ });
277
+ }
278
+ else {
279
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] Awaiting authStateReady...');
280
+ try {
281
+ await withTimeout(this.auth.authStateReady(), 5000, 'authStateReady');
282
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] Auth state ready, signing in with custom token...');
283
+ }
284
+ catch (readyErr) {
285
+ console.warn('[PLUTO-RTC][AUTH-HOST] authStateReady did not resolve in time; continuing custom token sign-in', {
286
+ callbackId: payload.callbackId,
287
+ message: readyErr?.message,
288
+ });
289
+ }
290
+ }
291
+ let result;
292
+ try {
293
+ result = await withTimeout(signInWithCustomToken(this.auth, payload.idToken), 25000, 'signInWithCustomToken (custom token path)');
294
+ }
295
+ catch (firstErr) {
296
+ console.warn('[PLUTO-RTC][AUTH-HOST] First custom token sign-in attempt failed; retrying once', {
297
+ callbackId: payload.callbackId,
298
+ message: firstErr?.message,
299
+ code: firstErr?.code,
300
+ });
301
+ await sleep(250);
302
+ result = await withTimeout(signInWithCustomToken(this.auth, payload.idToken), 25000, 'signInWithCustomToken (custom token path retry)');
303
+ }
304
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] signInWithCustomToken success', {
305
+ callbackId: payload.callbackId,
306
+ provider: payload.provider,
307
+ uid: result.user?.uid,
308
+ elapsedMs: Date.now() - startedAt,
309
+ });
310
+ if (result.user && (payload.displayName || payload.photoURL)) {
311
+ try {
312
+ await updateProfile(result.user, {
313
+ displayName: payload.displayName || result.user.displayName || undefined,
314
+ photoURL: payload.photoURL || result.user.photoURL || undefined,
315
+ });
316
+ }
317
+ catch (profileError) {
318
+ console.warn('[PLUTO-RTC][AUTH-HOST] Failed to update custom-token user profile', {
319
+ callbackId: payload.callbackId,
320
+ message: profileError?.message,
321
+ });
322
+ }
323
+ }
324
+ return result.user;
325
+ }
326
+ catch (err) {
327
+ console.error('[PLUTO-RTC][AUTH-HOST] signInWithCustomToken failed', {
328
+ callbackId: payload.callbackId,
329
+ error: err?.message,
330
+ code: err?.code,
331
+ stack: err?.stack,
332
+ elapsedMs: Date.now() - startedAt,
333
+ });
334
+ throw err;
335
+ }
336
+ }
337
+ let credential;
338
+ if (payload.provider === 'google') {
339
+ credential = GoogleAuthProvider.credential(payload.idToken, payload.accessToken);
340
+ }
341
+ else {
342
+ const provider = new OAuthProvider('apple.com');
343
+ credential = provider.credential({
344
+ idToken: payload.idToken,
345
+ rawNonce: payload.nonce,
346
+ accessToken: payload.accessToken,
347
+ });
348
+ }
349
+ try {
350
+ const result = await withTimeout(signInWithCredential(this.auth, credential), 10000, 'firebase signInWithCredential');
351
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] signInWithCredential success', {
352
+ callbackId: payload.callbackId,
353
+ provider: payload.provider,
354
+ uid: result.user?.uid,
355
+ elapsedMs: Date.now() - startedAt,
356
+ });
357
+ return result.user;
358
+ }
359
+ catch (error) {
360
+ console.warn('[PLUTO-RTC][AUTH-HOST] signInWithCredential primary path failed; trying custom token fallback', {
361
+ callbackId: payload.callbackId,
362
+ provider: payload.provider,
363
+ elapsedMs: Date.now() - startedAt,
364
+ code: error?.code,
365
+ message: error?.message,
366
+ });
367
+ const customToken = await withTimeout(this.mintSessionTokenFromProviderToken(payload), 15000, 'mintSessionTokenFromProviderToken');
368
+ const fallbackResult = await withTimeout(signInWithCustomToken(this.auth, customToken), 15000, 'signInWithCustomToken');
369
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] signInWithCustomToken fallback success', {
370
+ callbackId: payload.callbackId,
371
+ provider: payload.provider,
372
+ uid: fallbackResult.user?.uid,
373
+ elapsedMs: Date.now() - startedAt,
374
+ });
375
+ return fallbackResult.user;
376
+ }
377
+ }
378
+ async mintSessionTokenFromProviderToken(payload) {
379
+ const callable = httpsCallable(this.functions, 'mintSessionToken');
380
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] mintSessionToken fallback request start', {
381
+ provider: payload.provider,
382
+ hasIdToken: !!payload.idToken,
383
+ hasNonce: !!payload.nonce,
384
+ });
385
+ const result = await callable({
386
+ provider: payload.provider,
387
+ idToken: payload.idToken,
388
+ nonce: payload.nonce,
389
+ });
390
+ const token = result.data?.token;
391
+ if (!token) {
392
+ throw new Error('mintSessionToken did not return a token for provider exchange');
393
+ }
394
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] mintSessionToken fallback request success', {
395
+ provider: payload.provider,
396
+ });
397
+ return token;
398
+ }
399
+ async signInWithPopup(provider) {
400
+ if (provider === 'google') {
401
+ const googleProvider = new GoogleAuthProvider();
402
+ googleProvider.addScope('email');
403
+ googleProvider.addScope('profile');
404
+ googleProvider.setCustomParameters({ prompt: 'select_account' });
405
+ const result = await signInWithPopup(this.auth, googleProvider);
406
+ return {
407
+ idToken: await firebaseGetIdToken(result.user, true),
408
+ refreshToken: result.user.refreshToken,
409
+ };
410
+ }
411
+ const appleProvider = new OAuthProvider('apple.com');
412
+ appleProvider.addScope('email');
413
+ appleProvider.addScope('name');
414
+ const nonce = generateNonce(32);
415
+ if (typeof sessionStorage !== 'undefined') {
416
+ sessionStorage.setItem('apple_auth_nonce', nonce);
417
+ }
418
+ appleProvider.setCustomParameters({ nonce: await sha256(nonce) });
419
+ const result = await signInWithPopup(this.auth, appleProvider);
420
+ return {
421
+ idToken: await firebaseGetIdToken(result.user, true),
422
+ refreshToken: result.user.refreshToken,
423
+ };
424
+ }
425
+ async signInWithRedirect(provider, options) {
426
+ if (provider === 'google') {
427
+ const googleProvider = new GoogleAuthProvider();
428
+ googleProvider.addScope('email');
429
+ googleProvider.addScope('profile');
430
+ googleProvider.setCustomParameters({ prompt: 'select_account' });
431
+ await signInWithRedirect(this.auth, googleProvider);
432
+ return;
433
+ }
434
+ const appleProvider = new OAuthProvider('apple.com');
435
+ appleProvider.addScope('email');
436
+ appleProvider.addScope('name');
437
+ const rawNonce = options?.nonce || generateNonce(32);
438
+ if (typeof sessionStorage !== 'undefined') {
439
+ sessionStorage.setItem('apple_auth_nonce', rawNonce);
440
+ }
441
+ appleProvider.setCustomParameters({ nonce: await sha256(rawNonce) });
442
+ await signInWithRedirect(this.auth, appleProvider);
443
+ }
444
+ async consumeRedirectResult() {
445
+ if (this.redirectResultConsumed) {
446
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] consumeRedirectResult skipped (already consumed)');
447
+ return null;
448
+ }
449
+ if (this.redirectResultPromise) {
450
+ return this.redirectResultPromise;
451
+ }
452
+ this.redirectResultPromise = (async () => {
453
+ try {
454
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] consumeRedirectResult start');
455
+ const result = await getRedirectResult(this.auth);
456
+ if (!result) {
457
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] consumeRedirectResult: no redirect result');
458
+ return {
459
+ fromRedirect: false,
460
+ user: null,
461
+ };
462
+ }
463
+ this.redirectResultConsumed = true;
464
+ const idToken = await firebaseGetIdToken(result.user, true).catch(() => null);
465
+ const providerId = result.providerId || result.user.providerData?.[0]?.providerId || null;
466
+ authDebugLog('[PLUTO-RTC][AUTH-HOST] consumeRedirectResult success', {
467
+ uid: result.user.uid,
468
+ providerId,
469
+ hasIdToken: !!idToken,
470
+ });
471
+ return {
472
+ fromRedirect: true,
473
+ user: result.user,
474
+ providerId,
475
+ idToken,
476
+ };
477
+ }
478
+ finally {
479
+ this.redirectResultPromise = null;
480
+ }
481
+ })();
482
+ return this.redirectResultPromise;
483
+ }
484
+ async startGoogleSignIn() {
485
+ await this.signInWithRedirect('google');
486
+ }
487
+ async startAppleSignIn() {
488
+ await this.signInWithRedirect('apple');
489
+ }
490
+ async mintSessionToken() {
491
+ const callable = httpsCallable(this.functions, 'mintSessionToken');
492
+ const result = await callable();
493
+ const payload = result.data;
494
+ if (!payload?.token) {
495
+ throw new Error('mintSessionToken did not return a token');
496
+ }
497
+ return payload.token;
498
+ }
499
+ }
500
+ let authHostInstance = null;
501
+ export function getAuthHost() {
502
+ if (!authHostInstance) {
503
+ authHostInstance = new AuthHost();
504
+ }
505
+ return authHostInstance;
506
+ }
507
+ export const authHost = new Proxy({}, {
508
+ get(_target, prop, _receiver) {
509
+ const host = getAuthHost();
510
+ const value = host[prop];
511
+ return typeof value === 'function' ? value.bind(host) : value;
512
+ },
513
+ });
@@ -0,0 +1 @@
1
+ export { getAuthHost, authHost } from './host';
@@ -0,0 +1 @@
1
+ export { getAuthHost, authHost } from './host';