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