@amityco/ts-sdk 7.12.1-501d118.0 → 7.12.1-5cb139d3.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.
package/dist/index.esm.js CHANGED
@@ -26724,12 +26724,15 @@ const blockUser = async (userId) => {
26724
26724
  const { data } = await client.http.post(`/api/v4/me/user-blocks/${userId}`);
26725
26725
  const cachedAt = client.cache && Date.now();
26726
26726
  const { follows, followCounts } = data;
26727
- const followStatus = { follows };
26727
+ const blockedFollowStatus = { follows };
26728
26728
  if (client.cache) {
26729
- ingestInCache(followStatus, { cachedAt });
26729
+ ingestInCache(blockedFollowStatus, { cachedAt });
26730
26730
  upsertInCache(['followInfo', 'get', userId], followCounts[0], { cachedAt });
26731
26731
  }
26732
- const payload = prepareFollowStatusPayload(followStatus);
26732
+ const blockedUnfollowStatus = {
26733
+ follows: follows.map(follow => follow.status === 'blocked' ? Object.assign(Object.assign({}, follow), { to: follow.from, from: follow.to }) : follow),
26734
+ };
26735
+ const payload = prepareFollowStatusPayload(blockedUnfollowStatus);
26733
26736
  fireEvent('local.follow.unfollowed', payload);
26734
26737
  return data;
26735
26738
  };
@@ -28634,6 +28637,414 @@ const getInvitation = async (params) => {
28634
28637
  };
28635
28638
  /* end_public_function */
28636
28639
 
