@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.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/CHANGELOG.md +9 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +266 -267
- package/package.json +2 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +68 -68
- package/pipeline/skills/shared/README.md +70 -70
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,76 +1,62 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: callkit-voip
|
|
3
|
-
description: "
|
|
3
|
+
description: "CallKit and PushKit VoIP calling: CXProvider and CXCallController, incoming and outgoing call flows, call actions, VoIP push registration and reporting, call audio sessions, Call Directory caller ID and blocking. Use when implementing VoIP calls or VoIP push handling."
|
|
4
4
|
metadata:
|
|
5
|
-
source:
|
|
5
|
+
source: multi-agent-pipeline
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# CallKit
|
|
8
|
+
# CallKit and PushKit VoIP
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
CallKit puts an app's VoIP calls into the same system call UI that cellular
|
|
11
|
+
calls use: the lock-screen answer slider, the in-call screen, Recents and
|
|
12
|
+
Bluetooth controls. PushKit wakes the app when a call arrives. Everything here
|
|
13
|
+
targets Swift 6.3 and iOS 26 or later unless a line names another release.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- [Setup](#setup)
|
|
18
|
-
- [Provider Configuration](#provider-configuration)
|
|
19
|
-
- [Incoming Call Flow](#incoming-call-flow)
|
|
20
|
-
- [Outgoing Call Flow](#outgoing-call-flow)
|
|
21
|
-
- [PushKit VoIP Registration](#pushkit-voip-registration)
|
|
22
|
-
- [Audio Session Coordination](#audio-session-coordination)
|
|
23
|
-
- [Call Directory Extension and Manager](#call-directory-extension-and-manager)
|
|
24
|
-
- [Common Mistakes](#common-mistakes)
|
|
25
|
-
- [Review Checklist](#review-checklist)
|
|
26
|
-
- [References](#references)
|
|
15
|
+
Rotating APNs auth keys and everyday remote-notification registration live
|
|
16
|
+
in `push-notifications`. This skill starts at the VoIP push type.
|
|
27
17
|
|
|
28
18
|
## Setup
|
|
29
19
|
|
|
30
|
-
### Project
|
|
20
|
+
### Project configuration
|
|
31
21
|
|
|
32
|
-
1.
|
|
33
|
-
2.
|
|
34
|
-
3.
|
|
22
|
+
1. Signing & Capabilities: turn on Background Modes and tick **Voice over IP**.
|
|
23
|
+
2. Push Notifications must also appear in the capability list.
|
|
24
|
+
3. Caller ID or blocking lists need a separate target of type Call Directory Extension.
|
|
35
25
|
|
|
36
|
-
###
|
|
26
|
+
### The types you touch
|
|
37
27
|
|
|
38
|
-
| Type |
|
|
39
|
-
|
|
40
|
-
| `CXProvider` |
|
|
41
|
-
| `CXCallController` |
|
|
42
|
-
| `CXCallUpdate` | Describes call
|
|
43
|
-
| `CXProviderDelegate` |
|
|
44
|
-
| `PKPushRegistry` | Registers for
|
|
45
|
-
| `PKVoIPPushMetadata` | iOS 26.4
|
|
28
|
+
| Type | Job |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| `CXProvider` | Tells the system about calls and receives the actions the system wants performed |
|
|
31
|
+
| `CXCallController` | Asks the system to perform an action: start, end, hold, mute |
|
|
32
|
+
| `CXCallUpdate` | Describes a call: caller name, handle, video |
|
|
33
|
+
| `CXProviderDelegate` | Performs call actions and receives audio session activation |
|
|
34
|
+
| `PKPushRegistry` | Registers for VoIP pushes and delivers them |
|
|
35
|
+
| `PKVoIPPushMetadata` | iOS 26.4+: says whether a given VoIP push has to be reported |
|
|
46
36
|
|
|
47
|
-
## Provider
|
|
37
|
+
## Provider configuration
|
|
48
38
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
39
|
+
Build one `CXProvider` when the app launches and keep that instance until the
|
|
40
|
+
process dies. Creating a second provider or recreating it per call confuses the
|
|
41
|
+
system about which calls exist.
|
|
52
42
|
|
|
53
43
|
```swift
|
|
54
44
|
import CallKit
|
|
55
45
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
final class CallManager: NSObject, @unchecked Sendable {
|
|
60
|
-
static let shared = CallManager()
|
|
46
|
+
@MainActor
|
|
47
|
+
final class PhoneLine: NSObject {
|
|
48
|
+
static let shared = PhoneLine()
|
|
61
49
|
|
|
62
50
|
let provider: CXProvider
|
|
63
|
-
let
|
|
51
|
+
let controller = CXCallController()
|
|
64
52
|
|
|
65
|
-
|
|
53
|
+
override init() {
|
|
66
54
|
let config = CXProviderConfiguration()
|
|
67
|
-
config.localizedName = "My VoIP App"
|
|
68
55
|
config.supportsVideo = true
|
|
69
|
-
config.maximumCallsPerCallGroup = 1
|
|
70
56
|
config.maximumCallGroups = 2
|
|
57
|
+
config.maximumCallsPerCallGroup = 1
|
|
71
58
|
config.supportedHandleTypes = [.phoneNumber, .emailAddress]
|
|
72
59
|
config.includesCallsInRecents = true
|
|
73
|
-
|
|
74
60
|
provider = CXProvider(configuration: config)
|
|
75
61
|
super.init()
|
|
76
62
|
provider.setDelegate(self, queue: nil)
|
|
@@ -78,204 +64,188 @@ final class CallManager: NSObject, @unchecked Sendable {
|
|
|
78
64
|
}
|
|
79
65
|
```
|
|
80
66
|
|
|
81
|
-
|
|
67
|
+
`PhoneLine` is `@MainActor` because every delegate callback arrives on the
|
|
68
|
+
queue passed to `setDelegate(_:queue:)`, and `nil` means the main queue. The
|
|
69
|
+
delegate conformance below is `@preconcurrency`, so those callbacks run as
|
|
70
|
+
main-actor code and the `Task` each one starts inherits that isolation. That is
|
|
71
|
+
what lets a `Task` hold on to the non-`Sendable` `CXAction` and `CXProvider`
|
|
72
|
+
it was handed; from a nonisolated method Swift 6 rejects the same capture with
|
|
73
|
+
"passing closure as a 'sending' parameter risks causing data races". The name the
|
|
74
|
+
system shows comes from `localizedName`; on current SDKs it is read from the
|
|
75
|
+
bundle display name, and the old `init(localizedName:)` is deprecated.
|
|
82
76
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
the
|
|
77
|
+
## Incoming call flow
|
|
78
|
+
|
|
79
|
+
When a VoIP push says a call is ringing, report it to CallKit right away; the
|
|
80
|
+
system then draws the native incoming-call UI. For a push that must be
|
|
81
|
+
reported, the report has to happen before the PushKit completion handler runs,
|
|
82
|
+
or the system kills the app.
|
|
87
83
|
|
|
88
84
|
```swift
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
try await withCheckedThrowingContinuation {
|
|
100
|
-
(continuation: CheckedContinuation<Void, Error>) in
|
|
101
|
-
provider.reportNewIncomingCall(
|
|
102
|
-
with: uuid,
|
|
103
|
-
update: update
|
|
104
|
-
) { error in
|
|
105
|
-
if let error {
|
|
106
|
-
continuation.resume(throwing: error)
|
|
107
|
-
} else {
|
|
108
|
-
continuation.resume()
|
|
85
|
+
extension PhoneLine {
|
|
86
|
+
func announceIncoming(id: UUID, number: String, name: String, video: Bool) async throws {
|
|
87
|
+
let update = CXCallUpdate()
|
|
88
|
+
update.remoteHandle = CXHandle(type: .phoneNumber, value: number.trimmingCharacters(in: .whitespaces))
|
|
89
|
+
update.localizedCallerName = name
|
|
90
|
+
update.hasVideo = video
|
|
91
|
+
|
|
92
|
+
try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, Error>) in
|
|
93
|
+
provider.reportNewIncomingCall(with: id, update: update) { error in
|
|
94
|
+
if let error { cont.resume(throwing: error) } else { cont.resume() }
|
|
109
95
|
}
|
|
110
96
|
}
|
|
111
97
|
}
|
|
112
98
|
}
|
|
113
99
|
```
|
|
114
100
|
|
|
115
|
-
|
|
101
|
+
The report throws when Do Not Disturb or the user's block list filters the call.
|
|
102
|
+
That is a normal outcome, not a crash path.
|
|
116
103
|
|
|
117
|
-
|
|
104
|
+
### Answering and ending
|
|
118
105
|
|
|
119
106
|
```swift
|
|
120
|
-
extension
|
|
121
|
-
func providerDidReset(_
|
|
122
|
-
|
|
107
|
+
extension PhoneLine: @preconcurrency CXProviderDelegate {
|
|
108
|
+
func providerDidReset(_ cx: CXProvider) {
|
|
109
|
+
MediaSession.shared.hangUpEverything()
|
|
110
|
+
AudioRoute.tearDown()
|
|
123
111
|
}
|
|
124
112
|
|
|
125
|
-
func provider(_
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
endedAt: Date(),
|
|
135
|
-
reason: .failed
|
|
136
|
-
)
|
|
137
|
-
action.fail()
|
|
113
|
+
func provider(_ cx: CXProvider, perform answer: CXAnswerCallAction) {
|
|
114
|
+
AudioRoute.prepareForCall()
|
|
115
|
+
Task {
|
|
116
|
+
do {
|
|
117
|
+
try await SignalingClient.shared.join(call: answer.callUUID)
|
|
118
|
+
answer.fulfill()
|
|
119
|
+
} catch {
|
|
120
|
+
cx.reportCall(with: answer.callUUID, endedAt: .now, reason: .failed)
|
|
121
|
+
answer.fail()
|
|
138
122
|
}
|
|
139
123
|
}
|
|
140
124
|
}
|
|
141
125
|
|
|
142
|
-
func provider(_
|
|
143
|
-
|
|
144
|
-
|
|
126
|
+
func provider(_ cx: CXProvider, perform hangup: CXEndCallAction) {
|
|
127
|
+
Task {
|
|
128
|
+
await SignalingClient.shared.leave(call: hangup.callUUID)
|
|
129
|
+
hangup.fulfill()
|
|
130
|
+
}
|
|
145
131
|
}
|
|
146
132
|
}
|
|
147
133
|
```
|
|
148
134
|
|
|
149
|
-
|
|
135
|
+
`providerDidReset(_:)` means the system dropped every call: end all of them and
|
|
136
|
+
reset audio. The answer action stays pending until the media server connection
|
|
137
|
+
is up; only then is it fulfilled. If the connection fails, report the call as
|
|
138
|
+
ended with `.failed` and fail the action.
|
|
139
|
+
|
|
140
|
+
## Outgoing call flow
|
|
150
141
|
|
|
151
|
-
|
|
152
|
-
|
|
142
|
+
Outgoing calls start with a request to `CXCallController`. The system checks it
|
|
143
|
+
and hands it back through `provider(_:perform:)` with a `CXStartCallAction`.
|
|
153
144
|
|
|
154
145
|
```swift
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
callController.request(transaction) { error in
|
|
163
|
-
if let error {
|
|
164
|
-
print("Failed to start call: \(error)")
|
|
146
|
+
extension PhoneLine {
|
|
147
|
+
func dial(_ address: String, video: Bool) -> UUID {
|
|
148
|
+
let id = UUID()
|
|
149
|
+
let start = CXStartCallAction(call: id, handle: CXHandle(type: .phoneNumber, value: address))
|
|
150
|
+
start.isVideo = video
|
|
151
|
+
controller.request(CXTransaction(action: start)) { error in
|
|
152
|
+
if let error { Log.calls.error("dial refused: \(error)") }
|
|
165
153
|
}
|
|
154
|
+
return id
|
|
166
155
|
}
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
156
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
connectToServer(callUUID: action.callUUID) {
|
|
183
|
-
provider.reportOutgoingCall(
|
|
184
|
-
with: action.callUUID,
|
|
185
|
-
connectedAt: Date()
|
|
186
|
-
)
|
|
157
|
+
func provider(_ cx: CXProvider, perform dial: CXStartCallAction) {
|
|
158
|
+
AudioRoute.prepareForCall()
|
|
159
|
+
let id = dial.callUUID
|
|
160
|
+
cx.reportOutgoingCall(with: id, startedConnectingAt: .now)
|
|
161
|
+
Task {
|
|
162
|
+
do {
|
|
163
|
+
try await SignalingClient.shared.place(call: id, to: dial.handle.value)
|
|
164
|
+
cx.reportOutgoingCall(with: id, connectedAt: .now)
|
|
165
|
+
dial.fulfill()
|
|
166
|
+
} catch {
|
|
167
|
+
dial.fail()
|
|
168
|
+
}
|
|
187
169
|
}
|
|
188
|
-
action.fulfill()
|
|
189
170
|
}
|
|
190
171
|
}
|
|
191
172
|
```
|
|
192
173
|
|
|
193
|
-
|
|
174
|
+
Report both `startedConnectingAt` and `connectedAt`; Recents and the call timer
|
|
175
|
+
use them.
|
|
176
|
+
|
|
177
|
+
## PushKit VoIP registration
|
|
178
|
+
|
|
179
|
+
Create the registry on every launch, not lazily, and forward each token change
|
|
180
|
+
to your server.
|
|
181
|
+
|
|
182
|
+
Report rules:
|
|
194
183
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
the
|
|
200
|
-
|
|
201
|
-
|
|
184
|
+
- Linked against SDK 13 or newer: every VoIP push that is a call must be
|
|
185
|
+
reported to CallKit (LiveCommunicationKit apps report there instead)
|
|
186
|
+
before the PushKit completion handler is called.
|
|
187
|
+
- iOS 26.4 and later: `PKVoIPPushMetadata.mustReport` decides. When it is `true`,
|
|
188
|
+
the report precedes completion; when `false`, that push can be handled
|
|
189
|
+
without any report at all.
|
|
190
|
+
- A skipped required report may end the process; keep skipping and the
|
|
191
|
+
system may stop delivering VoIP pushes altogether.
|
|
202
192
|
|
|
203
|
-
|
|
|
204
|
-
|
|
205
|
-
|
|
|
206
|
-
|
|
|
207
|
-
| Older delegate |
|
|
193
|
+
| Delivery path | Report? | When to call completion |
|
|
194
|
+
| --- | --- | --- |
|
|
195
|
+
| 26.4+ delegate, `mustReport == true` | Yes | After the report callback |
|
|
196
|
+
| 26.4+ delegate, `mustReport == false` | No | After local handling |
|
|
197
|
+
| Older delegate | Treat every VoIP call push as required | After the report callback |
|
|
208
198
|
|
|
209
199
|
```swift
|
|
210
200
|
import PushKit
|
|
211
201
|
|
|
212
|
-
|
|
213
|
-
|
|
202
|
+
@MainActor
|
|
203
|
+
final class VoIPInbox: NSObject, @preconcurrency PKPushRegistryDelegate {
|
|
204
|
+
private let registry = PKPushRegistry(queue: .main)
|
|
214
205
|
|
|
215
|
-
|
|
216
|
-
registry = PKPushRegistry(queue: .main)
|
|
217
|
-
super.init()
|
|
206
|
+
func start() {
|
|
218
207
|
registry.delegate = self
|
|
219
208
|
registry.desiredPushTypes = [.voIP]
|
|
220
209
|
}
|
|
221
210
|
|
|
222
|
-
func pushRegistry(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
for type: PKPushType
|
|
226
|
-
) {
|
|
227
|
-
let token = pushCredentials.token
|
|
228
|
-
.map { String(format: "%02x", $0) }
|
|
229
|
-
.joined()
|
|
230
|
-
// Send token to your server
|
|
231
|
-
sendTokenToServer(token)
|
|
211
|
+
func pushRegistry(_ reg: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
|
|
212
|
+
let hex = credentials.token.reduce(into: "") { out, byte in out += String(format: "%02x", byte) }
|
|
213
|
+
Backend.shared.upload(voipToken: hex)
|
|
232
214
|
}
|
|
233
215
|
|
|
234
216
|
@available(iOS 26.4, *)
|
|
235
|
-
func pushRegistry(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
withCompletionHandler completion: @escaping @Sendable () -> Void
|
|
240
|
-
) {
|
|
217
|
+
func pushRegistry(_ reg: PKPushRegistry,
|
|
218
|
+
didReceiveIncomingVoIPPushWith payload: PKPushPayload,
|
|
219
|
+
metadata: PKVoIPPushMetadata,
|
|
220
|
+
withCompletionHandler completion: @escaping @Sendable () -> Void) {
|
|
241
221
|
guard metadata.mustReport else {
|
|
242
222
|
completion()
|
|
243
223
|
return
|
|
244
224
|
}
|
|
245
|
-
|
|
225
|
+
ring(from: payload, then: completion)
|
|
246
226
|
}
|
|
247
227
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
for type: PKPushType,
|
|
253
|
-
completion: @escaping () -> Void
|
|
254
|
-
) {
|
|
228
|
+
func pushRegistry(_ reg: PKPushRegistry,
|
|
229
|
+
didReceiveIncomingPushWith payload: PKPushPayload,
|
|
230
|
+
for type: PKPushType,
|
|
231
|
+
completion: @escaping () -> Void) {
|
|
255
232
|
guard type == .voIP else {
|
|
256
233
|
completion()
|
|
257
234
|
return
|
|
258
235
|
}
|
|
259
|
-
|
|
260
|
-
handleIncomingVoIPPush(payload, completion: completion)
|
|
236
|
+
ring(from: payload, then: completion)
|
|
261
237
|
}
|
|
262
238
|
|
|
263
|
-
private func
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
let
|
|
268
|
-
let handle = payload.dictionaryPayload["handle"] as? String ?? "Unknown"
|
|
269
|
-
|
|
239
|
+
private func ring(from payload: PKPushPayload, then completion: @escaping () -> Void) {
|
|
240
|
+
let body = payload.dictionaryPayload
|
|
241
|
+
let number = body["from"] as? String ?? "Unknown"
|
|
242
|
+
let name = body["display"] as? String ?? number
|
|
243
|
+
let video = body["video"] as? Bool ?? false
|
|
270
244
|
Task {
|
|
271
245
|
do {
|
|
272
|
-
try await
|
|
273
|
-
uuid: callUUID,
|
|
274
|
-
handle: handle,
|
|
275
|
-
hasVideo: false
|
|
276
|
-
)
|
|
246
|
+
try await PhoneLine.shared.announceIncoming(id: UUID(), number: number, name: name, video: video)
|
|
277
247
|
} catch {
|
|
278
|
-
|
|
248
|
+
Log.calls.notice("call filtered or rejected: \(error)")
|
|
279
249
|
}
|
|
280
250
|
completion()
|
|
281
251
|
}
|
|
@@ -283,218 +253,167 @@ final class PushManager: NSObject, PKPushRegistryDelegate {
|
|
|
283
253
|
}
|
|
284
254
|
```
|
|
285
255
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
256
|
+
Keep the older delegate method for iOS 26.0 through 26.3 and for lower
|
|
257
|
+
deployment targets. The registry delivers on `.main`, which is why the inbox is
|
|
258
|
+
`@MainActor`: the `Task` in `ring` can then carry the non-`Sendable` completion
|
|
259
|
+
handler without leaving the actor. Completion runs after the report attempt on both the
|
|
260
|
+
success and the error path.
|
|
290
261
|
|
|
291
|
-
|
|
262
|
+
Server side: send VoIP pushes with `apns-expiration: 0` or a few seconds; a
|
|
263
|
+
ring that arrives a minute late is worse than none. After the first push has
|
|
264
|
+
woken the app, hangups and changed call details travel over your own socket or
|
|
265
|
+
API channel instead of further VoIP pushes.
|
|
292
266
|
|
|
293
|
-
|
|
294
|
-
session when CallKit tells you to, not before.
|
|
295
|
-
Review answers should name both sides: start media only in
|
|
296
|
-
`provider(_:didActivate:)`, and stop/tear down media in
|
|
297
|
-
`provider(_:didDeactivate:)` or reset paths.
|
|
267
|
+
## Audio session coordination
|
|
298
268
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
|
|
302
|
-
// Audio session is now active -- start audio engine / WebRTC
|
|
303
|
-
startAudioEngine()
|
|
304
|
-
}
|
|
269
|
+
CallKit owns activation and deactivation of the audio session. Set the category
|
|
270
|
+
while preparing the call, and wait for CallKit before touching media.
|
|
305
271
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
stopAudioEngine()
|
|
309
|
-
}
|
|
272
|
+
```swift
|
|
273
|
+
import AVFAudio
|
|
310
274
|
|
|
311
|
-
|
|
312
|
-
|
|
275
|
+
enum AudioRoute {
|
|
276
|
+
static func prepareForCall() {
|
|
313
277
|
do {
|
|
314
|
-
try
|
|
315
|
-
|
|
316
|
-
mode: .voiceChat,
|
|
317
|
-
options: [.allowBluetooth, .allowBluetoothA2DP]
|
|
318
|
-
)
|
|
278
|
+
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .voiceChat,
|
|
279
|
+
options: [.allowBluetoothHFP, .allowBluetoothA2DP])
|
|
319
280
|
} catch {
|
|
320
|
-
|
|
281
|
+
Log.calls.error("audio category failed: \(error)")
|
|
321
282
|
}
|
|
322
283
|
}
|
|
323
|
-
}
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
## Call Directory Extension and Manager
|
|
327
284
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
`CXCallDirectoryManager` to check enabled status, open Call Blocking &
|
|
331
|
-
Identification settings when disabled, and reload after data changes. Store
|
|
332
|
-
`CXCallDirectoryPhoneNumber` as country code plus digits in ascending order
|
|
333
|
-
(for example `18005551234`), not a formatted string.
|
|
334
|
-
|
|
335
|
-
```swift
|
|
336
|
-
import CallKit
|
|
285
|
+
static func tearDown() { MediaSession.shared.stopEngine() }
|
|
286
|
+
}
|
|
337
287
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
) {
|
|
342
|
-
if context.isIncremental {
|
|
343
|
-
addOrRemoveIncrementalEntries(to: context)
|
|
344
|
-
} else {
|
|
345
|
-
addAllEntries(to: context)
|
|
346
|
-
}
|
|
347
|
-
context.completeRequest()
|
|
288
|
+
extension PhoneLine {
|
|
289
|
+
func provider(_ cx: CXProvider, didActivate session: AVAudioSession) {
|
|
290
|
+
MediaSession.shared.startEngine()
|
|
348
291
|
}
|
|
349
292
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
) {
|
|
353
|
-
// Country code + digits, sorted in ascending order
|
|
354
|
-
let blockedNumbers: [CXCallDirectoryPhoneNumber] = [
|
|
355
|
-
18005551234, 18005555678
|
|
356
|
-
]
|
|
357
|
-
for number in blockedNumbers {
|
|
358
|
-
context.addBlockingEntry(
|
|
359
|
-
withNextSequentialPhoneNumber: number
|
|
360
|
-
)
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
let identifiedNumbers: [(CXCallDirectoryPhoneNumber, String)] = [
|
|
364
|
-
(18005551111, "Local Pizza"),
|
|
365
|
-
(18005552222, "Dentist Office")
|
|
366
|
-
]
|
|
367
|
-
for (number, label) in identifiedNumbers {
|
|
368
|
-
context.addIdentificationEntry(
|
|
369
|
-
withNextSequentialPhoneNumber: number,
|
|
370
|
-
label: label
|
|
371
|
-
)
|
|
372
|
-
}
|
|
293
|
+
func provider(_ cx: CXProvider, didDeactivate session: AVAudioSession) {
|
|
294
|
+
MediaSession.shared.stopEngine()
|
|
373
295
|
}
|
|
374
296
|
}
|
|
375
297
|
```
|
|
376
298
|
|
|
377
|
-
|
|
299
|
+
Media starts in `provider(_:didActivate:)` and stops in
|
|
300
|
+
`provider(_:didDeactivate:)` or a reset path. A review should point at both
|
|
301
|
+
callbacks by name.
|
|
302
|
+
|
|
303
|
+
## Call Directory extension and manager
|
|
304
|
+
|
|
305
|
+
A Call Directory supplies caller ID labels and blocked numbers that are loaded
|
|
306
|
+
in advance. It cannot look anything up while a call is ringing.
|
|
378
307
|
|
|
379
308
|
```swift
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
309
|
+
import CallKit
|
|
310
|
+
|
|
311
|
+
final class SpamListProvider: CXCallDirectoryProvider {
|
|
312
|
+
override func beginRequest(with ctx: CXCallDirectoryExtensionContext) {
|
|
313
|
+
if ctx.isIncremental {
|
|
314
|
+
applyChanges(to: ctx)
|
|
315
|
+
} else {
|
|
316
|
+
for number in SharedStore.blocked().sorted() {
|
|
317
|
+
ctx.addBlockingEntry(withNextSequentialPhoneNumber: number)
|
|
318
|
+
}
|
|
319
|
+
for entry in SharedStore.labels().sorted(by: { $0.key < $1.key }) {
|
|
320
|
+
ctx.addIdentificationEntry(withNextSequentialPhoneNumber: entry.key, label: entry.value)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
ctx.completeRequest()
|
|
385
324
|
}
|
|
386
|
-
manager.reloadExtension(withIdentifier: extensionID) { _ in }
|
|
387
325
|
}
|
|
388
326
|
```
|
|
389
327
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
call ended with `.failed`.
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
Call Directory
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
- [ ] VoIP background mode enabled in capabilities
|
|
460
|
-
- [ ] Single `CXProvider` instance created at app launch and retained
|
|
461
|
-
- [ ] `CXProviderDelegate` set before reporting any calls
|
|
462
|
-
- [ ] iOS 26.4+ PushKit path reports when `mustReport` is true and may skip when false
|
|
463
|
-
- [ ] iOS 13 SDK+ PushKit VoIP call pushes report to CallKit before completion
|
|
464
|
-
- [ ] VoIP APNs requests use `apns-expiration` of `0` or only a few seconds
|
|
465
|
-
- [ ] Hangups and detail updates use the app-server connection after the initial push
|
|
466
|
-
- [ ] `action.fulfill()` or `action.fail()` called for every provider delegate action
|
|
467
|
-
- [ ] `CXAnswerCallAction` fulfilled only after the call server/media connection is ready
|
|
468
|
-
- [ ] Audio engine started only after `provider(_:didActivate:)` callback
|
|
469
|
-
- [ ] Audio engine stopped in `provider(_:didDeactivate:)` callback
|
|
470
|
-
- [ ] Audio session category set to `.playAndRecord` with `.voiceChat` mode
|
|
471
|
-
- [ ] VoIP push token sent to server on every `didUpdate pushCredentials` callback
|
|
472
|
-
- [ ] `PKPushRegistry` created at every app launch (not lazily)
|
|
473
|
-
- [ ] Call Directory data is preloaded, not fetched per incoming call
|
|
474
|
-
- [ ] `CXCallDirectoryPhoneNumber` documented as country calling code + digits
|
|
475
|
-
- [ ] `CXCallDirectoryManager` names status check, reload, and settings-opening APIs
|
|
476
|
-
- [ ] `CXCallUpdate` populated with `localizedCallerName` and `remoteHandle`
|
|
477
|
-
- [ ] Outgoing calls report `startedConnectingAt` and `connectedAt` timestamps
|
|
478
|
-
- [ ] iOS 26 call translation keeps upstream audio active during mute
|
|
479
|
-
- [ ] Encrypted metadata filtering mentions the notification service extension entitlement
|
|
328
|
+
A `CXCallDirectoryPhoneNumber` is an `Int64`: country calling code followed by
|
|
329
|
+
the national digits, for example `14155550123`, never a formatted string.
|
|
330
|
+
Entries go in ascending order.
|
|
331
|
+
|
|
332
|
+
In the containing app, use `CXCallDirectoryManager.sharedInstance`:
|
|
333
|
+
|
|
334
|
+
- `getEnabledStatusForExtension(withIdentifier:)` before assuming the extension
|
|
335
|
+
is on.
|
|
336
|
+
- When the status is not `.enabled`, `openSettings { _ in }` takes the user to
|
|
337
|
+
Call Blocking & Identification.
|
|
338
|
+
- After the dataset changes, `reloadExtension(withIdentifier:)`.
|
|
339
|
+
|
|
340
|
+
Rotating APNs keys and the usual notification registration are covered by
|
|
341
|
+
`push-notifications`.
|
|
342
|
+
|
|
343
|
+
## Common mistakes
|
|
344
|
+
|
|
345
|
+
- **Treating a call push as data.** A required VoIP call push that is not
|
|
346
|
+
reported ends the process. Hand it to CallKit first and let the report's
|
|
347
|
+
callback be where PushKit's completion runs.
|
|
348
|
+
- **Fulfilling the answer too early.** Keep `CXAnswerCallAction` pending while
|
|
349
|
+
connecting; fulfill once media is ready; on failure, fail it and report the
|
|
350
|
+
call ended with `.failed`.
|
|
351
|
+
- **Starting audio before activation.** Media started before
|
|
352
|
+
`provider(_:didActivate:)` plays silence or gets deactivated at once, because
|
|
353
|
+
CallKit arbitrates session priority. Prepare in the answer or start action,
|
|
354
|
+
play only after activation.
|
|
355
|
+
- **Missing iOS 26 call translation.** Set
|
|
356
|
+
`CXProviderConfiguration.supportsAudioTranslation` when the app supports it
|
|
357
|
+
and handle `CXSetTranslatingCallAction`.
|
|
358
|
+
- **Muting by killing audio.** During a translated call, mute the app's own
|
|
359
|
+
input in `CXSetMutedCallAction`; do not deactivate the upstream audio that
|
|
360
|
+
translation reads.
|
|
361
|
+
- **Overusing encrypted payload reporting.** `CXProvider.reportNewIncomingVoIPPushPayload`
|
|
362
|
+
belongs in a notification service extension, and only when the server cannot
|
|
363
|
+
tell whether encrypted content is a call. If it can tell, send a normal
|
|
364
|
+
PushKit VoIP push. The extension needs the
|
|
365
|
+
entitlement `com.apple.developer.usernotifications.filtering`.
|
|
366
|
+
- **Leaving an action unresolved.** A call action that is neither fulfilled nor
|
|
367
|
+
failed hangs until its timeout fires. Every branch, including network errors
|
|
368
|
+
and cancellation, ends with `fulfill()` or `fail()`.
|
|
369
|
+
- **Caching the push token.** It can change at any moment; a stale server copy
|
|
370
|
+
makes pushes vanish silently. Upload on every `didUpdate` credentials call.
|
|
371
|
+
- **Per-call lookup in Call Directory.** The extension cannot call a web service
|
|
372
|
+
when a call is presented. Fetch ahead, reload, and add sorted sequential
|
|
373
|
+
entries.
|
|
374
|
+
|
|
375
|
+
## Review checklist
|
|
376
|
+
|
|
377
|
+
- [ ] Voice over IP background mode is on
|
|
378
|
+
- [ ] Exactly one `CXProvider`, created at launch and retained
|
|
379
|
+
- [ ] Provider delegate is set before any call is reported
|
|
380
|
+
- [ ] On 26.4+, a `mustReport` of true triggers a report; false allows skipping it
|
|
381
|
+
- [ ] With SDK 13 or newer, each VoIP call push is reported ahead of completion
|
|
382
|
+
- [ ] `apns-expiration` is 0 or a few seconds
|
|
383
|
+
- [ ] After the waking push, hangups and call details use the app's own server channel
|
|
384
|
+
- [ ] Every delegate action ends in `fulfill()` or `fail()`
|
|
385
|
+
- [ ] Answer is fulfilled only when the server and media connection are ready
|
|
386
|
+
- [ ] Audio engine starts after `didActivate` and stops in `didDeactivate`
|
|
387
|
+
- [ ] Audio category is `.playAndRecord` with mode `.voiceChat`
|
|
388
|
+
- [ ] Token uploaded on every `didUpdate` credentials callback
|
|
389
|
+
- [ ] `PKPushRegistry` created on every launch, not lazily
|
|
390
|
+
- [ ] Call Directory data is preloaded, never fetched per call
|
|
391
|
+
- [ ] `CXCallDirectoryPhoneNumber` described as country calling code plus digits
|
|
392
|
+
- [ ] `CXCallDirectoryManager` status check, reload and settings APIs are named
|
|
393
|
+
- [ ] `CXCallUpdate` sets `localizedCallerName` and `remoteHandle`
|
|
394
|
+
- [ ] Dialed calls send both `startedConnectingAt` and `connectedAt` timestamps
|
|
395
|
+
- [ ] Translated calls keep upstream audio live while muted
|
|
396
|
+
- [ ] Encrypted payload filtering lists the filtering entitlement the extension needs
|
|
480
397
|
|
|
481
398
|
## References
|
|
482
399
|
|
|
483
|
-
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
- [
|
|
487
|
-
- [
|
|
488
|
-
- [
|
|
489
|
-
- [
|
|
490
|
-
- [
|
|
491
|
-
- [
|
|
492
|
-
- [
|
|
493
|
-
- [
|
|
494
|
-
- [
|
|
495
|
-
- [
|
|
496
|
-
- [
|
|
497
|
-
- [
|
|
498
|
-
- [
|
|
499
|
-
- [
|
|
500
|
-
- [
|
|
400
|
+
- [Longer patterns](references/callkit-patterns.md): observable manager, hold
|
|
401
|
+
and mute, concurrent calls, call observer, encrypted filtering, incremental
|
|
402
|
+
Call Directory, local testing
|
|
403
|
+
- [CallKit](https://developer.apple.com/documentation/callkit)
|
|
404
|
+
- [CXProvider](https://developer.apple.com/documentation/callkit/cxprovider)
|
|
405
|
+
- [CXCallController](https://developer.apple.com/documentation/callkit/cxcallcontroller)
|
|
406
|
+
- [CXCallAction](https://developer.apple.com/documentation/callkit/cxcallaction)
|
|
407
|
+
- [CXCallUpdate](https://developer.apple.com/documentation/callkit/cxcallupdate)
|
|
408
|
+
- [CXProviderConfiguration](https://developer.apple.com/documentation/callkit/cxproviderconfiguration)
|
|
409
|
+
- [CXProviderDelegate](https://developer.apple.com/documentation/callkit/cxproviderdelegate)
|
|
410
|
+
- [CXSetTranslatingCallAction](https://developer.apple.com/documentation/callkit/cxsettranslatingcallaction)
|
|
411
|
+
- [reportNewIncomingVoIPPushPayload(_:completion:)](https://developer.apple.com/documentation/callkit/cxprovider/reportnewincomingvoippushpayload(_:completion:))
|
|
412
|
+
- [CXCallDirectoryProvider](https://developer.apple.com/documentation/callkit/cxcalldirectoryprovider)
|
|
413
|
+
- [CXCallDirectoryPhoneNumber](https://developer.apple.com/documentation/callkit/cxcalldirectoryphonenumber)
|
|
414
|
+
- [CXCallDirectoryManager](https://developer.apple.com/documentation/callkit/cxcalldirectorymanager)
|
|
415
|
+
- [PKPushRegistry](https://developer.apple.com/documentation/pushkit/pkpushregistry)
|
|
416
|
+
- [PKPushRegistryDelegate](https://developer.apple.com/documentation/pushkit/pkpushregistrydelegate)
|
|
417
|
+
- [PKVoIPPushMetadata](https://developer.apple.com/documentation/pushkit/pkvoippushmetadata)
|
|
418
|
+
- Article: [VoIP call handling with CallKit](https://developer.apple.com/documentation/callkit/making-and-receiving-voip-calls)
|
|
419
|
+
- Article: [PushKit VoIP notification handling](https://developer.apple.com/documentation/pushkit/responding-to-voip-notifications-from-pushkit)
|