@amityco/ts-sdk-react-native 7.12.1-f6ba70a0.0 → 7.13.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 (42) hide show
  1. package/dist/@types/core/events.d.ts +1 -1
  2. package/dist/@types/core/events.d.ts.map +1 -1
  3. package/dist/@types/domains/liveStreamPlayer.d.ts +6 -2
  4. package/dist/@types/domains/liveStreamPlayer.d.ts.map +1 -1
  5. package/dist/@types/domains/room.d.ts +29 -0
  6. package/dist/@types/domains/room.d.ts.map +1 -1
  7. package/dist/client/api/setSessionState.d.ts +1 -1
  8. package/dist/client/api/setSessionState.d.ts.map +1 -1
  9. package/dist/client/events/onSessionStateChange.d.ts +1 -1
  10. package/dist/client/events/onSessionStateChange.d.ts.map +1 -1
  11. package/dist/client/utils/SessionWatcher.d.ts +2 -2
  12. package/dist/client/utils/SessionWatcher.d.ts.map +1 -1
  13. package/dist/core/events.d.ts +1 -1
  14. package/dist/core/events.d.ts.map +1 -1
  15. package/dist/feedRepository/observers/utils.d.ts +1 -1
  16. package/dist/index.cjs.js +433 -90
  17. package/dist/index.esm.js +433 -90
  18. package/dist/index.umd.js +2 -2
  19. package/dist/liveStreamPlayer/utils/cryptoSignatureUtils.d.ts +2 -2
  20. package/dist/liveStreamPlayer/utils/cryptoSignatureUtils.d.ts.map +1 -1
  21. package/dist/postRepository/observers/utils.d.ts +2 -2
  22. package/dist/reactionRepository/events/onReactionAdded.d.ts +1 -1
  23. package/dist/reactionRepository/events/onReactionAdded.d.ts.map +1 -1
  24. package/dist/reactionRepository/events/onReactionRemoved.d.ts +1 -1
  25. package/dist/reactionRepository/events/onReactionRemoved.d.ts.map +1 -1
  26. package/dist/roomRepository/api/analytics/AmityRoomAnalytics.d.ts +30 -0
  27. package/dist/roomRepository/api/analytics/AmityRoomAnalytics.d.ts.map +1 -0
  28. package/dist/roomRepository/api/analytics/WatchSessionStorage.d.ts +38 -0
  29. package/dist/roomRepository/api/analytics/WatchSessionStorage.d.ts.map +1 -0
  30. package/dist/roomRepository/api/analytics/index.d.ts +4 -0
  31. package/dist/roomRepository/api/analytics/index.d.ts.map +1 -0
  32. package/dist/roomRepository/api/analytics/syncWatchSessions.d.ts +6 -0
  33. package/dist/roomRepository/api/analytics/syncWatchSessions.d.ts.map +1 -0
  34. package/dist/roomRepository/api/index.d.ts +1 -0
  35. package/dist/roomRepository/api/index.d.ts.map +1 -1
  36. package/dist/userRepository/relationship/block/api/blockUser.d.ts.map +1 -1
  37. package/dist/utils/event.d.ts +1 -1
  38. package/dist/utils/event.d.ts.map +1 -1
  39. package/dist/utils/linkedObject/roomLinkedObject.d.ts.map +1 -1
  40. package/dist/utils/liveObject.d.ts +1 -1
  41. package/dist/utils/liveObject.d.ts.map +1 -1
  42. package/package.json +2 -2
package/dist/index.esm.js CHANGED
@@ -226,8 +226,8 @@ var AmityEventOrderOption;
226
226
 