28640
+ /**
28641
+ * WatchSessionStorage manages watch session data in memory
28642
+ * Similar to UsageCollector for stream watch minutes
28643
+ */
28644
+ class WatchSessionStorage {
28645
+ constructor() {
28646
+ this.sessions = new Map();
28647
+ }
28648
+ /**
28649
+ * Add a new watch session
28650
+ */
28651
+ addSession(session) {
28652
+ this.sessions.set(session.sessionId, session);
28653
+ }
28654
+ /**
28655
+ * Get a watch session by sessionId
28656
+ */
28657
+ getSession(sessionId) {
28658
+ return this.sessions.get(sessionId);
28659
+ }
28660
+ /**
28661
+ * Update a watch session
28662
+ */
28663
+ updateSession(sessionId, updates) {
28664
+ const session = this.sessions.get(sessionId);
28665
+ if (session) {
28666
+ this.sessions.set(sessionId, Object.assign(Object.assign({}, session), updates));
28667
+ }
28668
+ }
28669
+ /**
28670
+ * Get all sessions with a specific sync state
28671
+ */
28672
+ getSessionsByState(state) {
28673
+ return Array.from(this.sessions.values()).filter(session => session.syncState === state);
28674
+ }
28675
+ /**
28676
+ * Delete a session
28677
+ */
28678
+ deleteSession(sessionId) {
28679
+ this.sessions.delete(sessionId);
28680
+ }
28681
+ /**
28682
+ * Get all pending sessions
28683
+ */
28684
+ getPendingSessions() {
28685
+ return this.getSessionsByState('PENDING');
28686
+ }
28687
+ /**
28688
+ * Get all syncing sessions
28689
+ */
28690
+ getSyncingSessions() {
28691
+ return this.getSessionsByState('SYNCING');
28692
+ }
28693
+ }
28694
+ // Singleton instance
28695
+ let storageInstance = null;
28696
+ const getWatchSessionStorage = () => {
28697
+ if (!storageInstance) {
28698
+ storageInstance = new WatchSessionStorage();
28699
+ }
28700
+ return storageInstance;
28701
+ };
28702
+
28703
+ const privateKey = "MIIEpQIBAAKCAQEAwAEc/oZgYIvKSUG/C3mONYLR4ZPgAjMEX4bJ+xqqakUDRtqlNO+eZs2blQ1Ko0DBkqPExyQezvjibH5W2UZBV5RaBTlTcNVKTToMBEGesAfaEcM3qUyQHxdbFYZv6P4sb14dcwxTQ8usmaV8ooiR1Fcaso5ZWYcZ8Hb46FbQ7OoVumsBtPWwfZ4f003o5VCl6AIM6lcLv9UDLlFVYhE+PeXpRHtfWlGqxMvqC9oinlwhL6nWv6VjQXW4nhcib72dPBzfHT7k/PMKto2SxALYdb68ENiAGuJLWi3AUHSyYCJK2w7wIlWfJUAI0v26ub10IpExr6D5QuW2577jjP93iwIDAQABAoIBAFWfqXhwIIatkFY+9Z1+ZcbDQimgsmMIsUiQaX6Lk7e0cxOj6czDlxYtVtaPiNtow2pLkjNkjkCqiP7tEHnwdK9DvylZOTa2R15NJpK3WLcTqVIGhsn/FL5owfvFah6zSsmXZParZm5zY9NZE03ALZhOB9/cz0e3kf/EbpfeL2mW7MApyiUt5i09ycchroOpcWp73ipIxvgigtZyUGFmsQicWhUs28F0D7w4Qfk76yG3nqXeb+BAMhCaIaa/k/aAxhiZG/ygEQWQrcC8gfe+jyicMAQPDEVS9YuUMGsLjIjKuVLZzp2xirQnhc2i2zVNEIvG6soprPOBEMQugzrtX5ECgYEA3b7KAbBIbDl1e4ZSCWhHdHkiWVZHaopsR/LhqDDNhXjWjq3AesgV6k0j9EdziMn/HmmOso0bz99GTV3JZf4A9ztTLumJlkHbdVtlgOqSjrFLj12rH9KXTheyIhWSpUmm8+WB1xasFbqpvJaGo7F3pd2Fqj1XR4mp5BO7c/t7LJ0CgYEA3aouEzXQ9THRKYocdfY69EI1Il1t/d/RSqqd9BxEjxBgxkM13ZiYIn/R4WW/nCUrlmhxG44Aa2Gob4Ahfsui2xKTg/g/3Zk/rAxAEGkfOLGoenaJMD41fH4wUq3FRYwkvnaMb9Hd6f/TlBHslIRa2NN58bSBGJCyBP2b59+2+EcCgYEAixDVRXvV37GlYUOa/XVdosk5Zoe6oDGRuQm0xbNdoUBoZvDHDvme7ONWEiQha/8qtVsD+CyQ7awcPfb8kK9c0bBt+bTS6d4BkTcxkEkMgtrkBVR8Nqfu5jXsLH4VCv4G61zbMhZw8+ut+az5YX2yCN7Frj9sFlxapMRPQmzMEe0CgYEAumsAzM8ZqNv4mAK65Mnr0rhLj1cbxcKRdUYACOgtEFQpzxN/HZnTeFAe5nx3pI3uFlRHq3DFEYnT6dHMWaJQmAULYpVIwMi9L6gtyJ9fzoI6uqMtxRDMUqKdaSsTGOY/kJ6KhQ/unXi1K3XXjR+yd1+C0q+HUm1+CYxvrZYLfskCgYEArsEy+IQOiqniJ0NE2vVUF+UK/IRZaic9YKcpov5Ot7Vvzm/MnnW4N1ljVskocETBWMmPUvNSExVjPebi+rxd8fa5kY8BJScPTzMFbunZn/wjtGdcM10qdlVQ9doG61A/9P3ezFKCfS4AvF/H/59LcSx2Bh28fp3/efiVIOpVd4Y=";
28704
+ /*
28705
+ * The crypto algorithm used for importing key and signing string
28706
+ */
28707
+ const ALGORITHM = {
28708
+ name: 'RSASSA-PKCS1-v1_5',
28709
+ hash: { name: 'SHA-256' },
28710
+ };
28711
+ /*
28712
+ * IMPORTANT!
28713
+ * If you are recieving key from other platforms use an online tool to convert
28714
+ * the PKCS1 to PKCS8. For instance the key from Android SDK is of the format
28715
+ * PKCS1.
28716
+ *
28717
+ * If recieving from the platform, verify if it's already in the expected
28718
+ * format. Otherwise the crypto.subtle.importKey will throw a DOMException
28719
+ */
28720
+ const PRIVATE_KEY_SIGNATURE = 'pkcs8';
28721
+ /*
28722
+ * Ensure that the private key in the .env follows this format
28723
+ */
28724
+ const PEM_HEADER = '-----BEGIN PRIVATE KEY-----';
28725
+ const PEM_FOOTER = '-----END PRIVATE KEY-----';
28726
+ /*
28727
+ * The crypto.subtle.sign function returns an ArrayBuffer whereas the server
28728
+ * expects a base64 string. This util helps facilitate that process
28729
+ */
28730
+ function base64FromArrayBuffer(buffer) {
28731
+ const uint8Array = new Uint8Array(buffer);
28732
+ let binary = '';
28733
+ uint8Array.forEach(byte => {
28734
+ binary += String.fromCharCode(byte);
28735
+ });
28736
+ return btoa(binary);
28737
+ }
28738
+ /*
28739
+ * Encode ASN.1 length field
28740
+ */
28741
+ function encodeLength(length) {
28742
+ if (length < 128) {
28743
+ return new Uint8Array([length]);
28744
+ }
28745
+ if (length < 256) {
28746
+ return new Uint8Array([0x81, length]);
28747
+ }
28748
+ // eslint-disable-next-line no-bitwise
28749
+ return new Uint8Array([0x82, (length >> 8) & 0xff, length & 0xff]);
28750
+ }
28751
+ /*
28752
+ * Convert PKCS1 private key to PKCS8 format
28753
+ * PKCS1 is RSA-specific format, PKCS8 is generic format that crypto.subtle requires
28754
+ * Android uses PKCS8EncodedKeySpec which expects PKCS8 format
28755
+ */
28756
+ function pkcs1ToPkcs8(pkcs1) {
28757
+ // Algorithm identifier for RSA
28758
+ const algorithmIdentifier = new Uint8Array([
28759
+ 0x30,
28760
+ 0x0d,
28761
+ 0x06,
28762
+ 0x09,
28763
+ 0x2a,
28764
+ 0x86,
28765
+ 0x48,
28766
+ 0x86,
28767
+ 0xf7,
28768
+ 0x0d,
28769
+ 0x01,
28770
+ 0x01,
28771
+ 0x01,
28772
+ 0x05,
28773
+ 0x00, // NULL
28774
+ ]);
28775
+ // Version (INTEGER 0)
28776
+ const version = new Uint8Array([0x02, 0x01, 0x00]);
28777
+ // OCTET STRING tag + length + pkcs1 data
28778
+ const octetStringTag = 0x04;
28779
+ const octetStringLength = encodeLength(pkcs1.length);
28780
+ // Calculate total content length (version + algorithm + octet string)
28781
+ const contentLength = version.length + algorithmIdentifier.length + 1 + octetStringLength.length + pkcs1.length;
28782
+ // SEQUENCE tag + length
28783
+ const sequenceTag = 0x30;
28784
+ const sequenceLength = encodeLength(contentLength);
28785
+ // Build the PKCS8 structure
28786
+ const pkcs8 = new Uint8Array(1 + sequenceLength.length + contentLength);
28787
+ let offset = 0;
28788
+ pkcs8[offset] = sequenceTag;
28789
+ offset += 1;
28790
+ pkcs8.set(sequenceLength, offset);
28791
+ offset += sequenceLength.length;
28792
+ pkcs8.set(version, offset);
28793
+ offset += version.length;
28794
+ pkcs8.set(algorithmIdentifier, offset);
28795
+ offset += algorithmIdentifier.length;
28796
+ pkcs8[offset] = octetStringTag;
28797
+ offset += 1;
28798
+ pkcs8.set(octetStringLength, offset);
28799
+ offset += octetStringLength.length;
28800
+ pkcs8.set(pkcs1, offset);
28801
+ return pkcs8;
28802
+ }
28803
+ async function importPrivateKey(keyString) {
28804
+ // Remove PEM headers if present and any whitespace
28805
+ let base64Key = keyString;
28806
+ if (keyString.includes(PEM_HEADER)) {
28807
+ base64Key = keyString
28808
+ .substring(PEM_HEADER.length, keyString.length - PEM_FOOTER.length)
28809
+ .replace(/\s/g, '');
28810
+ }
28811
+ else {
28812
+ base64Key = keyString.replace(/\s/g, '');
28813
+ }
28814
+ // Base64 decode to get binary data
28815
+ const binaryDerString = atob(base64Key);
28816
+ // Convert to Uint8Array for manipulation
28817
+ const pkcs1 = new Uint8Array(binaryDerString.length);
28818
+ for (let i = 0; i < binaryDerString.length; i += 1) {
28819
+ pkcs1[i] = binaryDerString.charCodeAt(i);
28820
+ }
28821
+ // Convert PKCS1 to PKCS8 (crypto.subtle requires PKCS8)
28822
+ const pkcs8 = pkcs1ToPkcs8(pkcs1);
28823
+ // Import the key
28824
+ const key = await crypto.subtle.importKey(PRIVATE_KEY_SIGNATURE, pkcs8.buffer, ALGORITHM, false, ['sign']);
28825
+ return key;
28826
+ }
28827
+ async function createSignature({ timestamp, rooms, }) {
28828
+ const dataStr = rooms
28829
+ .map(item => Object.keys(item)
28830
+ .sort()
28831
+ .map(key => `${key}=${item[key]}`)
28832
+ .join('&'))
28833
+ .join(';');
28834
+ /*
28835
+ * nonceStr needs to be unique for each request
28836
+ */
28837
+ const nonceStr = uuid$1.v4();
28838
+ const signStr = `nonceStr=${nonceStr}&timestamp=${timestamp}&data=${dataStr}==`;
28839
+ const encoder = new TextEncoder();
28840
+ const data = encoder.encode(signStr);
28841
+ const key = await importPrivateKey(privateKey);
28842
+ const sign = await crypto.subtle.sign(ALGORITHM, key, data);
28843
+ return { signature: base64FromArrayBuffer(sign), nonceStr };
28844
+ }
28845
+
28846
+ /**
28847
+ * Sync pending watch sessions to backend
28848
+ * This function implements the full sync flow with network resilience
28849
+ */
28850
+ async function syncWatchSessions() {
28851
+ const storage = getWatchSessionStorage();
28852
+ // Get all pending sessions
28853
+ const pendingSessions = storage.getPendingSessions();
28854
+ if (pendingSessions.length === 0) {
28855
+ return true;
28856
+ }
28857
+ try {
28858
+ const timestamp = new Date().toISOString();
28859
+ // Convert sessions to API format - always include all fields like syncUsage does
28860
+ const rooms = pendingSessions.map(session => ({
28861
+ sessionId: session.sessionId,
28862
+ roomId: session.roomId,
28863
+ watchSeconds: session.watchSeconds,
28864
+ startTime: session.startTime.toISOString(),
28865
+ endTime: session.endTime ? session.endTime.toISOString() : '',
28866
+ }));
28867
+ // Create signature (reuse from stream feature) - pass directly like syncUsage
28868
+ const signatureData = await createSignature({
28869
+ timestamp,
28870
+ rooms,
28871
+ });
28872
+ if (!signatureData || !signatureData.signature) {
28873
+ throw new Error('Signature is undefined');
28874
+ }
28875
+ // Update sync state to SYNCING
28876
+ pendingSessions.forEach(session => {
28877
+ storage.updateSession(session.sessionId, { syncState: 'SYNCING' });
28878
+ });
28879
+ const payload = {
28880
+ signature: signatureData.signature,
28881
+ nonceStr: signatureData.nonceStr,
28882
+ timestamp,
28883
+ rooms,
28884
+ };
28885
+ const client = getActiveClient();
28886
+ // Send to backend
28887
+ await client.http.post('/api/v3/user-event/room', payload);
28888
+ // Success - update to SYNCED
28889
+ pendingSessions.forEach(session => {
28890
+ storage.updateSession(session.sessionId, {
28891
+ syncState: 'SYNCED',
28892
+ syncedAt: new Date(),
28893
+ });
28894
+ });
28895
+ return true;
28896
+ }
28897
+ catch (err) {
28898
+ console.error('[SDK syncWatchSessions] ERROR caught:', (err === null || err === void 0 ? void 0 : err.message) || err);
28899
+ // Failure - update back to PENDING and increment retry count
28900
+ pendingSessions.forEach(session => {
28901
+ const currentSession = storage.getSession(session.sessionId);
28902
+ if (currentSession) {
28903
+ const newRetryCount = currentSession.retryCount + 1;
28904
+ if (newRetryCount >= 3) {
28905
+ // Delete session if retry count exceeds 3
28906
+ storage.deleteSession(session.sessionId);
28907
+ }
28908
+ else {
28909
+ // Update to PENDING with incremented retry count
28910
+ storage.updateSession(session.sessionId, {
28911
+ syncState: 'PENDING',
28912
+ retryCount: newRetryCount,
28913
+ });
28914
+ }
28915
+ }
28916
+ });
28917
+ return false;
28918
+ }
28919
+ }
28920
+
28921
+ /**
28922
+ * Room statuses that are considered watchable
28923
+ */
28924
+ const WATCHABLE_ROOM_STATUSES = ['live', 'recorded'];
28925
+ /**
28926
+ * Generate a random jitter delay between 5 and 30 seconds
28927
+ */
28928
+ const getJitterDelay = () => {
28929
+ const minDelay = 5000; // 5 seconds
28930
+ const maxDelay = 30000; // 30 seconds
28931
+ return Math.floor(Math.random() * (maxDelay - minDelay + 1)) + minDelay;
28932
+ };
28933
+ /**
28934
+ * Wait for network connection with timeout
28935
+ * @param maxWaitTime Maximum time to wait in milliseconds (default: 60000 = 60 seconds)
28936
+ * @returns Promise that resolves when network is available or timeout is reached
28937
+ */
28938
+ const waitForNetwork = (maxWaitTime = 60000) => {
28939
+ return new Promise(resolve => {
28940
+ // Simple check - if navigator.onLine is available, use it
28941
+ // Otherwise, assume network is available
28942
+ if (typeof navigator === 'undefined' || typeof navigator.onLine === 'undefined') {
28943
+ resolve();
28944
+ return;
28945
+ }
28946
+ const startTime = Date.now();
28947
+ const checkInterval = 1000; // 1 second
28948
+ const checkConnection = () => {
28949
+ if (navigator.onLine || Date.now() - startTime >= maxWaitTime) {
28950
+ resolve();
28951
+ }
28952
+ else {
28953
+ setTimeout(checkConnection, checkInterval);
28954
+ }
28955
+ };
28956
+ checkConnection();
28957
+ });
28958
+ };
28959
+ /**
28960
+ * AmityRoomAnalytics provides analytics capabilities for room watch sessions
28961
+ */
28962
+ class AmityRoomAnalytics {
28963
+ constructor(room) {
28964
+ this.storage = getWatchSessionStorage();
28965
+ this.client = getActiveClient();
28966
+ this.room = room;
28967
+ }
28968
+ /**
28969
+ * Create a new watch session for the current room
28970
+ * @param startedAt The timestamp when watching started
28971
+ * @returns Promise<string> sessionId unique identifier for this watch session
28972
+ * @throws ASCApiError if room is not in watchable state (not LIVE or RECORDED)
28973
+ */
28974
+ async createWatchSession(startedAt) {
28975
+ // Validate room status
28976
+ if (!WATCHABLE_ROOM_STATUSES.includes(this.room.status)) {
28977
+ throw new ASCApiError('room is not in watchable state', 500000 /* Amity.ServerError.BUSINESS_ERROR */, "error" /* Amity.ErrorLevel.ERROR */);
28978
+ }
28979
+ // Generate session ID with prefix
28980
+ const prefix = this.room.status === 'live' ? 'room_' : 'room_playback_';
28981
+ const sessionId = prefix + uuid$1.v4();
28982
+ // Create watch session entity
28983
+ const session = {
28984
+ sessionId,
28985
+ roomId: this.room.roomId,
28986
+ watchSeconds: 0,
28987
+ startTime: startedAt,
28988
+ endTime: null,
28989
+ syncState: 'PENDING',
28990
+ syncedAt: null,
28991
+ retryCount: 0,
28992
+ };
28993
+ // Persist to storage
28994
+ this.storage.addSession(session);
28995
+ return sessionId;
28996
+ }
28997
+ /**
28998
+ * Update an existing watch session with duration
28999
+ * @param sessionId The unique identifier of the watch session
29000
+ * @param duration The total watch duration in seconds
29001
+ * @param endedAt The timestamp when this update occurred
29002
+ * @returns Promise<void> Completion status
29003
+ */
29004
+ async updateWatchSession(sessionId, duration, endedAt) {
29005
+ const session = this.storage.getSession(sessionId);
29006
+ if (!session) {
29007
+ throw new Error(`Watch session ${sessionId} not found`);
29008
+ }
29009
+ // Update session
29010
+ this.storage.updateSession(sessionId, {
29011
+ watchSeconds: duration,
29012
+ endTime: endedAt,
29013
+ });
29014
+ }
29015
+ /**
29016
+ * Sync all pending watch sessions to backend
29017
+ * This function uses jitter delay and handles network resilience
29018
+ */
29019
+ syncPendingWatchSessions() {
29020
+ // Execute with jitter delay (5-30 seconds)
29021
+ const jitterDelay = getJitterDelay();
29022
+ this.client.log('room/RoomAnalytics: syncPendingWatchSessions called, jitter delay:', `${jitterDelay}ms`);
29023
+ setTimeout(async () => {
29024
+ this.client.log('room/RoomAnalytics: Jitter delay completed, starting sync process');
29025
+ try {
29026
+ // Reset any SYNCING sessions back to PENDING
29027
+ const syncingSessions = this.storage.getSyncingSessions();
29028
+ this.client.log('room/RoomAnalytics: SYNCING sessions to reset:', syncingSessions.length);
29029
+ syncingSessions.forEach(session => {
29030
+ this.storage.updateSession(session.sessionId, { syncState: 'PENDING' });
29031
+ });
29032
+ // Wait for network connection (max 60 seconds)
29033
+ await waitForNetwork(60000);
29034
+ this.client.log('room/RoomAnalytics: Network available');
29035
+ // Sync pending sessions
29036
+ this.client.log('room/RoomAnalytics: Calling syncWatchSessions()');
29037
+ await syncWatchSessions();
29038
+ this.client.log('room/RoomAnalytics: syncWatchSessions completed');
29039
+ }
29040
+ catch (error) {
29041
+ // Error is already handled in syncWatchSessions
29042
+ console.error('Failed to sync watch sessions:', error);
29043
+ }
29044
+ }, jitterDelay);
29045
+ }
29046
+ }
29047
+
28637
29048
  const roomLinkedObject = (room) => {
28638
29049
  return Object.assign(Object.assign({}, room), { get post() {
28639
29050
  var _a;
@@ -28680,6 +29091,9 @@ const roomLinkedObject = (room) => {
28680
29091
  type: "livestreamCohostInvite" /* InvitationTypeEnum.LivestreamCohostInvite */,
28681
29092
  });
28682
29093
  return data;
29094
+ }, analytics() {
29095
+ // Use 'this' to avoid creating a new room object
29096
+ return new AmityRoomAnalytics(this);
28683
29097
  } });
