@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,412 @@
|
|
|
1
|
+
# Group calls (meetings) on Angular
|
|
2
|
+
|
|
3
|
+
Angular wraps the same JS Calls SDK as React/Vue/Svelte; the SDK semantics are identical. The wiring differences are Angular-specific: NgZone for active-speaker updates, `@Input` / `@Output` patterns for participant components, OnPush + `markForCheck` for roster changes.
|
|
4
|
+
|
|
5
|
+
For SDK-level semantics — capacity limits, bandwidth scaling, moderator actions — see `cometchat-native-calls/references/group-calls.md`. This reference covers the Angular-specific patterns.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Signaling architecture — meeting-message broadcast, NOT call ringing
|
|
10
|
+
|
|
11
|
+
**Critical to read first.** Group calls do NOT use the Ringing channel (`CometChat.initiateCall` → `onIncomingCallReceived`). That channel is **1:1 user calls only**. The kit broadcasts a **custom message of type `"meeting"`** to the group; receivers see a "Join meeting" card in their `<cometchat-message-list />` (kit Angular components) or need an explicit `MessageListener.onCustomMessageReceived` (custom UI — the common Angular path since many Angular apps build custom call surfaces).
|
|
12
|
+
|
|
13
|
+
Confirmed 2026-05-15 by direct test: Pixel 3 (kit-based RN, group call initiated) → Angular custom-UI app at `localhost:4200` (uid-5 in same group) → Angular received NOTHING because it only registered `CometChat.addCallListener`. Adding `CometChat.addMessageListener` filtering for `category === CATEGORY_CUSTOM && type === 'meeting'` fixed it.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Caller (uid-A, member of group-X) CometChat Receivers (members of group-X)
|
|
17
|
+
│ │ │
|
|
18
|
+
│ kit: <cometchat-call-buttons │ │
|
|
19
|
+
│ [group]="g"> │ │
|
|
20
|
+
│ — OR — custom Angular code: │ │
|
|
21
|
+
│ CometChat.sendCustomMessage( │ │
|
|
22
|
+
│ new CometChat.CustomMessage( │ │
|
|
23
|
+
│ guid, GROUP, "meeting", │ │
|
|
24
|
+
│ { callType, sessionId })) │ │
|
|
25
|
+
├─────────────────────────────────────────>│ │
|
|
26
|
+
│ │ onCustomMessageReceived │
|
|
27
|
+
│ ├─────────────────────────────>│ (each receiver's
|
|
28
|
+
│ │ │ MessageListener,
|
|
29
|
+
│ │ │ wrapped in NgZone.run)
|
|
30
|
+
│ ───── WebRTC session active (sessionId = group GUID) ───── │
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
| Channel | 1:1 user calls | Group calls |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| Signaling API (caller) | `CometChat.initiateCall(call)` | `CometChat.sendCustomMessage(meetingMessage)` with type="meeting" |
|
|
36
|
+
| Receiver event | `CallListener.onIncomingCallReceived` | `MessageListener.onCustomMessageReceived` (category=CATEGORY_CUSTOM + type="meeting") |
|
|
37
|
+
| Session ID | server-generated unique per call | the group's GUID (persistent) |
|
|
38
|
+
| Ring/decline semantics | yes — `CometChat.acceptCall` / `rejectCall` | no — receivers join or ignore |
|
|
39
|
+
| Auto-cancel timeout | yes (45s default) | no — meeting persists in chat history |
|
|
40
|
+
|
|
41
|
+
### Receiver — custom Angular UI: `CallStateService` extension
|
|
42
|
+
|
|
43
|
+
The canonical pattern in `cometchat-angular-calls/SKILL.md` puts call state in a `CallStateService`. Extend it with a `MessageListener` for group meetings:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
// services/call-state.service.ts (extension)
|
|
47
|
+
import { Injectable, NgZone, inject, signal } from '@angular/core';
|
|
48
|
+
import { CometChat } from '@cometchat/chat-sdk-javascript';
|
|
49
|
+
|
|
50
|
+
const GROUP_MEETING_LISTENER_ID = 'app-root-group-meeting-listener';
|
|
51
|
+
|
|
52
|
+
@Injectable({ providedIn: 'root' })
|
|
53
|
+
export class CallStateService {
|
|
54
|
+
private readonly zone = inject(NgZone);
|
|
55
|
+
|
|
56
|
+
// existing signals: phase, statusMessage, errorMessage, incoming, ...
|
|
57
|
+
readonly incomingMeeting = signal<{
|
|
58
|
+
sessionId: string;
|
|
59
|
+
callType: 'audio' | 'video';
|
|
60
|
+
fromUid: string;
|
|
61
|
+
groupGuid: string;
|
|
62
|
+
} | null>(null);
|
|
63
|
+
|
|
64
|
+
start(): void {
|
|
65
|
+
// existing CometChat.addCallListener (1:1 user calls)
|
|
66
|
+
// ...
|
|
67
|
+
|
|
68
|
+
// NEW — addMessageListener for group meetings:
|
|
69
|
+
CometChat.addMessageListener(
|
|
70
|
+
GROUP_MEETING_LISTENER_ID,
|
|
71
|
+
new CometChat.MessageListener({
|
|
72
|
+
onCustomMessageReceived: (msg: any) => {
|
|
73
|
+
if (msg.getCategory?.() !== CometChat.CATEGORY_CUSTOM) return;
|
|
74
|
+
if (msg.getType?.() !== 'meeting') return;
|
|
75
|
+
const customData = msg.getCustomData?.() ?? {};
|
|
76
|
+
this.zone.run(() => {
|
|
77
|
+
this.incomingMeeting.set({
|
|
78
|
+
sessionId: customData.sessionId ?? msg.getReceiverId(),
|
|
79
|
+
callType: customData.callType ?? 'video',
|
|
80
|
+
fromUid: msg.getSender().getUid(),
|
|
81
|
+
groupGuid: msg.getReceiverId(),
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Then in your root template (next to where the 1:1 incoming-call overlay lives):
|
|
92
|
+
|
|
93
|
+
```html
|
|
94
|
+
@if (state.incomingMeeting(); as meeting) {
|
|
95
|
+
<app-incoming-meeting-overlay
|
|
96
|
+
[meeting]="meeting"
|
|
97
|
+
(joined)="state.acceptMeeting(meeting.sessionId, meeting.callType)"
|
|
98
|
+
(dismissed)="state.incomingMeeting.set(null)"
|
|
99
|
+
/>
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Why this matters specifically for Angular
|
|
104
|
+
|
|
105
|
+
Angular apps disproportionately build custom call UI (raw SDK + their own component templates) rather than dropping in `<cometchat-call-buttons>` / `<cometchat-incoming-call>` selectors. The Angular UI Kit is less prevalent than React's UI Kit. So **most Angular customers hit this gap** if they only follow the 1:1 ringing-integration guidance.
|
|
106
|
+
|
|
107
|
+
The two listeners (`addCallListener` for user channel + `addMessageListener` for meeting channel) MUST coexist in any Angular app that supports both 1:1 and group calling. Wire both in `CallStateService.start()` and tear both down in `ngOnDestroy` / hangup.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Initiating a group call
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
// services/group-call.service.ts
|
|
115
|
+
import { Injectable } from "@angular/core";
|
|
116
|
+
import { CometChat } from "@cometchat/chat-sdk-javascript";
|
|
117
|
+
|
|
118
|
+
@Injectable({ providedIn: "root" })
|
|
119
|
+
export class GroupCallService {
|
|
120
|
+
async initiate(guid: string, callType: "voice" | "video"): Promise<unknown> {
|
|
121
|
+
const group = await CometChat.getGroup(guid);
|
|
122
|
+
const cometchatType = callType === "video"
|
|
123
|
+
? CometChat.CALL_TYPE.VIDEO
|
|
124
|
+
: CometChat.CALL_TYPE.AUDIO;
|
|
125
|
+
const call = new CometChat.Call(group.getGuid(), cometchatType, CometChat.RECEIVER_TYPE.GROUP);
|
|
126
|
+
return CometChat.initiateCall(call);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The receiver is the GROUP (`RECEIVER_TYPE.GROUP`) — anyone in the group can `acceptCall` until the call ends.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Roster + active-speaker — NgZone-aware
|
|
136
|
+
|
|
137
|
+
The SDK's `OngoingCallListener` callbacks fire outside Angular's zone. Wrap with `NgZone.run` (general rule from `references/ngzone-and-async-callbacks.md`):
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
@Component({
|
|
141
|
+
selector: "app-group-call",
|
|
142
|
+
template: `
|
|
143
|
+
<div class="spotlight">
|
|
144
|
+
<app-participant-tile *ngIf="activeSpeaker" [participant]="activeSpeaker" [size]="'large'"></app-participant-tile>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="strip">
|
|
147
|
+
<app-participant-tile
|
|
148
|
+
*ngFor="let p of others; trackBy: trackByUid"
|
|
149
|
+
[participant]="p"
|
|
150
|
+
[size]="'small'">
|
|
151
|
+
</app-participant-tile>
|
|
152
|
+
</div>
|
|
153
|
+
`,
|
|
154
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
155
|
+
})
|
|
156
|
+
export class GroupCallComponent implements OnInit, OnDestroy {
|
|
157
|
+
participants: Participant[] = [];
|
|
158
|
+
activeSpeaker: Participant | null = null;
|
|
159
|
+
others: Participant[] = [];
|
|
160
|
+
|
|
161
|
+
constructor(private zone: NgZone, private cd: ChangeDetectorRef) {}
|
|
162
|
+
|
|
163
|
+
ngOnInit() {
|
|
164
|
+
const listener = new CometChatCalls.OngoingCallListener({
|
|
165
|
+
onUserListUpdated: (users: Participant[]) => {
|
|
166
|
+
this.zone.run(() => {
|
|
167
|
+
this.participants = users;
|
|
168
|
+
this.recomputeLayout();
|
|
169
|
+
this.cd.markForCheck(); // OnPush — must mark
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
onActiveSpeakerUpdated: (uid: string) => {
|
|
173
|
+
this.zone.run(() => {
|
|
174
|
+
// Debounce — see "Debouncing active-speaker" below
|
|
175
|
+
this.scheduleSpeakerSwap(uid);
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
// ... build CallSettings, startSession ...
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
trackByUid(_: number, p: Participant) {
|
|
183
|
+
return p.uid;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private recomputeLayout() {
|
|
187
|
+
const localUid = this.getLocalUid();
|
|
188
|
+
const remote = this.participants.filter(p => p.uid !== localUid);
|
|
189
|
+
this.activeSpeaker = remote.find(p => p.uid === this.activeSpeakerUid) ?? remote[0] ?? null;
|
|
190
|
+
this.others = remote.filter(p => p.uid !== this.activeSpeaker?.uid);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private speakerSwapTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
194
|
+
private activeSpeakerUid: string | null = null;
|
|
195
|
+
private scheduleSpeakerSwap(uid: string) {
|
|
196
|
+
if (this.speakerSwapTimeout) clearTimeout(this.speakerSwapTimeout);
|
|
197
|
+
this.speakerSwapTimeout = setTimeout(() => {
|
|
198
|
+
this.zone.run(() => {
|
|
199
|
+
this.activeSpeakerUid = uid;
|
|
200
|
+
this.recomputeLayout();
|
|
201
|
+
this.cd.markForCheck();
|
|
202
|
+
});
|
|
203
|
+
}, 500);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`trackByUid` is critical — without it, every roster update re-creates every tile, which destroys the WebRTC video track each time. The roster jitters and call quality degrades.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Debouncing active-speaker
|
|
213
|
+
|
|
214
|
+
Same rule as RN — `onActiveSpeakerUpdated` can fire many times per second in a noisy call. Debounce to 500ms minimum:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
private speakerSwapTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
218
|
+
|
|
219
|
+
scheduleSpeakerSwap(uid: string) {
|
|
220
|
+
if (this.speakerSwapTimeout) clearTimeout(this.speakerSwapTimeout);
|
|
221
|
+
this.speakerSwapTimeout = setTimeout(() => {
|
|
222
|
+
this.zone.run(() => {
|
|
223
|
+
this.activeSpeakerUid = uid;
|
|
224
|
+
this.recomputeLayout();
|
|
225
|
+
this.cd.markForCheck();
|
|
226
|
+
});
|
|
227
|
+
}, 500);
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Or use RxJS:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
import { Subject } from "rxjs";
|
|
235
|
+
import { debounceTime } from "rxjs/operators";
|
|
236
|
+
|
|
237
|
+
private speakerSubject = new Subject<string>();
|
|
238
|
+
|
|
239
|
+
ngOnInit() {
|
|
240
|
+
this.speakerSubject.pipe(debounceTime(500)).subscribe((uid) => {
|
|
241
|
+
this.zone.run(() => { this.activeSpeakerUid = uid; this.recomputeLayout(); this.cd.markForCheck(); });
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const listener = new CometChatCalls.OngoingCallListener({
|
|
245
|
+
onActiveSpeakerUpdated: (uid: string) => this.speakerSubject.next(uid),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Moderator actions — guarded by group scope
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
// In your component or a CallControlsService
|
|
256
|
+
async muteParticipant(uid: string) {
|
|
257
|
+
if (!this.canModerate()) return;
|
|
258
|
+
await CometChatCalls.muteUser(uid);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async kickParticipant(uid: string) {
|
|
262
|
+
if (!this.canModerate()) return;
|
|
263
|
+
await CometChatCalls.removeUser(uid);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async muteAll() {
|
|
267
|
+
if (!this.canModerate()) return;
|
|
268
|
+
for (const p of this.others) {
|
|
269
|
+
await CometChatCalls.muteUser(p.uid);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private canModerate(): boolean {
|
|
274
|
+
const scope = this.group?.getScope();
|
|
275
|
+
return scope === "admin" || scope === "moderator";
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Hide the moderator-only buttons in the template:
|
|
280
|
+
|
|
281
|
+
```html
|
|
282
|
+
<div *ngIf="canModerate()" class="moderator-controls">
|
|
283
|
+
<button (click)="muteAll()">Mute all</button>
|
|
284
|
+
<button (click)="kickParticipant(p.uid)" *ngFor="let p of others">Kick</button>
|
|
285
|
+
</div>
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Capacity-error handling
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
const listener = new CometChatCalls.OngoingCallListener({
|
|
294
|
+
onError: (error: { code: string; message: string }) => {
|
|
295
|
+
this.zone.run(() => {
|
|
296
|
+
if (error.code === "ERR_CALL_FULL") {
|
|
297
|
+
this.snackBar.open("This meeting is full.", "Dismiss", { duration: 5000 });
|
|
298
|
+
} else if (error.code === "ERR_NOT_GROUP_MEMBER") {
|
|
299
|
+
this.snackBar.open("You're not a member of this group.", "Dismiss", { duration: 5000 });
|
|
300
|
+
} else {
|
|
301
|
+
this.snackBar.open(`Call error: ${error.message}`, "Dismiss", { duration: 5000 });
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Material's `MatSnackBar` is the canonical Angular surface for transient errors — pick whatever the project uses.
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Bandwidth + battery — auto-downgrade to audio-only
|
|
313
|
+
|
|
314
|
+
Angular doesn't have a first-party battery API, but `BatteryManager` works in browsers (deprecated but functional in Chrome/Edge):
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
async observeBattery() {
|
|
318
|
+
if ("getBattery" in navigator) {
|
|
319
|
+
const battery = await (navigator as unknown as { getBattery: () => Promise<{ level: number; addEventListener: (e: string, h: () => void) => void }> }).getBattery();
|
|
320
|
+
|
|
321
|
+
const check = () => {
|
|
322
|
+
if (battery.level < 0.2 && this.callActive) {
|
|
323
|
+
this.zone.run(() => {
|
|
324
|
+
CometChatCalls.pauseVideo(true);
|
|
325
|
+
this.snackBar.open("Video paused — low battery", "Dismiss", { duration: 5000 });
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
battery.addEventListener("levelchange", check);
|
|
330
|
+
check();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Firefox + Safari don't have `getBattery()` — gracefully skip. For mobile-web users, network-quality detection (Network Information API) is more reliable.
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Joining a group call already in progress
|
|
340
|
+
|
|
341
|
+
Angular `Resolver` for the group-detail route can pre-check active call state:
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
@Injectable({ providedIn: "root" })
|
|
345
|
+
export class GroupCallStateResolver implements Resolve<{ activeCall: unknown | null }> {
|
|
346
|
+
async resolve(route: ActivatedRouteSnapshot): Promise<{ activeCall: unknown | null }> {
|
|
347
|
+
const activeCall = await CometChat.getActiveCall();
|
|
348
|
+
return {
|
|
349
|
+
activeCall: activeCall && activeCall.getReceiverType() === "group"
|
|
350
|
+
? activeCall
|
|
351
|
+
: null,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Wire on the route:
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
{
|
|
361
|
+
path: "groups/:guid",
|
|
362
|
+
component: GroupDetailComponent,
|
|
363
|
+
resolve: { callState: GroupCallStateResolver },
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
In the component:
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
ngOnInit() {
|
|
371
|
+
const callState = this.route.snapshot.data["callState"];
|
|
372
|
+
if (callState.activeCall) {
|
|
373
|
+
// Show "Join meeting" button
|
|
374
|
+
this.activeCall = callState.activeCall;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Anti-patterns
|
|
382
|
+
|
|
383
|
+
1. **Roster updates without `trackByUid`** in `*ngFor`. Tiles re-create; video tracks die.
|
|
384
|
+
2. **Skipping `markForCheck`** with OnPush. Roster updates don't render.
|
|
385
|
+
3. **No debounce on active-speaker.** Tiles jitter at 5+ Hz.
|
|
386
|
+
4. **Moderator buttons rendered for non-moderators.** SDK rejects but UI is misleading.
|
|
387
|
+
5. **`getBattery()` without feature detect.** Firefox/Safari throws.
|
|
388
|
+
6. **Leaving the call doesn't unsubscribe RxJS subjects.** Memory leak across multiple call sessions.
|
|
389
|
+
7. **Group call from a lazy-loaded module.** CallInitService must be eager (cf. `references/lazy-loading-pitfalls.md`).
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Verification checklist
|
|
394
|
+
|
|
395
|
+
- [ ] `*ngFor` over participants uses `trackBy: trackByUid`
|
|
396
|
+
- [ ] OnPush components call `cd.markForCheck()` on roster updates
|
|
397
|
+
- [ ] Active-speaker updates debounced ≥500ms
|
|
398
|
+
- [ ] All SDK callbacks wrapped in `zone.run`
|
|
399
|
+
- [ ] Moderator controls hidden for non-moderator scopes
|
|
400
|
+
- [ ] Capacity error handled (`ERR_CALL_FULL`) with user-facing snackbar
|
|
401
|
+
- [ ] Battery API feature-detected before use
|
|
402
|
+
- [ ] `Resolver` checks `getActiveCall()` on group route entry
|
|
403
|
+
- [ ] RxJS subscriptions cleaned up in `ngOnDestroy`
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Pointers
|
|
408
|
+
|
|
409
|
+
- `references/ngzone-and-async-callbacks.md` — NgZone primer
|
|
410
|
+
- `references/lazy-loading-pitfalls.md` — eager-load CallInitService rule
|
|
411
|
+
- `references/custom-ui.md` — multi-tile custom layouts
|
|
412
|
+
- `cometchat-native-calls/references/group-calls.md` — sister reference (same SDK, RN-flavored patterns; capacity/bandwidth/moderator actions covered there)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Idle timeout on Angular
|
|
2
|
+
|
|
3
|
+
Same SDK API as web. Angular-specific delta: NgZone wrap on the timeout event + Angular Material dialog or custom modal for the prompt.
|
|
4
|
+
|
|
5
|
+
**Canonical docs:** https://www.cometchat.com/docs/calls/javascript/idle-timeout
|
|
6
|
+
**Read first:** `cometchat-react-calls/references/idle-timeout.md` — settings + archetype timeouts + custom prompt pattern.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## SDK API
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
const settings = new CometChatCalls.CallSettingsBuilder()
|
|
14
|
+
.setSessionID(sessionId)
|
|
15
|
+
.setIdleTimeoutPeriodBeforePrompt(60_000)
|
|
16
|
+
.setIdleTimeoutPeriodAfterPrompt(120_000)
|
|
17
|
+
.build();
|
|
18
|
+
|
|
19
|
+
CometChatCalls.addEventListener("onSessionTimedOut", () => {
|
|
20
|
+
this.zone.run(() => {
|
|
21
|
+
this.router.navigate(["/"]);
|
|
22
|
+
this.snackBar.open("Call ended due to inactivity", "Dismiss", { duration: 5000 });
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`NgZone.run` wrap is mandatory — without it, the navigation + snackbar don't fire because the SDK callback is outside Angular's zone.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Service-level wiring
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// services/idle-timeout.service.ts
|
|
35
|
+
import { Injectable, NgZone } from "@angular/core";
|
|
36
|
+
import { Router } from "@angular/router";
|
|
37
|
+
import { MatSnackBar } from "@angular/material/snack-bar";
|
|
38
|
+
import { CometChatCalls } from "@cometchat/calls-sdk-javascript";
|
|
39
|
+
|
|
40
|
+
@Injectable({ providedIn: "root" })
|
|
41
|
+
export class IdleTimeoutService {
|
|
42
|
+
private listener?: () => void;
|
|
43
|
+
|
|
44
|
+
constructor(
|
|
45
|
+
private zone: NgZone,
|
|
46
|
+
private router: Router,
|
|
47
|
+
private snackBar: MatSnackBar,
|
|
48
|
+
) {}
|
|
49
|
+
|
|
50
|
+
attachToCallSession() {
|
|
51
|
+
this.listener = () => {
|
|
52
|
+
this.zone.run(() => {
|
|
53
|
+
this.router.navigate(["/"]);
|
|
54
|
+
this.snackBar.open("Call ended due to inactivity", "Dismiss", { duration: 5000 });
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
CometChatCalls.addEventListener("onSessionTimedOut", this.listener);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
detach() {
|
|
61
|
+
if (this.listener) {
|
|
62
|
+
CometChatCalls.removeEventListener("onSessionTimedOut", this.listener);
|
|
63
|
+
this.listener = undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Call `attachToCallSession()` from the OngoingCallComponent's `ngOnInit`, `detach()` from `ngOnDestroy`.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Custom prompt — Angular Material Dialog
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { MatDialog } from "@angular/material/dialog";
|
|
77
|
+
import { Component } from "@angular/core";
|
|
78
|
+
|
|
79
|
+
@Component({
|
|
80
|
+
template: `
|
|
81
|
+
<h2 mat-dialog-title>Still there?</h2>
|
|
82
|
+
<mat-dialog-content>
|
|
83
|
+
You're alone in this call. It'll end in 60 seconds.
|
|
84
|
+
</mat-dialog-content>
|
|
85
|
+
<mat-dialog-actions>
|
|
86
|
+
<button mat-button [mat-dialog-close]="'stay'">Stay</button>
|
|
87
|
+
<button mat-button color="warn" [mat-dialog-close]="'end'">End now</button>
|
|
88
|
+
</mat-dialog-actions>
|
|
89
|
+
`,
|
|
90
|
+
})
|
|
91
|
+
export class IdleTimeoutDialogComponent {}
|
|
92
|
+
|
|
93
|
+
// Use:
|
|
94
|
+
@Component({ /* ... */ })
|
|
95
|
+
export class OngoingCallComponent {
|
|
96
|
+
constructor(private dialog: MatDialog) {}
|
|
97
|
+
|
|
98
|
+
showIdlePrompt() {
|
|
99
|
+
const ref = this.dialog.open(IdleTimeoutDialogComponent, {
|
|
100
|
+
disableClose: true,
|
|
101
|
+
ariaLabel: "Idle timeout warning",
|
|
102
|
+
});
|
|
103
|
+
ref.afterClosed().subscribe(result => {
|
|
104
|
+
if (result === "end") {
|
|
105
|
+
CometChatCalls.leaveSession(); // v5 canonical (endSession is deprecated)
|
|
106
|
+
}
|
|
107
|
+
// 'stay' → reset your custom timer (see Pattern A in web ref)
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`disableClose: true` prevents the user from dismissing via Esc/backdrop — they must explicitly choose. `ariaLabel` for screen readers.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Anti-patterns
|
|
118
|
+
|
|
119
|
+
Web sister reference rules apply, plus Angular-specific:
|
|
120
|
+
|
|
121
|
+
1. **No `NgZone.run` wrap on the listener.** Navigation + snackbar fire silently because change detection doesn't trigger.
|
|
122
|
+
2. **`MatDialog` opened from `setTimeout` callback** without zone wrap. Dialog opens but template doesn't render.
|
|
123
|
+
3. **Subscription leaks** — `dialog.afterClosed()` returns an Observable; if you don't unsubscribe (or use `take(1)` / `first()`), it leaks across calls.
|
|
124
|
+
4. **Service NOT provided in `root`.** Lazy-loaded → listener attaches late.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Verification checklist
|
|
129
|
+
|
|
130
|
+
- [ ] `IdleTimeoutService` provided in root
|
|
131
|
+
- [ ] Listener wraps in `NgZone.run`
|
|
132
|
+
- [ ] Service `attachToCallSession()` called from OngoingCall `ngOnInit`
|
|
133
|
+
- [ ] Service `detach()` called from `ngOnDestroy`
|
|
134
|
+
- [ ] Dialog uses `disableClose: true` + `ariaLabel`
|
|
135
|
+
- [ ] `dialog.afterClosed()` subscription doesn't leak (use `first()` or unsubscribe)
|
|
136
|
+
- [ ] Browser smoke: 2 tabs in call, hangup one → other shows snackbar after configured delay
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Pointers
|
|
141
|
+
|
|
142
|
+
- `cometchat-react-calls/references/idle-timeout.md` — sister reference
|
|
143
|
+
- `cometchat-angular-calls` SKILL.md
|
|
144
|
+
- `references/ngzone-and-async-callbacks.md` — NgZone primer
|
|
145
|
+
- Canonical docs: https://www.cometchat.com/docs/calls/javascript/idle-timeout
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# In-call chat on Angular
|
|
2
|
+
|
|
3
|
+
Same SDK API as web. Angular wraps panel state in a service + uses Angular Material's `MatSidenav` for the side-drawer chat panel pattern.
|
|
4
|
+
|
|
5
|
+
**Canonical docs:** https://www.cometchat.com/docs/calls/javascript/in-call-chat
|
|
6
|
+
**Read first:** `cometchat-react-calls/references/in-call-chat.md` — group-as-session architecture, anti-patterns.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## SDK + service
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { Injectable, NgZone } from "@angular/core";
|
|
14
|
+
import { BehaviorSubject } from "rxjs";
|
|
15
|
+
import { CometChatCalls } from "@cometchat/calls-sdk-javascript";
|
|
16
|
+
import { CometChat } from "@cometchat/chat-sdk-javascript";
|
|
17
|
+
|
|
18
|
+
@Injectable({ providedIn: "root" })
|
|
19
|
+
export class InCallChatService {
|
|
20
|
+
readonly open$ = new BehaviorSubject<boolean>(false);
|
|
21
|
+
readonly unread$ = new BehaviorSubject<number>(0);
|
|
22
|
+
|
|
23
|
+
constructor(private zone: NgZone) {
|
|
24
|
+
CometChatCalls.addEventListener("onChatButtonClicked", () => {
|
|
25
|
+
this.zone.run(() => this.open$.next(true));
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async ensureGroup(sessionId: string): Promise<CometChat.Group> {
|
|
30
|
+
try {
|
|
31
|
+
return await CometChat.getGroup(sessionId);
|
|
32
|
+
} catch {
|
|
33
|
+
const group = new CometChat.Group(sessionId, `Call ${sessionId}`, CometChat.GROUP_TYPE.PUBLIC);
|
|
34
|
+
return await CometChat.createGroup(group);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async joinGroup(sessionId: string): Promise<void> {
|
|
39
|
+
await CometChat.joinGroup(sessionId, CometChat.GROUP_TYPE.PUBLIC, "");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
trackUnread(sessionId: string): void {
|
|
43
|
+
const listenerId = `in-call-chat-${sessionId}`;
|
|
44
|
+
const listener = new CometChat.MessageListener({
|
|
45
|
+
onTextMessageReceived: (msg: CometChat.TextMessage) => {
|
|
46
|
+
if (
|
|
47
|
+
msg.getReceiverType() === CometChat.RECEIVER_TYPE.GROUP &&
|
|
48
|
+
msg.getReceiverId() === sessionId &&
|
|
49
|
+
!this.open$.value
|
|
50
|
+
) {
|
|
51
|
+
this.zone.run(() => {
|
|
52
|
+
const next = this.unread$.value + 1;
|
|
53
|
+
this.unread$.next(next);
|
|
54
|
+
CometChatCalls.setChatButtonUnreadCount(next);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
CometChat.addMessageListener(listenerId, listener);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
open(): void {
|
|
63
|
+
this.open$.next(true);
|
|
64
|
+
this.unread$.next(0);
|
|
65
|
+
CometChatCalls.setChatButtonUnreadCount(0);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
close(): void {
|
|
69
|
+
this.open$.next(false);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Panel component (MatSidenav)
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
@Component({
|
|
80
|
+
selector: "app-in-call-chat",
|
|
81
|
+
template: `
|
|
82
|
+
<mat-sidenav-container [hasBackdrop]="false">
|
|
83
|
+
<mat-sidenav
|
|
84
|
+
#drawer
|
|
85
|
+
position="end"
|
|
86
|
+
mode="side"
|
|
87
|
+
[opened]="open$ | async"
|
|
88
|
+
(closed)="svc.close()"
|
|
89
|
+
role="dialog"
|
|
90
|
+
aria-label="In-call chat"
|
|
91
|
+
>
|
|
92
|
+
<ng-container *ngIf="group">
|
|
93
|
+
<cometchat-message-list [group]="group" [hideReplyInThreadOption]="true"></cometchat-message-list>
|
|
94
|
+
<cometchat-message-composer [group]="group"></cometchat-message-composer>
|
|
95
|
+
</ng-container>
|
|
96
|
+
</mat-sidenav>
|
|
97
|
+
<mat-sidenav-content>
|
|
98
|
+
<!-- call surface goes here -->
|
|
99
|
+
<ng-content></ng-content>
|
|
100
|
+
</mat-sidenav-content>
|
|
101
|
+
</mat-sidenav-container>
|
|
102
|
+
`,
|
|
103
|
+
})
|
|
104
|
+
export class InCallChatComponent implements OnInit {
|
|
105
|
+
@Input() sessionId!: string;
|
|
106
|
+
open$ = this.svc.open$;
|
|
107
|
+
group?: CometChat.Group;
|
|
108
|
+
|
|
109
|
+
constructor(public svc: InCallChatService) {}
|
|
110
|
+
|
|
111
|
+
async ngOnInit() {
|
|
112
|
+
this.group = await this.svc.ensureGroup(this.sessionId);
|
|
113
|
+
this.svc.trackUnread(this.sessionId);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`mode="side"` makes the drawer slide out without an overlay backdrop — call view stays visible. `[hasBackdrop]="false"` doubles down.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Anti-patterns
|
|
123
|
+
|
|
124
|
+
Web sister rules apply, plus Angular-specific:
|
|
125
|
+
|
|
126
|
+
1. **No `NgZone.run` wrap on `onChatButtonClicked`.** Drawer doesn't open in OnPush components.
|
|
127
|
+
2. **`MatSidenav` `mode="over"`.** Backdrop blocks the call. Use `mode="side"`.
|
|
128
|
+
3. **Service NOT in `root`.** Lazy module → button events miss.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Verification checklist
|
|
133
|
+
|
|
134
|
+
- [ ] `InCallChatService` provided in root
|
|
135
|
+
- [ ] `NgZone.run` wraps the chat-button event
|
|
136
|
+
- [ ] `MatSidenav` uses `mode="side"` + `hasBackdrop="false"`
|
|
137
|
+
- [ ] Group resolved on `ngOnInit`
|
|
138
|
+
- [ ] Unread badge updates via service, clears on open
|
|
139
|
+
- [ ] Browser smoke: 2 tabs, chat works, unread badge increments
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Pointers
|
|
144
|
+
|
|
145
|
+
- `cometchat-react-calls/references/in-call-chat.md` — sister web reference
|
|
146
|
+
- `cometchat-angular-calls` SKILL.md
|
|
147
|
+
- `references/ngzone-and-async-callbacks.md`
|
|
148
|
+
- Canonical docs: https://www.cometchat.com/docs/calls/javascript/in-call-chat
|