@harmoni-org/sdk 0.0.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.
package/dist/index.mjs ADDED
@@ -0,0 +1,2013 @@
1
+ import axios from 'axios';
2
+ import { io } from 'socket.io-client';
3
+
4
+ var __defProp = Object.defineProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+
10
+ // src/core/errors/ApiError.ts
11
+ var ApiError = class _ApiError extends Error {
12
+ constructor(message, status, code, details) {
13
+ super(message);
14
+ this.isApiError = true;
15
+ this.name = "ApiError";
16
+ this.status = status;
17
+ this.code = code;
18
+ this.details = details;
19
+ if (Error.captureStackTrace) {
20
+ Error.captureStackTrace(this, _ApiError);
21
+ }
22
+ }
23
+ toJSON() {
24
+ return {
25
+ message: this.message,
26
+ code: this.code,
27
+ status: this.status,
28
+ details: this.details
29
+ };
30
+ }
31
+ static isApiError(error) {
32
+ return error instanceof _ApiError || error?.isApiError === true;
33
+ }
34
+ };
35
+ var NetworkError = class extends ApiError {
36
+ constructor(message = "Network error occurred") {
37
+ super(message, 0, "NETWORK_ERROR");
38
+ this.name = "NetworkError";
39
+ }
40
+ };
41
+ var TimeoutError = class extends ApiError {
42
+ constructor(message = "Request timeout") {
43
+ super(message, 408, "TIMEOUT_ERROR");
44
+ this.name = "TimeoutError";
45
+ }
46
+ };
47
+ var UnauthorizedError = class extends ApiError {
48
+ constructor(message = "Unauthorized") {
49
+ super(message, 401, "UNAUTHORIZED");
50
+ this.name = "UnauthorizedError";
51
+ }
52
+ };
53
+ var ForbiddenError = class extends ApiError {
54
+ constructor(message = "Forbidden") {
55
+ super(message, 403, "FORBIDDEN");
56
+ this.name = "ForbiddenError";
57
+ }
58
+ };
59
+ var NotFoundError = class extends ApiError {
60
+ constructor(message = "Resource not found") {
61
+ super(message, 404, "NOT_FOUND");
62
+ this.name = "NotFoundError";
63
+ }
64
+ };
65
+ var ValidationError = class extends ApiError {
66
+ constructor(message = "Validation failed", details) {
67
+ super(message, 422, "VALIDATION_ERROR", details);
68
+ this.name = "ValidationError";
69
+ }
70
+ };
71
+
72
+ // src/core/http/HttpClient.ts
73
+ var HttpClient = class {
74
+ constructor(config) {
75
+ this.config = config;
76
+ this.authToken = null;
77
+ this.retryCount = /* @__PURE__ */ new Map();
78
+ this.client = axios.create({
79
+ baseURL: config.baseURL,
80
+ timeout: config.timeout || 3e4,
81
+ headers: {
82
+ "Content-Type": "application/json",
83
+ ...config.headers
84
+ },
85
+ withCredentials: config.withCredentials || false
86
+ });
87
+ this.setupInterceptors();
88
+ }
89
+ setupInterceptors() {
90
+ this.client.interceptors.request.use(
91
+ (config) => {
92
+ const requestConfig = config;
93
+ if (this.authToken && !requestConfig.skipAuth) {
94
+ config.headers = config.headers || {};
95
+ config.headers.Authorization = `Bearer ${this.authToken}`;
96
+ }
97
+ return config;
98
+ },
99
+ (error) => {
100
+ return Promise.reject(this.handleError(error));
101
+ }
102
+ );
103
+ this.client.interceptors.response.use(
104
+ (response) => response,
105
+ async (error) => {
106
+ const originalRequest = error.config;
107
+ if (error.response?.status === 401 && !originalRequest._retry && this.refreshTokenFn) {
108
+ originalRequest._retry = true;
109
+ try {
110
+ const newToken = await this.refreshTokenFn();
111
+ this.setAuthToken(newToken);
112
+ if (!originalRequest.headers) {
113
+ originalRequest.headers = {};
114
+ }
115
+ originalRequest.headers.Authorization = `Bearer ${newToken}`;
116
+ return this.client.request(originalRequest);
117
+ } catch (refreshError) {
118
+ this.authToken = null;
119
+ return Promise.reject(this.handleError(refreshError));
120
+ }
121
+ }
122
+ if (this.shouldRetry(error, originalRequest)) {
123
+ return this.retryRequest(originalRequest);
124
+ }
125
+ return Promise.reject(this.handleError(error));
126
+ }
127
+ );
128
+ }
129
+ shouldRetry(error, config) {
130
+ if (!config || config.skipRetry) return false;
131
+ const retryConfig = this.config.retryConfig;
132
+ if (!retryConfig || !retryConfig.maxRetries) return false;
133
+ const method = config.method?.toUpperCase();
134
+ const safeToRetry = method === "GET" || method === "HEAD" || method === "OPTIONS";
135
+ if (!safeToRetry && !config._retry) {
136
+ return false;
137
+ }
138
+ const requestId = this.getRequestId(config);
139
+ const currentRetries = this.retryCount.get(requestId) || 0;
140
+ if (currentRetries >= retryConfig.maxRetries) {
141
+ this.retryCount.delete(requestId);
142
+ return false;
143
+ }
144
+ const retryableStatuses = retryConfig.retryableStatuses || [408, 429, 500, 502, 503, 504];
145
+ return error.response ? retryableStatuses.includes(error.response.status) : true;
146
+ }
147
+ async retryRequest(config) {
148
+ const requestId = this.getRequestId(config);
149
+ const currentRetries = this.retryCount.get(requestId) || 0;
150
+ this.retryCount.set(requestId, currentRetries + 1);
151
+ const delay = this.config.retryConfig?.retryDelay || 1e3;
152
+ const backoffDelay = delay * Math.pow(2, currentRetries);
153
+ await this.sleep(backoffDelay);
154
+ config._retryCount = currentRetries + 1;
155
+ return this.client.request(config);
156
+ }
157
+ getRequestId(config) {
158
+ return `${config.method}-${config.url}`;
159
+ }
160
+ sleep(ms) {
161
+ return new Promise((resolve) => setTimeout(resolve, ms));
162
+ }
163
+ handleError(error) {
164
+ if (ApiError.isApiError(error)) {
165
+ return error;
166
+ }
167
+ if (axios.isAxiosError(error)) {
168
+ const axiosError = error;
169
+ if (!axiosError.response) {
170
+ if (axiosError.code === "ECONNABORTED") {
171
+ return new TimeoutError();
172
+ }
173
+ return new NetworkError(axiosError.message);
174
+ }
175
+ const status = axiosError.response.status;
176
+ const data = axiosError.response.data;
177
+ const message = data?.message || axiosError.message;
178
+ switch (status) {
179
+ case 401:
180
+ return new UnauthorizedError(message);
181
+ case 403:
182
+ return new ForbiddenError(message);
183
+ case 404:
184
+ return new NotFoundError(message);
185
+ case 422:
186
+ return new ValidationError(message, data);
187
+ default:
188
+ return new ApiError(message, status, data?.code, data);
189
+ }
190
+ }
191
+ return new ApiError(error instanceof Error ? error.message : "An unknown error occurred", 500);
192
+ }
193
+ // Public methods
194
+ setAuthToken(token) {
195
+ this.authToken = token;
196
+ }
197
+ getAuthToken() {
198
+ return this.authToken;
199
+ }
200
+ setRefreshTokenFunction(fn) {
201
+ this.refreshTokenFn = fn;
202
+ }
203
+ addRequestInterceptor(interceptor) {
204
+ return this.client.interceptors.request.use(
205
+ interceptor.onFulfilled,
206
+ interceptor.onRejected
207
+ );
208
+ }
209
+ addResponseInterceptor(interceptor) {
210
+ return this.client.interceptors.response.use(interceptor.onFulfilled, interceptor.onRejected);
211
+ }
212
+ removeRequestInterceptor(id) {
213
+ this.client.interceptors.request.eject(id);
214
+ }
215
+ removeResponseInterceptor(id) {
216
+ this.client.interceptors.response.eject(id);
217
+ }
218
+ // HTTP methods
219
+ async get(url, config) {
220
+ const response = await this.client.get(url, config);
221
+ return response.data;
222
+ }
223
+ async post(url, data, config) {
224
+ const requestConfig = { ...config };
225
+ if (data instanceof FormData) {
226
+ requestConfig.headers = {
227
+ ...requestConfig.headers,
228
+ "Content-Type": "multipart/form-data"
229
+ };
230
+ }
231
+ const response = await this.client.post(url, data, requestConfig);
232
+ return response.data;
233
+ }
234
+ async put(url, data, config) {
235
+ const requestConfig = { ...config };
236
+ if (data instanceof FormData) {
237
+ requestConfig.headers = {
238
+ ...requestConfig.headers,
239
+ "Content-Type": "multipart/form-data"
240
+ };
241
+ }
242
+ const response = await this.client.put(url, data, requestConfig);
243
+ return response.data;
244
+ }
245
+ async patch(url, data, config) {
246
+ const requestConfig = { ...config };
247
+ if (data instanceof FormData) {
248
+ requestConfig.headers = {
249
+ ...requestConfig.headers,
250
+ "Content-Type": "multipart/form-data"
251
+ };
252
+ }
253
+ const response = await this.client.patch(url, data, requestConfig);
254
+ return response.data;
255
+ }
256
+ async delete(url, config) {
257
+ const response = await this.client.delete(url, config);
258
+ return response.data;
259
+ }
260
+ };
261
+
262
+ // src/modules/auth/AuthModule.ts
263
+ var AuthModule = class {
264
+ constructor(http) {
265
+ this.http = http;
266
+ }
267
+ /**
268
+ * Authenticates the user by calling the login endpoint.
269
+ * @param credentials - The login request payload containing emailOrUsername and password.
270
+ * @returns The logged-in user information including the access token.
271
+ */
272
+ async login(credentials) {
273
+ const response = await this.http.post("/auth/login", credentials, {
274
+ skipAuth: true
275
+ });
276
+ const { user, accessToken, refreshToken } = response;
277
+ this.http.setAuthToken(accessToken);
278
+ const normalizedUser = {
279
+ ...user,
280
+ id: user.userId || user.id,
281
+ token: accessToken,
282
+ refreshToken
283
+ };
284
+ return normalizedUser;
285
+ }
286
+ /**
287
+ * Registers a new user by calling the registration endpoint.
288
+ * @param registrationData - The registration request payload containing username, password, and optionally email.
289
+ * @returns The newly created user information including the access token.
290
+ */
291
+ async register(data) {
292
+ const response = await this.http.post("/auth/register", data, {
293
+ skipAuth: true
294
+ });
295
+ const { user, accessToken, refreshToken } = response;
296
+ this.http.setAuthToken(accessToken);
297
+ const normalizedUser = {
298
+ ...user,
299
+ id: user.userId || user.id,
300
+ token: accessToken,
301
+ refreshToken
302
+ };
303
+ return normalizedUser;
304
+ }
305
+ /**
306
+ * Verifies if the provided JWT token is valid.
307
+ * @returns The user information if the token is valid.
308
+ */
309
+ async verifyToken() {
310
+ const response = await this.http.get("/auth/verify-token");
311
+ if (response.user && response.user.userId) {
312
+ response.user.id = response.user.userId;
313
+ }
314
+ return response;
315
+ }
316
+ /**
317
+ * Refreshes the access token using the stored refresh token.
318
+ * @returns The newly issued access token.
319
+ */
320
+ async refreshAccessToken() {
321
+ const response = await this.http.get("/auth/refresh-token");
322
+ const { accessToken } = response;
323
+ this.http.setAuthToken(accessToken);
324
+ return accessToken;
325
+ }
326
+ /**
327
+ * Checks if the provided email is unique (not already registered).
328
+ * @param email - The email address to check.
329
+ * @returns True if the email is unique, otherwise false.
330
+ */
331
+ async isEmailUnique(email) {
332
+ const response = await this.http.get(`/auth/check-email/${email}`, {
333
+ skipAuth: true
334
+ });
335
+ return response.isUnique;
336
+ }
337
+ /**
338
+ * Checks if the provided username is unique (not already taken).
339
+ * @param username - The username to check.
340
+ * @returns True if the username is unique, otherwise false.
341
+ */
342
+ async isUsernameUnique(username) {
343
+ const response = await this.http.get(
344
+ `/auth/check-username/${username}`,
345
+ { skipAuth: true }
346
+ );
347
+ return response.isUnique;
348
+ }
349
+ /**
350
+ * Logout the current user
351
+ */
352
+ async logout() {
353
+ this.http.setAuthToken(null);
354
+ }
355
+ /**
356
+ * Request password reset
357
+ */
358
+ async requestPasswordReset(email) {
359
+ await this.http.post("/auth/password/reset-request", { email }, { skipAuth: true });
360
+ }
361
+ /**
362
+ * Reset password with token
363
+ */
364
+ async resetPassword(token, newPassword) {
365
+ await this.http.post("/auth/password/reset", { token, newPassword }, { skipAuth: true });
366
+ }
367
+ /**
368
+ * Change password for authenticated user
369
+ */
370
+ async changePassword(currentPassword, newPassword) {
371
+ await this.http.post("/auth/password/change", {
372
+ currentPassword,
373
+ newPassword
374
+ });
375
+ }
376
+ /**
377
+ * Verify email with token
378
+ */
379
+ async verifyEmail(token) {
380
+ await this.http.post("/auth/email/verify", { token }, { skipAuth: true });
381
+ }
382
+ /**
383
+ * Resend verification email
384
+ */
385
+ async resendVerificationEmail(email) {
386
+ await this.http.post("/auth/email/resend", { email }, { skipAuth: true });
387
+ }
388
+ };
389
+
390
+ // src/modules/user/UserModule.ts
391
+ var UserModule = class {
392
+ constructor(http) {
393
+ this.http = http;
394
+ }
395
+ /**
396
+ * Get user by ID
397
+ */
398
+ async getById(userId) {
399
+ return this.http.get(`/users/${userId}`);
400
+ }
401
+ /**
402
+ * Get current user profile
403
+ */
404
+ async getCurrentUser() {
405
+ return this.http.get("/users/me");
406
+ }
407
+ /**
408
+ * Update current user profile
409
+ */
410
+ async updateProfile(data) {
411
+ return this.http.patch("/users/me", data);
412
+ }
413
+ /**
414
+ * Delete current user account
415
+ */
416
+ async deleteAccount() {
417
+ await this.http.delete("/users/me");
418
+ }
419
+ /**
420
+ * Get list of users with pagination and filters
421
+ */
422
+ async list(params) {
423
+ return this.http.get("/users", { params });
424
+ }
425
+ /**
426
+ * Search users by query
427
+ */
428
+ async search(query, params) {
429
+ return this.http.get("/users/search", {
430
+ params: { query, ...params }
431
+ });
432
+ }
433
+ /**
434
+ * Upload user avatar
435
+ */
436
+ async uploadAvatar(file, fileName) {
437
+ const formData = new FormData();
438
+ formData.append("avatar", file, fileName);
439
+ return this.http.post("/users/me/avatar", formData);
440
+ }
441
+ /**
442
+ * Delete user avatar
443
+ */
444
+ async deleteAvatar() {
445
+ await this.http.delete("/users/me/avatar");
446
+ }
447
+ };
448
+
449
+ // src/modules/watchTogether/WatchTogetherError.ts
450
+ var WatchTogetherError = class _WatchTogetherError extends Error {
451
+ constructor(message, code, details) {
452
+ super(message);
453
+ this.code = code;
454
+ this.details = details;
455
+ this.isWatchTogetherError = true;
456
+ this.name = "WatchTogetherError";
457
+ if (Error.captureStackTrace) {
458
+ Error.captureStackTrace(this, _WatchTogetherError);
459
+ }
460
+ }
461
+ toJSON() {
462
+ return {
463
+ name: this.name,
464
+ message: this.message,
465
+ code: this.code,
466
+ details: this.details
467
+ };
468
+ }
469
+ static isWatchTogetherError(error) {
470
+ return error instanceof _WatchTogetherError || error?.isWatchTogetherError === true;
471
+ }
472
+ };
473
+
474
+ // src/types/actions.ts
475
+ var USER_ACTIONS = {
476
+ // Core playback actions (synced with Watch Together)
477
+ PLAY: "PLAY",
478
+ PAUSE: "PAUSE",
479
+ SEEK: "SEEK",
480
+ // File/media actions
481
+ FILE_UPDATE: "FILE_UPDATE",
482
+ // Advanced actions (may not be synced)
483
+ STOP: "STOP",
484
+ VOLUME_UPDATE: "VOLUME_UPDATE",
485
+ ENTER_FULLSCREEN: "ENTER_FULLSCREEN",
486
+ EXIT_FULLSCREEN: "EXIT_FULLSCREEN",
487
+ SPEED_UPDATE: "SPEED_UPDATE",
488
+ AUDIO_DELAY_UPDATE: "AUDIO_DELAY_UPDATE",
489
+ SUBTITLE_DELAY_UPDATE: "SUBTITLE_DELAY_UPDATE",
490
+ ASPECT_RATIO_UPDATE: "ASPECT_RATIO_UPDATE",
491
+ TOGGLE_REPEAT: "TOGGLE_REPEAT",
492
+ TOGGLE_LOOP: "TOGGLE_LOOP",
493
+ VIDEO_EFFECT_UPDATE: "VIDEO_EFFECT_UPDATE",
494
+ AUDIO_FILTER_UPDATE: "AUDIO_FILTER_UPDATE",
495
+ TOGGLE_RANDOM: "TOGGLE_RANDOM",
496
+ API_VERSION_UPDATE: "API_VERSION_UPDATE",
497
+ PLAYLIST_UPDATE: "PLAYLIST_UPDATE",
498
+ TITLE_OR_CHAPTER_UPDATE: "TITLE_OR_CHAPTER_UPDATE",
499
+ NO_CHANGE: "NO_CHANGE"
500
+ };
501
+
502
+ // src/types/watchTogether.ts
503
+ var SyncActions = {
504
+ PLAY: "play",
505
+ PAUSE: "pause",
506
+ SEEK: "seek"
507
+ };
508
+ var WatchTogetherErrorCodes = {
509
+ ROOM_NOT_FOUND: "ROOM_NOT_FOUND",
510
+ UNAUTHORIZED: "UNAUTHORIZED",
511
+ ROOM_FULL: "ROOM_FULL",
512
+ INVALID_PASSWORD: "INVALID_PASSWORD",
513
+ NOT_IN_ROOM: "NOT_IN_ROOM",
514
+ PERMISSION_DENIED: "PERMISSION_DENIED",
515
+ CONNECTION_ERROR: "CONNECTION_ERROR",
516
+ ALREADY_IN_ROOM: "ALREADY_IN_ROOM"
517
+ };
518
+ var WatchTogetherConstants = {
519
+ NAMESPACE: "/watch-together",
520
+ SYNC_CHECK_INTERVAL_MS: 3e4,
521
+ // 30 seconds
522
+ SYNC_TOLERANCE_SECONDS: 1,
523
+ // Time difference tolerance
524
+ MAX_RECONNECT_ATTEMPTS: 5,
525
+ RECONNECT_DELAY_MS: 2e3
526
+ };
527
+ var WatchTogetherIDs = {
528
+ JOIN_ROOM_INPUT: "join-room-code",
529
+ JOIN_AND_INVITE_SECTION: "join-invite-section",
530
+ JOIN_BUTTON: "join-room-button",
531
+ START_BUTTON: "watchtogether-start-button",
532
+ LEAVE_BUTTON: "watchtogether-leave-button",
533
+ JOINED_ROOM_SECTION: "joined-room-section",
534
+ ACTION_SECTION: "watchtogether-action-section",
535
+ CREATE_ROOM_BUTTON: "watchtogether-create-room-button"
536
+ };
537
+ var SECTION_NAMES = {
538
+ JOIN_AND_INVITE: "JOIN_AND_INVITE",
539
+ JOINED_ROOM: "JOINED_ROOM"
540
+ };
541
+ var WatchTogetherMessages = {
542
+ START_SUCCESS: "WatchTogether session started successfully!",
543
+ JOIN_SUCCESS: "Joined the WatchTogether session!",
544
+ LEAVE_SUCCESS: "You have left the WatchTogether session.",
545
+ INVALID_ARGS: "Missing required arguments. Please check the usage.",
546
+ CONNECTION_SUCCESS: "Connected to WatchTogether service.",
547
+ DISCONNECTED: "Disconnected from WatchTogether service."
548
+ };
549
+
550
+ // src/modules/watchTogether/WatchTogetherModule.ts
551
+ var WatchTogetherModule = class {
552
+ // Flag for persistent connection
553
+ constructor(http, config) {
554
+ this.http = http;
555
+ this.config = config;
556
+ this.socket = null;
557
+ this.currentRoom = null;
558
+ this.syncInterval = null;
559
+ this.eventListeners = /* @__PURE__ */ new Map();
560
+ this.connectionStatus = {
561
+ connected: false,
562
+ connecting: false,
563
+ authenticated: false,
564
+ inRoom: false,
565
+ roomId: null,
566
+ error: null
567
+ };
568
+ this.shouldStayConnected = false;
569
+ if (config.autoConnect || config.persistentConnection) {
570
+ this.initializePersistentConnection();
571
+ }
572
+ }
573
+ /**
574
+ * Initialize persistent connection (always on)
575
+ */
576
+ initializePersistentConnection() {
577
+ this.shouldStayConnected = true;
578
+ this.connect().catch(() => {
579
+ setTimeout(() => {
580
+ if (this.shouldStayConnected) {
581
+ this.initializePersistentConnection();
582
+ }
583
+ }, 2e3);
584
+ });
585
+ }
586
+ /**
587
+ * Connect to the Watch Together service
588
+ */
589
+ async connect() {
590
+ if (this.socket?.connected) {
591
+ this.updateConnectionStatus({ connected: true, connecting: false });
592
+ return;
593
+ }
594
+ const isPersistent = this.config.persistentConnection || this.shouldStayConnected;
595
+ const token = this.http.getAuthToken();
596
+ if (!token && !isPersistent) {
597
+ throw new WatchTogetherError("Authentication required. Please login first.", "UNAUTHORIZED");
598
+ }
599
+ this.updateConnectionStatus({ connecting: true, error: null });
600
+ const namespace = this.config.namespace || WatchTogetherConstants.NAMESPACE;
601
+ const url = `${this.config.baseURL}${namespace}`;
602
+ const socketOptions = {
603
+ query: { connectionId: "watchTogether" },
604
+ forceNew: !this.socket,
605
+ // Reuse socket if exists
606
+ reconnection: true,
607
+ reconnectionAttempts: Infinity,
608
+ // Infinite reconnection attempts
609
+ reconnectionDelay: 1e3,
610
+ reconnectionDelayMax: 5e3,
611
+ timeout: 1e4
612
+ };
613
+ if (token) {
614
+ socketOptions.extraHeaders = {
615
+ Authorization: `Bearer ${token}`
616
+ };
617
+ this.updateConnectionStatus({ authenticated: true });
618
+ }
619
+ this.socket = io(url, socketOptions);
620
+ this.setupSocketListeners();
621
+ return new Promise((resolve, reject) => {
622
+ const timeout = setTimeout(() => {
623
+ if (!isPersistent) {
624
+ reject(new WatchTogetherError("Connection timeout", "CONNECTION_ERROR"));
625
+ } else {
626
+ resolve();
627
+ }
628
+ }, 1e4);
629
+ this.socket.once("connect", () => {
630
+ clearTimeout(timeout);
631
+ this.updateConnectionStatus({
632
+ connected: true,
633
+ connecting: false,
634
+ error: null
635
+ });
636
+ this.emit("connect", void 0);
637
+ resolve();
638
+ });
639
+ this.socket.once("connect_error", (error) => {
640
+ clearTimeout(timeout);
641
+ this.updateConnectionStatus({
642
+ connected: false,
643
+ connecting: false,
644
+ error: error.message
645
+ });
646
+ if (!isPersistent) {
647
+ reject(new WatchTogetherError(error.message || "Connection failed", "CONNECTION_ERROR"));
648
+ } else {
649
+ resolve();
650
+ }
651
+ });
652
+ });
653
+ }
654
+ /**
655
+ * Disconnect from the Watch Together service
656
+ */
657
+ disconnect() {
658
+ this.shouldStayConnected = false;
659
+ this.stopSyncCheck();
660
+ if (this.socket) {
661
+ this.socket.removeAllListeners();
662
+ this.socket.disconnect();
663
+ this.socket = null;
664
+ }
665
+ this.currentRoom = null;
666
+ this.updateConnectionStatus({
667
+ connected: false,
668
+ connecting: false,
669
+ authenticated: false,
670
+ inRoom: false,
671
+ roomId: null
672
+ });
673
+ this.emit("disconnect", "manual");
674
+ }
675
+ /**
676
+ * Enable persistent connection mode (always on)
677
+ */
678
+ enablePersistentConnection() {
679
+ if (!this.shouldStayConnected) {
680
+ this.shouldStayConnected = true;
681
+ this.initializePersistentConnection();
682
+ }
683
+ }
684
+ /**
685
+ * Disable persistent connection mode
686
+ */
687
+ disablePersistentConnection() {
688
+ this.shouldStayConnected = false;
689
+ }
690
+ /**
691
+ * Create a new room
692
+ */
693
+ async createRoom(options) {
694
+ this.ensureConnected();
695
+ return new Promise((resolve, reject) => {
696
+ const timeout = setTimeout(() => {
697
+ reject(new WatchTogetherError("Create room timeout", "CONNECTION_ERROR"));
698
+ }, 5e3);
699
+ this.socket.once("roomState", (room) => {
700
+ clearTimeout(timeout);
701
+ this.currentRoom = room;
702
+ this.startSyncCheck();
703
+ resolve(room);
704
+ });
705
+ this.socket.once("error", (error) => {
706
+ clearTimeout(timeout);
707
+ reject(new WatchTogetherError(error.message, error.code));
708
+ });
709
+ this.socket.emit("createRoom", options || {});
710
+ });
711
+ }
712
+ /**
713
+ * Join an existing room or create a new one
714
+ */
715
+ async joinRoom(options) {
716
+ this.ensureConnected();
717
+ return new Promise((resolve, reject) => {
718
+ const timeout = setTimeout(() => {
719
+ reject(new WatchTogetherError("Join room timeout", "CONNECTION_ERROR"));
720
+ }, 5e3);
721
+ this.socket.once("roomState", (room) => {
722
+ clearTimeout(timeout);
723
+ this.currentRoom = room;
724
+ this.updateConnectionStatus({
725
+ inRoom: true,
726
+ roomId: room.roomId
727
+ });
728
+ this.startSyncCheck();
729
+ resolve(room);
730
+ });
731
+ this.socket.once("error", (error) => {
732
+ clearTimeout(timeout);
733
+ reject(new WatchTogetherError(error.message, error.code));
734
+ });
735
+ this.socket.emit("joinRoom", options);
736
+ });
737
+ }
738
+ /**
739
+ * Leave the current room
740
+ */
741
+ leaveRoom() {
742
+ if (!this.currentRoom) {
743
+ return;
744
+ }
745
+ this.ensureConnected();
746
+ this.stopSyncCheck();
747
+ this.socket.emit("leaveRoom", { roomId: this.currentRoom.roomId });
748
+ this.currentRoom = null;
749
+ this.updateConnectionStatus({
750
+ inRoom: false,
751
+ roomId: null
752
+ });
753
+ }
754
+ /**
755
+ * Play the video (synced to all users)
756
+ */
757
+ play() {
758
+ this.ensureInRoom();
759
+ this.updateSyncState("play");
760
+ }
761
+ /**
762
+ * Pause the video (synced to all users)
763
+ */
764
+ pause() {
765
+ this.ensureInRoom();
766
+ this.updateSyncState("pause");
767
+ }
768
+ /**
769
+ * Seek to a specific time (synced to all users)
770
+ */
771
+ seek(timeInSeconds) {
772
+ this.ensureInRoom();
773
+ this.updateSyncState("seek", timeInSeconds);
774
+ }
775
+ /**
776
+ * Request sync state from server
777
+ */
778
+ requestSync() {
779
+ this.ensureInRoom();
780
+ this.socket.emit("checkSync", { roomId: this.currentRoom.roomId });
781
+ }
782
+ /**
783
+ * Update current file information
784
+ */
785
+ updateFileInfo(fileInfo) {
786
+ this.ensureInRoom();
787
+ this.socket.emit("updateFileInfo", {
788
+ roomId: this.currentRoom.roomId,
789
+ fileInfo
790
+ });
791
+ }
792
+ /**
793
+ * Send a chat message
794
+ */
795
+ async sendMessage(text) {
796
+ this.ensureInRoom();
797
+ const tempMessageId = `temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
798
+ return new Promise((resolve, reject) => {
799
+ const timeout = setTimeout(() => {
800
+ reject(new WatchTogetherError("Send message timeout", "CONNECTION_ERROR"));
801
+ }, 5e3);
802
+ this.socket.once("chat:ack", () => {
803
+ clearTimeout(timeout);
804
+ resolve();
805
+ });
806
+ this.socket.once("chat:error", (error) => {
807
+ clearTimeout(timeout);
808
+ reject(new WatchTogetherError(error.message, "CHAT_ERROR"));
809
+ });
810
+ this.socket.emit("chat:send", {
811
+ roomId: this.currentRoom.roomId,
812
+ text,
813
+ tempMessageId
814
+ });
815
+ });
816
+ }
817
+ /**
818
+ * Get chat history for current room
819
+ */
820
+ getChatHistory() {
821
+ return this.currentRoom?.chat || [];
822
+ }
823
+ /**
824
+ * Get current room state
825
+ */
826
+ getCurrentRoom() {
827
+ return this.currentRoom;
828
+ }
829
+ /**
830
+ * Check if connected to the service
831
+ */
832
+ isConnected() {
833
+ return this.socket?.connected || false;
834
+ }
835
+ /**
836
+ * Check if currently in a room
837
+ */
838
+ isInRoom() {
839
+ return this.currentRoom !== null;
840
+ }
841
+ /**
842
+ * Get current connection status
843
+ */
844
+ getConnectionStatus() {
845
+ return { ...this.connectionStatus };
846
+ }
847
+ /**
848
+ * Check if user is online (connected to socket)
849
+ */
850
+ isOnline() {
851
+ return this.connectionStatus.connected;
852
+ }
853
+ // ============================================
854
+ // EVENT LISTENERS
855
+ // ============================================
856
+ /**
857
+ * Listen for room state events
858
+ */
859
+ onRoomState(callback) {
860
+ return this.addEventListener("roomState", callback);
861
+ }
862
+ /**
863
+ * Listen for room update events
864
+ */
865
+ onRoomUpdate(callback) {
866
+ return this.addEventListener("roomUpdates", callback);
867
+ }
868
+ /**
869
+ * Listen for sync state events
870
+ */
871
+ onSyncState(callback) {
872
+ return this.addEventListener("syncState", callback);
873
+ }
874
+ /**
875
+ * Listen for chat messages
876
+ */
877
+ onChatMessage(callback) {
878
+ return this.addEventListener("chat:receive", callback);
879
+ }
880
+ /**
881
+ * Listen for system messages
882
+ */
883
+ onSystemMessage(callback) {
884
+ return this.addEventListener("newMessage", callback);
885
+ }
886
+ /**
887
+ * Listen for chat history
888
+ */
889
+ onChatHistory(callback) {
890
+ return this.addEventListener("chatHistory", callback);
891
+ }
892
+ /**
893
+ * Listen for errors
894
+ */
895
+ onError(callback) {
896
+ return this.addEventListener("error", callback);
897
+ }
898
+ /**
899
+ * Listen for connection events
900
+ */
901
+ onConnect(callback) {
902
+ return this.addEventListener("connect", callback);
903
+ }
904
+ /**
905
+ * Listen for disconnection events
906
+ */
907
+ onDisconnect(callback) {
908
+ return this.addEventListener("disconnect", callback);
909
+ }
910
+ /**
911
+ * Listen for connection status changes
912
+ */
913
+ onConnectionStatusChange(callback) {
914
+ return this.addEventListener("connectionStatusChange", callback);
915
+ }
916
+ /**
917
+ * Listen for online/offline status
918
+ */
919
+ onOnlineStatusChange(callback) {
920
+ return this.addEventListener("onlineStatusChange", callback);
921
+ }
922
+ // ============================================
923
+ // PRIVATE METHODS
924
+ // ============================================
925
+ setupSocketListeners() {
926
+ if (!this.socket) return;
927
+ this.socket.on("roomState", (room) => {
928
+ this.currentRoom = room;
929
+ this.updateConnectionStatus({
930
+ inRoom: true,
931
+ roomId: room.roomId
932
+ });
933
+ this.emit("roomState", room);
934
+ });
935
+ this.socket.on("roomUpdates", (update) => {
936
+ if (this.currentRoom && update.roomUpdates) {
937
+ this.currentRoom = {
938
+ ...this.currentRoom,
939
+ ...update.roomUpdates
940
+ };
941
+ }
942
+ this.emit("roomUpdates", update);
943
+ });
944
+ this.socket.on("syncState", (syncState) => {
945
+ if (this.currentRoom) {
946
+ this.currentRoom.syncState = syncState;
947
+ }
948
+ this.emit("syncState", syncState);
949
+ });
950
+ this.socket.on("chat:receive", (data) => {
951
+ if (this.currentRoom) {
952
+ this.currentRoom.chat.push(data.message);
953
+ }
954
+ this.emit("chat:receive", data.message);
955
+ });
956
+ this.socket.on("newMessage", (data) => {
957
+ const message = { ...data.message, systemMessage: true };
958
+ if (this.currentRoom) {
959
+ this.currentRoom.chat.push(message);
960
+ }
961
+ this.emit("newMessage", message);
962
+ });
963
+ this.socket.on("chatHistory", (data) => {
964
+ if (this.currentRoom) {
965
+ this.currentRoom.chat = data.messages;
966
+ }
967
+ this.emit("chatHistory", data.messages);
968
+ });
969
+ this.socket.on("chat:ack", (data) => {
970
+ this.emit("chat:ack", data.message);
971
+ });
972
+ this.socket.on("chat:error", (data) => {
973
+ this.emit("chat:error", data);
974
+ });
975
+ this.socket.on("error", (data) => {
976
+ this.updateConnectionStatus({ error: data.message });
977
+ this.emit("error", data);
978
+ });
979
+ this.socket.on("connect", () => {
980
+ this.updateConnectionStatus({
981
+ connected: true,
982
+ connecting: false,
983
+ error: null
984
+ });
985
+ });
986
+ this.socket.on("disconnect", (reason) => {
987
+ this.stopSyncCheck();
988
+ this.updateConnectionStatus({
989
+ connected: false,
990
+ error: `Disconnected: ${reason}`
991
+ });
992
+ this.emit("disconnect", reason);
993
+ if (this.shouldStayConnected) {
994
+ setTimeout(() => {
995
+ if (this.shouldStayConnected && !this.socket?.connected) {
996
+ this.connect().catch(() => {
997
+ });
998
+ }
999
+ }, 2e3);
1000
+ }
1001
+ });
1002
+ this.socket.on("connect_error", async (error) => {
1003
+ this.updateConnectionStatus({
1004
+ connected: false,
1005
+ connecting: false,
1006
+ error: error.message
1007
+ });
1008
+ if (error.message.includes("Unauthorized") || error.message.includes("401")) {
1009
+ try {
1010
+ await this.refreshTokenAndReconnect();
1011
+ } catch {
1012
+ this.emit("error", {
1013
+ code: "UNAUTHORIZED",
1014
+ message: "Session expired. Please login again."
1015
+ });
1016
+ }
1017
+ }
1018
+ if (this.shouldStayConnected) {
1019
+ setTimeout(() => {
1020
+ if (this.shouldStayConnected && !this.socket?.connected) {
1021
+ this.connect().catch(() => {
1022
+ });
1023
+ }
1024
+ }, 3e3);
1025
+ }
1026
+ });
1027
+ this.socket.on("reconnect_attempt", (attempt) => {
1028
+ this.updateConnectionStatus({
1029
+ connecting: true,
1030
+ error: `Reconnecting (attempt ${attempt})...`
1031
+ });
1032
+ });
1033
+ this.socket.on("reconnect", (_attempt) => {
1034
+ this.updateConnectionStatus({
1035
+ connected: true,
1036
+ connecting: false,
1037
+ error: null
1038
+ });
1039
+ this.emit("connect", void 0);
1040
+ });
1041
+ this.socket.on("reconnect_failed", () => {
1042
+ this.updateConnectionStatus({
1043
+ connected: false,
1044
+ connecting: false,
1045
+ error: "Reconnection failed"
1046
+ });
1047
+ if (this.shouldStayConnected) {
1048
+ setTimeout(() => {
1049
+ if (this.shouldStayConnected) {
1050
+ this.connect().catch(() => {
1051
+ });
1052
+ }
1053
+ }, 5e3);
1054
+ }
1055
+ });
1056
+ }
1057
+ async refreshTokenAndReconnect() {
1058
+ if (!this.shouldStayConnected) {
1059
+ this.disconnect();
1060
+ throw new WatchTogetherError("Session expired. Please login again.", "UNAUTHORIZED");
1061
+ }
1062
+ this.updateConnectionStatus({ authenticated: false });
1063
+ }
1064
+ /**
1065
+ * Update authentication token after refresh
1066
+ */
1067
+ updateAuthToken(token) {
1068
+ if (this.socket && this.socket.io.opts.extraHeaders) {
1069
+ this.socket.io.opts.extraHeaders.Authorization = `Bearer ${token}`;
1070
+ this.updateConnectionStatus({ authenticated: true, error: null });
1071
+ if (!this.socket.connected) {
1072
+ this.socket.connect();
1073
+ }
1074
+ }
1075
+ }
1076
+ /**
1077
+ * Update connection status and emit events
1078
+ */
1079
+ updateConnectionStatus(updates) {
1080
+ const previousOnlineStatus = this.connectionStatus.connected;
1081
+ this.connectionStatus = {
1082
+ ...this.connectionStatus,
1083
+ ...updates
1084
+ };
1085
+ this.emit("connectionStatusChange", this.connectionStatus);
1086
+ if (previousOnlineStatus !== this.connectionStatus.connected) {
1087
+ this.emit("onlineStatusChange", this.connectionStatus.connected);
1088
+ }
1089
+ }
1090
+ updateSyncState(action, value) {
1091
+ this.ensureInRoom();
1092
+ const payload = {
1093
+ roomId: this.currentRoom.roomId,
1094
+ action
1095
+ };
1096
+ if (action === "seek" && value !== void 0) {
1097
+ payload.value = value;
1098
+ }
1099
+ this.socket.emit("updateSyncState", payload);
1100
+ }
1101
+ startSyncCheck() {
1102
+ this.stopSyncCheck();
1103
+ this.syncInterval = setInterval(() => {
1104
+ if (this.currentRoom && this.socket?.connected) {
1105
+ this.socket.emit("checkSync", { roomId: this.currentRoom.roomId });
1106
+ }
1107
+ }, WatchTogetherConstants.SYNC_CHECK_INTERVAL_MS);
1108
+ }
1109
+ stopSyncCheck() {
1110
+ if (this.syncInterval) {
1111
+ clearInterval(this.syncInterval);
1112
+ this.syncInterval = null;
1113
+ }
1114
+ }
1115
+ ensureConnected() {
1116
+ if (!this.socket?.connected) {
1117
+ throw new WatchTogetherError(
1118
+ "Not connected. Please call connect() first.",
1119
+ "CONNECTION_ERROR"
1120
+ );
1121
+ }
1122
+ }
1123
+ ensureInRoom() {
1124
+ this.ensureConnected();
1125
+ if (!this.currentRoom) {
1126
+ throw new WatchTogetherError(
1127
+ "Not in a room. Please join or create a room first.",
1128
+ "NOT_IN_ROOM"
1129
+ );
1130
+ }
1131
+ }
1132
+ addEventListener(event, callback) {
1133
+ if (!this.eventListeners.has(event)) {
1134
+ this.eventListeners.set(event, /* @__PURE__ */ new Set());
1135
+ }
1136
+ this.eventListeners.get(event).add(callback);
1137
+ return () => {
1138
+ const listeners = this.eventListeners.get(event);
1139
+ if (listeners) {
1140
+ listeners.delete(callback);
1141
+ }
1142
+ };
1143
+ }
1144
+ emit(event, data) {
1145
+ const listeners = this.eventListeners.get(event);
1146
+ if (listeners) {
1147
+ listeners.forEach((callback) => {
1148
+ try {
1149
+ callback(data);
1150
+ } catch (error) {
1151
+ console.error(`Error in event listener for ${event}:`, error);
1152
+ }
1153
+ });
1154
+ }
1155
+ }
1156
+ };
1157
+
1158
+ // src/sdk/HarmoniSDK.ts
1159
+ var HarmoniSDK = class {
1160
+ constructor(config) {
1161
+ this.httpClient = new HttpClient(config);
1162
+ if (config.autoRefreshToken) {
1163
+ this.setupAutoRefresh();
1164
+ }
1165
+ this.auth = new AuthModule(this.httpClient);
1166
+ this.user = new UserModule(this.httpClient);
1167
+ this.watchTogether = new WatchTogetherModule(this.httpClient, {
1168
+ baseURL: config.baseURL,
1169
+ namespace: config.watchTogetherNamespace,
1170
+ persistentConnection: config.persistentConnection
1171
+ });
1172
+ }
1173
+ /**
1174
+ * Setup automatic token refresh
1175
+ */
1176
+ setupAutoRefresh() {
1177
+ this.httpClient.setRefreshTokenFunction(async () => {
1178
+ const accessToken = await this.auth.refreshAccessToken();
1179
+ this.storeAccessToken(accessToken);
1180
+ return accessToken;
1181
+ });
1182
+ }
1183
+ /**
1184
+ * Set authentication token
1185
+ */
1186
+ setAuthToken(token) {
1187
+ this.httpClient.setAuthToken(token);
1188
+ }
1189
+ /**
1190
+ * Get current authentication token
1191
+ */
1192
+ getAuthToken() {
1193
+ return this.httpClient.getAuthToken();
1194
+ }
1195
+ /**
1196
+ * Store access token (override this method or use storage)
1197
+ */
1198
+ storeAccessToken(accessToken) {
1199
+ if (typeof globalThis !== "undefined" && "localStorage" in globalThis) {
1200
+ const storage = globalThis.localStorage;
1201
+ storage?.setItem("harmoni_access_token", accessToken);
1202
+ }
1203
+ }
1204
+ /**
1205
+ * Get the underlying HTTP client (for advanced usage)
1206
+ */
1207
+ getHttpClient() {
1208
+ return this.httpClient;
1209
+ }
1210
+ };
1211
+
1212
+ // src/types/player.ts
1213
+ var PlayerConstants = {
1214
+ DEFAULT_POLLING_INTERVAL_MS: 2e3,
1215
+ SEEK_THRESHOLD_SECONDS: 0.5,
1216
+ COMMAND_STACK_TIMEOUT_MS: 3e3,
1217
+ MIN_INTERVAL_MS: 100
1218
+ };
1219
+
1220
+ // src/utils/string.ts
1221
+ var string_exports = {};
1222
+ __export(string_exports, {
1223
+ camelToSnake: () => camelToSnake,
1224
+ capitalize: () => capitalize,
1225
+ isValidEmail: () => isValidEmail,
1226
+ randomString: () => randomString,
1227
+ slugify: () => slugify,
1228
+ snakeToCamel: () => snakeToCamel,
1229
+ titleCase: () => titleCase,
1230
+ truncate: () => truncate
1231
+ });
1232
+ function capitalize(str) {
1233
+ if (!str) return "";
1234
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
1235
+ }
1236
+ function titleCase(str) {
1237
+ if (!str) return "";
1238
+ return str.toLowerCase().split(" ").map((word) => capitalize(word)).join(" ");
1239
+ }
1240
+ function camelToSnake(str) {
1241
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
1242
+ }
1243
+ function snakeToCamel(str) {
1244
+ return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
1245
+ }
1246
+ function truncate(str, length, suffix = "...") {
1247
+ if (!str || str.length <= length) return str;
1248
+ return str.slice(0, length - suffix.length) + suffix;
1249
+ }
1250
+ function isValidEmail(email) {
1251
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1252
+ return emailRegex.test(email);
1253
+ }
1254
+ function randomString(length) {
1255
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
1256
+ let result = "";
1257
+ for (let i = 0; i < length; i++) {
1258
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
1259
+ }
1260
+ return result;
1261
+ }
1262
+ function slugify(str) {
1263
+ return str.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
1264
+ }
1265
+
1266
+ // src/utils/date.ts
1267
+ var date_exports = {};
1268
+ __export(date_exports, {
1269
+ addDays: () => addDays,
1270
+ addHours: () => addHours,
1271
+ formatDate: () => formatDate,
1272
+ isFuture: () => isFuture,
1273
+ isPast: () => isPast,
1274
+ isToday: () => isToday,
1275
+ isValidDate: () => isValidDate,
1276
+ timeAgo: () => timeAgo,
1277
+ toISOString: () => toISOString
1278
+ });
1279
+ function toISOString(date) {
1280
+ return new Date(date).toISOString();
1281
+ }
1282
+ function isValidDate(date) {
1283
+ return date instanceof Date && !isNaN(date.getTime());
1284
+ }
1285
+ function timeAgo(date) {
1286
+ const now = /* @__PURE__ */ new Date();
1287
+ const past = new Date(date);
1288
+ const diffInSeconds = Math.floor((now.getTime() - past.getTime()) / 1e3);
1289
+ if (diffInSeconds < 60) return "just now";
1290
+ if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} minutes ago`;
1291
+ if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} hours ago`;
1292
+ if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)} days ago`;
1293
+ if (diffInSeconds < 2592e3) return `${Math.floor(diffInSeconds / 604800)} weeks ago`;
1294
+ if (diffInSeconds < 31536e3) return `${Math.floor(diffInSeconds / 2592e3)} months ago`;
1295
+ return `${Math.floor(diffInSeconds / 31536e3)} years ago`;
1296
+ }
1297
+ function addDays(date, days) {
1298
+ const result = new Date(date);
1299
+ result.setDate(result.getDate() + days);
1300
+ return result;
1301
+ }
1302
+ function addHours(date, hours) {
1303
+ const result = new Date(date);
1304
+ result.setHours(result.getHours() + hours);
1305
+ return result;
1306
+ }
1307
+ function formatDate(date, format = "YYYY-MM-DD") {
1308
+ const d = new Date(date);
1309
+ const year = d.getFullYear();
1310
+ const month = String(d.getMonth() + 1).padStart(2, "0");
1311
+ const day = String(d.getDate()).padStart(2, "0");
1312
+ const hours = String(d.getHours()).padStart(2, "0");
1313
+ const minutes = String(d.getMinutes()).padStart(2, "0");
1314
+ const seconds = String(d.getSeconds()).padStart(2, "0");
1315
+ return format.replace("YYYY", String(year)).replace("MM", month).replace("DD", day).replace("HH", hours).replace("mm", minutes).replace("ss", seconds);
1316
+ }
1317
+ function isToday(date) {
1318
+ const today = /* @__PURE__ */ new Date();
1319
+ const checkDate = new Date(date);
1320
+ return checkDate.getDate() === today.getDate() && checkDate.getMonth() === today.getMonth() && checkDate.getFullYear() === today.getFullYear();
1321
+ }
1322
+ function isPast(date) {
1323
+ return new Date(date).getTime() < Date.now();
1324
+ }
1325
+ function isFuture(date) {
1326
+ return new Date(date).getTime() > Date.now();
1327
+ }
1328
+
1329
+ // src/utils/object.ts
1330
+ var object_exports = {};
1331
+ __export(object_exports, {
1332
+ deepClone: () => deepClone,
1333
+ deepMerge: () => deepMerge,
1334
+ getNestedValue: () => getNestedValue,
1335
+ isEmpty: () => isEmpty,
1336
+ isObject: () => isObject,
1337
+ omit: () => omit,
1338
+ pick: () => pick,
1339
+ setNestedValue: () => setNestedValue
1340
+ });
1341
+ function deepClone(obj) {
1342
+ if (obj === null || typeof obj !== "object") return obj;
1343
+ if (obj instanceof Date) return new Date(obj.getTime());
1344
+ if (obj instanceof Array) return obj.map((item) => deepClone(item));
1345
+ if (obj instanceof Object) {
1346
+ const clonedObj = {};
1347
+ for (const key in obj) {
1348
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
1349
+ clonedObj[key] = deepClone(obj[key]);
1350
+ }
1351
+ }
1352
+ return clonedObj;
1353
+ }
1354
+ return obj;
1355
+ }
1356
+ function deepMerge(target, source) {
1357
+ const output = { ...target };
1358
+ if (isObject(target) && isObject(source)) {
1359
+ Object.keys(source).forEach((key) => {
1360
+ const sourceValue = source[key];
1361
+ const targetValue = target[key];
1362
+ if (isObject(sourceValue) && isObject(targetValue)) {
1363
+ output[key] = deepMerge(
1364
+ targetValue,
1365
+ sourceValue
1366
+ );
1367
+ } else {
1368
+ output[key] = sourceValue;
1369
+ }
1370
+ });
1371
+ }
1372
+ return output;
1373
+ }
1374
+ function isObject(value) {
1375
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1376
+ }
1377
+ function pick(obj, keys) {
1378
+ const result = {};
1379
+ keys.forEach((key) => {
1380
+ if (key in obj) {
1381
+ result[key] = obj[key];
1382
+ }
1383
+ });
1384
+ return result;
1385
+ }
1386
+ function omit(obj, keys) {
1387
+ const result = { ...obj };
1388
+ keys.forEach((key) => {
1389
+ delete result[key];
1390
+ });
1391
+ return result;
1392
+ }
1393
+ function isEmpty(obj) {
1394
+ if (obj === null || obj === void 0) return true;
1395
+ if (typeof obj === "string" || Array.isArray(obj)) return obj.length === 0;
1396
+ if (typeof obj === "object") return Object.keys(obj).length === 0;
1397
+ return false;
1398
+ }
1399
+ function getNestedValue(obj, path) {
1400
+ const keys = path.split(".");
1401
+ let result = obj;
1402
+ for (const key of keys) {
1403
+ if (result && typeof result === "object" && key in result) {
1404
+ result = result[key];
1405
+ } else {
1406
+ return void 0;
1407
+ }
1408
+ }
1409
+ return result;
1410
+ }
1411
+ function setNestedValue(obj, path, value) {
1412
+ const keys = path.split(".");
1413
+ const lastKey = keys.pop();
1414
+ if (!lastKey) return;
1415
+ let current = obj;
1416
+ for (const key of keys) {
1417
+ if (!(key in current) || typeof current[key] !== "object") {
1418
+ current[key] = {};
1419
+ }
1420
+ current = current[key];
1421
+ }
1422
+ current[lastKey] = value;
1423
+ }
1424
+
1425
+ // src/utils/validation.ts
1426
+ var validation_exports = {};
1427
+ __export(validation_exports, {
1428
+ maxLength: () => maxLength,
1429
+ minLength: () => minLength,
1430
+ required: () => required,
1431
+ validateEmail: () => validateEmail,
1432
+ validatePassword: () => validatePassword,
1433
+ validatePhone: () => validatePhone,
1434
+ validateUrl: () => validateUrl
1435
+ });
1436
+ function validateEmail(email) {
1437
+ if (!email) {
1438
+ return { valid: false, message: "Email is required" };
1439
+ }
1440
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1441
+ if (!emailRegex.test(email)) {
1442
+ return { valid: false, message: "Invalid email format" };
1443
+ }
1444
+ return { valid: true };
1445
+ }
1446
+ function validatePassword(password, options = {}) {
1447
+ const {
1448
+ minLength: minLength2 = 8,
1449
+ requireUppercase = true,
1450
+ requireLowercase = true,
1451
+ requireNumbers = true,
1452
+ requireSpecialChars = true
1453
+ } = options;
1454
+ if (!password) {
1455
+ return { valid: false, message: "Password is required" };
1456
+ }
1457
+ if (password.length < minLength2) {
1458
+ return { valid: false, message: `Password must be at least ${minLength2} characters` };
1459
+ }
1460
+ if (requireUppercase && !/[A-Z]/.test(password)) {
1461
+ return { valid: false, message: "Password must contain at least one uppercase letter" };
1462
+ }
1463
+ if (requireLowercase && !/[a-z]/.test(password)) {
1464
+ return { valid: false, message: "Password must contain at least one lowercase letter" };
1465
+ }
1466
+ if (requireNumbers && !/\d/.test(password)) {
1467
+ return { valid: false, message: "Password must contain at least one number" };
1468
+ }
1469
+ if (requireSpecialChars && !/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
1470
+ return { valid: false, message: "Password must contain at least one special character" };
1471
+ }
1472
+ return { valid: true };
1473
+ }
1474
+ function validateUrl(url) {
1475
+ if (!url) {
1476
+ return { valid: false, message: "URL is required" };
1477
+ }
1478
+ try {
1479
+ new URL(url);
1480
+ return { valid: true };
1481
+ } catch {
1482
+ return { valid: false, message: "Invalid URL format" };
1483
+ }
1484
+ }
1485
+ function validatePhone(phone) {
1486
+ if (!phone) {
1487
+ return { valid: false, message: "Phone number is required" };
1488
+ }
1489
+ const phoneRegex = /^[+]?[(]?[0-9]{1,4}[)]?[-\s.]?[(]?[0-9]{1,4}[)]?[-\s.]?[0-9]{1,9}$/;
1490
+ if (!phoneRegex.test(phone)) {
1491
+ return { valid: false, message: "Invalid phone number format" };
1492
+ }
1493
+ return { valid: true };
1494
+ }
1495
+ function required(value, fieldName = "Field") {
1496
+ if (value === null || value === void 0 || value === "") {
1497
+ return { valid: false, message: `${fieldName} is required` };
1498
+ }
1499
+ return { valid: true };
1500
+ }
1501
+ function minLength(value, min, fieldName = "Field") {
1502
+ if (value.length < min) {
1503
+ return { valid: false, message: `${fieldName} must be at least ${min} characters` };
1504
+ }
1505
+ return { valid: true };
1506
+ }
1507
+ function maxLength(value, max, fieldName = "Field") {
1508
+ if (value.length > max) {
1509
+ return { valid: false, message: `${fieldName} must be at most ${max} characters` };
1510
+ }
1511
+ return { valid: true };
1512
+ }
1513
+
1514
+ // src/utils/storage.ts
1515
+ var isBrowser = typeof globalThis !== "undefined" && "localStorage" in globalThis;
1516
+ var Storage = class {
1517
+ constructor(type = "local") {
1518
+ this.memoryStorage = /* @__PURE__ */ new Map();
1519
+ if (isBrowser) {
1520
+ const global = globalThis;
1521
+ this.storage = type === "local" ? global.localStorage ?? null : global.sessionStorage ?? null;
1522
+ } else {
1523
+ this.storage = null;
1524
+ }
1525
+ }
1526
+ /**
1527
+ * Set item in storage
1528
+ */
1529
+ set(key, value) {
1530
+ try {
1531
+ const serialized = JSON.stringify(value);
1532
+ if (this.storage) {
1533
+ this.storage.setItem(key, serialized);
1534
+ } else {
1535
+ this.memoryStorage.set(key, serialized);
1536
+ }
1537
+ } catch (error) {
1538
+ if (process.env.NODE_ENV !== "production") {
1539
+ console.error(`Error saving to storage:`, error);
1540
+ }
1541
+ }
1542
+ }
1543
+ /**
1544
+ * Get item from storage
1545
+ */
1546
+ get(key) {
1547
+ try {
1548
+ let item;
1549
+ if (this.storage) {
1550
+ item = this.storage.getItem(key);
1551
+ } else {
1552
+ item = this.memoryStorage.get(key);
1553
+ }
1554
+ if (!item) return null;
1555
+ return JSON.parse(item);
1556
+ } catch (error) {
1557
+ if (process.env.NODE_ENV !== "production") {
1558
+ console.error(`Error reading from storage:`, error);
1559
+ }
1560
+ return null;
1561
+ }
1562
+ }
1563
+ /**
1564
+ * Remove item from storage
1565
+ */
1566
+ remove(key) {
1567
+ try {
1568
+ if (this.storage) {
1569
+ this.storage.removeItem(key);
1570
+ } else {
1571
+ this.memoryStorage.delete(key);
1572
+ }
1573
+ } catch (error) {
1574
+ if (process.env.NODE_ENV !== "production") {
1575
+ console.error(`Error removing from storage:`, error);
1576
+ }
1577
+ }
1578
+ }
1579
+ /**
1580
+ * Clear all items from storage
1581
+ */
1582
+ clear() {
1583
+ try {
1584
+ if (this.storage) {
1585
+ this.storage.clear();
1586
+ } else {
1587
+ this.memoryStorage.clear();
1588
+ }
1589
+ } catch (error) {
1590
+ if (process.env.NODE_ENV !== "production") {
1591
+ console.error(`Error clearing storage:`, error);
1592
+ }
1593
+ }
1594
+ }
1595
+ /**
1596
+ * Check if key exists in storage
1597
+ */
1598
+ has(key) {
1599
+ if (this.storage) {
1600
+ return this.storage.getItem(key) !== null;
1601
+ } else {
1602
+ return this.memoryStorage.has(key);
1603
+ }
1604
+ }
1605
+ /**
1606
+ * Get all keys from storage
1607
+ */
1608
+ keys() {
1609
+ if (this.storage) {
1610
+ return Object.keys(this.storage);
1611
+ } else {
1612
+ return Array.from(this.memoryStorage.keys());
1613
+ }
1614
+ }
1615
+ /**
1616
+ * Check if storage is available (browser environment)
1617
+ */
1618
+ isAvailable() {
1619
+ return this.storage !== null;
1620
+ }
1621
+ };
1622
+ var localStorage = new Storage("local");
1623
+ var sessionStorage = new Storage("session");
1624
+
1625
+ // src/player/stateDetection.ts
1626
+ function identifyUserActions(oldState, newState, intervalMs) {
1627
+ const actions = [];
1628
+ if (!oldState || !newState) return [];
1629
+ const normalizedInterval = Math.max(intervalMs, PlayerConstants.MIN_INTERVAL_MS);
1630
+ if (oldState.isPlaying !== newState.isPlaying) {
1631
+ actions.push({
1632
+ event: newState.isPlaying ? USER_ACTIONS.PLAY : USER_ACTIONS.PAUSE,
1633
+ value: {
1634
+ isPlaying: newState.isPlaying,
1635
+ currentTime: newState.currentTime
1636
+ }
1637
+ });
1638
+ }
1639
+ const timeDifference = Math.abs(oldState.currentTime - newState.currentTime);
1640
+ const expectedTimeAdvance = normalizedInterval / 1e3 * (newState.isPlaying ? 1 : 0);
1641
+ const seekThreshold = Math.max(PlayerConstants.SEEK_THRESHOLD_SECONDS, expectedTimeAdvance * 1.5);
1642
+ if (timeDifference > expectedTimeAdvance + seekThreshold) {
1643
+ actions.push({
1644
+ event: USER_ACTIONS.SEEK,
1645
+ value: { currentTime: newState.currentTime }
1646
+ });
1647
+ }
1648
+ if (oldState.filename !== newState.filename) {
1649
+ actions.push({
1650
+ event: USER_ACTIONS.FILE_UPDATE,
1651
+ value: {
1652
+ filename: newState.filename === "no-input" ? null : newState.filename
1653
+ }
1654
+ });
1655
+ }
1656
+ if (oldState.loop !== newState.loop) {
1657
+ actions.push({
1658
+ event: USER_ACTIONS.TOGGLE_LOOP,
1659
+ value: { loop: newState.loop }
1660
+ });
1661
+ }
1662
+ if (oldState.repeat !== newState.repeat) {
1663
+ actions.push({
1664
+ event: USER_ACTIONS.TOGGLE_REPEAT,
1665
+ value: { repeat: newState.repeat }
1666
+ });
1667
+ }
1668
+ if (oldState.title !== newState.title || oldState.chapter !== newState.chapter) {
1669
+ actions.push({
1670
+ event: USER_ACTIONS.TITLE_OR_CHAPTER_UPDATE,
1671
+ value: { title: newState.title, chapter: newState.chapter }
1672
+ });
1673
+ }
1674
+ return actions;
1675
+ }
1676
+ function normalizePlayerState(state) {
1677
+ return {
1678
+ isPlaying: state.isPlaying ?? false,
1679
+ currentTime: Number.isFinite(state.currentTime) ? state.currentTime : 0,
1680
+ loop: state.loop ?? false,
1681
+ repeat: state.repeat ?? false,
1682
+ title: state.title ?? "",
1683
+ chapter: state.chapter ?? "",
1684
+ filename: state.filename ?? "",
1685
+ duration: Number.isFinite(state.duration) ? state.duration : 0,
1686
+ filepath: state.filepath ?? ""
1687
+ };
1688
+ }
1689
+
1690
+ // src/player/PlayerStatusMonitor.ts
1691
+ var PlayerStatusMonitor = class {
1692
+ constructor(player, onStateChange, onStop, pollingIntervalMs = PlayerConstants.DEFAULT_POLLING_INTERVAL_MS) {
1693
+ this.player = player;
1694
+ this.onStateChange = onStateChange;
1695
+ this.onStop = onStop;
1696
+ this.pollingIntervalMs = pollingIntervalMs;
1697
+ this.lastKnownState = null;
1698
+ this.lastCheckTime = 0;
1699
+ this.pollingTimer = null;
1700
+ this.isRunning = false;
1701
+ }
1702
+ /**
1703
+ * Starts monitoring the player status.
1704
+ */
1705
+ start() {
1706
+ if (this.isRunning) {
1707
+ return;
1708
+ }
1709
+ this.isRunning = true;
1710
+ this.lastCheckTime = Date.now();
1711
+ this.checkStatus();
1712
+ }
1713
+ /**
1714
+ * Stops monitoring the player status.
1715
+ */
1716
+ stop() {
1717
+ this.isRunning = false;
1718
+ if (this.pollingTimer) {
1719
+ clearTimeout(this.pollingTimer);
1720
+ this.pollingTimer = null;
1721
+ }
1722
+ }
1723
+ /**
1724
+ * Check if monitor is currently running.
1725
+ */
1726
+ getIsRunning() {
1727
+ return this.isRunning;
1728
+ }
1729
+ /**
1730
+ * Get the last known player state.
1731
+ */
1732
+ getLastKnownState() {
1733
+ return this.lastKnownState;
1734
+ }
1735
+ /**
1736
+ * Manually update the last known state (used when app initiates actions).
1737
+ */
1738
+ updateLastKnownState(state) {
1739
+ this.lastKnownState = state;
1740
+ }
1741
+ async checkStatus() {
1742
+ if (!this.isRunning) {
1743
+ return;
1744
+ }
1745
+ try {
1746
+ const isRunning = await this.player.checkPlayerRunning();
1747
+ if (!isRunning) {
1748
+ this.isRunning = false;
1749
+ this.onStop();
1750
+ return;
1751
+ }
1752
+ const currentTime = Date.now();
1753
+ const timeSinceLastCheck = this.lastCheckTime > 0 ? currentTime - this.lastCheckTime : this.pollingIntervalMs;
1754
+ this.lastCheckTime = currentTime;
1755
+ const currentState = await this.player.getStatus();
1756
+ if (this.lastKnownState) {
1757
+ const detectedActions = identifyUserActions(
1758
+ this.lastKnownState,
1759
+ currentState,
1760
+ timeSinceLastCheck
1761
+ );
1762
+ if (detectedActions.length > 0) {
1763
+ const source = this.determineActionSource(detectedActions);
1764
+ this.onStateChange(detectedActions, source);
1765
+ }
1766
+ }
1767
+ this.lastKnownState = currentState;
1768
+ if (this.isRunning) {
1769
+ this.pollingTimer = setTimeout(() => this.checkStatus(), this.pollingIntervalMs);
1770
+ }
1771
+ } catch (error) {
1772
+ console.error("Error fetching player status:", error);
1773
+ this.isRunning = false;
1774
+ this.onStop();
1775
+ }
1776
+ }
1777
+ /**
1778
+ * Determines if the action came from app sync or user interaction.
1779
+ * Should be overridden to integrate with command tracking.
1780
+ */
1781
+ determineActionSource(_actions) {
1782
+ return "user";
1783
+ }
1784
+ };
1785
+
1786
+ // src/player/HTML5VideoController.ts
1787
+ var HTML5VideoController = class {
1788
+ constructor(videoElementId) {
1789
+ this.videoElementId = videoElementId;
1790
+ this.video = null;
1791
+ this.statusMonitor = null;
1792
+ }
1793
+ async start(onReady) {
1794
+ if (typeof document === "undefined") {
1795
+ throw new Error("HTML5VideoController requires a browser environment");
1796
+ }
1797
+ this.video = document.getElementById(this.videoElementId);
1798
+ if (!this.video) {
1799
+ throw new Error(`Video element with id "${this.videoElementId}" not found`);
1800
+ }
1801
+ if (this.video.readyState >= 2) {
1802
+ onReady?.();
1803
+ } else {
1804
+ this.video.addEventListener("canplay", () => onReady?.(), { once: true });
1805
+ }
1806
+ }
1807
+ async play() {
1808
+ if (!this.video) {
1809
+ throw new Error("Player not initialized");
1810
+ }
1811
+ await this.video.play();
1812
+ }
1813
+ async pause() {
1814
+ if (!this.video) {
1815
+ throw new Error("Player not initialized");
1816
+ }
1817
+ this.video.pause();
1818
+ }
1819
+ async stop() {
1820
+ if (this.video) {
1821
+ this.video.pause();
1822
+ this.video.currentTime = 0;
1823
+ }
1824
+ }
1825
+ async seek(timeInSeconds) {
1826
+ if (!this.video) {
1827
+ throw new Error("Player not initialized");
1828
+ }
1829
+ this.video.currentTime = timeInSeconds;
1830
+ }
1831
+ async loadMedia(source) {
1832
+ if (!this.video) {
1833
+ throw new Error("Player not initialized");
1834
+ }
1835
+ this.video.src = source;
1836
+ this.video.load();
1837
+ }
1838
+ async getStatus() {
1839
+ if (!this.video) {
1840
+ throw new Error("Player not initialized");
1841
+ }
1842
+ return {
1843
+ isPlaying: !this.video.paused && !this.video.ended,
1844
+ currentTime: this.video.currentTime,
1845
+ loop: this.video.loop,
1846
+ repeat: false,
1847
+ title: "",
1848
+ chapter: "",
1849
+ filename: this.video.src.split("/").pop() || "",
1850
+ duration: Number.isFinite(this.video.duration) ? this.video.duration : 0,
1851
+ filepath: this.video.src
1852
+ };
1853
+ }
1854
+ async checkPlayerRunning() {
1855
+ return this.video !== null && this.video.readyState >= 2;
1856
+ }
1857
+ async quit() {
1858
+ if (this.video) {
1859
+ this.video.pause();
1860
+ this.video.src = "";
1861
+ }
1862
+ this.statusMonitor?.stop();
1863
+ }
1864
+ observeStatusChange(onChange, onStop, intervalMs = 500) {
1865
+ this.statusMonitor?.stop();
1866
+ this.statusMonitor = new PlayerStatusMonitor(this, onChange, onStop, intervalMs);
1867
+ this.statusMonitor.start();
1868
+ }
1869
+ async displayMessage(message, durationSeconds = 5) {
1870
+ console.log(`[Player OSD] ${message} (${durationSeconds}s)`);
1871
+ }
1872
+ /**
1873
+ * Get the underlying HTMLVideoElement (for advanced usage).
1874
+ */
1875
+ getVideoElement() {
1876
+ return this.video;
1877
+ }
1878
+ };
1879
+
1880
+ // src/player/SyncedPlayer.ts
1881
+ var SyncedPlayer = class {
1882
+ constructor(player, watchTogether, options) {
1883
+ this.player = player;
1884
+ this.watchTogether = watchTogether;
1885
+ this.options = options;
1886
+ this.commandStack = [];
1887
+ this.currentUserId = null;
1888
+ this.unsubscribers = [];
1889
+ this.currentUserId = options.getCurrentUserId();
1890
+ this.setupListeners();
1891
+ }
1892
+ /**
1893
+ * Setup listeners for sync and player changes.
1894
+ */
1895
+ setupListeners() {
1896
+ const unsubRoomUpdate = this.watchTogether.onRoomUpdate((update) => {
1897
+ this.handleRemoteSyncUpdate(update);
1898
+ });
1899
+ this.unsubscribers.push(unsubRoomUpdate);
1900
+ this.player.observeStatusChange(
1901
+ (actions, _source) => this.handleLocalPlayerChange(actions),
1902
+ () => {
1903
+ this.options.onPlayerStopped?.();
1904
+ }
1905
+ );
1906
+ }
1907
+ /**
1908
+ * Handle sync updates from other users.
1909
+ */
1910
+ async handleRemoteSyncUpdate(update) {
1911
+ const { action, userId } = update.metadata;
1912
+ if (userId === this.currentUserId) {
1913
+ return;
1914
+ }
1915
+ this.commandStack.push({ action, timestamp: Date.now() });
1916
+ try {
1917
+ switch (action) {
1918
+ case "play":
1919
+ await this.player.play();
1920
+ break;
1921
+ case "pause":
1922
+ await this.player.pause();
1923
+ break;
1924
+ case "seek": {
1925
+ const time = update.roomUpdates.syncState?.time;
1926
+ if (time !== void 0) {
1927
+ await this.player.seek(time);
1928
+ }
1929
+ break;
1930
+ }
1931
+ }
1932
+ } catch (error) {
1933
+ console.error("Error applying remote sync update:", error);
1934
+ }
1935
+ }
1936
+ /**
1937
+ * Handle local player changes and broadcast to room.
1938
+ */
1939
+ handleLocalPlayerChange(actions) {
1940
+ for (const action of actions) {
1941
+ const isFromSync = this.popMatchingCommand(action.event);
1942
+ if (isFromSync) {
1943
+ continue;
1944
+ }
1945
+ try {
1946
+ switch (action.event) {
1947
+ case USER_ACTIONS.PLAY:
1948
+ this.watchTogether.play();
1949
+ break;
1950
+ case USER_ACTIONS.PAUSE:
1951
+ this.watchTogether.pause();
1952
+ break;
1953
+ case USER_ACTIONS.SEEK: {
1954
+ const time = action.value?.currentTime;
1955
+ if (time !== void 0) {
1956
+ this.watchTogether.seek(time);
1957
+ }
1958
+ break;
1959
+ }
1960
+ case USER_ACTIONS.FILE_UPDATE: {
1961
+ const filename = action.value?.filename;
1962
+ if (filename) {
1963
+ }
1964
+ break;
1965
+ }
1966
+ }
1967
+ } catch (error) {
1968
+ console.error("Error broadcasting player action:", error);
1969
+ }
1970
+ }
1971
+ }
1972
+ /**
1973
+ * Check if a matching command exists in the stack (from sync).
1974
+ * Returns true if found (and removes it from stack).
1975
+ */
1976
+ popMatchingCommand(event) {
1977
+ const now = Date.now();
1978
+ this.commandStack = this.commandStack.filter(
1979
+ (cmd) => now - cmd.timestamp < PlayerConstants.COMMAND_STACK_TIMEOUT_MS
1980
+ );
1981
+ const index = this.commandStack.findIndex(
1982
+ (cmd) => cmd.action === this.eventToAction(event) && now - cmd.timestamp < PlayerConstants.COMMAND_STACK_TIMEOUT_MS
1983
+ );
1984
+ if (index !== -1) {
1985
+ this.commandStack.splice(index, 1);
1986
+ return true;
1987
+ }
1988
+ return false;
1989
+ }
1990
+ /**
1991
+ * Map user action event to sync action name.
1992
+ */
1993
+ eventToAction(event) {
1994
+ const mapping = {
1995
+ [USER_ACTIONS.PLAY]: "play",
1996
+ [USER_ACTIONS.PAUSE]: "pause",
1997
+ [USER_ACTIONS.SEEK]: "seek"
1998
+ };
1999
+ return mapping[event] || event.toLowerCase();
2000
+ }
2001
+ /**
2002
+ * Cleanup listeners.
2003
+ */
2004
+ destroy() {
2005
+ this.unsubscribers.forEach((unsub) => unsub());
2006
+ this.unsubscribers = [];
2007
+ this.commandStack = [];
2008
+ }
2009
+ };
2010
+
2011
+ export { ApiError, AuthModule, ForbiddenError, HTML5VideoController, HarmoniSDK, HttpClient, NetworkError, NotFoundError, PlayerConstants, PlayerStatusMonitor, SECTION_NAMES, Storage, SyncActions, SyncedPlayer, TimeoutError, USER_ACTIONS, UnauthorizedError, UserModule, ValidationError, WatchTogetherConstants, WatchTogetherError, WatchTogetherErrorCodes, WatchTogetherIDs, WatchTogetherMessages, WatchTogetherModule, date_exports as dateUtils, HarmoniSDK as default, identifyUserActions, localStorage, normalizePlayerState, object_exports as objectUtils, sessionStorage, string_exports as stringUtils, validation_exports as validationUtils };
2012
+ //# sourceMappingURL=index.mjs.map
2013
+ //# sourceMappingURL=index.mjs.map