@capgo/capacitor-stream-call 0.0.71 → 0.0.78

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/README.md CHANGED
@@ -252,6 +252,8 @@ export class CallService {
252
252
  * [`setSpeaker(...)`](#setspeaker)
253
253
  * [`switchCamera(...)`](#switchcamera)
254
254
  * [`getCallInfo(...)`](#getcallinfo)
255
+ * [`setDynamicStreamVideoApikey(...)`](#setdynamicstreamvideoapikey)
256
+ * [`getDynamicStreamVideoApikey()`](#getdynamicstreamvideoapikey)
255
257
  * [Interfaces](#interfaces)
256
258
  * [Type Aliases](#type-aliases)
257
259
  * [Enums](#enums)
@@ -506,6 +508,36 @@ Get detailed information about an active call including caller details
506
508
  --------------------
507
509
 
508
510
 
511
+ ### setDynamicStreamVideoApikey(...)
512
+
513
+ ```typescript
514
+ setDynamicStreamVideoApikey(options: { apiKey: string; }) => Promise<SuccessResponse>
515
+ ```
516
+
517
+ Set a dynamic Stream Video API key that overrides the static one
518
+
519
+ | Param | Type | Description |
520
+ | ------------- | -------------------------------- | -------------------- |
521
+ | **`options`** | <code>{ apiKey: string; }</code> | - The API key to set |
522
+
523
+ **Returns:** <code>Promise&lt;<a href="#successresponse">SuccessResponse</a>&gt;</code>
524
+
525
+ --------------------
526
+
527
+
528
+ ### getDynamicStreamVideoApikey()
529
+
530
+ ```typescript
531
+ getDynamicStreamVideoApikey() => Promise<DynamicApiKeyResponse>
532
+ ```
533
+
534
+ Get the currently set dynamic Stream Video API key
535
+
536
+ **Returns:** <code>Promise&lt;<a href="#dynamicapikeyresponse">DynamicApiKeyResponse</a>&gt;</code>
537
+
538
+ --------------------
539
+
540
+
509
541
  ### Interfaces
510
542
 
511
543
 
@@ -539,13 +571,14 @@ Get detailed information about an active call including caller details
539
571
 
540
572
  #### CallOptions
541
573
 
542
- | Prop | Type | Description |
543
- | ------------- | --------------------------------------------- | --------------------------------------------------------------- |
544
- | **`userIds`** | <code>string[]</code> | User ID of the person to call |
545
- | **`type`** | <code><a href="#calltype">CallType</a></code> | Type of call, defaults to 'default' |
546
- | **`ring`** | <code>boolean</code> | Whether to ring the other user, defaults to true |
547
- | **`team`** | <code>string</code> | Team name to call |
548
- | **`video`** | <code>boolean</code> | Whether to start the call with video enabled, defaults to false |
574
+ | Prop | Type | Description |
575
+ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
576
+ | **`userIds`** | <code>string[]</code> | User ID of the person to call |
577
+ | **`type`** | <code><a href="#calltype">CallType</a></code> | Type of call, defaults to 'default' |
578
+ | **`ring`** | <code>boolean</code> | Whether to ring the other user, defaults to true |
579
+ | **`team`** | <code>string</code> | Team name to call |
580
+ | **`video`** | <code>boolean</code> | Whether to start the call with video enabled, defaults to false |
581
+ | **`custom`** | <code><a href="#record">Record</a>&lt; string, \| string \| boolean \| number \| null \| <a href="#record">Record</a>&lt;string, string \| boolean \| number \| null&gt; \| string[] \| boolean[] \| number[] &gt;</code> | Custom data to be passed to the call |
549
582
 
550
583
 
551
584
  #### CallEvent
@@ -763,11 +796,12 @@ The JSON representation for <a href="#listvalue">`ListValue`</a> is JSON array.
763
796
 
764
797
  #### IncomingCallPayload
765
798
 
766
- | Prop | Type | Description |
767
- | ------------ | ------------------------------------------------- | ---------------------------------------- |
768
- | **`cid`** | <code>string</code> | Full call CID (e.g. default:123) |
769
- | **`type`** | <code>'incoming'</code> | Event type (currently always "incoming") |
770
- | **`caller`** | <code><a href="#callmember">CallMember</a></code> | Information about the caller |
799
+ | Prop | Type | Description |
800
+ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
801
+ | **`cid`** | <code>string</code> | Full call CID (e.g. default:123) |
802
+ | **`type`** | <code>'incoming'</code> | Event type (currently always "incoming") |
803
+ | **`caller`** | <code><a href="#callmember">CallMember</a></code> | Information about the caller |
804
+ | **`custom`** | <code><a href="#record">Record</a>&lt; string, \| string \| boolean \| number \| null \| <a href="#record">Record</a>&lt;string, string \| boolean \| number \| null&gt; \| string[] \| boolean[] \| number[] &gt;</code> | Custom data to be passed to the call |
771
805
 
772
806
 
773
807
  #### CameraEnabledResponse
@@ -777,6 +811,14 @@ The JSON representation for <a href="#listvalue">`ListValue`</a> is JSON array.
777
811
  | **`enabled`** | <code>boolean</code> |
778
812
 
779
813
 
814
+ #### DynamicApiKeyResponse
815
+
816
+ | Prop | Type | Description |
817
+ | ------------------- | --------------------------- | --------------------------------------- |
818
+ | **`apiKey`** | <code>string \| null</code> | The dynamic API key if set, null if not |
819
+ | **`hasDynamicKey`** | <code>boolean</code> | Whether a dynamic key is currently set |
820
+
821
+
780
822
  ### Type Aliases
781
823
 
782
824
 
@@ -785,6 +827,13 @@ The JSON representation for <a href="#listvalue">`ListValue`</a> is JSON array.
785
827
  <code>'default' | 'audio_room' | 'livestream' | 'development'</code>
786
828
 
787
829
 
830
+ #### Record
831
+
832
+ Construct a type with a set of properties K of type T
833
+
834
+ <code>{
788
835
  [P in K]: T;
789
836
  }</code>
837
+
838
+
790
839
  #### CallState
791
840
 
792
841
  <code>'idle' | 'ringing' | 'joining' | 'reconnecting' | 'joined' | 'leaving' | 'left' | 'created' | 'session_started' | 'rejected' | 'missed' | 'accepted' | 'ended' | 'camera_enabled' | 'camera_disabled' | 'microphone_enabled' | 'microphone_disabled' | 'unknown'</code>
@@ -75,10 +75,10 @@ dependencies {
75
75
  implementation "androidx.compose.material3:material3:1.3.2"
76
76
 
77
77
  // Stream dependencies
78
- implementation("io.getstream:stream-video-android-ui-compose:1.6.3")
79
- implementation("io.getstream:stream-video-android-core:1.6.3")
80
- implementation("io.getstream:stream-android-push:1.3.1")
81
- implementation("io.getstream:stream-android-push-firebase:1.3.1")
78
+ implementation("io.getstream:stream-video-android-ui-compose:1.9.1")
79
+ implementation("io.getstream:stream-video-android-core:1.9.1")
80
+ implementation("io.getstream:stream-android-push:1.3.2")
81
+ implementation("io.getstream:stream-android-push-firebase:1.3.2")
82
82
 
83
83
  // Firebase dependencies using BOM
84
84
  implementation(platform('com.google.firebase:firebase-bom:33.13.0'))
@@ -14,6 +14,7 @@ import io.getstream.video.android.core.notifications.DefaultNotificationHandler
14
14
  import io.getstream.video.android.core.notifications.NotificationHandler
15
15
  import io.getstream.video.android.model.StreamCallId
16
16
  import io.getstream.video.android.model.streamCallId
17
+ import io.getstream.video.android.core.R
17
18
 
18
19
  // declare "incoming_calls_custom" as a constant
19
20
  const val INCOMING_CALLS_CUSTOM = "incoming_calls_custom"
@@ -206,6 +207,45 @@ class CustomNotificationHandler(
206
207
  endCall(callId)
207
208
  super.onMissedCall(callId, callDisplayName)
208
209
  }
210
+
211
+ override fun getOngoingCallNotification(
212
+ callId: StreamCallId,
213
+ callDisplayName: String?,
214
+ isOutgoingCall: Boolean,
215
+ remoteParticipantCount: Int
216
+ ): Notification? {
217
+ Log.d("CustomNotificationHandler", "getOngoingCallNotification called: callId=$callId, isOutgoing=$isOutgoingCall, participants=$remoteParticipantCount")
218
+ createOngoingCallChannel()
219
+
220
+ val launchIntent = application.packageManager.getLaunchIntentForPackage(application.packageName)
221
+ val contentIntent = if (launchIntent != null) {
222
+ launchIntent.putExtra(NotificationHandler.INTENT_EXTRA_CALL_CID, callId)
223
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
224
+ PendingIntent.getActivity(
225
+ application,
226
+ callId.cid.hashCode(),
227
+ launchIntent,
228
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
229
+ )
230
+ } else {
231
+ Log.e("CustomNotificationHandler", "Could not get launch intent for package: ${application.packageName}. Ongoing call notification will not open the app.")
232
+ null
233
+ }
234
+
235
+ return getNotification {
236
+ setContentTitle(callDisplayName ?: "Ongoing Call")
237
+ setContentText("Tap to return to the call")
238
+ setSmallIcon(R.drawable.stream_video_ic_call)
239
+ setChannelId("ongoing_calls")
240
+ setOngoing(true)
241
+ setAutoCancel(false)
242
+ setCategory(NotificationCompat.CATEGORY_CALL)
243
+ setDefaults(0)
244
+ if (contentIntent != null) {
245
+ setContentIntent(contentIntent)
246
+ }
247
+ }
248
+ }
209
249
 
210
250
  private fun customCreateIncomingCallChannel() {
211
251
  Log.d("CustomNotificationHandler", "customCreateIncomingCallChannel called")
@@ -233,6 +273,26 @@ class CustomNotificationHandler(
233
273
  },
234
274
  )
235
275
  }
276
+
277
+ private fun createOngoingCallChannel() {
278
+ Log.d("CustomNotificationHandler", "createOngoingCallChannel called")
279
+ maybeCreateChannel(
280
+ channelId = "ongoing_calls",
281
+ context = application,
282
+ configure = {
283
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
284
+ name = "Ongoing calls"
285
+ description = "Notifications for ongoing calls"
286
+ importance = NotificationManager.IMPORTANCE_LOW
287
+ this.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
288
+ this.setShowBadge(false)
289
+ setSound(null, null)
290
+ enableVibration(false)
291
+ enableLights(false)
292
+ }
293
+ },
294
+ )
295
+ }
236
296
 
237
297
  public fun clone(): CustomNotificationHandler {
238
298
  Log.d("CustomNotificationHandler", "clone called")
@@ -0,0 +1,38 @@
1
+ package ee.forgr.capacitor.streamcall
2
+
3
+ import org.json.JSONArray
4
+ import org.json.JSONObject
5
+
6
+ fun JSONObject.toMap(): Map<String, Any> {
7
+ val map = mutableMapOf<String, Any>()
8
+ val keys = this.keys()
9
+ while (keys.hasNext()) {
10
+ val key = keys.next()
11
+ var value = this.get(key)
12
+
13
+ // Handle nested JSONObjects and JSONArrays
14
+ value = when (value) {
15
+ is JSONObject -> value.toMap()
16
+ is JSONArray -> value.toList()
17
+ else -> value
18
+ }
19
+
20
+ map[key] = value
21
+ }
22
+ return map
23
+ }
24
+
25
+ // Helper for JSONArray
26
+ fun JSONArray.toList(): List<Any> {
27
+ val list = mutableListOf<Any>()
28
+ for (i in 0 until this.length()) {
29
+ var value = this.get(i)
30
+ value = when (value) {
31
+ is JSONObject -> value.toMap()
32
+ is JSONArray -> value.toList()
33
+ else -> value
34
+ }
35
+ list.add(value)
36
+ }
37
+ return list
38
+ }
@@ -10,6 +10,7 @@ import android.content.BroadcastReceiver
10
10
  import android.content.Context
11
11
  import android.content.Intent
12
12
  import android.content.IntentFilter
13
+ import android.content.SharedPreferences
13
14
  import android.content.pm.PackageManager
14
15
  import android.graphics.Color
15
16
  import android.media.RingtoneManager
@@ -83,6 +84,7 @@ import kotlinx.coroutines.Dispatchers
83
84
  import kotlinx.coroutines.launch
84
85
  import kotlinx.coroutines.tasks.await
85
86
  import androidx.core.net.toUri
87
+ import org.json.JSONObject
86
88
 
87
89
  // I am not a religious pearson, but at this point, I am not sure even god himself would understand this code
88
90
  // It's a spaghetti-like, tangled, unreadable mess and frankly, I am deeply sorry for the code crimes commited in the Android impl
@@ -121,6 +123,7 @@ public class StreamCallPlugin : Plugin() {
121
123
  private var pendingCallType: String? = null
122
124
  private var pendingCallShouldRing: Boolean? = null
123
125
  private var pendingCallTeam: String? = null
126
+ private var pendingCustomObject: JSObject? = null
124
127
  private var pendingAcceptCall: Call? = null // Store the actual call object for acceptance
125
128
 
126
129
  private enum class State {
@@ -228,6 +231,7 @@ public class StreamCallPlugin : Plugin() {
228
231
  try {
229
232
  val callInfo = call?.get()
230
233
  val callerInfo = callInfo?.getOrNull()?.call?.createdBy
234
+ val custom = callInfo?.getOrNull()?.call?.custom
231
235
 
232
236
  val payload = com.getcapacitor.JSObject().apply {
233
237
  put("cid", cid.cid)
@@ -241,6 +245,9 @@ public class StreamCallPlugin : Plugin() {
241
245
  }
242
246
  put("caller", caller)
243
247
  }
248
+ if (custom != null) {
249
+ put("custom", JSONObject(custom))
250
+ }
244
251
  }
245
252
 
246
253
  // Notify WebView/JS about incoming call so it can render its own UI
@@ -449,9 +456,10 @@ public class StreamCallPlugin : Plugin() {
449
456
  val token = call.getString("token")
450
457
  val userId = call.getString("userId")
451
458
  val name = call.getString("name")
459
+ val apiKey = call.getString("apiKey")
452
460
 
453
- if (token == null || userId == null || name == null) {
454
- call.reject("Missing required parameters: token, userId, or name")
461
+ if (token == null || userId == null || name == null || apiKey == null) {
462
+ call.reject("Missing required parameters: token, userId, name, or apiKey")
455
463
  return
456
464
  }
457
465
 
@@ -614,7 +622,7 @@ public class StreamCallPlugin : Plugin() {
614
622
  // Initialize StreamVideo client
615
623
  streamVideoClient = StreamVideoBuilder(
616
624
  context = contextToUse,
617
- apiKey = contextToUse.getString(R.string.CAPACITOR_STREAM_VIDEO_APIKEY),
625
+ apiKey = getEffectiveApiKey(contextToUse),
618
626
  geo = GEO.GlobalEdgeNetwork,
619
627
  user = savedCredentials.user,
620
628
  token = savedCredentials.tokenValue,
@@ -1355,6 +1363,7 @@ public class StreamCallPlugin : Plugin() {
1355
1363
  val callType = pendingCallType
1356
1364
  val shouldRing = pendingCallShouldRing
1357
1365
  val team = pendingCallTeam
1366
+ val custom = pendingCustomObject
1358
1367
 
1359
1368
  if (call != null && userIds != null && callType != null && shouldRing != null) {
1360
1369
  android.util.Log.d("StreamCallPlugin", "executePendingCall: Executing call with ${userIds.size} users")
@@ -1363,7 +1372,7 @@ public class StreamCallPlugin : Plugin() {
1363
1372
  clearPendingCall()
1364
1373
 
1365
1374
  // Execute the call creation logic
1366
- createAndStartCall(call, userIds, callType, shouldRing, team)
1375
+ createAndStartCall(call, userIds, callType, shouldRing, team, custom)
1367
1376
  } else {
1368
1377
  android.util.Log.w("StreamCallPlugin", "executePendingCall: Missing pending call data")
1369
1378
  call?.reject("Internal error: missing call parameters")
@@ -1378,11 +1387,14 @@ public class StreamCallPlugin : Plugin() {
1378
1387
  pendingCallShouldRing = null
1379
1388
  pendingCallTeam = null
1380
1389
  pendingAcceptCall = null
1390
+ pendingCallTeam = null
1381
1391
  permissionAttemptCount = 0 // Reset attempt count when clearing
1382
1392
  }
1383
1393
 
1394
+
1395
+
1384
1396
  @OptIn(DelicateCoroutinesApi::class, InternalStreamVideoApi::class)
1385
- private fun createAndStartCall(call: PluginCall, userIds: List<String>, callType: String, shouldRing: Boolean, team: String?) {
1397
+ private fun createAndStartCall(call: PluginCall, userIds: List<String>, callType: String, shouldRing: Boolean, team: String?, custom: JSObject?) {
1386
1398
  val selfUserId = streamVideoClient?.userId
1387
1399
  if (selfUserId == null) {
1388
1400
  call.reject("No self-user id found. Are you not logged in?")
@@ -1400,11 +1412,12 @@ public class StreamCallPlugin : Plugin() {
1400
1412
  // Note: We no longer start tracking here - we'll wait for CallSessionStartedEvent
1401
1413
  // instead, which contains the actual participant list
1402
1414
 
1415
+
1403
1416
  android.util.Log.d("StreamCallPlugin", "Creating call with members...")
1404
1417
  // Create the call with all members
1405
1418
  val createResult = streamCall?.create(
1406
1419
  memberIds = userIds + selfUserId,
1407
- custom = emptyMap(),
1420
+ custom = custom?.toMap() ?: emptyMap(),
1408
1421
  ring = shouldRing,
1409
1422
  team = team,
1410
1423
  )
@@ -1924,6 +1937,8 @@ public class StreamCallPlugin : Plugin() {
1924
1937
  return
1925
1938
  }
1926
1939
 
1940
+ val custom = call.getObject("custom")
1941
+
1927
1942
  try {
1928
1943
  if (state != State.INITIALIZED) {
1929
1944
  call.reject("StreamVideo not initialized")
@@ -1946,6 +1961,9 @@ public class StreamCallPlugin : Plugin() {
1946
1961
  android.util.Log.d("StreamCallPlugin", "- Call Type: $callType")
1947
1962
  android.util.Log.d("StreamCallPlugin", "- Users: $userIds")
1948
1963
  android.util.Log.d("StreamCallPlugin", "- Should Ring: $shouldRing")
1964
+ if (custom != null) {
1965
+ android.util.Log.d("StreamCallPlugin", "- Custom data: $custom")
1966
+ }
1949
1967
 
1950
1968
  // Check permissions before creating the call
1951
1969
  if (!checkPermissions()) {
@@ -1956,6 +1974,9 @@ public class StreamCallPlugin : Plugin() {
1956
1974
  pendingCallType = callType
1957
1975
  pendingCallShouldRing = shouldRing
1958
1976
  pendingCallTeam = team
1977
+ custom?.let {
1978
+ pendingCustomObject = it
1979
+ }
1959
1980
  // Reset attempt count for new permission flow
1960
1981
  permissionAttemptCount = 0
1961
1982
  requestPermissions()
@@ -1963,7 +1984,7 @@ public class StreamCallPlugin : Plugin() {
1963
1984
  }
1964
1985
 
1965
1986
  // Execute call creation immediately if permissions are granted
1966
- createAndStartCall(call, userIds, callType, shouldRing, team)
1987
+ createAndStartCall(call, userIds, callType, shouldRing, team, custom)
1967
1988
  } catch (e: Exception) {
1968
1989
  call.reject("Failed to make call: ${e.message}")
1969
1990
  }
@@ -2180,6 +2201,92 @@ public class StreamCallPlugin : Plugin() {
2180
2201
  }
2181
2202
  }
2182
2203
 
2204
+ @PluginMethod
2205
+ fun setDynamicStreamVideoApikey(call: PluginCall) {
2206
+ val apiKey = call.getString("apiKey")
2207
+ if (apiKey == null) {
2208
+ call.reject("Missing required parameter: apiKey")
2209
+ return
2210
+ }
2211
+
2212
+ try {
2213
+ saveDynamicApiKey(apiKey)
2214
+ android.util.Log.d("StreamCallPlugin", "Dynamic API key saved successfully")
2215
+ call.resolve(JSObject().apply {
2216
+ put("success", true)
2217
+ })
2218
+ } catch (e: Exception) {
2219
+ android.util.Log.e("StreamCallPlugin", "Error saving dynamic API key", e)
2220
+ call.reject("Failed to save API key: ${e.message}")
2221
+ }
2222
+ }
2223
+
2224
+ @PluginMethod
2225
+ fun getDynamicStreamVideoApikey(call: PluginCall) {
2226
+ try {
2227
+ val apiKey = getDynamicApiKey()
2228
+ call.resolve(JSObject().apply {
2229
+ if (apiKey != null) {
2230
+ put("apiKey", apiKey)
2231
+ put("hasDynamicKey", true)
2232
+ } else {
2233
+ put("apiKey", null)
2234
+ put("hasDynamicKey", false)
2235
+ }
2236
+ })
2237
+ } catch (e: Exception) {
2238
+ android.util.Log.e("StreamCallPlugin", "Error getting dynamic API key", e)
2239
+ call.reject("Failed to get API key: ${e.message}")
2240
+ }
2241
+ }
2242
+
2243
+ // Helper functions for managing dynamic API key in SharedPreferences
2244
+ private fun saveDynamicApiKey(apiKey: String) {
2245
+ val sharedPrefs = getApiKeyPreferences()
2246
+ sharedPrefs.edit()
2247
+ .putString(DYNAMIC_API_KEY_PREF, apiKey)
2248
+ .apply()
2249
+ }
2250
+
2251
+ // Helper functions for managing dynamic API key in SharedPreferences
2252
+ private fun clearDynamicApiKey() {
2253
+ val sharedPrefs = getApiKeyPreferences()
2254
+ sharedPrefs.edit()
2255
+ .remove(DYNAMIC_API_KEY_PREF)
2256
+ .apply()
2257
+ }
2258
+
2259
+ private fun getDynamicApiKey(): String? {
2260
+ val sharedPrefs = getApiKeyPreferences()
2261
+ return sharedPrefs.getString(DYNAMIC_API_KEY_PREF, null)
2262
+ }
2263
+
2264
+ private fun getDynamicApiKey(context: Context): String? {
2265
+ val sharedPrefs = getApiKeyPreferences(context)
2266
+ return sharedPrefs.getString(DYNAMIC_API_KEY_PREF, null)
2267
+ }
2268
+
2269
+ private fun getApiKeyPreferences(): SharedPreferences {
2270
+ return context.getSharedPreferences(API_KEY_PREFS_NAME, Context.MODE_PRIVATE)
2271
+ }
2272
+
2273
+ private fun getApiKeyPreferences(context: Context): SharedPreferences {
2274
+ return context.getSharedPreferences(API_KEY_PREFS_NAME, Context.MODE_PRIVATE)
2275
+ }
2276
+
2277
+ private fun getEffectiveApiKey(context: Context): String {
2278
+ // A) Check if the key exists in the custom preference
2279
+ val dynamicApiKey = getDynamicApiKey(context)
2280
+ return if (!dynamicApiKey.isNullOrEmpty() && dynamicApiKey.trim().isNotEmpty()) {
2281
+ android.util.Log.d("StreamCallPlugin", "Using dynamic API key")
2282
+ dynamicApiKey
2283
+ } else {
2284
+ // B) If not, use R.string.CAPACITOR_STREAM_VIDEO_APIKEY
2285
+ android.util.Log.d("StreamCallPlugin", "Using static API key from resources")
2286
+ context.getString(R.string.CAPACITOR_STREAM_VIDEO_APIKEY)
2287
+ }
2288
+ }
2289
+
2183
2290
  // Helper method to update call status and notify listeners
2184
2291
  private fun updateCallStatusAndNotify(callId: String, state: String, userId: String? = null, reason: String? = null, members: List<Map<String, Any>>? = null, caller: Map<String, Any>? = null) {
2185
2292
  android.util.Log.d("StreamCallPlugin", "updateCallStatusAndNotify called: callId=$callId, state=$state, userId=$userId, reason=$reason")
@@ -2314,5 +2421,9 @@ public class StreamCallPlugin : Plugin() {
2314
2421
  }
2315
2422
  }
2316
2423
  private var holder: StreamCallPlugin? = null
2424
+
2425
+ // Constants for SharedPreferences
2426
+ private const val API_KEY_PREFS_NAME = "stream_video_api_key_prefs"
2427
+ private const val DYNAMIC_API_KEY_PREF = "dynamic_api_key"
2317
2428
  }
2318
2429
  }
package/dist/docs.json CHANGED
@@ -431,6 +431,58 @@
431
431
  "CallEvent"
432
432
  ],
433
433
  "slug": "getcallinfo"
434
+ },
435
+ {
436
+ "name": "setDynamicStreamVideoApikey",
437
+ "signature": "(options: { apiKey: string; }) => Promise<SuccessResponse>",
438
+ "parameters": [
439
+ {
440
+ "name": "options",
441
+ "docs": "- The API key to set",
442
+ "type": "{ apiKey: string; }"
443
+ }
444
+ ],
445
+ "returns": "Promise<SuccessResponse>",
446
+ "tags": [
447
+ {
448
+ "name": "param",
449
+ "text": "options - The API key to set"
450
+ },
451
+ {
452
+ "name": "returns",
453
+ "text": "Success status"
454
+ },
455
+ {
456
+ "name": "example",
457
+ "text": "await StreamCall.setDynamicStreamVideoApikey({ apiKey: 'new-api-key' });"
458
+ }
459
+ ],
460
+ "docs": "Set a dynamic Stream Video API key that overrides the static one",
461
+ "complexTypes": [
462
+ "SuccessResponse"
463
+ ],
464
+ "slug": "setdynamicstreamvideoapikey"
465
+ },
466
+ {
467
+ "name": "getDynamicStreamVideoApikey",
468
+ "signature": "() => Promise<DynamicApiKeyResponse>",
469
+ "parameters": [],
470
+ "returns": "Promise<DynamicApiKeyResponse>",
471
+ "tags": [
472
+ {
473
+ "name": "returns",
474
+ "text": "The dynamic API key and whether it's set"
475
+ },
476
+ {
477
+ "name": "example",
478
+ "text": "const result = await StreamCall.getDynamicStreamVideoApikey();\nif (result.hasDynamicKey) {\n console.log('Dynamic API key:', result.apiKey);\n} else {\n console.log('Using static API key from resources');\n}"
479
+ }
480
+ ],
481
+ "docs": "Get the currently set dynamic Stream Video API key",
482
+ "complexTypes": [
483
+ "DynamicApiKeyResponse"
484
+ ],
485
+ "slug": "getdynamicstreamvideoapikey"
434
486
  }
435
487
  ],
436
488
  "properties": []
@@ -649,6 +701,15 @@
649
701
  "docs": "Whether to start the call with video enabled, defaults to false",
650
702
  "complexTypes": [],
651
703
  "type": "boolean | undefined"
704
+ },
705
+ {
706
+ "name": "custom",
707
+ "tags": [],
708
+ "docs": "Custom data to be passed to the call",
709
+ "complexTypes": [
710
+ "Record"
711
+ ],
712
+ "type": "Record<\n string,\n | string\n | boolean\n | number\n | null\n | Record<string, string | boolean | number | null>\n | string[]\n | boolean[]\n | number[]\n >"
652
713
  }
653
714
  ]
654
715
  },
@@ -1334,6 +1395,15 @@
1334
1395
  "CallMember"
1335
1396
  ],
1336
1397
  "type": "CallMember"
1398
+ },
1399
+ {
1400
+ "name": "custom",
1401
+ "tags": [],
1402
+ "docs": "Custom data to be passed to the call",
1403
+ "complexTypes": [
1404
+ "Record"
1405
+ ],
1406
+ "type": "Record<\n string,\n | string\n | boolean\n | number\n | null\n | Record<string, string | boolean | number | null>\n | string[]\n | boolean[]\n | number[]\n >"
1337
1407
  }
1338
1408
  ]
1339
1409
  },
@@ -1352,6 +1422,46 @@
1352
1422
  "type": "boolean"
1353
1423
  }
1354
1424
  ]
1425
+ },
1426
+ {
1427
+ "name": "DynamicApiKeyResponse",
1428
+ "slug": "dynamicapikeyresponse",
1429
+ "docs": "",
1430
+ "tags": [
1431
+ {
1432
+ "text": "DynamicApiKeyResponse",
1433
+ "name": "interface"
1434
+ },
1435
+ {
1436
+ "text": "Response from getDynamicStreamVideoApikey",
1437
+ "name": "description"
1438
+ },
1439
+ {
1440
+ "text": "{string|null} apiKey - The dynamic API key if set, null if not",
1441
+ "name": "property"
1442
+ },
1443
+ {
1444
+ "text": "{boolean} hasDynamicKey - Whether a dynamic key is currently set",
1445
+ "name": "property"
1446
+ }
1447
+ ],
1448
+ "methods": [],
1449
+ "properties": [
1450
+ {
1451
+ "name": "apiKey",
1452
+ "tags": [],
1453
+ "docs": "The dynamic API key if set, null if not",
1454
+ "complexTypes": [],
1455
+ "type": "string | null"
1456
+ },
1457
+ {
1458
+ "name": "hasDynamicKey",
1459
+ "tags": [],
1460
+ "docs": "Whether a dynamic key is currently set",
1461
+ "complexTypes": [],
1462
+ "type": "boolean"
1463
+ }
1464
+ ]
1355
1465
  }
1356
1466
  ],
1357
1467
  "enums": [
@@ -1508,6 +1618,20 @@
1508
1618
  }
1509
1619
  ]
1510
1620
  },
1621
+ {
1622
+ "name": "Record",
1623
+ "slug": "record",
1624
+ "docs": "Construct a type with a set of properties K of type T",
1625
+ "types": [
1626
+ {
1627
+ "text": "{\r\n [P in K]: T;\r\n}",
1628
+ "complexTypes": [
1629
+ "K",
1630
+ "T"
1631
+ ]
1632
+ }
1633
+ ]
1634
+ },
1511
1635
  {
1512
1636
  "name": "CallState",
1513
1637
  "slug": "callstate",