@cometchat/skills 4.0.0 → 4.2.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/README.md +42 -2
- package/bin/install.js +448 -57
- package/package.json +1 -1
- package/skills/cometchat/SKILL.md +337 -80
- package/skills/cometchat-a11y/SKILL.md +476 -0
- package/skills/cometchat-android-v5/SKILL.md +1 -1
- package/skills/cometchat-android-v5-calls/SKILL.md +413 -0
- package/skills/cometchat-android-v5-calls/references/README.md +53 -0
- package/skills/cometchat-android-v5-calls/references/add-calls-to-existing-chat.md +195 -0
- package/skills/cometchat-android-v5-calls/references/audio-controls.md +91 -0
- package/skills/cometchat-android-v5-calls/references/background-handling.md +168 -0
- package/skills/cometchat-android-v5-calls/references/call-layouts.md +123 -0
- package/skills/cometchat-android-v5-calls/references/call-logs.md +113 -0
- package/skills/cometchat-android-v5-calls/references/call-session.md +274 -0
- package/skills/cometchat-android-v5-calls/references/custom-ui.md +150 -0
- package/skills/cometchat-android-v5-calls/references/event-listeners.md +123 -0
- package/skills/cometchat-android-v5-calls/references/group-calls.md +205 -0
- package/skills/cometchat-android-v5-calls/references/idle-timeout.md +111 -0
- package/skills/cometchat-android-v5-calls/references/in-call-chat.md +134 -0
- package/skills/cometchat-android-v5-calls/references/join-session.md +120 -0
- package/skills/cometchat-android-v5-calls/references/migration-v4-to-v5.md +206 -0
- package/skills/cometchat-android-v5-calls/references/participant-management.md +110 -0
- package/skills/cometchat-android-v5-calls/references/picture-in-picture.md +106 -0
- package/skills/cometchat-android-v5-calls/references/raise-hand.md +242 -0
- package/skills/cometchat-android-v5-calls/references/recording.md +101 -0
- package/skills/cometchat-android-v5-calls/references/ringing-integration.md +152 -0
- package/skills/cometchat-android-v5-calls/references/screen-sharing.md +82 -0
- package/skills/cometchat-android-v5-calls/references/server-fcm-voip.md +215 -0
- package/skills/cometchat-android-v5-calls/references/session-settings.md +121 -0
- package/skills/cometchat-android-v5-calls/references/setup.md +137 -0
- package/skills/cometchat-android-v5-calls/references/share-invite.md +151 -0
- package/skills/cometchat-android-v5-calls/references/video-controls.md +87 -0
- package/skills/cometchat-android-v5-calls/references/voip-calling.md +526 -0
- package/skills/cometchat-android-v5-components/SKILL.md +1 -1
- package/skills/cometchat-android-v5-core/SKILL.md +1 -1
- package/skills/cometchat-android-v5-customization/SKILL.md +1 -1
- package/skills/cometchat-android-v5-extensions/SKILL.md +1 -1
- package/skills/cometchat-android-v5-features/SKILL.md +1 -1
- package/skills/cometchat-android-v5-placement/SKILL.md +1 -1
- package/skills/cometchat-android-v5-production/SKILL.md +1 -1
- package/skills/cometchat-android-v5-push/SKILL.md +1 -1
- package/skills/cometchat-android-v5-testing/SKILL.md +1 -1
- package/skills/cometchat-android-v5-theming/SKILL.md +1 -1
- package/skills/cometchat-android-v5-troubleshooting/SKILL.md +1 -1
- package/skills/cometchat-android-v6/SKILL.md +1 -1
- package/skills/cometchat-android-v6-builder-settings/SKILL.md +1 -1
- package/skills/cometchat-android-v6-calls/SKILL.md +375 -0
- package/skills/cometchat-android-v6-calls/references/add-calls-to-existing-chat.md +97 -0
- package/skills/cometchat-android-v6-calls/references/call-layouts.md +91 -0
- package/skills/cometchat-android-v6-calls/references/call-session.md +163 -0
- package/skills/cometchat-android-v6-calls/references/device-management.md +171 -0
- package/skills/cometchat-android-v6-calls/references/group-calls.md +191 -0
- package/skills/cometchat-android-v6-calls/references/idle-timeout.md +107 -0
- package/skills/cometchat-android-v6-calls/references/in-call-chat.md +136 -0
- package/skills/cometchat-android-v6-calls/references/raise-hand.md +186 -0
- package/skills/cometchat-android-v6-calls/references/ringing-integration.md +176 -0
- package/skills/cometchat-android-v6-calls/references/server-fcm-voip.md +127 -0
- package/skills/cometchat-android-v6-calls/references/share-invite.md +105 -0
- package/skills/cometchat-android-v6-compose-components/SKILL.md +2 -2
- package/skills/cometchat-android-v6-compose-customization/SKILL.md +1 -1
- package/skills/cometchat-android-v6-compose-placement/SKILL.md +1 -1
- package/skills/cometchat-android-v6-compose-theming/SKILL.md +1 -1
- package/skills/cometchat-android-v6-core/SKILL.md +1 -1
- package/skills/cometchat-android-v6-events/SKILL.md +1 -1
- package/skills/cometchat-android-v6-extensions/SKILL.md +1 -1
- package/skills/cometchat-android-v6-features/SKILL.md +1 -1
- package/skills/cometchat-android-v6-kotlin-components/SKILL.md +1 -1
- package/skills/cometchat-android-v6-kotlin-customization/SKILL.md +1 -1
- package/skills/cometchat-android-v6-kotlin-placement/SKILL.md +1 -1
- package/skills/cometchat-android-v6-kotlin-theming/SKILL.md +1 -1
- package/skills/cometchat-android-v6-migration/SKILL.md +398 -0
- package/skills/cometchat-android-v6-production/SKILL.md +1 -1
- package/skills/cometchat-android-v6-push/SKILL.md +1 -1
- package/skills/cometchat-android-v6-testing/SKILL.md +1 -1
- package/skills/cometchat-android-v6-troubleshooting/SKILL.md +1 -1
- package/skills/cometchat-angular-calls/SKILL.md +511 -0
- package/skills/cometchat-angular-calls/references/add-calls-to-existing-chat.md +95 -0
- package/skills/cometchat-angular-calls/references/call-layouts.md +106 -0
- package/skills/cometchat-angular-calls/references/call-session.md +167 -0
- package/skills/cometchat-angular-calls/references/custom-ui.md +231 -0
- package/skills/cometchat-angular-calls/references/device-management.md +135 -0
- package/skills/cometchat-angular-calls/references/group-calls.md +412 -0
- package/skills/cometchat-angular-calls/references/idle-timeout.md +145 -0
- package/skills/cometchat-angular-calls/references/in-call-chat.md +148 -0
- package/skills/cometchat-angular-calls/references/lazy-loading-pitfalls.md +205 -0
- package/skills/cometchat-angular-calls/references/migration-v4-to-v5.md +53 -0
- package/skills/cometchat-angular-calls/references/ngzone-and-async-callbacks.md +183 -0
- package/skills/cometchat-angular-calls/references/picture-in-picture.md +285 -0
- package/skills/cometchat-angular-calls/references/raise-hand.md +167 -0
- package/skills/cometchat-angular-calls/references/recording-screen-share.md +267 -0
- package/skills/cometchat-angular-calls/references/ringing-integration.md +131 -0
- package/skills/cometchat-angular-calls/references/server-web-push-vapid.md +100 -0
- package/skills/cometchat-angular-calls/references/share-invite.md +116 -0
- package/skills/cometchat-angular-components/SKILL.md +1 -1
- package/skills/cometchat-angular-core/SKILL.md +1 -1
- package/skills/cometchat-angular-customization/SKILL.md +1 -1
- package/skills/cometchat-angular-features/SKILL.md +1 -1
- package/skills/cometchat-angular-patterns/SKILL.md +1 -1
- package/skills/cometchat-angular-placement/SKILL.md +1 -1
- package/skills/cometchat-angular-production/SKILL.md +1 -1
- package/skills/cometchat-angular-push/SKILL.md +403 -0
- package/skills/cometchat-angular-testing/SKILL.md +333 -0
- package/skills/cometchat-angular-theming/SKILL.md +1 -1
- package/skills/cometchat-angular-troubleshooting/SKILL.md +1 -1
- package/skills/cometchat-astro-patterns/SKILL.md +1 -1
- package/skills/cometchat-calls/SKILL.md +358 -0
- package/skills/cometchat-calls/references/use-case-broadcast.md +194 -0
- package/skills/cometchat-calls/references/use-case-marketplace.md +158 -0
- package/skills/cometchat-calls/references/use-case-support.md +190 -0
- package/skills/cometchat-calls/references/use-case-team.md +169 -0
- package/skills/cometchat-calls/references/use-case-telehealth.md +154 -0
- package/skills/cometchat-components/SKILL.md +1 -1
- package/skills/cometchat-core/SKILL.md +1 -1
- package/skills/cometchat-customization/SKILL.md +2 -2
- package/skills/cometchat-features/SKILL.md +2 -2
- package/skills/cometchat-flutter-v5/SKILL.md +1 -1
- package/skills/cometchat-flutter-v5-calls/SKILL.md +325 -94
- package/skills/cometchat-flutter-v5-calls/references/add-calls-to-existing-chat.md +169 -0
- package/skills/cometchat-flutter-v5-calls/references/call-layouts.md +98 -0
- package/skills/cometchat-flutter-v5-calls/references/call-session.md +330 -0
- package/skills/cometchat-flutter-v5-calls/references/device-management.md +120 -0
- package/skills/cometchat-flutter-v5-calls/references/group-calls.md +187 -0
- package/skills/cometchat-flutter-v5-calls/references/idle-timeout.md +97 -0
- package/skills/cometchat-flutter-v5-calls/references/in-call-chat.md +132 -0
- package/skills/cometchat-flutter-v5-calls/references/migration-v4-to-v5.md +160 -0
- package/skills/cometchat-flutter-v5-calls/references/raise-hand.md +244 -0
- package/skills/cometchat-flutter-v5-calls/references/ringing-integration.md +193 -0
- package/skills/cometchat-flutter-v5-calls/references/server-push-bridge.md +101 -0
- package/skills/cometchat-flutter-v5-calls/references/share-invite.md +142 -0
- package/skills/cometchat-flutter-v5-conversations/SKILL.md +1 -1
- package/skills/cometchat-flutter-v5-core/SKILL.md +1 -1
- package/skills/cometchat-flutter-v5-customization/SKILL.md +1 -1
- package/skills/cometchat-flutter-v5-events/SKILL.md +1 -1
- package/skills/cometchat-flutter-v5-messages/SKILL.md +1 -1
- package/skills/cometchat-flutter-v5-production/SKILL.md +1 -1
- package/skills/cometchat-flutter-v5-push/SKILL.md +1 -1
- package/skills/cometchat-flutter-v5-testing/SKILL.md +390 -0
- package/skills/cometchat-flutter-v5-theming/SKILL.md +1 -1
- package/skills/cometchat-flutter-v5-troubleshooting/SKILL.md +1 -1
- package/skills/cometchat-flutter-v5-users-groups/SKILL.md +1 -1
- package/skills/cometchat-flutter-v6/SKILL.md +1 -1
- package/skills/cometchat-flutter-v6-calls/SKILL.md +308 -243
- package/skills/cometchat-flutter-v6-calls/references/add-calls-to-existing-chat.md +134 -0
- package/skills/cometchat-flutter-v6-calls/references/call-layouts.md +102 -0
- package/skills/cometchat-flutter-v6-calls/references/call-session.md +229 -0
- package/skills/cometchat-flutter-v6-calls/references/device-management.md +99 -0
- package/skills/cometchat-flutter-v6-calls/references/group-calls.md +190 -0
- package/skills/cometchat-flutter-v6-calls/references/idle-timeout.md +80 -0
- package/skills/cometchat-flutter-v6-calls/references/in-call-chat.md +122 -0
- package/skills/cometchat-flutter-v6-calls/references/raise-hand.md +268 -0
- package/skills/cometchat-flutter-v6-calls/references/ringing-integration.md +171 -0
- package/skills/cometchat-flutter-v6-calls/references/server-push-bridge.md +93 -0
- package/skills/cometchat-flutter-v6-calls/references/share-invite.md +102 -0
- package/skills/cometchat-flutter-v6-components/SKILL.md +1 -1
- package/skills/cometchat-flutter-v6-conversations/SKILL.md +1 -1
- package/skills/cometchat-flutter-v6-core/SKILL.md +1 -1
- package/skills/cometchat-flutter-v6-customization/SKILL.md +1 -1
- package/skills/cometchat-flutter-v6-events/SKILL.md +1 -1
- package/skills/cometchat-flutter-v6-features/SKILL.md +1 -1
- package/skills/cometchat-flutter-v6-messages/SKILL.md +1 -1
- package/skills/cometchat-flutter-v6-migration/SKILL.md +1 -1
- package/skills/cometchat-flutter-v6-placement/SKILL.md +1 -1
- package/skills/cometchat-flutter-v6-production/SKILL.md +1 -1
- package/skills/cometchat-flutter-v6-push/SKILL.md +452 -0
- package/skills/cometchat-flutter-v6-testing/SKILL.md +319 -0
- package/skills/cometchat-flutter-v6-theming/SKILL.md +1 -1
- package/skills/cometchat-flutter-v6-troubleshooting/SKILL.md +1 -1
- package/skills/cometchat-flutter-v6-users-groups/SKILL.md +1 -1
- package/skills/cometchat-i18n/SKILL.md +358 -0
- package/skills/cometchat-ios/SKILL.md +5 -5
- package/skills/cometchat-ios-calls/SKILL.md +409 -0
- package/skills/cometchat-ios-calls/references/add-calls-to-existing-chat.md +172 -0
- package/skills/cometchat-ios-calls/references/avaudiosession-routing.md +189 -0
- package/skills/cometchat-ios-calls/references/call-layouts.md +136 -0
- package/skills/cometchat-ios-calls/references/call-session.md +221 -0
- package/skills/cometchat-ios-calls/references/callkit-and-pushkit.md +353 -0
- package/skills/cometchat-ios-calls/references/custom-ui.md +322 -0
- package/skills/cometchat-ios-calls/references/device-management.md +171 -0
- package/skills/cometchat-ios-calls/references/group-calls.md +396 -0
- package/skills/cometchat-ios-calls/references/idle-timeout.md +185 -0
- package/skills/cometchat-ios-calls/references/in-call-chat.md +183 -0
- package/skills/cometchat-ios-calls/references/migration-v4-to-v5.md +138 -0
- package/skills/cometchat-ios-calls/references/picture-in-picture.md +246 -0
- package/skills/cometchat-ios-calls/references/raise-hand.md +239 -0
- package/skills/cometchat-ios-calls/references/recording-screen-share.md +339 -0
- package/skills/cometchat-ios-calls/references/ringing-integration.md +142 -0
- package/skills/cometchat-ios-calls/references/server-apns-pushkit.md +267 -0
- package/skills/cometchat-ios-calls/references/share-invite.md +152 -0
- package/skills/cometchat-ios-calls/references/swiftui-uikit-hosting.md +246 -0
- package/skills/cometchat-ios-calls/references/voip-cert-setup.md +166 -0
- package/skills/cometchat-ios-components/SKILL.md +1 -1
- package/skills/cometchat-ios-core/SKILL.md +1 -1
- package/skills/cometchat-ios-customization/SKILL.md +1 -1
- package/skills/cometchat-ios-features/SKILL.md +1 -1
- package/skills/cometchat-ios-placement/SKILL.md +1 -1
- package/skills/cometchat-ios-production/SKILL.md +1 -1
- package/skills/cometchat-ios-push/SKILL.md +1 -1
- package/skills/cometchat-ios-testing/SKILL.md +371 -0
- package/skills/cometchat-ios-theming/SKILL.md +1 -1
- package/skills/cometchat-ios-troubleshooting/SKILL.md +1 -1
- package/skills/cometchat-native-bare-patterns/SKILL.md +45 -3
- package/skills/cometchat-native-calls/SKILL.md +599 -0
- package/skills/cometchat-native-calls/references/add-calls-to-existing-chat.md +203 -0
- package/skills/cometchat-native-calls/references/call-layouts.md +124 -0
- package/skills/cometchat-native-calls/references/call-session.md +182 -0
- package/skills/cometchat-native-calls/references/custom-ui.md +250 -0
- package/skills/cometchat-native-calls/references/device-management.md +135 -0
- package/skills/cometchat-native-calls/references/expo-vs-bare.md +179 -0
- package/skills/cometchat-native-calls/references/group-calls.md +291 -0
- package/skills/cometchat-native-calls/references/idle-timeout.md +128 -0
- package/skills/cometchat-native-calls/references/in-call-chat.md +143 -0
- package/skills/cometchat-native-calls/references/migration-v4-to-v5.md +72 -0
- package/skills/cometchat-native-calls/references/picture-in-picture.md +276 -0
- package/skills/cometchat-native-calls/references/raise-hand.md +203 -0
- package/skills/cometchat-native-calls/references/recording-screen-share.md +290 -0
- package/skills/cometchat-native-calls/references/ringing-integration.md +118 -0
- package/skills/cometchat-native-calls/references/server-push-bridge.md +135 -0
- package/skills/cometchat-native-calls/references/server-push-payloads.md +193 -0
- package/skills/cometchat-native-calls/references/share-invite.md +144 -0
- package/skills/cometchat-native-calls/references/voip-push-end-to-end.md +317 -0
- package/skills/cometchat-native-components/SKILL.md +1 -1
- package/skills/cometchat-native-core/SKILL.md +1 -1
- package/skills/cometchat-native-customization/SKILL.md +1 -1
- package/skills/cometchat-native-expo-patterns/SKILL.md +18 -1
- package/skills/cometchat-native-features/SKILL.md +1 -1
- package/skills/cometchat-native-placement/SKILL.md +1 -1
- package/skills/cometchat-native-production/SKILL.md +1 -1
- package/skills/cometchat-native-push/SKILL.md +1 -1
- package/skills/cometchat-native-testing/SKILL.md +1 -1
- package/skills/cometchat-native-theming/SKILL.md +1 -1
- package/skills/cometchat-native-troubleshooting/SKILL.md +1 -1
- package/skills/cometchat-nextjs-patterns/SKILL.md +1 -1
- package/skills/cometchat-placement/SKILL.md +29 -1
- package/skills/cometchat-production/SKILL.md +1 -1
- package/skills/cometchat-react-calls/SKILL.md +539 -0
- package/skills/cometchat-react-calls/references/add-calls-to-existing-chat.md +145 -0
- package/skills/cometchat-react-calls/references/call-layouts.md +161 -0
- package/skills/cometchat-react-calls/references/call-session.md +235 -0
- package/skills/cometchat-react-calls/references/custom-ui.md +201 -0
- package/skills/cometchat-react-calls/references/device-management.md +206 -0
- package/skills/cometchat-react-calls/references/group-calls.md +262 -0
- package/skills/cometchat-react-calls/references/idle-timeout.md +175 -0
- package/skills/cometchat-react-calls/references/in-call-chat.md +212 -0
- package/skills/cometchat-react-calls/references/migration-v4-to-v5.md +172 -0
- package/skills/cometchat-react-calls/references/picture-in-picture.md +245 -0
- package/skills/cometchat-react-calls/references/raise-hand.md +238 -0
- package/skills/cometchat-react-calls/references/recording-screen-share.md +143 -0
- package/skills/cometchat-react-calls/references/ringing-integration.md +255 -0
- package/skills/cometchat-react-calls/references/server-web-push-vapid.md +241 -0
- package/skills/cometchat-react-calls/references/share-invite.md +176 -0
- package/skills/cometchat-react-calls/references/testing-calls-on-web.md +198 -0
- package/skills/cometchat-react-calls/references/voip-and-web-push.md +165 -0
- package/skills/cometchat-react-patterns/SKILL.md +56 -8
- package/skills/cometchat-react-push/SKILL.md +448 -0
- package/skills/cometchat-react-router-patterns/SKILL.md +1 -1
- package/skills/cometchat-react-testing/SKILL.md +404 -0
- package/skills/cometchat-theming/SKILL.md +3 -3
- package/skills/cometchat-troubleshooting/SKILL.md +2 -2
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# Ringing — call signaling with custom UI (web)
|
|
2
|
+
|
|
3
|
+
The Ringing flow uses the **Chat SDK's call entity** for signaling (initiate / ring / accept / reject / cancel) and the **Calls SDK** for the actual WebRTC session. This is the right pattern when you want a custom incoming-call UI on top of CometChat's signaling — not the kit's prebuilt `CometChatIncomingCall`.
|
|
4
|
+
|
|
5
|
+
**Three calling modes — pick the right one:**
|
|
6
|
+
|
|
7
|
+
| Mode | Driver | When to use |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| **Standard** | UI Kit (`CometChatCallButtons` + `CometChatIncomingCall`) | 80% case — chat-driven calls with prebuilt UI |
|
|
10
|
+
| **Ringing (this doc)** | Chat SDK call entity + Calls SDK session | Custom incoming/outgoing call UI on top of CometChat signaling |
|
|
11
|
+
| **Call Session** (see `call-session.md`) | Calls SDK `joinSession` directly | Meeting-room URLs, scheduled calls, no ringing |
|
|
12
|
+
|
|
13
|
+
**Canonical docs:** https://www.cometchat.com/docs/calls/javascript/ringing
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Caller CometChat Recipient
|
|
21
|
+
│ │ │
|
|
22
|
+
│ CometChat.initiateCall(call) │ │
|
|
23
|
+
├────────────────────────────────────>│ │
|
|
24
|
+
│ │ onIncomingCallReceived │
|
|
25
|
+
│ ├───────────────────────────────>│
|
|
26
|
+
│ │ │
|
|
27
|
+
│ │ CometChat.acceptCall(sid) │
|
|
28
|
+
│ │<───────────────────────────────│
|
|
29
|
+
│ onOutgoingCallAccepted │ │
|
|
30
|
+
│<────────────────────────────────────┤ │
|
|
31
|
+
│ │ │
|
|
32
|
+
│ CometChatCalls.joinSession(token) │ CometChatCalls.joinSession(token)
|
|
33
|
+
├────────────────────────────────────>│<───────────────────────────────│
|
|
34
|
+
│ ───── WebRTC session active ───── │
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The Chat SDK is the signaling channel; the Calls SDK is the media channel. Same sessionId binds them.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Hard rules
|
|
42
|
+
|
|
43
|
+
1. **Both SDKs must be initialized** — `CometChat.init` for signaling, `CometChatCalls.init` for media. Sequential, in that order.
|
|
44
|
+
2. **`addCallListener` must be live BEFORE you start receiving calls.** Add it on app start, after login. Otherwise incoming-call events are missed.
|
|
45
|
+
3. **`CometChatCalls.joinSession` happens AFTER `CometChat.acceptCall` resolves** — accepting tells the backend you're ready; joining the session enters WebRTC. Reversing this fires media before the chat-side state agrees.
|
|
46
|
+
4. **End the call on BOTH SDKs.** `CometChatCalls.leaveSession()` for media, `CometChat.endCall(sessionId)` for the chat-side call record. Skipping either leaves a zombie.
|
|
47
|
+
5. **Listen for `onCallEndedMessageReceived`** for the case where the OTHER party ended. Without it, your UI keeps showing "in-call" while the session is dead.
|
|
48
|
+
6. **Custom timeout (default 45s)** — `CometChat.initiateCall(call, 60)` sets ringing duration. After timeout, the backend auto-cancels and the recipient stops seeing the call.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Initiate (caller side)
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { CometChat } from "@cometchat/chat-sdk-javascript";
|
|
56
|
+
|
|
57
|
+
async function initiateCall(receiverUid: string, callType: "audio" | "video" = "video") {
|
|
58
|
+
const call = new CometChat.Call(
|
|
59
|
+
receiverUid,
|
|
60
|
+
callType === "video" ? CometChat.CALL_TYPE.VIDEO : CometChat.CALL_TYPE.AUDIO,
|
|
61
|
+
CometChat.RECEIVER_TYPE.USER,
|
|
62
|
+
);
|
|
63
|
+
// Optional: 60-second ring timeout (default 45)
|
|
64
|
+
const outgoingCall = await CometChat.initiateCall(call, 60);
|
|
65
|
+
showOutgoingCallScreen(outgoingCall);
|
|
66
|
+
return outgoingCall;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For **group calls**, change `RECEIVER_TYPE` to `CometChat.RECEIVER_TYPE.GROUP` and use the group GUID. Only group members get the ring.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Listen for events (recipient + caller)
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { useEffect } from "react";
|
|
78
|
+
|
|
79
|
+
const LISTENER_ID = "app-call-listener";
|
|
80
|
+
|
|
81
|
+
export function useCometChatCallListener() {
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
CometChat.addCallListener(
|
|
84
|
+
LISTENER_ID,
|
|
85
|
+
new CometChat.CallListener({
|
|
86
|
+
onIncomingCallReceived: (call) => {
|
|
87
|
+
// Show your custom incoming-call UI
|
|
88
|
+
showIncomingCallScreen(call);
|
|
89
|
+
},
|
|
90
|
+
onOutgoingCallAccepted: (acceptedCall) => {
|
|
91
|
+
// Other party accepted — start the session
|
|
92
|
+
hideOutgoingCallScreen();
|
|
93
|
+
startCallSession(acceptedCall.getSessionId());
|
|
94
|
+
},
|
|
95
|
+
onOutgoingCallRejected: (rejectedCall) => {
|
|
96
|
+
hideOutgoingCallScreen();
|
|
97
|
+
// Optional: toast "User declined"
|
|
98
|
+
},
|
|
99
|
+
onIncomingCallCancelled: (cancelledCall) => {
|
|
100
|
+
// Caller hung up before you accepted
|
|
101
|
+
hideIncomingCallScreen();
|
|
102
|
+
},
|
|
103
|
+
onCallEndedMessageReceived: (endedCall) => {
|
|
104
|
+
// Other party ended the active session
|
|
105
|
+
teardownCallUI();
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
return () => CometChat.removeCallListener(LISTENER_ID);
|
|
110
|
+
}, []);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Mount this hook at app root (e.g. in `App.tsx`) so listeners survive route changes — the same reasoning as for `<CometChatIncomingCall />`.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Accept (recipient side)
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
async function acceptIncomingCall(call: CometChat.Call) {
|
|
122
|
+
const accepted = await CometChat.acceptCall(call.getSessionId());
|
|
123
|
+
hideIncomingCallScreen();
|
|
124
|
+
startCallSession(accepted.getSessionId());
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Reject (recipient side)
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
async function rejectIncomingCall(call: CometChat.Call) {
|
|
134
|
+
await CometChat.rejectCall(
|
|
135
|
+
call.getSessionId(),
|
|
136
|
+
CometChat.CALL_STATUS.REJECTED,
|
|
137
|
+
);
|
|
138
|
+
hideIncomingCallScreen();
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Cancel outgoing (caller side, before recipient answers)
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
async function cancelOutgoingCall(call: CometChat.Call) {
|
|
148
|
+
await CometChat.rejectCall(
|
|
149
|
+
call.getSessionId(),
|
|
150
|
+
CometChat.CALL_STATUS.CANCELLED,
|
|
151
|
+
);
|
|
152
|
+
hideOutgoingCallScreen();
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`rejectCall` is the same API for both recipient-rejecting and caller-cancelling — the `CALL_STATUS` enum distinguishes intent.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Start the session (both parties, after accept)
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
async function startCallSession(sessionId: string) {
|
|
164
|
+
const container = document.getElementById("call-container")!;
|
|
165
|
+
const tokenResult = await CometChatCalls.generateToken(sessionId);
|
|
166
|
+
|
|
167
|
+
await CometChatCalls.joinSession(
|
|
168
|
+
tokenResult.token,
|
|
169
|
+
{ sessionType: "VIDEO", layout: "TILE" },
|
|
170
|
+
container,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// Listen for session end (you ended it OR connection lost)
|
|
174
|
+
const unsub = CometChatCalls.addEventListener("onSessionLeft", () => {
|
|
175
|
+
endCall(sessionId);
|
|
176
|
+
unsub();
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The container element must exist in the DOM **before** calling `joinSession`. In React, render a div with `id="call-container"` inside your call screen component, and call `startCallSession` from `useEffect`.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## End the call
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
async function endCall(sessionId: string) {
|
|
189
|
+
CometChatCalls.leaveSession();
|
|
190
|
+
await CometChat.endCall(sessionId);
|
|
191
|
+
teardownCallUI();
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Custom incoming-call UI
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
function IncomingCallScreen({ call, onAccept, onReject }: Props) {
|
|
201
|
+
const caller = call.getCallInitiator();
|
|
202
|
+
return (
|
|
203
|
+
<div role="alertdialog" aria-labelledby="incoming-title" className="incoming-call-overlay">
|
|
204
|
+
<div className="card">
|
|
205
|
+
<img src={caller.getAvatar()} alt="" />
|
|
206
|
+
<h3 id="incoming-title">{caller.getName()}</h3>
|
|
207
|
+
<p>Incoming {call.getType()} call</p>
|
|
208
|
+
<div className="actions">
|
|
209
|
+
<button onClick={onReject} className="reject" aria-label="Decline call">Decline</button>
|
|
210
|
+
<button onClick={onAccept} className="accept" aria-label="Accept call">Accept</button>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
<audio src="/sounds/ringtone.mp3" autoPlay loop />
|
|
214
|
+
</div>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`role="alertdialog"` is the right ARIA role — interrupting modal that requires user action. `<audio autoPlay loop>` plays ringtone; pause it on accept/reject in the parent.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Anti-patterns
|
|
224
|
+
|
|
225
|
+
1. **`addCallListener` inside the chat route component.** Listener tears down on route change → calls received while user is on home screen are silently dropped.
|
|
226
|
+
2. **Calling `joinSession` before `acceptCall` resolves.** Race — recipient enters WebRTC before backend marks the call as "ongoing." Caller's `onOutgoingCallAccepted` may never fire.
|
|
227
|
+
3. **Forgetting to `removeCallListener` on unmount.** Multiple listeners → events fire N times. Causes "double accept" toasts.
|
|
228
|
+
4. **No `onCallEndedMessageReceived` handler.** The other party ends the call → your UI stays "in-call" → user has to refresh.
|
|
229
|
+
5. **Using `endCall` on the chat SDK without `leaveSession` on the calls SDK.** Camera light stays on; mic stays hot. The flex-shrink trap of calls.
|
|
230
|
+
6. **Mounting `CometChatIncomingCall` (kit) AND custom listener AT THE SAME TIME.** Both fire — you get duplicate incoming-call UIs. Pick one mode per app.
|
|
231
|
+
7. **`CometChat.initiateCall` without timeout when phone-ringing UX is the goal.** Default 45s is fine; if your UX rings longer, override explicitly so the recipient sees consistent UI.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Verification checklist
|
|
236
|
+
|
|
237
|
+
- [ ] Both SDKs initialized in order (`CometChat.init` → `CometChatCalls.init`)
|
|
238
|
+
- [ ] `addCallListener` registered at app root (survives route changes)
|
|
239
|
+
- [ ] `removeCallListener` on unmount
|
|
240
|
+
- [ ] `acceptCall` precedes `joinSession`
|
|
241
|
+
- [ ] `endCall` (chat) AND `leaveSession` (calls) both fire on hangup
|
|
242
|
+
- [ ] `onCallEndedMessageReceived` tears down UI when other party ends
|
|
243
|
+
- [ ] `onIncomingCallCancelled` tears down UI when caller cancels mid-ring
|
|
244
|
+
- [ ] Custom UI: `role="alertdialog"`, ringtone audio, accept/decline buttons with aria-labels
|
|
245
|
+
- [ ] Smoke: 2 tabs, caller initiates → recipient sees custom incoming UI → accept → both join session → either ends → both UIs dismiss
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Pointers
|
|
250
|
+
|
|
251
|
+
- `cometchat-react-calls/SKILL.md` — architecture + seven hard rules
|
|
252
|
+
- `cometchat-react-calls/references/call-session.md` — Mode 3 (joinSession with no ringing)
|
|
253
|
+
- `cometchat-react-calls/references/in-call-chat.md` — chat panel during a call
|
|
254
|
+
- `cometchat-react-calls/references/recording.md` — recording mid-call
|
|
255
|
+
- Canonical docs: https://www.cometchat.com/docs/calls/javascript/ringing
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# Server-side Web Push VAPID for browser ringing
|
|
2
|
+
|
|
3
|
+
Browsers don't have a true VoIP-push equivalent (the way iOS PushKit + Android FCM data-messages do). But **Web Push** with VAPID is the closest analog: when a CometChat call event fires, your server sends a push, the browser's Service Worker receives it (even with the tab closed), and posts a notification + plays a ringtone.
|
|
4
|
+
|
|
5
|
+
**Caveat — set expectations:** Web Push is best-effort. It works reliably when:
|
|
6
|
+
- The user has the tab open OR a recent installed PWA
|
|
7
|
+
- The browser is running (Chrome/Edge/Firefox running but minimized → ✓)
|
|
8
|
+
- Battery saver isn't aggressive
|
|
9
|
+
|
|
10
|
+
It does NOT work when:
|
|
11
|
+
- The user closed all browser windows
|
|
12
|
+
- iOS Safari except in PWA-only mode (16.4+)
|
|
13
|
+
- The user denied notification permission
|
|
14
|
+
|
|
15
|
+
For production-grade ringing on the web, recommend customers offer the iOS/Android app for unreliable-recipient scenarios.
|
|
16
|
+
|
|
17
|
+
**Canonical docs:** https://datatracker.ietf.org/doc/html/rfc8030 (Web Push), https://datatracker.ietf.org/doc/html/rfc8292 (VAPID), https://developer.mozilla.org/en-US/docs/Web/API/Push_API
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Hard rules
|
|
22
|
+
|
|
23
|
+
1. **VAPID keys are mandatory.** Browsers will reject pushes without them since 2018.
|
|
24
|
+
2. **Use the `web-push` library or its equivalents** — implementing VAPID JWT + AES128GCM payload encryption from scratch is error-prone.
|
|
25
|
+
3. **HTTPS required.** Web Push doesn't work on HTTP except localhost.
|
|
26
|
+
4. **Subscriptions expire.** Browsers rotate `endpoint` URLs every ~3 months on Chrome desktop. Implement re-subscription on push failure (410 Gone).
|
|
27
|
+
5. **TTL ≤ 60s for VoIP-style ringing.** A push that arrives 5 minutes late is worse than no push.
|
|
28
|
+
6. **Topic must be set** so a fresh ringing push displaces an older queued one (avoid stacking).
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Generate VAPID keys (one-time)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx web-push generate-vapid-keys
|
|
36
|
+
# Output:
|
|
37
|
+
# Public Key: BNa... (paste into your client code)
|
|
38
|
+
# Private Key: 3kL... (server-side env var only)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or via Node API:
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
import webpush from "web-push";
|
|
45
|
+
const keys = webpush.generateVAPIDKeys();
|
|
46
|
+
console.log("VAPID_PUBLIC_KEY=", keys.publicKey);
|
|
47
|
+
console.log("VAPID_PRIVATE_KEY=", keys.privateKey);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Node.js template
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
// npm install web-push
|
|
56
|
+
import webpush from "web-push";
|
|
57
|
+
|
|
58
|
+
webpush.setVapidDetails(
|
|
59
|
+
`mailto:${process.env.VAPID_CONTACT_EMAIL}`,
|
|
60
|
+
process.env.VAPID_PUBLIC_KEY,
|
|
61
|
+
process.env.VAPID_PRIVATE_KEY,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
export async function sendWebVoipPush(opts) {
|
|
65
|
+
const { recipientUid, sessionId, callerName, callerUid, callType } = opts;
|
|
66
|
+
|
|
67
|
+
// A user may have multiple subscriptions (laptop + work computer); fan out.
|
|
68
|
+
const subscriptions = await db.getWebPushSubscriptions(recipientUid);
|
|
69
|
+
|
|
70
|
+
const payload = JSON.stringify({
|
|
71
|
+
type: "incoming_call",
|
|
72
|
+
sessionId,
|
|
73
|
+
callerName,
|
|
74
|
+
callerUid,
|
|
75
|
+
callType,
|
|
76
|
+
timestamp: Date.now(),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await Promise.allSettled(
|
|
80
|
+
subscriptions.map(async (sub) => {
|
|
81
|
+
try {
|
|
82
|
+
await webpush.sendNotification(sub, payload, {
|
|
83
|
+
TTL: 30, // Hard Rule 5
|
|
84
|
+
urgency: "high", // urgency header
|
|
85
|
+
topic: `call-${sessionId}`.slice(0, 32), // Hard Rule 6 — replaces older with same topic
|
|
86
|
+
});
|
|
87
|
+
} catch (err) {
|
|
88
|
+
if (err.statusCode === 410 || err.statusCode === 404) {
|
|
89
|
+
// Endpoint dead — purge from DB
|
|
90
|
+
await db.removeWebPushSubscription(recipientUid, sub.endpoint);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
console.error("Web push failed", err);
|
|
94
|
+
}
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Python template (pywebpush)
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
# pip install pywebpush
|
|
106
|
+
from pywebpush import webpush, WebPushException
|
|
107
|
+
import json, os
|
|
108
|
+
|
|
109
|
+
VAPID_CLAIMS = {"sub": f"mailto:{os.environ['VAPID_CONTACT_EMAIL']}"}
|
|
110
|
+
VAPID_PRIVATE_KEY = os.environ["VAPID_PRIVATE_KEY"]
|
|
111
|
+
|
|
112
|
+
def send_web_voip_push(subscription_info: dict, payload: dict):
|
|
113
|
+
try:
|
|
114
|
+
webpush(
|
|
115
|
+
subscription_info=subscription_info,
|
|
116
|
+
data=json.dumps(payload),
|
|
117
|
+
vapid_private_key=VAPID_PRIVATE_KEY,
|
|
118
|
+
vapid_claims=VAPID_CLAIMS,
|
|
119
|
+
ttl=30,
|
|
120
|
+
headers={
|
|
121
|
+
"Urgency": "high",
|
|
122
|
+
"Topic": f"call-{payload['sessionId']}"[:32],
|
|
123
|
+
},
|
|
124
|
+
)
|
|
125
|
+
except WebPushException as ex:
|
|
126
|
+
if ex.response and ex.response.status_code in (404, 410):
|
|
127
|
+
return "expired"
|
|
128
|
+
raise
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Receiver: Service Worker
|
|
134
|
+
|
|
135
|
+
`/public/sw.js`:
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
self.addEventListener("push", (event) => {
|
|
139
|
+
if (!event.data) return;
|
|
140
|
+
const data = event.data.json();
|
|
141
|
+
if (data.type !== "incoming_call") return;
|
|
142
|
+
|
|
143
|
+
event.waitUntil((async () => {
|
|
144
|
+
// 1. Show notification (must happen synchronously in `push` event,
|
|
145
|
+
// otherwise browser shows "[App] is sending notifications in the background"
|
|
146
|
+
// indicator and may unsubscribe you).
|
|
147
|
+
await self.registration.showNotification(`Incoming call from ${data.callerName}`, {
|
|
148
|
+
body: data.callType === "video" ? "Video call" : "Audio call",
|
|
149
|
+
tag: `call-${data.sessionId}`,
|
|
150
|
+
icon: "/icons/incoming-call-192.png",
|
|
151
|
+
badge: "/icons/badge-72.png",
|
|
152
|
+
requireInteraction: true, // stays until user acts
|
|
153
|
+
silent: false, // play default ringtone (some OSes ignore this for browser pushes)
|
|
154
|
+
vibrate: [500, 200, 500], // mobile haptic
|
|
155
|
+
actions: [
|
|
156
|
+
{ action: "accept", title: "Accept" },
|
|
157
|
+
{ action: "reject", title: "Decline" },
|
|
158
|
+
],
|
|
159
|
+
data: {
|
|
160
|
+
sessionId: data.sessionId,
|
|
161
|
+
callerName: data.callerName,
|
|
162
|
+
callerUid: data.callerUid,
|
|
163
|
+
callType: data.callType,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// 2. Wake any open clients so the in-app ringer can fire too
|
|
168
|
+
const clients = await self.clients.matchAll({ type: "window", includeUncontrolled: true });
|
|
169
|
+
clients.forEach((client) => client.postMessage({ type: "incoming_call", ...data }));
|
|
170
|
+
})());
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
self.addEventListener("notificationclick", (event) => {
|
|
174
|
+
const { sessionId } = event.notification.data;
|
|
175
|
+
event.notification.close();
|
|
176
|
+
const url = `/call/${sessionId}?action=${event.action || "open"}`;
|
|
177
|
+
|
|
178
|
+
event.waitUntil((async () => {
|
|
179
|
+
const clients = await self.clients.matchAll({ type: "window" });
|
|
180
|
+
const existing = clients.find((c) => c.url.includes(`/call/${sessionId}`));
|
|
181
|
+
if (existing) {
|
|
182
|
+
return existing.focus();
|
|
183
|
+
}
|
|
184
|
+
return self.clients.openWindow(url);
|
|
185
|
+
})());
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Register in `main.tsx`:
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
if (!("serviceWorker" in navigator)) return;
|
|
194
|
+
navigator.serviceWorker.register("/sw.js")
|
|
195
|
+
.then(async (reg) => {
|
|
196
|
+
const sub = await reg.pushManager.subscribe({
|
|
197
|
+
userVisibleOnly: true,
|
|
198
|
+
applicationServerKey: urlBase64ToUint8Array(import.meta.env.VITE_VAPID_PUBLIC_KEY),
|
|
199
|
+
});
|
|
200
|
+
await api.registerWebPushSubscription(currentUserUid, sub.toJSON());
|
|
201
|
+
})
|
|
202
|
+
.catch((err) => console.warn("SW register / push subscribe failed:", err));
|
|
203
|
+
}, []);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Anti-patterns
|
|
209
|
+
|
|
210
|
+
1. **Embedding VAPID private key in client code.** Push spam by anyone who views source. Server-only.
|
|
211
|
+
2. **Skipping `requireInteraction: true`.** Notification disappears in 5s on Chrome desktop — call seems to "ring once" then vanish.
|
|
212
|
+
3. **No `tag` on the notification.** Multiple incoming-call notifications stack.
|
|
213
|
+
4. **TTL: 0.** Notification dies the instant the device is offline. Use 30s minimum.
|
|
214
|
+
5. **`silent: true`.** Browser doesn't ring, only shows the visual notification.
|
|
215
|
+
6. **Forgetting to handle `notificationclick`.** User taps notification → nothing happens.
|
|
216
|
+
7. **Implementing Web Push for iOS Safari without checking PWA-only mode.** iOS Safari requires the user to "Add to Home Screen" first — Web Push doesn't work in regular tabs.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Verification checklist
|
|
221
|
+
|
|
222
|
+
- [ ] VAPID keys generated; private key in env var
|
|
223
|
+
- [ ] HTTPS (or localhost for dev)
|
|
224
|
+
- [ ] Subscription stored server-side keyed to user UID
|
|
225
|
+
- [ ] `topic` set so newer push replaces older
|
|
226
|
+
- [ ] `requireInteraction: true` + `tag` set on notification
|
|
227
|
+
- [ ] 410/404 responses purge dead subscriptions
|
|
228
|
+
- [ ] `notificationclick` opens a call route
|
|
229
|
+
- [ ] Smoke: 2 browsers, recipient minimizes window, caller dials, recipient sees notification + ringtone
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Pointers
|
|
234
|
+
|
|
235
|
+
- `cometchat-react-calls/SKILL.md` — calls architecture
|
|
236
|
+
- `cometchat-react-push/SKILL.md` — chat web push (sister, different payload)
|
|
237
|
+
- `cometchat-ios-calls/references/server-apns-pushkit.md` — iOS sibling
|
|
238
|
+
- `cometchat-android-v5-calls/references/server-fcm-voip.md` — Android sibling
|
|
239
|
+
- Web Push spec (RFC 8030): https://datatracker.ietf.org/doc/html/rfc8030
|
|
240
|
+
- VAPID (RFC 8292): https://datatracker.ietf.org/doc/html/rfc8292
|
|
241
|
+
- web-push npm: https://www.npmjs.com/package/web-push
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Share invite on web (React)
|
|
2
|
+
|
|
3
|
+
Let participants share the call link with others. The kit ships a share-invite button (hidden by default); your app intercepts the click and runs the platform's native share UI.
|
|
4
|
+
|
|
5
|
+
**Canonical docs:** https://www.cometchat.com/docs/calls/javascript/share-invite
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Hard rule: deep-link routing must work BEFORE you ship share-invite
|
|
10
|
+
|
|
11
|
+
A share button that copies a URL nobody can open is worse than no share button. Verify your app's `/call/:sessionId` route handles all four states:
|
|
12
|
+
|
|
13
|
+
1. **User logged in, in-app** → join call directly
|
|
14
|
+
2. **User logged in, fresh tab** → restore session, join call
|
|
15
|
+
3. **User logged out** → login, then redirect to call
|
|
16
|
+
4. **User doesn't have account** → signup flow with `?invite=...` query param
|
|
17
|
+
|
|
18
|
+
Build the deep-link route first, then turn on the share button.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Show the kit's share button
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
const callSettings = {
|
|
26
|
+
hideShareInviteButton: false,
|
|
27
|
+
// ... rest
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Handle the click
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const handler = () => shareCallInvite(sessionId);
|
|
38
|
+
CometChatCalls.addEventListener("onShareInviteButtonClicked", handler);
|
|
39
|
+
return () => {
|
|
40
|
+
CometChatCalls.removeEventListener("onShareInviteButtonClicked", handler);
|
|
41
|
+
};
|
|
42
|
+
}, [sessionId]);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Native Web Share API + clipboard fallback
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
async function shareCallInvite(sessionId: string) {
|
|
51
|
+
const url = `https://yourapp.com/call/${sessionId}`;
|
|
52
|
+
const shareData = {
|
|
53
|
+
title: "Join my call",
|
|
54
|
+
text: "I'm on a call — tap to join.",
|
|
55
|
+
url,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
if (navigator.share && navigator.canShare?.(shareData)) {
|
|
59
|
+
try {
|
|
60
|
+
await navigator.share(shareData);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
// User cancelled — that's fine, no toast needed
|
|
63
|
+
if ((err as Error).name !== "AbortError") {
|
|
64
|
+
console.warn("Share failed", err);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Fallback: clipboard
|
|
71
|
+
try {
|
|
72
|
+
await navigator.clipboard.writeText(url);
|
|
73
|
+
showToast("Link copied to clipboard");
|
|
74
|
+
} catch {
|
|
75
|
+
// Clipboard API requires user gesture + secure context (https) — must always have a manual fallback
|
|
76
|
+
promptManualCopy(url);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`navigator.share` is iOS Safari + Android Chrome + recent desktop Chrome. `navigator.clipboard.writeText` is everywhere modern but **only works in secure contexts (https)** — localhost is fine; non-https staging hosts will throw.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Custom share dialog (when you want more control)
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
function ShareDialog({ sessionId, onClose }: Props) {
|
|
89
|
+
const url = `https://yourapp.com/call/${sessionId}`;
|
|
90
|
+
return (
|
|
91
|
+
<dialog open className="share-dialog" role="dialog" aria-label="Share call invite">
|
|
92
|
+
<button onClick={() => navigator.clipboard.writeText(url)}>Copy link</button>
|
|
93
|
+
<a href={`mailto:?subject=Join%20my%20call&body=${encodeURIComponent(url)}`}>Email</a>
|
|
94
|
+
<a href={`sms:?body=${encodeURIComponent(url)}`}>SMS</a>
|
|
95
|
+
<button onClick={onClose}>Close</button>
|
|
96
|
+
</dialog>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`mailto:` opens default email client; `sms:` opens default SMS app on mobile (no-op on most desktops). Both are universal — no provider lock-in.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## QR code for in-person sharing
|
|
106
|
+
|
|
107
|
+
For "share to the person sitting next to you" UX, a QR is faster than a link:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import QRCode from "qrcode.react";
|
|
111
|
+
|
|
112
|
+
function CallQR({ sessionId }: { sessionId: string }) {
|
|
113
|
+
const url = `https://yourapp.com/call/${sessionId}`;
|
|
114
|
+
return (
|
|
115
|
+
<div>
|
|
116
|
+
<QRCode value={url} size={192} />
|
|
117
|
+
<p>{url}</p>
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Anti-patterns
|
|
126
|
+
|
|
127
|
+
1. **Sharing the raw `sessionId` instead of a deep link.** Recipients can't open it.
|
|
128
|
+
2. **Wiring share before the deep-link route works.** Recipients click → 404.
|
|
129
|
+
3. **`navigator.clipboard.writeText` without a manual-copy fallback.** Fails in non-https contexts (e.g., embedded iframes, dev tunnels).
|
|
130
|
+
4. **Showing "Link copied!" toast even when share was cancelled.** `AbortError` is the user dismissing the share sheet — silent.
|
|
131
|
+
5. **Forgetting `removeEventListener` on unmount.** Listener accumulates → multiple share sheets per click.
|
|
132
|
+
6. **Hard-coding `https://yourapp.com`.** Use `window.location.origin` or `import.meta.env.VITE_APP_URL`.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Server-side: deep links + auth bridge
|
|
137
|
+
|
|
138
|
+
If invitee isn't logged in, `https://yourapp.com/call/SESSION_ID` should redirect them through login THEN to the call. Pattern:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
// pages/call/[sessionId].tsx (Next.js example)
|
|
142
|
+
export async function getServerSideProps({ params, req }) {
|
|
143
|
+
const session = await getSession(req);
|
|
144
|
+
if (!session) {
|
|
145
|
+
return {
|
|
146
|
+
redirect: {
|
|
147
|
+
destination: `/login?next=/call/${params.sessionId}`,
|
|
148
|
+
permanent: false,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return { props: { sessionId: params.sessionId } };
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
For invitees without an account, send them to `/signup?invite=SESSION_ID` and store the pending invite in `localStorage` until signup completes.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Verification checklist
|
|
161
|
+
|
|
162
|
+
- [ ] Deep-link route `/call/:sessionId` works in 4 states (logged in / logged out / no account / fresh tab)
|
|
163
|
+
- [ ] `hideShareInviteButton: false` in CallSettings
|
|
164
|
+
- [ ] `onShareInviteButtonClicked` listener cleaned up on unmount
|
|
165
|
+
- [ ] `navigator.share` used when available; clipboard fallback; manual-copy fallback for non-https
|
|
166
|
+
- [ ] AbortError silenced
|
|
167
|
+
- [ ] App URL not hard-coded — read from env
|
|
168
|
+
- [ ] Smoke: copy link → paste in incognito → can join call
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Pointers
|
|
173
|
+
|
|
174
|
+
- `cometchat-react-calls/SKILL.md` — call architecture
|
|
175
|
+
- `cometchat-react-calls/references/in-call-chat.md` — sister cross-cutting concern
|
|
176
|
+
- Canonical docs: https://www.cometchat.com/docs/calls/javascript/share-invite
|