227
227
  function getVersion() {
228
228
  try {
229
- // the string ''v7.12.0-esm'' should be replaced by actual value by @rollup/plugin-replace
230
- return 'v7.12.0-esm';
229
+ // the string ''v7.13.0-esm'' should be replaced by actual value by @rollup/plugin-replace
230
+ return 'v7.13.0-esm';
231
231
  }
232
232
  catch (error) {
233
233
  return '__dev__';
@@ -22977,11 +22977,11 @@ class SessionWatcher {
22977
22977
  this._listener.delete(callback);
22978
22978
  };
22979
22979
  }
22980
- setSessionState(state) {
22980
+ setSessionState(state, reason) {
22981
22981
  if (this._sessionState === state)
22982
22982
  return;
22983
22983
  this._sessionState = state;
22984
- this._listener.forEach(cb => cb(state));
22984
+ this._listener.forEach(cb => cb(state, reason));
22985
22985
  }
22986
22986
  destroy() {
22987
22987
  this._listener.clear();
@@ -23421,9 +23421,9 @@ const isValidStateChange = (prevState, nextState) => {
23421
23421
  *
23422
23422
  * @category private
23423
23423
  */
23424
- const setSessionState = (state) => {
23424
+ const setSessionState = (state, reason) => {
23425
23425
  const client = getActiveClient();
23426
- client.log('client/api/setSessionState', state);
23426
+ client.log('client/api/setSessionState', state, reason);
23427
23427
  const { sessionState: prevState } = client;
23428
23428
  if (prevState === state)
23429
23429
  return false;
@@ -23432,7 +23432,7 @@ const setSessionState = (state) => {
23432
23432
  throw new ASCError(`Session state cannot change from ${prevState} to ${state}`, 800000 /* Amity.ClientError.UNKNOWN_ERROR */, "error" /* Amity.ErrorLevel.ERROR */);
23433
23433
  }
23434
23434
  client.sessionState = state;
23435
- SessionWatcher$1.getInstance().setSessionState(state);
23435
+ SessionWatcher$1.getInstance().setSessionState(state, reason);
23436
23436
  return true;
23437
23437
  };
23438
23438
 
@@ -24915,7 +24915,7 @@ const logout = async () => {
24915
24915
  */
24916
24916
  const terminateClient = (terminationReason) => {
24917
24917
  const client = getActiveClient();
24918
- setSessionState("terminated" /* Amity.SessionStates.TERMINATED */);
24918
+ setSessionState("terminated" /* Amity.SessionStates.TERMINATED */, terminationReason);
24919
24919
  if (client.http.defaults.metadata) {
24920
24920
  if (terminationReason === "globalBan" /* Amity.TokenTerminationReason.GLOBAL_BAN */)
24921
24921
  client.http.defaults.metadata.isGlobalBanned = true;
@@ -26872,12 +26872,19 @@ const blockUser = async (userId) => {
26872
26872
  const { data } = await client.http.post(`/api/v4/me/user-blocks/${userId}`);
26873
26873
  const cachedAt = client.cache && Date.now();
26874
26874
  const { follows, followCounts } = data;
26875
- const followStatus = { follows };
26875
+ const blockedFollowStatus = { follows };
26876
26876
  if (client.cache) {
26877
- ingestInCache(followStatus, { cachedAt });
26877
+ ingestInCache(blockedFollowStatus, { cachedAt });
26878
26878
  upsertInCache(['followInfo', 'get', userId], followCounts[0], { cachedAt });
26879
26879
  }
26880
- const payload = prepareFollowStatusPayload(followStatus);
26880
+ // need to swap {from} and {to} to get the correct id for unfollow event to filter from follower live collection
26881
+ const blockedUnfollowStatus = {
26882
+ follows: follows.map(follow => {
26883
+ const unfollowStatus = Object.assign(Object.assign({}, follow), { to: follow.from, from: follow.to, status: 'none' });
26884
+ return follow.status === 'blocked' ? unfollowStatus : follow;
26885
+ }),
26886
+ };
26887
+ const payload = prepareFollowStatusPayload(blockedUnfollowStatus);
26881
26888
  fireEvent('local.follow.unfollowed', payload);
26882
26889
  return data;
26883
26890
  };
@@ -28782,6 +28789,414 @@ const getInvitation = async (params) => {
28782
28789
  };
28783
28790
  /* end_public_function */
28784
28791
 
28792
+ /**
28793
+ * WatchSessionStorage manages watch session data in memory
28794
+ * Similar to UsageCollector for stream watch minutes
28795
+ */
28796
+ class WatchSessionStorage {
28797
+ constructor() {
28798
+ this.sessions = new Map();
28799
+ }
28800
+ /**
28801
+ * Add a new watch session
28802
+ */
28803
+ addSession(session) {
28804
+ this.sessions.set(session.sessionId, session);
28805
+ }
28806
+ /**
28807
+ * Get a watch session by sessionId
28808
+ */
28809
+ getSession(sessionId) {
28810
+ return this.sessions.get(sessionId);
28811
+ }
28812
+ /**
28813
+ * Update a watch session
28814
+ */
28815
+ updateSession(sessionId, updates) {
28816
+ const session = this.sessions.get(sessionId);
28817
+ if (session) {
28818
+ this.sessions.set(sessionId, Object.assign(Object.assign({}, session), updates));
28819
+ }
28820
+ }
28821
+ /**
28822
+ * Get all sessions with a specific sync state
28823
+ */
28824
+ getSessionsByState(state) {
28825
+ return Array.from(this.sessions.values()).filter(session => session.syncState === state);
28826
+ }
28827
+ /**
28828
+ * Delete a session
28829
+ */
28830
+ deleteSession(sessionId) {
28831
+ this.sessions.delete(sessionId);
28832
+ }
28833
+ /**
28834
+ * Get all pending sessions
28835
+ */
28836
+ getPendingSessions() {
28837
+ return this.getSessionsByState('PENDING');
28838
+ }
28839
+ /**
28840
+ * Get all syncing sessions
28841
+ */
28842
+ getSyncingSessions() {
28843
+ return this.getSessionsByState('SYNCING');
28844
+ }
28845
+ }
28846
+ // Singleton instance
28847
+ let storageInstance = null;
28848
+ const getWatchSessionStorage = () => {
28849
+ if (!storageInstance) {
28850
+ storageInstance = new WatchSessionStorage();
28851
+ }
28852
+ return storageInstance;
28853
+ };
28854
+
28855
+ const privateKey = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDHo80SecH7FuF2\nhFYnb+l26/VN8UMLXAQFLnxciNTEwkGVFMpdezlH8rU2HtUJL4RETogbAOLVY0XM\njs6sPn8G1nALmh9qeDpUtVqFOVtBHxEZ910TLOtQiunjqJKO5nWdqZ71EC3OFluR\niGQkO84BiIFbv37ub7xl3S8XarbtKoLcyVpkDHi+1wx1pgCAn6gtBUgckPL5NR8j\nLseabl3HAXQfhTCKo4tmOFM2Dxwl1IUMmIJrJg/aIU/U0tj/1Eoo7mG0JcNWX19l\nW3EecCbi0ncCJOrkUdwlBrcjaMayaX/ubEwyUeTGiLdyc4L3GRLHjyK8xgVNXRMH\nbZWJ2a5NAgMBAAECggEASxuE+35zTFO/XydKgmvIGcWL9FbgMlXb7Vcf0nBoG945\nbiz0NVc2paraIhJXc608xbYF3qLmtAE1MVBI0ORyRdBHNxY024l/6H6SH60Ed+uI\nM4ysp5ourY6Vj+DLwpdRiI9YDjqYAQDIUmhNxJP7XPhOMoZI6st+xZQBM34ic/bv\nAMSJm9OZphSp3+qXVkFZztr2mxD2EZSJJLYxi8BCdgM2qhazalbcJ6zDKHCZWVWm\n8RRxDGldyMb/237JxETzP40tAlzOZDmBAbUgEnurDJ93RVDIE3rbZUshwgeQd18a\nem096mWgvB1AIKYgsTAR3pw+V19YWAjq/glP6fz8wQKBgQD/oQq+ukKF0PRgBeM5\ngeTjSwsdGppQLmf5ndujvoiz/TpdjDEPu6R8kigQr1rG2t4K/yfdZoI8RdmJD1al\n3Q7N9hofooSy4rj6E3txzWZCHJjHad2cnCp/O26HiReGAl7wTcfTmNdiFHhZQzm5\nJBkvWAiwuvQMNfEbnXxw6/vIDwKBgQDH7fX8gsc77JLvAWgp1MaQN/sbqVb6JeT1\nFQfR8E/WFCSmzQBtNzd5KgYuCeelwr/8DyYytvN2BzCYZXp73gI1jF3YlW5jVn74\nOY6TwQ095digwo6Z0yuxopdIOApKgAkL9PRKgNrqAf3NAyMua6lOGifzjDojC3KU\nfylQmxMn4wKBgHp2B9O/H0dEBw5JQ8W0+JX6yWQz7mEjGiR2/1W+XXb8hQ1zr709\nw1r6Gb+EghRpnZ3fBpYGGbYOMFx8wKHM+N6qW3F0ReX8v2juFGE8aRSa5oYBrWzt\nU16Idjbv8hj84cZ1PJmdyvDtpYn9rpWHOZl4rxEbPvbqkIsOMyNVqdT5AoGAOSge\nmwIIU2le2FVeohbibXiToWTYKMuMmURZ5/r72AgKMmWJKbAPe+Q3wBG01/7FRBpQ\noU8Ma0HC8s6QJbliiEyIx9JwrJWd1vkdecBHONrtA4ibm/5zD2WcOllLF+FitLhi\n3qnX6+6F0IaFGFBPJrTzlv0P4dTz/OAdv52V7GECgYEA2TttOKBAqWllgOaZOkql\nLVMJVmgR7s6tLi1+cEP8ZcapV9aRbRzTAKXm4f8AEhtlG9F9kCOvHYCYGi6JaiWJ\nZkHjeex3T+eE6Di6y5Bm/Ift5jtVhJ4jCVwHOKTMej79NPUFTJfv8hCo29haBDv6\nRXFrv+T21KCcw8k3sJeJWWQ=\n-----END PRIVATE KEY-----";
28856
+ /*
28857
+ * The crypto algorithm used for importing key and signing string
28858
+ */
28859
+ const ALGORITHM = {
28860
+ name: 'RSASSA-PKCS1-v1_5',
28861
+ hash: { name: 'SHA-256' },
28862
+ };
28863
+ /*
28864
+ * IMPORTANT!
28865
+ * If you are recieving key from other platforms use an online tool to convert
28866
+ * the PKCS1 to PKCS8. For instance the key from Android SDK is of the format
28867
+ * PKCS1.
28868
+ *
28869
+ * If recieving from the platform, verify if it's already in the expected
28870
+ * format. Otherwise the crypto.subtle.importKey will throw a DOMException
28871
+ */
28872
+ const PRIVATE_KEY_SIGNATURE = 'pkcs8';
28873
+ /*
28874
+ * Ensure that the private key in the .env follows this format
28875
+ */
28876
+ const PEM_HEADER = '-----BEGIN PRIVATE KEY-----';
28877
+ const PEM_FOOTER = '-----END PRIVATE KEY-----';
28878
+ /*
28879
+ * The crypto.subtle.sign function returns an ArrayBuffer whereas the server
28880
+ * expects a base64 string. This util helps facilitate that process
28881
+ */
28882
+ function base64FromArrayBuffer(buffer) {
28883
+ const uint8Array = new Uint8Array(buffer);
28884
+ let binary = '';
28885
+ uint8Array.forEach(byte => {
28886
+ binary += String.fromCharCode(byte);
28887
+ });
28888
+ return btoa(binary);
28889
+ }
28890
+ /*
28891
+ * Encode ASN.1 length field
28892
+ */
28893
+ function encodeLength(length) {
28894
+ if (length < 128) {
28895
+ return new Uint8Array([length]);
28896
+ }
28897
+ if (length < 256) {
28898
+ return new Uint8Array([0x81, length]);
28899
+ }
28900
+ // eslint-disable-next-line no-bitwise
28901
+ return new Uint8Array([0x82, (length >> 8) & 0xff, length & 0xff]);
28902
+ }
28903
+ /*
28904
+ * Convert PKCS1 private key to PKCS8 format
28905
+ * PKCS1 is RSA-specific format, PKCS8 is generic format that crypto.subtle requires
28906
+ * Android uses PKCS8EncodedKeySpec which expects PKCS8 format
28907
+ */
28908
+ function pkcs1ToPkcs8(pkcs1) {
28909
+ // Algorithm identifier for RSA
28910
+ const algorithmIdentifier = new Uint8Array([
28911
+ 0x30,
28912
+ 0x0d,
28913
+ 0x06,
28914
+ 0x09,
28915
+ 0x2a,
28916
+ 0x86,
28917
+ 0x48,
28918
+ 0x86,
28919
+ 0xf7,
28920
+ 0x0d,
28921
+ 0x01,
28922
+ 0x01,
28923
+ 0x01,
28924
+ 0x05,
28925
+ 0x00, // NULL
28926
+ ]);
28927
+ // Version (INTEGER 0)
28928
+ const version = new Uint8Array([0x02, 0x01, 0x00]);
28929
+ // OCTET STRING tag + length + pkcs1 data
28930
+ const octetStringTag = 0x04;
28931
+ const octetStringLength = encodeLength(pkcs1.length);
28932
+ // Calculate total content length (version + algorithm + octet string)
28933
+ const contentLength = version.length + algorithmIdentifier.length + 1 + octetStringLength.length + pkcs1.length;
28934
+ // SEQUENCE tag + length
28935
+ const sequenceTag = 0x30;
28936
+ const sequenceLength = encodeLength(contentLength);
28937
+ // Build the PKCS8 structure
28938
+ const pkcs8 = new Uint8Array(1 + sequenceLength.length + contentLength);
28939
+ let offset = 0;
28940
+ pkcs8[offset] = sequenceTag;
28941
+ offset += 1;
28942
+ pkcs8.set(sequenceLength, offset);
28943
+ offset += sequenceLength.length;
28944
+ pkcs8.set(version, offset);
28945
+ offset += version.length;
28946
+ pkcs8.set(algorithmIdentifier, offset);
28947
+ offset += algorithmIdentifier.length;
28948
+ pkcs8[offset] = octetStringTag;
28949
+ offset += 1;
28950
+ pkcs8.set(octetStringLength, offset);
28951
+ offset += octetStringLength.length;
28952
+ pkcs8.set(pkcs1, offset);
28953
+ return pkcs8;
28954
+ }
28955
+ async function importPrivateKey(keyString) {
28956
+ // Remove PEM headers if present and any whitespace
28957
+ let base64Key = keyString;
28958
+ if (keyString.includes(PEM_HEADER)) {
28959
+ base64Key = keyString
28960
+ .substring(PEM_HEADER.length, keyString.length - PEM_FOOTER.length)
28961
+ .replace(/\s/g, '');
28962
+ }
28963
+ else {
28964
+ base64Key = keyString.replace(/\s/g, '');
28965
+ }
28966
+ // Base64 decode to get binary data
28967
+ const binaryDerString = atob(base64Key);
28968
+ // Convert to Uint8Array for manipulation
28969
+ const pkcs1 = new Uint8Array(binaryDerString.length);
28970
+ for (let i = 0; i < binaryDerString.length; i += 1) {
28971
+ pkcs1[i] = binaryDerString.charCodeAt(i);
28972
+ }
28973
+ // Convert PKCS1 to PKCS8 (crypto.subtle requires PKCS8)
28974
+ const pkcs8 = pkcs1ToPkcs8(pkcs1);
28975
+ // Import the key
28976
+ const key = await crypto.subtle.importKey(PRIVATE_KEY_SIGNATURE, pkcs8.buffer, ALGORITHM, false, ['sign']);
28977
+ return key;
28978
+ }
28979
+ async function createSignature({ timestamp, rooms, }) {
28980
+ const dataStr = rooms
28981
+ .map(item => Object.keys(item)
28982
+ .sort()
28983
+ .map(key => `${key}=${item[key]}`)
28984
+ .join('&'))
28985
+ .join(';');
28986
+ /*
28987
+ * nonceStr needs to be unique for each request
28988
+ */
28989
+ const nonceStr = uuid$1.v4();
28990
+ const signStr = `nonceStr=${nonceStr}&timestamp=${timestamp}&data=${dataStr}==`;
28991
+ const encoder = new TextEncoder();
28992
+ const data = encoder.encode(signStr);
28993
+ const key = await importPrivateKey(privateKey);
28994
+ const sign = await crypto.subtle.sign(ALGORITHM, key, data);
28995
+ return { signature: base64FromArrayBuffer(sign), nonceStr };
28996
+ }
28997
+
28998
+ /**
28999
+ * Sync pending watch sessions to backend
29000
+ * This function implements the full sync flow with network resilience
29001
+ */
29002
+ async function syncWatchSessions() {
29003
+ const storage = getWatchSessionStorage();
29004
+ // Get all pending sessions
29005
+ const pendingSessions = storage.getPendingSessions();
29006
+ if (pendingSessions.length === 0) {
29007
+ return true;
29008
+ }
29009
+ try {
29010
+ const timestamp = new Date().toISOString();
29011
+ // Convert sessions to API format - always include all fields like syncUsage does
29012
+ const rooms = pendingSessions.map(session => ({
29013
+ sessionId: session.sessionId,
29014
+ roomId: session.roomId,
29015
+ watchSeconds: session.watchSeconds,
29016
+ startTime: session.startTime.toISOString(),
29017
+ endTime: session.endTime ? session.endTime.toISOString() : '',
29018
+ }));
29019
+ // Create signature (reuse from stream feature) - pass directly like syncUsage
29020
+ const signatureData = await createSignature({
29021
+ timestamp,
29022
+ rooms,
29023
+ });
29024
+ if (!signatureData || !signatureData.signature) {
29025
+ throw new Error('Signature is undefined');
29026
+ }
29027
+ // Update sync state to SYNCING
29028
+ pendingSessions.forEach(session => {
29029
+ storage.updateSession(session.sessionId, { syncState: 'SYNCING' });
29030
+ });
29031
+ const payload = {
29032
+ signature: signatureData.signature,
29033
+ nonceStr: signatureData.nonceStr,
29034
+ timestamp,
29035
+ rooms,
29036
+ };
29037
+ const client = getActiveClient();
29038
+ // Send to backend
29039
+ await client.http.post('/api/v3/user-event/room', payload);
29040
+ // Success - update to SYNCED
29041
+ pendingSessions.forEach(session => {
29042
+ storage.updateSession(session.sessionId, {
29043
+ syncState: 'SYNCED',
29044
+ syncedAt: new Date(),
29045
+ });
29046
+ });
29047
+ return true;
29048
+ }
29049
+ catch (err) {
29050
+ console.error('[SDK syncWatchSessions] ERROR caught:', (err === null || err === void 0 ? void 0 : err.message) || err);
29051
+ // Failure - update back to PENDING and increment retry count
29052
+ pendingSessions.forEach(session => {
29053
+ const currentSession = storage.getSession(session.sessionId);
29054
+ if (currentSession) {
29055
+ const newRetryCount = currentSession.retryCount + 1;
29056
+ if (newRetryCount >= 3) {
29057
+ // Delete session if retry count exceeds 3
29058
+ storage.deleteSession(session.sessionId);
29059
+ }
29060
+ else {
29061
+ // Update to PENDING with incremented retry count
29062
+ storage.updateSession(session.sessionId, {
29063
+ syncState: 'PENDING',
29064
+ retryCount: newRetryCount,
29065
+ });
29066
+ }
29067
+ }
29068
+ });
29069
+ return false;
29070
+ }
29071
+ }
29072
+
29073
+ /**
29074
+ * Room statuses that are considered watchable
29075
+ */
29076
+ const WATCHABLE_ROOM_STATUSES = ['live', 'recorded'];
29077
+ /**
29078
+ * Generate a random jitter delay between 5 and 30 seconds
29079
+ */
29080
+ const getJitterDelay = () => {
29081
+ const minDelay = 5000; // 5 seconds
29082
+ const maxDelay = 30000; // 30 seconds
29083
+ return Math.floor(Math.random() * (maxDelay - minDelay + 1)) + minDelay;
29084
+ };
29085
+ /**
29086
+ * Wait for network connection with timeout
29087
+ * @param maxWaitTime Maximum time to wait in milliseconds (default: 60000 = 60 seconds)
29088
+ * @returns Promise that resolves when network is available or timeout is reached
29089
+ */
29090
+ const waitForNetwork = (maxWaitTime = 60000) => {
29091
+ return new Promise(resolve => {
29092
+ // Simple check - if navigator.onLine is available, use it
29093
+ // Otherwise, assume network is available
29094
+ if (typeof navigator === 'undefined' || typeof navigator.onLine === 'undefined') {
29095
+ resolve();
29096
+ return;
29097
+ }
29098
+ const startTime = Date.now();
29099
+ const checkInterval = 1000; // 1 second
29100
+ const checkConnection = () => {
29101
+ if (navigator.onLine || Date.now() - startTime >= maxWaitTime) {
29102
+ resolve();
29103
+ }
29104
+ else {
29105
+ setTimeout(checkConnection, checkInterval);
29106
+ }
29107
+ };
29108
+ checkConnection();
29109
+ });
29110
+ };
29111
+ /**
29112
+ * AmityRoomAnalytics provides analytics capabilities for room watch sessions
29113
+ */
29114
+ class AmityRoomAnalytics {
29115
+ constructor(room) {
29116
+ this.storage = getWatchSessionStorage();
29117
+ this.client = getActiveClient();
29118
+ this.room = room;
29119
+ }
29120
+ /**
29121
+ * Create a new watch session for the current room
29122
+ * @param startedAt The timestamp when watching started
29123
+ * @returns Promise<string> sessionId unique identifier for this watch session
29124
+ * @throws ASCApiError if room is not in watchable state (not LIVE or RECORDED)
29125
+ */
29126
+ async createWatchSession(startedAt) {
29127
+ // Validate room status
29128
+ if (!WATCHABLE_ROOM_STATUSES.includes(this.room.status)) {
29129
+ throw new ASCApiError('room is not in watchable state', 500000 /* Amity.ServerError.BUSINESS_ERROR */, "error" /* Amity.ErrorLevel.ERROR */);
29130
+ }
29131
+ // Generate session ID with prefix
29132
+ const prefix = this.room.status === 'live' ? 'room_' : 'room_playback_';
29133
+ const sessionId = prefix + uuid$1.v4();
29134
+ // Create watch session entity
29135
+ const session = {
29136
+ sessionId,
29137
+ roomId: this.room.roomId,
29138
+ watchSeconds: 0,
29139
+ startTime: startedAt,
29140
+ endTime: null,
29141
+ syncState: 'PENDING',
29142
+ syncedAt: null,
29143
+ retryCount: 0,
29144
+ };
29145
+ // Persist to storage
29146
+ this.storage.addSession(session);
29147
+ return sessionId;
29148
+ }
29149
+ /**
29150
+ * Update an existing watch session with duration
29151
+ * @param sessionId The unique identifier of the watch session
29152
+ * @param duration The total watch duration in seconds
29153
+ * @param endedAt The timestamp when this update occurred
29154
+ * @returns Promise<void> Completion status
29155
+ */
29156
+ async updateWatchSession(sessionId, duration, endedAt) {
29157
+ const session = this.storage.getSession(sessionId);
29158
+ if (!session) {
29159
+ throw new Error(`Watch session ${sessionId} not found`);
29160
+ }
29161
+ // Update session
29162
+ this.storage.updateSession(sessionId, {
29163
+ watchSeconds: duration,
29164
+ endTime: endedAt,
29165
+ });
29166
+ }
29167
+ /**
29168
+ * Sync all pending watch sessions to backend
29169
+ * This function uses jitter delay and handles network resilience
29170
+ */
29171
+ syncPendingWatchSessions() {
29172
+ // Execute with jitter delay (5-30 seconds)
29173
+ const jitterDelay = getJitterDelay();
29174
+ this.client.log('room/RoomAnalytics: syncPendingWatchSessions called, jitter delay:', `${jitterDelay}ms`);
29175
+ setTimeout(async () => {
29176
+ this.client.log('room/RoomAnalytics: Jitter delay completed, starting sync process');
29177
+ try {
29178
+ // Reset any SYNCING sessions back to PENDING
29179
+ const syncingSessions = this.storage.getSyncingSessions();
29180
+ this.client.log('room/RoomAnalytics: SYNCING sessions to reset:', syncingSessions.length);
29181
+ syncingSessions.forEach(session => {
29182
+ this.storage.updateSession(session.sessionId, { syncState: 'PENDING' });
29183
+ });
29184
+ // Wait for network connection (max 60 seconds)
29185
+ await waitForNetwork(60000);
29186
+ this.client.log('room/RoomAnalytics: Network available');
29187
+ // Sync pending sessions
29188
+ this.client.log('room/RoomAnalytics: Calling syncWatchSessions()');
29189
+ await syncWatchSessions();
29190
+ this.client.log('room/RoomAnalytics: syncWatchSessions completed');
29191
+ }
29192
+ catch (error) {
29193
+ // Error is already handled in syncWatchSessions
29194
+ console.error('Failed to sync watch sessions:', error);
29195
+ }
29196
+ }, jitterDelay);
29197
+ }
29198
+ }
29199
+
28785
29200
  const roomLinkedObject = (room) => {
28786
29201
  return Object.assign(Object.assign({}, room), { get post() {
28787
29202
  var _a;
@@ -28828,6 +29243,9 @@ const roomLinkedObject = (room) => {
28828
29243
  type: "livestreamCohostInvite" /* InvitationTypeEnum.LivestreamCohostInvite */,
28829
29244
  });
28830
29245
  return data;
29246
+ }, analytics() {
29247
+ // Use 'this' to avoid creating a new room object
29248
+ return new AmityRoomAnalytics(this);
28831
29249
  } });
28832
29250
  };
28833
29251
 
@@ -45206,6 +45624,10 @@ var index$b = /*#__PURE__*/Object.freeze({
45206
45624
  getRecordedUrl: getRecordedUrl,
45207
45625
  removeParticipant: removeParticipant,
45208
45626
  leaveRoom: leaveRoom,
45627
+ AmityRoomAnalytics: AmityRoomAnalytics,
45628
+ WatchSessionStorage: WatchSessionStorage,
45629
+ getWatchSessionStorage: getWatchSessionStorage,
45630
+ syncWatchSessions: syncWatchSessions,
45209
45631
  onRoomStartBroadcasting: onRoomStartBroadcasting,
45210
45632
  onRoomWaitingReconnect: onRoomWaitingReconnect,
45211
45633
  onRoomEndBroadcasting: onRoomEndBroadcasting,
@@ -46714,85 +47136,6 @@ var index$7 = /*#__PURE__*/Object.freeze({
46714
47136
  getPoll: getPoll
46715
47137
  });
46716
47138
 
46717
- const privateKey = "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDAARz+hmBgi8pJ\nQb8LeY41gtHhk+ACMwRfhsn7GqpqRQNG2qU0755mzZuVDUqjQMGSo8THJB7O+OJs\nflbZRkFXlFoFOVNw1UpNOgwEQZ6wB9oRwzepTJAfF1sVhm/o/ixvXh1zDFNDy6yZ\npXyiiJHUVxqyjllZhxnwdvjoVtDs6hW6awG09bB9nh/TTejlUKXoAgzqVwu/1QMu\nUVViET495elEe19aUarEy+oL2iKeXCEvqda/pWNBdbieFyJvvZ08HN8dPuT88wq2\njZLEAth1vrwQ2IAa4ktaLcBQdLJgIkrbDvAiVZ8lQAjS/bq5vXQikTGvoPlC5bbn\nvuOM/3eLAgMBAAECggEAVZ+peHAghq2QVj71nX5lxsNCKaCyYwixSJBpfouTt7Rz\nE6PpzMOXFi1W1o+I22jDakuSM2SOQKqI/u0QefB0r0O/KVk5NrZHXk0mkrdYtxOp\nUgaGyf8UvmjB+8VqHrNKyZdk9qtmbnNj01kTTcAtmE4H39zPR7eR/8Rul94vaZbs\nwCnKJS3mLT3JxyGug6lxanveKkjG+CKC1nJQYWaxCJxaFSzbwXQPvDhB+TvrIbee\npd5v4EAyEJohpr+T9oDGGJkb/KARBZCtwLyB976PKJwwBA8MRVL1i5QwawuMiMq5\nUtnOnbGKtCeFzaLbNU0Qi8bqyims84EQxC6DOu1fkQKBgQDdvsoBsEhsOXV7hlIJ\naEd0eSJZVkdqimxH8uGoMM2FeNaOrcB6yBXqTSP0R3OIyf8eaY6yjRvP30ZNXcll\n/gD3O1Mu6YmWQdt1W2WA6pKOsUuPXasf0pdOF7IiFZKlSabz5YHXFqwVuqm8loaj\nsXel3YWqPVdHiankE7tz+3ssnQKBgQDdqi4TNdD1MdEpihx19jr0QjUiXW3939FK\nqp30HESPEGDGQzXdmJgif9HhZb+cJSuWaHEbjgBrYahvgCF+y6LbEpOD+D/dmT+s\nDEAQaR84sah6dokwPjV8fjBSrcVFjCS+doxv0d3p/9OUEeyUhFrY03nxtIEYkLIE\n/Zvn37b4RwKBgQCLENVFe9XfsaVhQ5r9dV2iyTlmh7qgMZG5CbTFs12hQGhm8McO\n+Z7s41YSJCFr/yq1WwP4LJDtrBw99vyQr1zRsG35tNLp3gGRNzGQSQyC2uQFVHw2\np+7mNewsfhUK/gbrXNsyFnDz6635rPlhfbII3sWuP2wWXFqkxE9CbMwR7QKBgQC6\nawDMzxmo2/iYArrkyevSuEuPVxvFwpF1RgAI6C0QVCnPE38dmdN4UB7mfHekje4W\nVEercMURidPp0cxZolCYBQtilUjAyL0vqC3In1/Ogjq6oy3FEMxSop1pKxMY5j+Q\nnoqFD+6deLUrddeNH7J3X4LSr4dSbX4JjG+tlgt+yQKBgQCuwTL4hA6KqeInQ0Ta\n9VQX5Qr8hFlqJz1gpymi/k63tW/Ob8yedbg3WWNWyShwRMFYyY9S81ITFWM95uL6\nvF3x9rmRjwElJw9PMwVu6dmf/CO0Z1wzXSp2VVD12gbrUD/0/d7MUoJ9LgC8X8f/\nn0txLHYGHbx+nf95+JUg6lV3hg==\n-----END PRIVATE KEY-----";
46718
- /*
46719
- * The crypto algorithm used for importing key and signing string
46720
- */
46721
- const ALGORITHM = {
46722
- name: 'RSASSA-PKCS1-v1_5',
46723
- hash: { name: 'SHA-256' },
46724
- };
46725
- /*
46726
- * IMPORTANT!
46727
- * If you are recieving key from other platforms use an online tool to convert
46728
- * the PKCS1 to PKCS8. For instance the key from Android SDK is of the format
46729
- * PKCS1.
46730
- *
46731
- * If recieving from the platform, verify if it's already in the expected
46732
- * format. Otherwise the crypto.subtle.importKey will throw a DOMException
46733
- */
46734
- const PRIVATE_KEY_SIGNATURE = 'pkcs8';
46735
- /*
46736
- * Ensure that the private key in the .env follows this format
46737
- */
46738
- const PEM_HEADER = '-----BEGIN PRIVATE KEY-----';
46739
- const PEM_FOOTER = '-----END PRIVATE KEY-----';
46740
- /*
46741
- * The crypto.subtle.sign function returns an ArrayBuffer whereas the server
46742
- * expects a base64 string. This util helps facilitate that process
46743
- */
46744
- function base64FromArrayBuffer(buffer) {
46745
- const uint8Array = new Uint8Array(buffer);
46746
- let binary = '';
46747
- uint8Array.forEach(byte => {
46748
- binary += String.fromCharCode(byte);
46749
- });
46750
- return btoa(binary);
46751
- }
46752
- /*
46753
- * Convert a string into an ArrayBuffer
46754
- * from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
46755
- *
46756
- * Solely used by the importPrivateKey method
46757
- */
46758
- function str2ab(str) {
46759
- const buf = new ArrayBuffer(str.length);
46760
- const bufView = new Uint8Array(buf);
46761
- for (let i = 0, strLen = str.length; i < strLen; i += 1) {
46762
- bufView[i] = str.charCodeAt(i);
46763
- }
46764
- return buf;
46765
- }
46766
- function importPrivateKey(pem) {
46767
- // fetch the part of the PEM string between header and footer
46768
- const pemContents = pem.substring(PEM_HEADER.length, pem.length - PEM_FOOTER.length);
46769
- /*
46770
- * base64 decode the string to get the binary data
46771
- */
46772
- const binaryDerString = atob(pemContents);
46773
- // convert from a binary string to an ArrayBuffer
46774
- const binaryDer = str2ab(binaryDerString);
46775
- return crypto.subtle.importKey(PRIVATE_KEY_SIGNATURE, binaryDer, ALGORITHM, false, ['sign']);
46776
- }
46777
- async function createSignature({ timestamp, streams, }) {
46778
- const dataStr = streams
46779
- .map(item => Object.keys(item)
46780
- .sort()
46781
- .map(key => `${key}=${item[key]}`)
46782
- .join('&'))
46783
- .join(';');
46784
- /*
46785
- * nonceStr needs to be unique for each request
46786
- */
46787
- const nonceStr = uuid$1.v4();
46788
- const signStr = `nonceStr=${nonceStr}&timestamp=${timestamp}&data=${dataStr}==`;
46789
- const encoder = new TextEncoder();
46790
- const data = encoder.encode(signStr);
46791
- const key = await importPrivateKey(privateKey);
46792
- const sign = await crypto.subtle.sign(ALGORITHM, key, data);
46793
- return { signature: base64FromArrayBuffer(sign), nonceStr };
46794
- }
46795
-
46796
47139
  async function syncUsage({ bufferCurrentUsage, getActiveStreams, updateUsage, dispose, }) {
46797
47140
  const streams = bufferCurrentUsage();
46798
47141
  if (!streams.length)