28684
29098
  };
28685
29099
 
@@ -45058,6 +45472,10 @@ var index$b = /*#__PURE__*/Object.freeze({
45058
45472
  getRecordedUrl: getRecordedUrl,
45059
45473
  removeParticipant: removeParticipant,
45060
45474
  leaveRoom: leaveRoom,
45475
+ AmityRoomAnalytics: AmityRoomAnalytics,
45476
+ WatchSessionStorage: WatchSessionStorage,
45477
+ getWatchSessionStorage: getWatchSessionStorage,
45478
+ syncWatchSessions: syncWatchSessions,
45061
45479
  onRoomStartBroadcasting: onRoomStartBroadcasting,
45062
45480
  onRoomWaitingReconnect: onRoomWaitingReconnect,
45063
45481
  onRoomEndBroadcasting: onRoomEndBroadcasting,
@@ -46573,85 +46991,6 @@ var index$7 = /*#__PURE__*/Object.freeze({
46573
46991
  getPoll: getPoll
46574
46992
  });
46575
46993
 
46576
- 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-----";
46577
- /*
46578
- * The crypto algorithm used for importing key and signing string
46579
- */
46580
- const ALGORITHM = {
46581
- name: 'RSASSA-PKCS1-v1_5',
46582
- hash: { name: 'SHA-256' },
46583
- };
46584
- /*
46585
- * IMPORTANT!
46586
- * If you are recieving key from other platforms use an online tool to convert
46587
- * the PKCS1 to PKCS8. For instance the key from Android SDK is of the format
46588
- * PKCS1.
46589
- *
46590
- * If recieving from the platform, verify if it's already in the expected
46591
- * format. Otherwise the crypto.subtle.importKey will throw a DOMException
46592
- */
46593
- const PRIVATE_KEY_SIGNATURE = 'pkcs8';
46594
- /*
46595
- * Ensure that the private key in the .env follows this format
46596
- */
46597
- const PEM_HEADER = '-----BEGIN PRIVATE KEY-----';
46598
- const PEM_FOOTER = '-----END PRIVATE KEY-----';
46599
- /*
46600
- * The crypto.subtle.sign function returns an ArrayBuffer whereas the server
46601
- * expects a base64 string. This util helps facilitate that process
46602
- */
46603
- function base64FromArrayBuffer(buffer) {
46604
- const uint8Array = new Uint8Array(buffer);
46605
- let binary = '';
46606
- uint8Array.forEach(byte => {
46607
- binary += String.fromCharCode(byte);
46608
- });
46609
- return btoa(binary);
46610
- }
46611
- /*
46612
- * Convert a string into an ArrayBuffer
46613
- * from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
46614
- *
46615
- * Solely used by the importPrivateKey method
46616
- */
46617
- function str2ab(str) {
46618
- const buf = new ArrayBuffer(str.length);
46619
- const bufView = new Uint8Array(buf);
46620
- for (let i = 0, strLen = str.length; i < strLen; i += 1) {
46621
- bufView[i] = str.charCodeAt(i);
46622
- }
46623
- return buf;
46624
- }
46625
- function importPrivateKey(pem) {
46626
- // fetch the part of the PEM string between header and footer
46627
- const pemContents = pem.substring(PEM_HEADER.length, pem.length - PEM_FOOTER.length);
46628
- /*
46629
- * base64 decode the string to get the binary data
46630
- */
46631
- const binaryDerString = atob(pemContents);
46632
- // convert from a binary string to an ArrayBuffer
46633
- const binaryDer = str2ab(binaryDerString);
46634
- return crypto.subtle.importKey(PRIVATE_KEY_SIGNATURE, binaryDer, ALGORITHM, false, ['sign']);
46635
- }
46636
- async function createSignature({ timestamp, streams, }) {
46637
- const dataStr = streams
46638
- .map(item => Object.keys(item)
46639
- .sort()
46640
- .map(key => `${key}=${item[key]}`)
46641
- .join('&'))
46642
- .join(';');
46643
- /*
46644
- * nonceStr needs to be unique for each request
46645
- */
46646
- const nonceStr = uuid$1.v4();
46647
- const signStr = `nonceStr=${nonceStr}&timestamp=${timestamp}&data=${dataStr}==`;
46648
- const encoder = new TextEncoder();
46649
- const data = encoder.encode(signStr);
46650
- const key = await importPrivateKey(privateKey);
46651
- const sign = await crypto.subtle.sign(ALGORITHM, key, data);
46652
- return { signature: base64FromArrayBuffer(sign), nonceStr };
46653
- }
46654
-
46655
46994
  async function syncUsage({ bufferCurrentUsage, getActiveStreams, updateUsage, dispose, }) {
46656
46995
  const streams = bufferCurrentUsage();
46657
46996
  if (!streams.length)