@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,444 +1,276 @@
|
|
|
1
|
-
# CallKit
|
|
1
|
+
# CallKit Patterns
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Longer examples that back the `callkit-voip` skill. Read its SKILL.md first;
|
|
4
|
+
this file assumes the provider setup, push rules and audio rules described
|
|
5
|
+
there.
|
|
4
6
|
|
|
5
|
-
##
|
|
7
|
+
## An observable call manager
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- [Multiple Concurrent Calls](#multiple-concurrent-calls)
|
|
10
|
-
- [Call State Tracking](#call-state-tracking)
|
|
11
|
-
- [Encrypted VoIP Push Filtering](#encrypted-voip-push-filtering)
|
|
12
|
-
- [Call Directory Incremental Updates](#call-directory-incremental-updates)
|
|
13
|
-
- [Testing VoIP Locally](#testing-voip-locally)
|
|
14
|
-
|
|
15
|
-
## Full Call Manager
|
|
9
|
+
A `@MainActor` model that SwiftUI can watch. It owns the provider and the
|
|
10
|
+
controller and keeps a table of live calls.
|
|
16
11
|
|
|
17
12
|
```swift
|
|
18
13
|
import CallKit
|
|
19
|
-
import
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
var isMuted: Bool = false
|
|
36
|
-
var isConnected: Bool = false
|
|
37
|
-
}
|
|
14
|
+
import Observation
|
|
15
|
+
|
|
16
|
+
struct LiveCall: Identifiable, Sendable {
|
|
17
|
+
let id: UUID
|
|
18
|
+
let address: String
|
|
19
|
+
let outgoing: Bool
|
|
20
|
+
var held = false
|
|
21
|
+
var muted = false
|
|
22
|
+
var connected = false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@Observable @MainActor
|
|
26
|
+
final class Switchboard: NSObject {
|
|
27
|
+
@ObservationIgnored let provider: CXProvider
|
|
28
|
+
@ObservationIgnored let controller = CXCallController()
|
|
29
|
+
private(set) var calls: [UUID: LiveCall] = [:]
|
|
38
30
|
|
|
39
31
|
override init() {
|
|
40
|
-
let
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
provider = CXProvider(configuration: config)
|
|
32
|
+
let setup = CXProviderConfiguration()
|
|
33
|
+
setup.supportedHandleTypes = [.phoneNumber]
|
|
34
|
+
setup.includesCallsInRecents = true
|
|
35
|
+
setup.maximumCallsPerCallGroup = 1
|
|
36
|
+
setup.maximumCallGroups = 2
|
|
37
|
+
setup.supportsVideo = true
|
|
38
|
+
provider = CXProvider(configuration: setup)
|
|
49
39
|
super.init()
|
|
50
40
|
provider.setDelegate(self, queue: nil)
|
|
51
41
|
}
|
|
52
42
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
update.localizedCallerName = callerName
|
|
64
|
-
update.hasVideo = hasVideo
|
|
65
|
-
update.supportsHolding = true
|
|
66
|
-
update.supportsDTMF = true
|
|
67
|
-
update.supportsGrouping = false
|
|
68
|
-
update.supportsUngrouping = false
|
|
69
|
-
|
|
70
|
-
try await withCheckedThrowingContinuation {
|
|
71
|
-
(continuation: CheckedContinuation<Void, Error>) in
|
|
72
|
-
provider.reportNewIncomingCall(
|
|
73
|
-
with: uuid, update: update
|
|
74
|
-
) { error in
|
|
75
|
-
if let error {
|
|
76
|
-
continuation.resume(throwing: error)
|
|
77
|
-
} else {
|
|
78
|
-
continuation.resume()
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
activeCalls[uuid] = CallInfo(
|
|
84
|
-
uuid: uuid, handle: handle, isOutgoing: false
|
|
85
|
-
)
|
|
43
|
+
func reportIncoming(id: UUID, address: String, name: String) async throws {
|
|
44
|
+
let info = CXCallUpdate()
|
|
45
|
+
info.localizedCallerName = name
|
|
46
|
+
info.remoteHandle = CXHandle(type: .generic, value: address)
|
|
47
|
+
info.supportsDTMF = true
|
|
48
|
+
info.supportsHolding = true
|
|
49
|
+
info.supportsUngrouping = false
|
|
50
|
+
info.supportsGrouping = false
|
|
51
|
+
try await provider.reportNewIncomingCall(with: id, update: info)
|
|
52
|
+
calls[id] = LiveCall(id: id, address: address, outgoing: false)
|
|
86
53
|
}
|
|
87
54
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
let action = CXStartCallAction(call: uuid, handle: cxHandle)
|
|
94
|
-
action.isVideo = hasVideo
|
|
95
|
-
|
|
96
|
-
callController.request(
|
|
97
|
-
CXTransaction(action: action)
|
|
98
|
-
) { error in
|
|
99
|
-
if let error {
|
|
100
|
-
print("Start call failed: \(error)")
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
activeCalls[uuid] = CallInfo(
|
|
105
|
-
uuid: uuid, handle: handle, isOutgoing: true
|
|
106
|
-
)
|
|
55
|
+
func startCall(to address: String) async throws {
|
|
56
|
+
let id = UUID()
|
|
57
|
+
let action = CXStartCallAction(call: id, handle: CXHandle(type: .phoneNumber, value: address))
|
|
58
|
+
try await controller.request(CXTransaction(action: action))
|
|
59
|
+
calls[id] = LiveCall(id: id, address: address, outgoing: true)
|
|
107
60
|
}
|
|
108
61
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
func endCall(uuid: UUID) {
|
|
112
|
-
let action = CXEndCallAction(call: uuid)
|
|
113
|
-
callController.request(CXTransaction(action: action)) { error in
|
|
114
|
-
if let error { print("End call failed: \(error)") }
|
|
115
|
-
}
|
|
62
|
+
func hangUp(_ id: UUID) async throws {
|
|
63
|
+
try await controller.request(CXTransaction(action: CXEndCallAction(call: id)))
|
|
116
64
|
}
|
|
117
65
|
|
|
118
|
-
func setHeld(
|
|
119
|
-
|
|
120
|
-
callController.request(CXTransaction(action: action)) { error in
|
|
121
|
-
if let error { print("Hold failed: \(error)") }
|
|
122
|
-
}
|
|
66
|
+
func setHeld(_ id: UUID, _ held: Bool) async throws {
|
|
67
|
+
try await controller.request(CXTransaction(action: CXSetHeldCallAction(call: id, onHold: held)))
|
|
123
68
|
}
|
|
124
69
|
|
|
125
|
-
func setMuted(
|
|
126
|
-
|
|
127
|
-
callController.request(CXTransaction(action: action)) { error in
|
|
128
|
-
if let error { print("Mute failed: \(error)") }
|
|
129
|
-
}
|
|
70
|
+
func setMuted(_ id: UUID, _ muted: Bool) async throws {
|
|
71
|
+
try await controller.request(CXTransaction(action: CXSetMutedCallAction(call: id, muted: muted)))
|
|
130
72
|
}
|
|
131
73
|
}
|
|
132
74
|
```
|
|
133
75
|
|
|
134
|
-
|
|
76
|
+
A call enters `calls` only after its incoming report succeeds; an outgoing call
|
|
77
|
+
enters after the start transaction was accepted. Hanging up, holding and muting
|
|
78
|
+
all follow the same shape: build the action, wrap it in a `CXTransaction`, send
|
|
79
|
+
it with `request`.
|
|
135
80
|
|
|
136
|
-
|
|
137
|
-
extension VoIPCallManager: CXProviderDelegate {
|
|
138
|
-
nonisolated func providerDidReset(_ provider: CXProvider) {
|
|
139
|
-
Task { @MainActor in
|
|
140
|
-
for uuid in activeCalls.keys {
|
|
141
|
-
disconnectCall(uuid)
|
|
142
|
-
}
|
|
143
|
-
activeCalls.removeAll()
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
nonisolated func provider(
|
|
148
|
-
_ provider: CXProvider,
|
|
149
|
-
perform action: CXSetHeldCallAction
|
|
150
|
-
) {
|
|
151
|
-
Task { @MainActor in
|
|
152
|
-
activeCalls[action.callUUID]?.isOnHold = action.isOnHold
|
|
153
|
-
if action.isOnHold {
|
|
154
|
-
pauseAudio(for: action.callUUID)
|
|
155
|
-
} else {
|
|
156
|
-
resumeAudio(for: action.callUUID)
|
|
157
|
-
}
|
|
158
|
-
action.fulfill()
|
|
159
|
-
}
|
|
160
|
-
}
|
|
81
|
+
## Hold and mute in the delegate
|
|
161
82
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
// If call translation is active, mute app input without deactivating
|
|
169
|
-
// upstream audio that translated audio may need.
|
|
170
|
-
setMicrophoneMuted(action.isMuted)
|
|
171
|
-
action.fulfill()
|
|
172
|
-
}
|
|
173
|
-
}
|
|
83
|
+
The provider calls its delegate on the main queue here (`queue: nil`), so the
|
|
84
|
+
conformance is `@preconcurrency`: the methods stay main-actor isolated and
|
|
85
|
+
touch `calls` directly. Marking them `nonisolated` and hopping back with
|
|
86
|
+
`Task { @MainActor in ... }` does not compile under Swift 6, because the task
|
|
87
|
+
would carry the non-`Sendable` `CXAction` off the delegate call ("sending
|
|
88
|
+
'hold' risks causing data races").
|
|
174
89
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
connectToServer(callUUID: action.callUUID) { success in
|
|
182
|
-
if success {
|
|
183
|
-
activeCalls[action.callUUID]?.isConnected = true
|
|
184
|
-
action.fulfill()
|
|
185
|
-
} else {
|
|
186
|
-
provider.reportCall(
|
|
187
|
-
with: action.callUUID,
|
|
188
|
-
endedAt: Date(),
|
|
189
|
-
reason: .failed
|
|
190
|
-
)
|
|
191
|
-
action.fail()
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
nonisolated func provider(
|
|
198
|
-
_ provider: CXProvider,
|
|
199
|
-
perform action: CXStartCallAction
|
|
200
|
-
) {
|
|
201
|
-
Task { @MainActor in
|
|
202
|
-
configureAudioSession()
|
|
203
|
-
provider.reportOutgoingCall(
|
|
204
|
-
with: action.callUUID,
|
|
205
|
-
startedConnectingAt: Date()
|
|
206
|
-
)
|
|
207
|
-
connectToServer(callUUID: action.callUUID)
|
|
208
|
-
provider.reportOutgoingCall(
|
|
209
|
-
with: action.callUUID,
|
|
210
|
-
connectedAt: Date()
|
|
211
|
-
)
|
|
212
|
-
activeCalls[action.callUUID]?.isConnected = true
|
|
213
|
-
action.fulfill()
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
nonisolated func provider(
|
|
218
|
-
_ provider: CXProvider,
|
|
219
|
-
perform action: CXEndCallAction
|
|
220
|
-
) {
|
|
221
|
-
Task { @MainActor in
|
|
222
|
-
disconnectCall(action.callUUID)
|
|
223
|
-
activeCalls.removeValue(forKey: action.callUUID)
|
|
224
|
-
action.fulfill()
|
|
90
|
+
```swift
|
|
91
|
+
extension Switchboard: @preconcurrency CXProviderDelegate {
|
|
92
|
+
func providerDidReset(_ cx: CXProvider) {
|
|
93
|
+
Task {
|
|
94
|
+
for id in calls.keys { await CallMedia.shared.disconnect(id) }
|
|
95
|
+
calls.removeAll()
|
|
225
96
|
}
|
|
226
97
|
}
|
|
227
98
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
startAudioEngine()
|
|
234
|
-
}
|
|
99
|
+
func provider(_ cx: CXProvider, perform hold: CXSetHeldCallAction) {
|
|
100
|
+
let id = hold.callUUID
|
|
101
|
+
calls[id]?.held = hold.isOnHold
|
|
102
|
+
if hold.isOnHold { CallMedia.shared.pause(id) } else { CallMedia.shared.resume(id) }
|
|
103
|
+
hold.fulfill()
|
|
235
104
|
}
|
|
236
105
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
Task { @MainActor in
|
|
242
|
-
stopAudioEngine()
|
|
243
|
-
}
|
|
106
|
+
func provider(_ cx: CXProvider, perform mute: CXSetMutedCallAction) {
|
|
107
|
+
calls[mute.callUUID]?.muted = mute.isMuted
|
|
108
|
+
CallMedia.shared.setMicrophoneMuted(mute.isMuted)
|
|
109
|
+
mute.fulfill()
|
|
244
110
|
}
|
|
245
111
|
}
|
|
246
112
|
```
|
|
247
113
|
|
|
248
|
-
|
|
114
|
+
Muting only silences the microphone input. Upstream audio keeps flowing,
|
|
115
|
+
because iOS 26 call translation depends on it.
|
|
249
116
|
|
|
250
|
-
|
|
251
|
-
|
|
117
|
+
The answer, start, end, `didActivate` and `didDeactivate` handlers are the same
|
|
118
|
+
as in SKILL.md, with bookkeeping added: set `connected = true` once media
|
|
119
|
+
connects, and remove the entry when the call ends.
|
|
252
120
|
|
|
253
|
-
|
|
254
|
-
nonisolated func provider(
|
|
255
|
-
_ provider: CXProvider,
|
|
256
|
-
perform action: CXSetHeldCallAction
|
|
257
|
-
) {
|
|
258
|
-
Task { @MainActor in
|
|
259
|
-
if action.isOnHold {
|
|
260
|
-
// Pause the RTP stream for this call
|
|
261
|
-
pauseMediaStream(for: action.callUUID)
|
|
262
|
-
} else {
|
|
263
|
-
// Resume the RTP stream
|
|
264
|
-
resumeMediaStream(for: action.callUUID)
|
|
265
|
-
}
|
|
266
|
-
activeCalls[action.callUUID]?.isOnHold = action.isOnHold
|
|
267
|
-
action.fulfill()
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
```
|
|
121
|
+
## Several calls at once
|
|
271
122
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
123
|
+
When a second call rings while one is active and the user accepts it, CallKit
|
|
124
|
+
holds the first call on its own. The app sees a `CXSetHeldCallAction` for the
|
|
125
|
+
first call and must pause or resume its media stream (for example the RTP
|
|
126
|
+
session) in response. How many calls may coexist is set by
|
|
127
|
+
`maximumCallGroups` and `maximumCallsPerCallGroup` in the configuration.
|
|
275
128
|
|
|
276
|
-
##
|
|
129
|
+
## Watching call state
|
|
277
130
|
|
|
278
|
-
|
|
279
|
-
delegate
|
|
131
|
+
`CXCallObserver` reports every call on the device, which is useful outside the
|
|
132
|
+
provider delegate, for example in a status view.
|
|
280
133
|
|
|
281
134
|
```swift
|
|
282
135
|
import CallKit
|
|
283
136
|
|
|
284
|
-
final class
|
|
285
|
-
let
|
|
137
|
+
final class CallWatcher: NSObject, CXCallObserverDelegate {
|
|
138
|
+
private let monitor = CXCallObserver()
|
|
286
139
|
|
|
287
140
|
override init() {
|
|
288
141
|
super.init()
|
|
289
|
-
|
|
142
|
+
monitor.setDelegate(self, queue: .main)
|
|
290
143
|
}
|
|
291
144
|
|
|
292
|
-
func callObserver(
|
|
293
|
-
|
|
294
|
-
callChanged call: CXCall
|
|
295
|
-
) {
|
|
145
|
+
func callObserver(_ watcher: CXCallObserver, callChanged call: CXCall) {
|
|
146
|
+
let phase: String
|
|
296
147
|
if call.hasEnded {
|
|
297
|
-
|
|
148
|
+
phase = "ended"
|
|
298
149
|
} else if call.hasConnected {
|
|
299
|
-
|
|
150
|
+
phase = "connected"
|
|
300
151
|
} else if call.isOutgoing {
|
|
301
|
-
|
|
152
|
+
phase = "dialing"
|
|
302
153
|
} else {
|
|
303
|
-
|
|
154
|
+
phase = "ringing"
|
|
304
155
|
}
|
|
156
|
+
Log.calls.info("\(call.uuid) is \(phase)")
|
|
305
157
|
}
|
|
306
158
|
}
|
|
307
159
|
```
|
|
308
160
|
|
|
309
|
-
##
|
|
161
|
+
## Filtering encrypted VoIP pushes
|
|
310
162
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
is a VoIP call request or some other data. If the server knows the content is
|
|
315
|
-
a VoIP call, send a normal PushKit VoIP push instead.
|
|
163
|
+
When the server sends end-to-end encrypted content and cannot tell whether it
|
|
164
|
+
is a call, a notification service extension decrypts it and hands call
|
|
165
|
+
payloads to CallKit.
|
|
316
166
|
|
|
317
167
|
```swift
|
|
318
|
-
import UserNotifications
|
|
319
168
|
import CallKit
|
|
169
|
+
import UserNotifications
|
|
320
170
|
|
|
321
|
-
final class
|
|
322
|
-
override func didReceive(
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
guard let encryptedPayload = request.content
|
|
328
|
-
.userInfo["encrypted"] as? [AnyHashable: Any] else {
|
|
329
|
-
contentHandler(request.content)
|
|
171
|
+
final class PushFilter: UNNotificationServiceExtension {
|
|
172
|
+
override func didReceive(_ incoming: UNNotificationRequest,
|
|
173
|
+
withContentHandler deliver: @escaping (UNNotificationContent) -> Void) {
|
|
174
|
+
guard let sealed = incoming.content.userInfo["sealed"] as? String,
|
|
175
|
+
let opened = Envelope.open(sealed) else {
|
|
176
|
+
deliver(incoming.content)
|
|
330
177
|
return
|
|
331
178
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
) { error in
|
|
338
|
-
if let error {
|
|
339
|
-
// Show a missed-call notification instead
|
|
340
|
-
let content = UNMutableNotificationContent()
|
|
341
|
-
content.title = "Missed Call"
|
|
342
|
-
content.body = decryptedPayload["callerName"] as? String ?? ""
|
|
343
|
-
contentHandler(content)
|
|
344
|
-
} else {
|
|
345
|
-
// Call was reported; suppress the notification
|
|
346
|
-
contentHandler(UNNotificationContent())
|
|
179
|
+
nonisolated(unsafe) let deliver = deliver
|
|
180
|
+
CXProvider.reportNewIncomingVoIPPushPayload(opened) { failure in
|
|
181
|
+
guard failure != nil else {
|
|
182
|
+
deliver(UNNotificationContent())
|
|
183
|
+
return
|
|
347
184
|
}
|
|
185
|
+
let missed = UNMutableNotificationContent()
|
|
186
|
+
missed.title = String(localized: "Missed call")
|
|
187
|
+
deliver(missed)
|
|
348
188
|
}
|
|
349
189
|
}
|
|
350
190
|
}
|
|
351
191
|
```
|
|
352
192
|
|
|
353
|
-
|
|
193
|
+
Content that was not encrypted passes through untouched. The content handler
|
|
194
|
+
is not `Sendable`, and both forms of `reportNewIncomingVoIPPushPayload` need it
|
|
195
|
+
somewhere concurrent: the completion form runs its callback as `@Sendable`, and
|
|
196
|
+
the async form needs a `Task` whose closure Swift 6 rejects for capturing the
|
|
197
|
+
handler. The `nonisolated(unsafe)` copy lets the `@Sendable` callback hold it. A failed report turns
|
|
198
|
+
into a missed-call notification; a successful one is suppressed with empty
|
|
199
|
+
content. If the server does know the push is a call, skip all of this and use
|
|
200
|
+
an ordinary PushKit VoIP push. Entitlement required for the extension:
|
|
201
|
+
`com.apple.developer.usernotifications.filtering`.
|
|
354
202
|
|
|
355
|
-
## Call Directory
|
|
203
|
+
## Incremental Call Directory updates
|
|
356
204
|
|
|
357
|
-
|
|
358
|
-
|
|
205
|
+
Once the initial complete load has run, the extension can add and remove individual numbers
|
|
206
|
+
instead of rebuilding everything.
|
|
359
207
|
|
|
360
208
|
```swift
|
|
361
|
-
|
|
362
|
-
to context: CXCallDirectoryExtensionContext
|
|
363
|
-
)
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
context.addIdentificationEntry(
|
|
378
|
-
withNextSequentialPhoneNumber: number,
|
|
379
|
-
label: label
|
|
380
|
-
)
|
|
209
|
+
extension SpamListProvider {
|
|
210
|
+
func applyChanges(to context: CXCallDirectoryExtensionContext) {
|
|
211
|
+
let delta = SharedStore.pendingDelta()
|
|
212
|
+
for number in delta.unblocked {
|
|
213
|
+
context.removeBlockingEntry(withPhoneNumber: number)
|
|
214
|
+
}
|
|
215
|
+
for number in delta.unlabeled {
|
|
216
|
+
context.removeIdentificationEntry(withPhoneNumber: number)
|
|
217
|
+
}
|
|
218
|
+
for number in delta.blocked.sorted() {
|
|
219
|
+
context.addBlockingEntry(withNextSequentialPhoneNumber: number)
|
|
220
|
+
}
|
|
221
|
+
for pair in delta.labeled.sorted(by: { $0.key < $1.key }) {
|
|
222
|
+
context.addIdentificationEntry(withNextSequentialPhoneNumber: pair.key, label: pair.value)
|
|
223
|
+
}
|
|
224
|
+
SharedStore.clearDelta()
|
|
381
225
|
}
|
|
382
226
|
}
|
|
383
227
|
```
|
|
384
228
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
## Testing VoIP Locally
|
|
229
|
+
Additions are sorted before they are added. The system runs
|
|
230
|
+
`beginRequest(with:)` when it loads the extension, never per incoming call, so
|
|
231
|
+
any web lookup and dataset sync happens in the containing app before it calls
|
|
232
|
+
`reloadExtension(withIdentifier:)`.
|
|
390
233
|
|
|
391
|
-
|
|
234
|
+
## Testing VoIP locally
|
|
392
235
|
|
|
393
|
-
|
|
394
|
-
|
|
236
|
+
- Fire test pushes with the Push Notifications Console, or with any APNs
|
|
237
|
+
client you run from a terminal. The topic is the bundle identifier with `.voip` appended, for example
|
|
238
|
+
`com.example.dialer.voip`.
|
|
239
|
+
- A test payload needs only an empty `aps` plus your own keys:
|
|
395
240
|
|
|
396
241
|
```json
|
|
397
242
|
{
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
243
|
+
"aps": {},
|
|
244
|
+
"from": "+14155550123",
|
|
245
|
+
"display": "Test Caller",
|
|
246
|
+
"video": false
|
|
402
247
|
}
|
|
403
248
|
```
|
|
404
249
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
For development, you can bypass PushKit and directly call the incoming call
|
|
408
|
-
reporting method:
|
|
250
|
+
- Without a server, a debug-only helper can call the incoming report directly:
|
|
409
251
|
|
|
410
252
|
```swift
|
|
411
253
|
#if DEBUG
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
try? await CallManager.shared.reportIncomingCall(
|
|
416
|
-
uuid: uuid,
|
|
417
|
-
handle: "+15551234567",
|
|
418
|
-
hasVideo: false
|
|
419
|
-
)
|
|
254
|
+
extension Switchboard {
|
|
255
|
+
func simulateIncomingCall() async {
|
|
256
|
+
try? await reportIncoming(id: UUID(), address: "+14155550123", name: "Test Caller")
|
|
420
257
|
}
|
|
421
258
|
}
|
|
422
259
|
#endif
|
|
423
260
|
```
|
|
424
261
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
Verify that the Call Directory extension is enabled:
|
|
262
|
+
- Check the Call Directory extension state from the app:
|
|
428
263
|
|
|
429
264
|
```swift
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
) {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
print("Status unknown")
|
|
440
|
-
@unknown default:
|
|
441
|
-
break
|
|
265
|
+
func checkBlockerStatus() {
|
|
266
|
+
let manager = CXCallDirectoryManager.sharedInstance
|
|
267
|
+
manager.getEnabledStatusForExtension(withIdentifier: "org.sample.dialer.blocker") { state, failure in
|
|
268
|
+
switch state {
|
|
269
|
+
case .enabled: Log.calls.info("blocker active")
|
|
270
|
+
case .disabled: Log.calls.info("ask the user to enable it in Settings")
|
|
271
|
+
case .unknown: Log.calls.info("status unknown: \(String(describing: failure))")
|
|
272
|
+
@unknown default: break
|
|
273
|
+
}
|
|
442
274
|
}
|
|
443
275
|
}
|
|
444
276
|
```
|