@apirtc/expo-apirtc-options-plugin 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -3
- package/build/static/Atomic.swift +37 -0
- package/build/static/DarwinNotificationCenter.swift +29 -0
- package/build/static/ReactNativeApiRTC_RPK.m +14 -0
- package/build/static/ReactNativeApiRTC_RPK.swift +83 -0
- package/build/static/SampleHandler.swift +157 -0
- package/build/static/SampleUploader.swift +146 -0
- package/build/static/SocketConnection.swift +198 -0
- package/build/static/reactNativeApiRTCExpo-Bridging-Header.h +6 -0
- package/build/static/reactNativeApiRTCExpo.entitlements +10 -0
- package/build/withIosBroadcastExtension.d.ts +7 -0
- package/build/withIosBroadcastExtension.js +376 -0
- package/build/withIosRPKFiles.d.ts +6 -0
- package/build/withIosRPKFiles.js +129 -0
- package/build/withPlugin.d.ts +1 -0
- package/build/withPlugin.js +10 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,13 +13,16 @@ npm install @apizee/expo-apirtc-options-plugin-test
|
|
|
13
13
|
|
|
14
14
|
## Usage in app.json
|
|
15
15
|
|
|
16
|
-
You need to declare the plugin in app.json file :
|
|
16
|
+
You need to declare the plugin in app.json file of your application :
|
|
17
17
|
|
|
18
18
|
```js
|
|
19
19
|
module.exports = {
|
|
20
20
|
...
|
|
21
21
|
plugins: [
|
|
22
|
-
['@apizee/expo-apirtc-options-plugin-test', {
|
|
22
|
+
['@apizee/expo-apirtc-options-plugin-test', {
|
|
23
|
+
enableMediaProjectionService: false,
|
|
24
|
+
appleTeamId: "YOUR_TEAM_ID"
|
|
25
|
+
}]
|
|
23
26
|
],
|
|
24
27
|
};
|
|
25
28
|
```
|
|
@@ -29,14 +32,31 @@ module.exports = {
|
|
|
29
32
|
| Parameter | Description | Default value |
|
|
30
33
|
| --- | --- | --- |
|
|
31
34
|
| enableMediaProjectionService | Can be used to deactivate screenSharing activation on Android | true |
|
|
35
|
+
| appleTeamId | used to define appleTeamId in your application project and screensharing extension | "APPLE_TEAM_ID_NOT_SET" |
|
|
32
36
|
|
|
37
|
+
Example :
|
|
33
38
|
|
|
34
39
|
```js
|
|
35
40
|
module.exports = {
|
|
36
41
|
...
|
|
37
42
|
plugins: [
|
|
38
|
-
['@apizee/expo-apirtc-options-plugin-test', {
|
|
43
|
+
['@apizee/expo-apirtc-options-plugin-test', {
|
|
44
|
+
enableMediaProjectionService: false,
|
|
45
|
+
appleTeamId: "YOUR_TEAM_ID"
|
|
46
|
+
}]
|
|
39
47
|
],
|
|
40
48
|
};
|
|
41
49
|
```
|
|
42
50
|
|
|
51
|
+
### What does this plugin do ? :
|
|
52
|
+
|
|
53
|
+
On Android :
|
|
54
|
+
|
|
55
|
+
- Add needed permission in your app AndroidManifest file for screenSharing
|
|
56
|
+
- Manage the case where the application is killed by user when a screenShare is enabled :
|
|
57
|
+
A native module AppLifecycleModule is added in your application to manage the case where the user kill the application using swipe.
|
|
58
|
+
This module detect the onHostDestroy event to stop the screenSharing extension and unpublish stream.
|
|
59
|
+
|
|
60
|
+
On iOS :
|
|
61
|
+
- Add broadcast extension for screenSharing feature
|
|
62
|
+
- Configure project with needed modification for screenSharing feature
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Atomic.swift
|
|
3
|
+
// Broadcast Extension
|
|
4
|
+
//
|
|
5
|
+
// Created by Maksym Shcheglov.
|
|
6
|
+
// https://www.onswiftwings.com/posts/atomic-property-wrapper/
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import Foundation
|
|
10
|
+
|
|
11
|
+
@propertyWrapper
|
|
12
|
+
struct Atomic<Value> {
|
|
13
|
+
|
|
14
|
+
private var value: Value
|
|
15
|
+
private let lock = NSLock()
|
|
16
|
+
|
|
17
|
+
init(wrappedValue value: Value) {
|
|
18
|
+
self.value = value
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
var wrappedValue: Value {
|
|
22
|
+
get { load() }
|
|
23
|
+
set { store(newValue: newValue) }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func load() -> Value {
|
|
27
|
+
lock.lock()
|
|
28
|
+
defer { lock.unlock() }
|
|
29
|
+
return value
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
mutating func store(newValue: Value) {
|
|
33
|
+
lock.lock()
|
|
34
|
+
defer { lock.unlock() }
|
|
35
|
+
value = newValue
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//
|
|
2
|
+
// DarwinNotificationCenter.swift
|
|
3
|
+
// Broadcast Extension
|
|
4
|
+
//
|
|
5
|
+
// Created by Alex-Dan Bumbu on 23/03/2021.
|
|
6
|
+
// Copyright © 2021 8x8, Inc. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import Foundation
|
|
10
|
+
|
|
11
|
+
enum DarwinNotification: String {
|
|
12
|
+
case broadcastStarted = "iOS_BroadcastStarted"
|
|
13
|
+
case broadcastStopped = "iOS_BroadcastStopped"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class DarwinNotificationCenter {
|
|
17
|
+
|
|
18
|
+
static let shared = DarwinNotificationCenter()
|
|
19
|
+
|
|
20
|
+
private let notificationCenter: CFNotificationCenter
|
|
21
|
+
|
|
22
|
+
init() {
|
|
23
|
+
notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func postNotification(_ name: DarwinNotification) {
|
|
27
|
+
CFNotificationCenterPostNotification(notificationCenter, CFNotificationName(rawValue: name.rawValue as CFString), nil, nil, true)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//
|
|
2
|
+
// ReactNativeApiRTC_RPK.m
|
|
3
|
+
// reactNativeApiRTC
|
|
4
|
+
//
|
|
5
|
+
// Created by Fred on 22/04/2024.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import <Foundation/Foundation.h>
|
|
9
|
+
#import <React/RCTBridgeModule.h>
|
|
10
|
+
#import "React/RCTEventEmitter.h"
|
|
11
|
+
|
|
12
|
+
@interface RCT_EXTERN_MODULE(ReactNativeApiRTC_RPK, RCTEventEmitter)
|
|
13
|
+
RCT_EXTERN_METHOD(sendBroadcastNeedToBeStopped)
|
|
14
|
+
@end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
//
|
|
2
|
+
// ReactNativeApiRTC_RPK.swift
|
|
3
|
+
// reactNativeApiRTC
|
|
4
|
+
//
|
|
5
|
+
// Created by Fred on 22/04/2024.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import ReplayKit
|
|
10
|
+
import Photos
|
|
11
|
+
import UIKit
|
|
12
|
+
|
|
13
|
+
@objc(ReactNativeApiRTC_RPK)
|
|
14
|
+
class ReactNativeApiRTC_RPK: RCTEventEmitter {
|
|
15
|
+
|
|
16
|
+
private var status = "Empty"
|
|
17
|
+
|
|
18
|
+
var start_notification_callback: CFNotificationCallback = { center, observer, name, object, info in
|
|
19
|
+
NotificationCenter.default.post(name: Notification.Name("START_BROADCAST"), object: nil)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
var stop_notification_callback: CFNotificationCallback = { center, observer, name, object, info in
|
|
23
|
+
NotificationCenter.default.post(name: Notification.Name("STOP_BROADCAST"), object: nil)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
override init() {
|
|
27
|
+
super.init()
|
|
28
|
+
NotificationCenter.default.addObserver(self, selector: #selector(self.startBroadcastCallback(notification:)), name: Notification.Name("START_BROADCAST"), object: nil)
|
|
29
|
+
|
|
30
|
+
NotificationCenter.default.addObserver(self, selector: #selector(self.stopBroadcastCallback(notification:)), name: Notification.Name("STOP_BROADCAST"), object: nil)
|
|
31
|
+
|
|
32
|
+
let notificationStartIdentifier = "com.reactnativeapirtc.notification.broadcaststart" as CFString
|
|
33
|
+
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
|
34
|
+
|
|
35
|
+
CFNotificationCenterAddObserver(notificationCenter,
|
|
36
|
+
nil,
|
|
37
|
+
start_notification_callback,
|
|
38
|
+
notificationStartIdentifier,
|
|
39
|
+
nil,
|
|
40
|
+
CFNotificationSuspensionBehavior.deliverImmediately)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
let notificationStopIdentifier = "com.reactnativeapirtc.notification.broadcaststop" as CFString
|
|
44
|
+
|
|
45
|
+
CFNotificationCenterAddObserver(notificationCenter,
|
|
46
|
+
nil,
|
|
47
|
+
stop_notification_callback,
|
|
48
|
+
notificationStopIdentifier,
|
|
49
|
+
nil,
|
|
50
|
+
CFNotificationSuspensionBehavior.deliverImmediately)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@objc func startBroadcastCallback(notification: NSNotification){
|
|
54
|
+
status = "START_BROADCAST"
|
|
55
|
+
sendEvent(withName: "onScreenShare", body: status)
|
|
56
|
+
status="STARTED_BROADCASTING"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@objc func stopBroadcastCallback(notification: NSNotification){
|
|
60
|
+
status = "STOP_BROADCAST"
|
|
61
|
+
sendEvent(withName: "onScreenShare", body: status)
|
|
62
|
+
status = "Empty"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//This function is used to send a notification to the extension to stop the broadcast
|
|
66
|
+
@objc
|
|
67
|
+
func sendBroadcastNeedToBeStopped() {
|
|
68
|
+
//Sending event to screen sharing extension
|
|
69
|
+
let notificationName = CFNotificationName("com.reactnativeapirtc.notification.broadcastneedtobestopped" as CFString)
|
|
70
|
+
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
|
71
|
+
CFNotificationCenterPostNotification(notificationCenter, notificationName, nil, nil, true)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
override func supportedEvents() -> [String]! {
|
|
75
|
+
return ["onScreenShare"]
|
|
76
|
+
}
|
|
77
|
+
override func constantsToExport() -> [AnyHashable : Any]! {
|
|
78
|
+
return ["initialCount": status]
|
|
79
|
+
}
|
|
80
|
+
override static func requiresMainQueueSetup() -> Bool {
|
|
81
|
+
return true
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SampleHandler.swift
|
|
3
|
+
// screenSharing_Extension
|
|
4
|
+
//
|
|
5
|
+
// Created by Fred on 17/04/2024.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import ReplayKit
|
|
9
|
+
//import OSLog
|
|
10
|
+
|
|
11
|
+
private enum Constants {
|
|
12
|
+
// the App Group ID value that the app and the broadcast extension targets are setup with. It differs for each app.
|
|
13
|
+
static let appGroupIdentifier = "group.apirtc.reactNativeApiRTC.broadcast"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class SampleHandler: RPBroadcastSampleHandler {
|
|
17
|
+
|
|
18
|
+
//var logger = Logger()
|
|
19
|
+
|
|
20
|
+
private var clientConnection: SocketConnection?
|
|
21
|
+
private var uploader: SampleUploader?
|
|
22
|
+
|
|
23
|
+
private var frameCount: Int = 0
|
|
24
|
+
|
|
25
|
+
var socketFilePath: String {
|
|
26
|
+
let sharedContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.appGroupIdentifier)
|
|
27
|
+
return sharedContainer?.appendingPathComponent("rtc_SSFD").path ?? ""
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
var needtobestopped_notification_callback: CFNotificationCallback = { center, observer, name, object, info in
|
|
31
|
+
NotificationCenter.default.post(name: Notification.Name("NEED_TO_BE_STOPPED_BROADCAST"), object: nil)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override init() {
|
|
35
|
+
super.init()
|
|
36
|
+
if let connection = SocketConnection(filePath: socketFilePath) {
|
|
37
|
+
clientConnection = connection
|
|
38
|
+
setupConnection()
|
|
39
|
+
uploader = SampleUploader(connection: connection)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//Setting observer for broadcastneedtobestopped notification from application
|
|
43
|
+
NotificationCenter.default.addObserver(self, selector: #selector(self.needToBeStoppedBroadcastCallback(notification:)), name: Notification.Name("NEED_TO_BE_STOPPED_BROADCAST"), object: nil)
|
|
44
|
+
|
|
45
|
+
let notificationStartIdentifier = "com.reactnativeapirtc.notification.broadcastneedtobestopped" as CFString
|
|
46
|
+
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
|
47
|
+
|
|
48
|
+
CFNotificationCenterAddObserver(notificationCenter,
|
|
49
|
+
nil,
|
|
50
|
+
needtobestopped_notification_callback,
|
|
51
|
+
notificationStartIdentifier,
|
|
52
|
+
nil,
|
|
53
|
+
CFNotificationSuspensionBehavior.deliverImmediately)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
override func broadcastStarted(withSetupInfo setupInfo: [String : NSObject]?) {
|
|
57
|
+
// User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.
|
|
58
|
+
//logger.error("QQQ: broadcastStarted")
|
|
59
|
+
frameCount = 0
|
|
60
|
+
|
|
61
|
+
DarwinNotificationCenter.shared.postNotification(.broadcastStarted)
|
|
62
|
+
openConnection()
|
|
63
|
+
|
|
64
|
+
//Sending event to application
|
|
65
|
+
let notificationName = CFNotificationName("com.reactnativeapirtc.notification.broadcaststart" as CFString)
|
|
66
|
+
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
|
67
|
+
CFNotificationCenterPostNotification(notificationCenter, notificationName, nil, nil, true)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override func broadcastPaused() {
|
|
71
|
+
// User has requested to pause the broadcast. Samples will stop being delivered.
|
|
72
|
+
//logger.error("QQQ: broadcastPaused")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override func broadcastResumed() {
|
|
76
|
+
// User has requested to resume the broadcast. Samples delivery will resume.
|
|
77
|
+
//logger.error("QQQ: broadcastResumed")
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
override func broadcastFinished() {
|
|
81
|
+
// User has requested to finish the broadcast.
|
|
82
|
+
//logger.error("QQQ: broadcastFinished")
|
|
83
|
+
|
|
84
|
+
DarwinNotificationCenter.shared.postNotification(.broadcastStopped)
|
|
85
|
+
clientConnection?.close()
|
|
86
|
+
|
|
87
|
+
let notificationName = CFNotificationName("com.reactnativeapirtc.notification.broadcaststop" as CFString)
|
|
88
|
+
let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
|
|
89
|
+
CFNotificationCenterPostNotification(notificationCenter, notificationName, nil, nil, true)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
|
|
93
|
+
switch sampleBufferType {
|
|
94
|
+
case RPSampleBufferType.video:
|
|
95
|
+
// Handle video sample buffer
|
|
96
|
+
|
|
97
|
+
// very simple mechanism for adjusting frame rate by using every third frame
|
|
98
|
+
frameCount += 1
|
|
99
|
+
if frameCount % 3 == 0 {
|
|
100
|
+
uploader?.send(sample: sampleBuffer)
|
|
101
|
+
}
|
|
102
|
+
break
|
|
103
|
+
case RPSampleBufferType.audioApp:
|
|
104
|
+
// Handle audio sample buffer for app audio
|
|
105
|
+
break
|
|
106
|
+
case RPSampleBufferType.audioMic:
|
|
107
|
+
// Handle audio sample buffer for mic audio
|
|
108
|
+
break
|
|
109
|
+
@unknown default:
|
|
110
|
+
// Handle other sample buffer types
|
|
111
|
+
fatalError("Unknown type of sample buffer")
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private extension SampleHandler {
|
|
117
|
+
|
|
118
|
+
func setupConnection() {
|
|
119
|
+
clientConnection?.didClose = { [weak self] error in
|
|
120
|
+
print("client connection did close \(String(describing: error))")
|
|
121
|
+
|
|
122
|
+
if let error = error {
|
|
123
|
+
self?.finishBroadcastWithError(error)
|
|
124
|
+
} else {
|
|
125
|
+
// the displayed failure message is more user friendly when using NSError instead of Error
|
|
126
|
+
let JMScreenSharingStopped = 10001
|
|
127
|
+
let customError = NSError(domain: RPRecordingErrorDomain, code: JMScreenSharingStopped, userInfo: [NSLocalizedDescriptionKey: "Screen sharing stopped"])
|
|
128
|
+
self?.finishBroadcastWithError(customError)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
func openConnection() {
|
|
134
|
+
let queue = DispatchQueue(label: "broadcast.connectTimer")
|
|
135
|
+
let timer = DispatchSource.makeTimerSource(queue: queue)
|
|
136
|
+
timer.schedule(deadline: .now(), repeating: .milliseconds(100), leeway: .milliseconds(500))
|
|
137
|
+
timer.setEventHandler { [weak self] in
|
|
138
|
+
guard self?.clientConnection?.open() == true else {
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
timer.cancel()
|
|
142
|
+
}
|
|
143
|
+
timer.resume()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@objc func needToBeStoppedBroadcastCallback(notification: NSNotification){
|
|
147
|
+
//logger.error("QQQ: needToBeStoppedBroadcastCallback")
|
|
148
|
+
|
|
149
|
+
//needToBeStoppedBroadcast notification received from the application
|
|
150
|
+
//stopping the broadcast extension with finishBroadcastWithError()
|
|
151
|
+
|
|
152
|
+
// the displayed failure message is more user friendly when using NSError instead of Error
|
|
153
|
+
let JMScreenSharingStopped = 10001
|
|
154
|
+
let customError = NSError(domain: RPRecordingErrorDomain, code: JMScreenSharingStopped, userInfo: [NSLocalizedDescriptionKey: "User has requested screen sharing to be stopped"])
|
|
155
|
+
self.finishBroadcastWithError(customError)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SampleUploader.swift
|
|
3
|
+
// Broadcast Extension
|
|
4
|
+
//
|
|
5
|
+
// Created by Alex-Dan Bumbu on 22/03/2021.
|
|
6
|
+
// Copyright © 2021 8x8, Inc. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import Foundation
|
|
10
|
+
import ReplayKit
|
|
11
|
+
|
|
12
|
+
private enum Constants {
|
|
13
|
+
static let bufferMaxLength = 10240
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class SampleUploader {
|
|
17
|
+
|
|
18
|
+
private static var imageContext = CIContext(options: nil)
|
|
19
|
+
|
|
20
|
+
@Atomic private var isReady = false
|
|
21
|
+
private var connection: SocketConnection
|
|
22
|
+
|
|
23
|
+
private var dataToSend: Data?
|
|
24
|
+
private var byteIndex = 0
|
|
25
|
+
|
|
26
|
+
private let serialQueue: DispatchQueue
|
|
27
|
+
|
|
28
|
+
init(connection: SocketConnection) {
|
|
29
|
+
self.connection = connection
|
|
30
|
+
self.serialQueue = DispatchQueue(label: "org.jitsi.meet.broadcast.sampleUploader")
|
|
31
|
+
|
|
32
|
+
setupConnection()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@discardableResult func send(sample buffer: CMSampleBuffer) -> Bool {
|
|
36
|
+
guard isReady else {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
isReady = false
|
|
41
|
+
|
|
42
|
+
dataToSend = prepare(sample: buffer)
|
|
43
|
+
byteIndex = 0
|
|
44
|
+
|
|
45
|
+
serialQueue.async { [weak self] in
|
|
46
|
+
self?.sendDataChunk()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return true
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private extension SampleUploader {
|
|
54
|
+
|
|
55
|
+
func setupConnection() {
|
|
56
|
+
connection.didOpen = { [weak self] in
|
|
57
|
+
self?.isReady = true
|
|
58
|
+
}
|
|
59
|
+
connection.streamHasSpaceAvailable = { [weak self] in
|
|
60
|
+
self?.serialQueue.async {
|
|
61
|
+
if let success = self?.sendDataChunk() {
|
|
62
|
+
self?.isReady = !success
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@discardableResult func sendDataChunk() -> Bool {
|
|
69
|
+
guard let dataToSend = dataToSend else {
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
var bytesLeft = dataToSend.count - byteIndex
|
|
74
|
+
var length = bytesLeft > Constants.bufferMaxLength ? Constants.bufferMaxLength : bytesLeft
|
|
75
|
+
|
|
76
|
+
length = dataToSend[byteIndex..<(byteIndex + length)].withUnsafeBytes {
|
|
77
|
+
guard let ptr = $0.bindMemory(to: UInt8.self).baseAddress else {
|
|
78
|
+
return 0
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return connection.writeToStream(buffer: ptr, maxLength: length)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if length > 0 {
|
|
85
|
+
byteIndex += length
|
|
86
|
+
bytesLeft -= length
|
|
87
|
+
|
|
88
|
+
if bytesLeft == 0 {
|
|
89
|
+
self.dataToSend = nil
|
|
90
|
+
byteIndex = 0
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
print("writeBufferToStream failure")
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return true
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func prepare(sample buffer: CMSampleBuffer) -> Data? {
|
|
100
|
+
guard let imageBuffer = CMSampleBufferGetImageBuffer(buffer) else {
|
|
101
|
+
print("image buffer not available")
|
|
102
|
+
return nil
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
CVPixelBufferLockBaseAddress(imageBuffer, .readOnly)
|
|
106
|
+
|
|
107
|
+
let scaleFactor = 2.0
|
|
108
|
+
let width = CVPixelBufferGetWidth(imageBuffer)/Int(scaleFactor)
|
|
109
|
+
let height = CVPixelBufferGetHeight(imageBuffer)/Int(scaleFactor)
|
|
110
|
+
let orientation = CMGetAttachment(buffer, key: RPVideoSampleOrientationKey as CFString, attachmentModeOut: nil)?.uintValue ?? 0
|
|
111
|
+
|
|
112
|
+
let scaleTransform = CGAffineTransform(scaleX: CGFloat(1.0/scaleFactor), y: CGFloat(1.0/scaleFactor))
|
|
113
|
+
let bufferData = self.jpegData(from: imageBuffer, scale: scaleTransform)
|
|
114
|
+
|
|
115
|
+
CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly)
|
|
116
|
+
|
|
117
|
+
guard let messageData = bufferData else {
|
|
118
|
+
print("corrupted image buffer")
|
|
119
|
+
return nil
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let httpResponse = CFHTTPMessageCreateResponse(nil, 200, nil, kCFHTTPVersion1_1).takeRetainedValue()
|
|
123
|
+
CFHTTPMessageSetHeaderFieldValue(httpResponse, "Content-Length" as CFString, String(messageData.count) as CFString)
|
|
124
|
+
CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Width" as CFString, String(width) as CFString)
|
|
125
|
+
CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Height" as CFString, String(height) as CFString)
|
|
126
|
+
CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Orientation" as CFString, String(orientation) as CFString)
|
|
127
|
+
|
|
128
|
+
CFHTTPMessageSetBody(httpResponse, messageData as CFData)
|
|
129
|
+
|
|
130
|
+
let serializedMessage = CFHTTPMessageCopySerializedMessage(httpResponse)?.takeRetainedValue() as Data?
|
|
131
|
+
|
|
132
|
+
return serializedMessage
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
func jpegData(from buffer: CVPixelBuffer, scale scaleTransform: CGAffineTransform) -> Data? {
|
|
136
|
+
let image = CIImage(cvPixelBuffer: buffer).transformed(by: scaleTransform)
|
|
137
|
+
|
|
138
|
+
guard let colorSpace = image.colorSpace else {
|
|
139
|
+
return nil
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let options: [CIImageRepresentationOption: Float] = [kCGImageDestinationLossyCompressionQuality as CIImageRepresentationOption: 1.0]
|
|
143
|
+
|
|
144
|
+
return SampleUploader.imageContext.jpegRepresentation(of: image, colorSpace: colorSpace, options: options)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SocketConnection.swift
|
|
3
|
+
// Broadcast Extension
|
|
4
|
+
//
|
|
5
|
+
// Created by Alex-Dan Bumbu on 22/03/2021.
|
|
6
|
+
// Copyright © 2021 Atlassian Inc. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import Foundation
|
|
10
|
+
|
|
11
|
+
class SocketConnection: NSObject {
|
|
12
|
+
var didOpen: (() -> Void)?
|
|
13
|
+
var didClose: ((Error?) -> Void)?
|
|
14
|
+
var streamHasSpaceAvailable: (() -> Void)?
|
|
15
|
+
|
|
16
|
+
private let filePath: String
|
|
17
|
+
private var socketHandle: Int32 = -1
|
|
18
|
+
private var address: sockaddr_un?
|
|
19
|
+
|
|
20
|
+
private var inputStream: InputStream?
|
|
21
|
+
private var outputStream: OutputStream?
|
|
22
|
+
|
|
23
|
+
private var networkQueue: DispatchQueue?
|
|
24
|
+
private var shouldKeepRunning = false
|
|
25
|
+
|
|
26
|
+
init?(filePath path: String) {
|
|
27
|
+
filePath = path
|
|
28
|
+
socketHandle = Darwin.socket(AF_UNIX, SOCK_STREAM, 0)
|
|
29
|
+
|
|
30
|
+
guard socketHandle != -1 else {
|
|
31
|
+
print("failure: create socket")
|
|
32
|
+
return nil
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
func open() -> Bool {
|
|
37
|
+
print("open socket connection")
|
|
38
|
+
|
|
39
|
+
guard FileManager.default.fileExists(atPath: filePath) else {
|
|
40
|
+
print("failure: socket file missing")
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
guard setupAddress() == true else {
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
guard connectSocket() == true else {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
setupStreams()
|
|
53
|
+
|
|
54
|
+
inputStream?.open()
|
|
55
|
+
outputStream?.open()
|
|
56
|
+
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func close() {
|
|
61
|
+
unscheduleStreams()
|
|
62
|
+
|
|
63
|
+
inputStream?.delegate = nil
|
|
64
|
+
outputStream?.delegate = nil
|
|
65
|
+
|
|
66
|
+
inputStream?.close()
|
|
67
|
+
outputStream?.close()
|
|
68
|
+
|
|
69
|
+
inputStream = nil
|
|
70
|
+
outputStream = nil
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func writeToStream(buffer: UnsafePointer<UInt8>, maxLength length: Int) -> Int {
|
|
74
|
+
outputStream?.write(buffer, maxLength: length) ?? 0
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
extension SocketConnection: StreamDelegate {
|
|
79
|
+
|
|
80
|
+
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
|
|
81
|
+
switch eventCode {
|
|
82
|
+
case .openCompleted:
|
|
83
|
+
print("client stream open completed")
|
|
84
|
+
if aStream == outputStream {
|
|
85
|
+
didOpen?()
|
|
86
|
+
}
|
|
87
|
+
case .hasBytesAvailable:
|
|
88
|
+
if aStream == inputStream {
|
|
89
|
+
var buffer: UInt8 = 0
|
|
90
|
+
let numberOfBytesRead = inputStream?.read(&buffer, maxLength: 1)
|
|
91
|
+
if numberOfBytesRead == 0 && aStream.streamStatus == .atEnd {
|
|
92
|
+
print("server socket closed")
|
|
93
|
+
close()
|
|
94
|
+
notifyDidClose(error: nil)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
case .hasSpaceAvailable:
|
|
98
|
+
if aStream == outputStream {
|
|
99
|
+
streamHasSpaceAvailable?()
|
|
100
|
+
}
|
|
101
|
+
case .errorOccurred:
|
|
102
|
+
print("client stream error occured: \(String(describing: aStream.streamError))")
|
|
103
|
+
close()
|
|
104
|
+
notifyDidClose(error: aStream.streamError)
|
|
105
|
+
|
|
106
|
+
default:
|
|
107
|
+
break
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private extension SocketConnection {
|
|
113
|
+
|
|
114
|
+
func setupAddress() -> Bool {
|
|
115
|
+
var addr = sockaddr_un()
|
|
116
|
+
guard filePath.count < MemoryLayout.size(ofValue: addr.sun_path) else {
|
|
117
|
+
print("failure: fd path is too long")
|
|
118
|
+
return false
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_ = withUnsafeMutablePointer(to: &addr.sun_path.0) { ptr in
|
|
122
|
+
filePath.withCString {
|
|
123
|
+
strncpy(ptr, $0, filePath.count)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
address = addr
|
|
128
|
+
return true
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
func connectSocket() -> Bool {
|
|
132
|
+
guard var addr = address else {
|
|
133
|
+
return false
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let status = withUnsafePointer(to: &addr) { ptr in
|
|
137
|
+
ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) {
|
|
138
|
+
Darwin.connect(socketHandle, $0, socklen_t(MemoryLayout<sockaddr_un>.size))
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
guard status == noErr else {
|
|
143
|
+
print("failure: \(status)")
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return true
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
func setupStreams() {
|
|
151
|
+
var readStream: Unmanaged<CFReadStream>?
|
|
152
|
+
var writeStream: Unmanaged<CFWriteStream>?
|
|
153
|
+
|
|
154
|
+
CFStreamCreatePairWithSocket(kCFAllocatorDefault, socketHandle, &readStream, &writeStream)
|
|
155
|
+
|
|
156
|
+
inputStream = readStream?.takeRetainedValue()
|
|
157
|
+
inputStream?.delegate = self
|
|
158
|
+
inputStream?.setProperty(kCFBooleanTrue, forKey: Stream.PropertyKey(kCFStreamPropertyShouldCloseNativeSocket as String))
|
|
159
|
+
|
|
160
|
+
outputStream = writeStream?.takeRetainedValue()
|
|
161
|
+
outputStream?.delegate = self
|
|
162
|
+
outputStream?.setProperty(kCFBooleanTrue, forKey: Stream.PropertyKey(kCFStreamPropertyShouldCloseNativeSocket as String))
|
|
163
|
+
|
|
164
|
+
scheduleStreams()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
func scheduleStreams() {
|
|
168
|
+
shouldKeepRunning = true
|
|
169
|
+
|
|
170
|
+
networkQueue = DispatchQueue.global(qos: .userInitiated)
|
|
171
|
+
networkQueue?.async { [weak self] in
|
|
172
|
+
self?.inputStream?.schedule(in: .current, forMode: .common)
|
|
173
|
+
self?.outputStream?.schedule(in: .current, forMode: .common)
|
|
174
|
+
RunLoop.current.run()
|
|
175
|
+
|
|
176
|
+
var isRunning = false
|
|
177
|
+
|
|
178
|
+
repeat {
|
|
179
|
+
isRunning = self?.shouldKeepRunning ?? false && RunLoop.current.run(mode: .default, before: .distantFuture)
|
|
180
|
+
} while (isRunning)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
func unscheduleStreams() {
|
|
185
|
+
networkQueue?.sync { [weak self] in
|
|
186
|
+
self?.inputStream?.remove(from: .current, forMode: .common)
|
|
187
|
+
self?.outputStream?.remove(from: .current, forMode: .common)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
shouldKeepRunning = false
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
func notifyDidClose(error: Error?) {
|
|
194
|
+
if didClose != nil {
|
|
195
|
+
didClose?(error)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>com.apple.security.application-groups</key>
|
|
6
|
+
<array>
|
|
7
|
+
<string>group.reactnativeexpo.apirtc.com.screenSharing-Extension</string>
|
|
8
|
+
</array>
|
|
9
|
+
</dict>
|
|
10
|
+
</plist>
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const config_plugins_1 = require("@expo/config-plugins");
|
|
40
|
+
const plist_1 = __importDefault(require("@expo/plist"));
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const util = __importStar(require("util"));
|
|
44
|
+
const quoted = (str) => {
|
|
45
|
+
return util.format(`"%s"`, str);
|
|
46
|
+
};
|
|
47
|
+
const withIosBroadcastExtension = (config, props) => {
|
|
48
|
+
console.log('withIosBroadcastExtension called with props:', props);
|
|
49
|
+
config = withAppEntitlements(config);
|
|
50
|
+
config = withInfoPlistRTC(config);
|
|
51
|
+
config = withBroadcastExtensionXcodeTarget(config, props);
|
|
52
|
+
config = withBroadcastExtensionPlist(config);
|
|
53
|
+
//TODO Suite à relire
|
|
54
|
+
return config;
|
|
55
|
+
};
|
|
56
|
+
const withAppEntitlements = (config) => {
|
|
57
|
+
config = (0, config_plugins_1.withEntitlementsPlist)(config, (config) => {
|
|
58
|
+
const appGroupIdentifier = `group.${config.ios.bundleIdentifier}.screenSharing-Extension`;
|
|
59
|
+
config.modResults['com.apple.security.application-groups'] = [
|
|
60
|
+
appGroupIdentifier,
|
|
61
|
+
];
|
|
62
|
+
return config;
|
|
63
|
+
});
|
|
64
|
+
return config;
|
|
65
|
+
};
|
|
66
|
+
const withInfoPlistRTC = (config) => {
|
|
67
|
+
return (0, config_plugins_1.withInfoPlist)(config, (config) => {
|
|
68
|
+
const appGroupIdentifier = `group.${config.ios.bundleIdentifier}.screenSharing-Extension`;
|
|
69
|
+
const extensionBundleIdentifier = `${config.ios.bundleIdentifier}.screenSharing-Extension`;
|
|
70
|
+
config.modResults['RTCAppGroupIdentifier'] = appGroupIdentifier;
|
|
71
|
+
config.modResults['RTCScreenSharingExtension'] = extensionBundleIdentifier;
|
|
72
|
+
if (!config.modResults['UIBackgroundModes']) {
|
|
73
|
+
config.modResults['UIBackgroundModes'] = [];
|
|
74
|
+
}
|
|
75
|
+
config.modResults['UIBackgroundModes'].push('voip');
|
|
76
|
+
config.modResults['UIBackgroundModes'].push('audio');
|
|
77
|
+
config.modResults['UIBackgroundModes'].push('fetch');
|
|
78
|
+
config.modResults['UIBackgroundModes'].push('processing');
|
|
79
|
+
config.modResults['UIBackgroundModes'].push('remote-notification');
|
|
80
|
+
config.modResults['NSCameraUsageDescription'] = 'Camera permission description F';
|
|
81
|
+
config.modResults['NSMicrophoneUsageDescription'] = 'Microphone permission description F';
|
|
82
|
+
return config;
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
const withBroadcastExtensionPlist = (config) => {
|
|
86
|
+
return (0, config_plugins_1.withDangerousMod)(config, [
|
|
87
|
+
'ios',
|
|
88
|
+
async (config) => {
|
|
89
|
+
const extensionRootPath = path.join(config.modRequest.platformProjectRoot, 'screenSharing_Extension');
|
|
90
|
+
const extensionPlistPath = path.join(extensionRootPath, 'Info.plist');
|
|
91
|
+
const extensionPlist = {
|
|
92
|
+
NSExtension: {
|
|
93
|
+
NSExtensionPointIdentifier: 'com.apple.broadcast-services-upload',
|
|
94
|
+
NSExtensionPrincipalClass: '$(PRODUCT_MODULE_NAME).SampleHandler',
|
|
95
|
+
RPBroadcastProcessMode: 'RPBroadcastProcessModeSampleBuffer',
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
await fs.promises.mkdir(path.dirname(extensionPlistPath), {
|
|
99
|
+
recursive: true,
|
|
100
|
+
});
|
|
101
|
+
await fs.promises.writeFile(extensionPlistPath, plist_1.default.build(extensionPlist));
|
|
102
|
+
return config;
|
|
103
|
+
},
|
|
104
|
+
]);
|
|
105
|
+
};
|
|
106
|
+
const withBroadcastExtensionXcodeTarget = (config, props) => {
|
|
107
|
+
return (0, config_plugins_1.withXcodeProject)(config, async (config) => {
|
|
108
|
+
const appName = config.modRequest.projectName;
|
|
109
|
+
const extensionName = 'screenSharing_Extension';
|
|
110
|
+
const extensionBundleIdentifier = `${config.ios.bundleIdentifier}.screenSharing-Extension`;
|
|
111
|
+
const currentProjectVersion = config.ios.buildNumber || '1';
|
|
112
|
+
const marketingVersion = config.version;
|
|
113
|
+
let updatedProj = addBroadcastExtensionXcodeTarget(config.modResults, {
|
|
114
|
+
appName,
|
|
115
|
+
extensionName,
|
|
116
|
+
extensionBundleIdentifier,
|
|
117
|
+
currentProjectVersion,
|
|
118
|
+
marketingVersion,
|
|
119
|
+
appleTeamId: props.appleTeamId,
|
|
120
|
+
});
|
|
121
|
+
addBroadcastEntitlements(updatedProj, config, props.appleTeamId);
|
|
122
|
+
addExtensionSources(updatedProj, config);
|
|
123
|
+
return config;
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
const addBroadcastExtensionXcodeTarget = (proj, { appName, extensionName, extensionBundleIdentifier, currentProjectVersion, marketingVersion, appleTeamId, }) => {
|
|
127
|
+
var _a;
|
|
128
|
+
if (((_a = proj.getFirstProject().firstProject.targets) === null || _a === void 0 ? void 0 : _a.length) > 1) {
|
|
129
|
+
console.error("addBroadcastExtensionXcodeTarget targets?.length > 1");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const targetUuid = proj.generateUuid();
|
|
133
|
+
const groupName = 'Embed App Extensions';
|
|
134
|
+
const xCConfigurationList = addXCConfigurationList(proj, {
|
|
135
|
+
extensionBundleIdentifier,
|
|
136
|
+
currentProjectVersion,
|
|
137
|
+
marketingVersion,
|
|
138
|
+
extensionName,
|
|
139
|
+
appName,
|
|
140
|
+
appleTeamId,
|
|
141
|
+
});
|
|
142
|
+
const productFile = addProductFile(proj, extensionName, groupName);
|
|
143
|
+
const target = addToPbxNativeTargetSection(proj, {
|
|
144
|
+
extensionName,
|
|
145
|
+
targetUuid,
|
|
146
|
+
productFile,
|
|
147
|
+
xCConfigurationList,
|
|
148
|
+
});
|
|
149
|
+
addToPbxProjectSection(proj, target);
|
|
150
|
+
addTargetDependency(proj, target);
|
|
151
|
+
const frameworkFile = proj.addFramework('ReplayKit.framework', {
|
|
152
|
+
target: target.uuid,
|
|
153
|
+
link: false,
|
|
154
|
+
});
|
|
155
|
+
const frameworkPath = frameworkFile.path;
|
|
156
|
+
//console.log(`Added ReplayKit.framework to target ${target.uuid}`);
|
|
157
|
+
addBuildPhases(proj, {
|
|
158
|
+
groupName,
|
|
159
|
+
productFile,
|
|
160
|
+
targetUuid,
|
|
161
|
+
frameworkPath,
|
|
162
|
+
});
|
|
163
|
+
addPbxGroup(proj, productFile);
|
|
164
|
+
return proj;
|
|
165
|
+
};
|
|
166
|
+
const addXCConfigurationList = (proj, { extensionBundleIdentifier, currentProjectVersion, marketingVersion, extensionName, appName, appleTeamId, }) => {
|
|
167
|
+
const commonBuildSettings = {
|
|
168
|
+
CLANG_ANALYZER_NONNULL: 'YES',
|
|
169
|
+
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION: 'YES_AGGRESSIVE',
|
|
170
|
+
CLANG_CXX_LANGUAGE_STANDARD: quoted('gnu++17'),
|
|
171
|
+
CLANG_ENABLE_OBJC_WEAK: 'YES',
|
|
172
|
+
CLANG_WARN_DOCUMENTATION_COMMENTS: 'YES',
|
|
173
|
+
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER: 'YES',
|
|
174
|
+
CLANG_WARN_UNGUARDED_AVAILABILITY: 'YES_AGGRESSIVE',
|
|
175
|
+
DEVELOPMENT_TEAM: appleTeamId ? quoted(appleTeamId) : undefined, // <-- Ajoute ici
|
|
176
|
+
CODE_SIGN_ENTITLEMENTS: `${appName}/${appName}.entitlements`,
|
|
177
|
+
CODE_SIGN_STYLE: 'Automatic',
|
|
178
|
+
CURRENT_PROJECT_VERSION: currentProjectVersion,
|
|
179
|
+
GCC_C_LANGUAGE_STANDARD: 'gnu11',
|
|
180
|
+
GENERATE_INFOPLIST_FILE: 'YES',
|
|
181
|
+
INFOPLIST_FILE: `${extensionName}/Info.plist`,
|
|
182
|
+
INFOPLIST_KEY_CFBundleDisplayName: `${extensionName}`,
|
|
183
|
+
INFOPLIST_KEY_NSHumanReadableCopyright: quoted(''),
|
|
184
|
+
IPHONEOS_DEPLOYMENT_TARGET: '15.1',
|
|
185
|
+
LD_RUNPATH_SEARCH_PATHS: quoted('$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks'),
|
|
186
|
+
MARKETING_VERSION: marketingVersion,
|
|
187
|
+
MTL_FAST_MATH: 'YES',
|
|
188
|
+
PRODUCT_BUNDLE_IDENTIFIER: quoted(extensionBundleIdentifier),
|
|
189
|
+
PRODUCT_NAME: quoted('$(TARGET_NAME)'),
|
|
190
|
+
SKIP_INSTALL: 'YES',
|
|
191
|
+
SWIFT_EMIT_LOC_STRINGS: 'YES',
|
|
192
|
+
SWIFT_VERSION: '5.0',
|
|
193
|
+
TARGETED_DEVICE_FAMILY: quoted('1,2'),
|
|
194
|
+
//SWIFT_OBJC_BRIDGING_HEADER: `reactNativeApiRTC-Bridging-Header.h`,
|
|
195
|
+
};
|
|
196
|
+
const buildConfigurationsList = [
|
|
197
|
+
{
|
|
198
|
+
name: 'Debug',
|
|
199
|
+
isa: 'XCBuildConfiguration',
|
|
200
|
+
buildSettings: Object.assign(Object.assign({}, commonBuildSettings), { DEBUG_INFORMATION_FORMAT: 'dwarf', MTL_ENABLE_DEBUG_INFO: 'INCLUDE_SOURCE', SWIFT_ACTIVE_COMPILATION_CONDITIONS: 'DEBUG', SWIFT_OPTIMIZATION_LEVEL: quoted('-Onone') }),
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: 'Release',
|
|
204
|
+
isa: 'XCBuildConfiguration',
|
|
205
|
+
buildSettings: Object.assign(Object.assign({}, commonBuildSettings), { COPY_PHASE_STRIP: 'NO', DEBUG_INFORMATION_FORMAT: quoted('dwarf-with-dsym'), SWIFT_OPTIMIZATION_LEVEL: quoted('-Owholemodule') }),
|
|
206
|
+
},
|
|
207
|
+
];
|
|
208
|
+
const xCConfigurationList = proj.addXCConfigurationList(buildConfigurationsList, 'Release', `Build configuration list for PBXNativeTarget ${quoted(extensionName)}`);
|
|
209
|
+
//console.log(`Added XCConfigurationList ${xCConfigurationList.uuid}`);
|
|
210
|
+
// update other build properties
|
|
211
|
+
proj.updateBuildProperty('ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES', 'YES', null, proj.getFirstTarget().firstTarget.name);
|
|
212
|
+
proj.updateBuildProperty('IPHONEOS_DEPLOYMENT_TARGET', '15.1');
|
|
213
|
+
return xCConfigurationList;
|
|
214
|
+
};
|
|
215
|
+
const addProductFile = (proj, extensionName, groupName) => {
|
|
216
|
+
const productFile = {
|
|
217
|
+
basename: `${extensionName}.appex`,
|
|
218
|
+
fileRef: proj.generateUuid(),
|
|
219
|
+
uuid: proj.generateUuid(),
|
|
220
|
+
group: groupName,
|
|
221
|
+
explicitFileType: 'wrapper.app-extension',
|
|
222
|
+
settings: {
|
|
223
|
+
ATTRIBUTES: ['RemoveHeadersOnCopy'],
|
|
224
|
+
},
|
|
225
|
+
includeInIndex: 0,
|
|
226
|
+
path: `${extensionName}.appex`,
|
|
227
|
+
sourceTree: 'BUILT_PRODUCTS_DIR',
|
|
228
|
+
};
|
|
229
|
+
proj.addToPbxFileReferenceSection(productFile);
|
|
230
|
+
//console.log(`Added PBXFileReference: ${productFile.fileRef}`);
|
|
231
|
+
proj.addToPbxBuildFileSection(productFile);
|
|
232
|
+
//console.log(`Added PBXBuildFile: ${productFile.fileRef}`);
|
|
233
|
+
return productFile;
|
|
234
|
+
};
|
|
235
|
+
const addToPbxNativeTargetSection = (proj, { extensionName, targetUuid, productFile, xCConfigurationList, }) => {
|
|
236
|
+
const target = {
|
|
237
|
+
uuid: targetUuid,
|
|
238
|
+
pbxNativeTarget: {
|
|
239
|
+
isa: 'PBXNativeTarget',
|
|
240
|
+
buildConfigurationList: xCConfigurationList.uuid,
|
|
241
|
+
buildPhases: [],
|
|
242
|
+
buildRules: [],
|
|
243
|
+
dependencies: [],
|
|
244
|
+
name: extensionName,
|
|
245
|
+
productName: extensionName,
|
|
246
|
+
productReference: productFile.fileRef,
|
|
247
|
+
productType: quoted('com.apple.product-type.app-extension'),
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
proj.addToPbxNativeTargetSection(target);
|
|
251
|
+
//console.log(`Added PBXNativeTarget ${target.uuid}`);
|
|
252
|
+
return target;
|
|
253
|
+
};
|
|
254
|
+
const addToPbxProjectSection = (proj, target) => {
|
|
255
|
+
proj.addToPbxProjectSection(target);
|
|
256
|
+
//console.log(`Added target to pbx project section ${target.uuid}`);
|
|
257
|
+
// Add target attributes to project section
|
|
258
|
+
if (!proj.pbxProjectSection()[proj.getFirstProject().uuid].attributes
|
|
259
|
+
.TargetAttributes) {
|
|
260
|
+
proj.pbxProjectSection()[proj.getFirstProject().uuid].attributes.TargetAttributes = {};
|
|
261
|
+
}
|
|
262
|
+
proj.pbxProjectSection()[proj.getFirstProject().uuid].attributes.LastSwiftUpdateCheck = 1340;
|
|
263
|
+
proj.pbxProjectSection()[proj.getFirstProject().uuid].attributes.TargetAttributes[target.uuid] = {
|
|
264
|
+
CreatedOnToolsVersion: '13.4.1',
|
|
265
|
+
ProvisioningStyle: 'Automatic',
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
const addTargetDependency = (proj, target) => {
|
|
269
|
+
if (!proj.hash.project.objects['PBXTargetDependency']) {
|
|
270
|
+
proj.hash.project.objects['PBXTargetDependency'] = {};
|
|
271
|
+
}
|
|
272
|
+
if (!proj.hash.project.objects['PBXContainerItemProxy']) {
|
|
273
|
+
proj.hash.project.objects['PBXContainerItemProxy'] = {};
|
|
274
|
+
}
|
|
275
|
+
proj.addTargetDependency(proj.getFirstTarget().uuid, [target.uuid]);
|
|
276
|
+
//console.log(`Added target dependency for target ${target.uuid}`);
|
|
277
|
+
};
|
|
278
|
+
const addBuildPhases = (proj, { groupName, productFile, targetUuid, frameworkPath }) => {
|
|
279
|
+
const buildPath = quoted('');
|
|
280
|
+
// Sources build phase
|
|
281
|
+
const { uuid: sourcesBuildPhaseUuid } = proj.addBuildPhase([`SampleHandler.swift`, 'Atomic.swift', 'DarwinNotificationCenter.swift', 'SampleUploader.swift', 'SocketConnection.swift', 'screenSharing_Extension.entitlements'], 'PBXSourcesBuildPhase', 'Sources', targetUuid, 'app_extension', buildPath);
|
|
282
|
+
//console.log(`Added PBXSourcesBuildPhase ${sourcesBuildPhaseUuid}`);
|
|
283
|
+
// Copy files build phase
|
|
284
|
+
const { uuid: copyFilesBuildPhaseUuid } = proj.addBuildPhase([productFile.path], 'PBXCopyFilesBuildPhase', groupName, proj.getFirstTarget().uuid, 'app_extension', buildPath);
|
|
285
|
+
//console.log(`Added PBXCopyFilesBuildPhase ${copyFilesBuildPhaseUuid}`);
|
|
286
|
+
// Frameworks build phase
|
|
287
|
+
const { uuid: frameworksBuildPhaseUuid } = proj.addBuildPhase([frameworkPath], 'PBXFrameworksBuildPhase', 'Frameworks', targetUuid, 'app_extension', buildPath);
|
|
288
|
+
//console.log(`Added PBXFrameworksBuildPhase ${frameworksBuildPhaseUuid}`);
|
|
289
|
+
// Resources build phase
|
|
290
|
+
const { uuid: resourcesBuildPhaseUuid } = proj.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', targetUuid, 'app_extension', buildPath);
|
|
291
|
+
//console.log(`Added PBXResourcesBuildPhase ${resourcesBuildPhaseUuid}`);
|
|
292
|
+
};
|
|
293
|
+
const addPbxGroup = (proj, productFile) => {
|
|
294
|
+
// Add PBX group
|
|
295
|
+
const { uuid: pbxGroupUuid } = proj.addPbxGroup(['SampleHandler.swift', 'Atomic.swift', 'DarwinNotificationCenter.swift', 'SampleUploader.swift', 'SocketConnection.swift', 'screenSharing_Extension.entitlements'], 'screenSharing_Extension', 'screenSharing_Extension');
|
|
296
|
+
//console.log(`Added PBXGroup ${pbxGroupUuid}`);
|
|
297
|
+
// Add PBXGroup to top level group
|
|
298
|
+
const groups = proj.hash.project.objects['PBXGroup'];
|
|
299
|
+
if (pbxGroupUuid) {
|
|
300
|
+
Object.keys(groups).forEach(function (key) {
|
|
301
|
+
if (groups[key].name === undefined && groups[key].path === undefined) {
|
|
302
|
+
proj.addToPbxGroup(pbxGroupUuid, key);
|
|
303
|
+
//console.log(`Added PBXGroup ${pbxGroupUuid} root PBXGroup group ${key}`);
|
|
304
|
+
}
|
|
305
|
+
else if (groups[key].name === 'Products') {
|
|
306
|
+
proj.addToPbxGroup(productFile, key);
|
|
307
|
+
//console.log(`Added broadcast.apex to Products PBXGroup`);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
const addBroadcastEntitlements = async (proj, config, appleTeamId) => {
|
|
313
|
+
const appGroupIdentifier = `group.${config.ios.bundleIdentifier}.screenSharing-Extension`;
|
|
314
|
+
const extensionRootPath = path.join(config.modRequest.platformProjectRoot, 'screenSharing_Extension');
|
|
315
|
+
const entitlementsPath = path.join(extensionRootPath, 'screenSharing_Extension.entitlements');
|
|
316
|
+
const extensionEntitlements = {
|
|
317
|
+
'com.apple.security.application-groups': [appGroupIdentifier],
|
|
318
|
+
'com.apple.developer.team-identifier': appleTeamId,
|
|
319
|
+
};
|
|
320
|
+
// create file
|
|
321
|
+
await fs.promises.mkdir(path.dirname(entitlementsPath), { recursive: true, });
|
|
322
|
+
await fs.promises.writeFile(entitlementsPath, plist_1.default.build(extensionEntitlements));
|
|
323
|
+
const targetUuid = proj.findTargetKey('screenSharing_Extension');
|
|
324
|
+
const groupUuid = proj.findPBXGroupKey({ name: 'screenSharing_Extension' });
|
|
325
|
+
proj.addFile('screenSharing_Extension.entitlements', groupUuid, {
|
|
326
|
+
target: targetUuid,
|
|
327
|
+
lastKnownFileType: 'text.plist.entitlements',
|
|
328
|
+
});
|
|
329
|
+
// update build properties
|
|
330
|
+
proj.updateBuildProperty('CODE_SIGN_ENTITLEMENTS', 'screenSharing_Extension/screenSharing_Extension.entitlements', null, 'screenSharing_Extension');
|
|
331
|
+
};
|
|
332
|
+
const addExtensionSources = async (proj, config) => {
|
|
333
|
+
const appGroupIdentifier = `group.${config.ios
|
|
334
|
+
.bundleIdentifier}.screenSharing-Extension`;
|
|
335
|
+
const extensionRootPath = path.join(config.modRequest.platformProjectRoot, 'screenSharing_Extension');
|
|
336
|
+
const platformProjectRootPath = path.join(config.modRequest.platformProjectRoot);
|
|
337
|
+
await fs.promises.mkdir(extensionRootPath, { recursive: true });
|
|
338
|
+
await fs.promises.copyFile(path.join(__dirname, 'static', 'Atomic.swift'), path.join(extensionRootPath, 'Atomic.swift'));
|
|
339
|
+
await fs.promises.copyFile(path.join(__dirname, 'static', 'DarwinNotificationCenter.swift'), path.join(extensionRootPath, 'DarwinNotificationCenter.swift'));
|
|
340
|
+
// Override SampleHandler.swift initial template
|
|
341
|
+
await fs.promises.copyFile(path.join(__dirname, 'static', 'SampleHandler.swift'), path.join(extensionRootPath, 'SampleHandler.swift'));
|
|
342
|
+
await fs.promises.copyFile(path.join(__dirname, 'static', 'SampleUploader.swift'), path.join(extensionRootPath, 'SampleUploader.swift'));
|
|
343
|
+
await fs.promises.copyFile(path.join(__dirname, 'static', 'SocketConnection.swift'), path.join(extensionRootPath, 'SocketConnection.swift'));
|
|
344
|
+
// Update app group bundle id in SampleHandler code
|
|
345
|
+
const code = await fs.promises.readFile(path.join(extensionRootPath, 'SampleHandler.swift'), { encoding: 'utf-8' });
|
|
346
|
+
await fs.promises.writeFile(path.join(extensionRootPath, 'SampleHandler.swift'), code.replace('group.apirtc.reactNativeApiRTC.broadcast', appGroupIdentifier));
|
|
347
|
+
addSourceFiles(proj, extensionRootPath);
|
|
348
|
+
};
|
|
349
|
+
const addSourceFiles = (proj, extensionRootPath) => {
|
|
350
|
+
const targetUuid = proj.findTargetKey('screenSharing_Extension');
|
|
351
|
+
const groupUuid = proj.findPBXGroupKey({ name: 'screenSharing_Extension' });
|
|
352
|
+
if (!targetUuid) {
|
|
353
|
+
console.error(`Failed to find "screenSharing_Extension" target!`);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (!groupUuid) {
|
|
357
|
+
console.error(`Failed to find "screenSharing_Extension" group!`);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
proj.addSourceFile('Atomic.swift', {
|
|
361
|
+
target: targetUuid,
|
|
362
|
+
}, groupUuid);
|
|
363
|
+
proj.addSourceFile('DarwinNotificationCenter.swift', {
|
|
364
|
+
target: targetUuid,
|
|
365
|
+
}, groupUuid);
|
|
366
|
+
proj.addSourceFile('SampleHandler.swift', {
|
|
367
|
+
target: targetUuid,
|
|
368
|
+
}, groupUuid);
|
|
369
|
+
proj.addSourceFile('SampleUploader.swift', {
|
|
370
|
+
target: targetUuid,
|
|
371
|
+
}, groupUuid);
|
|
372
|
+
proj.addSourceFile('SocketConnection.swift', {
|
|
373
|
+
target: targetUuid,
|
|
374
|
+
}, groupUuid);
|
|
375
|
+
};
|
|
376
|
+
exports.default = withIosBroadcastExtension;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const config_plugins_1 = require("@expo/config-plugins");
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const NATIVE_FILES = [
|
|
40
|
+
'ReactNativeApiRTC_RPK.swift',
|
|
41
|
+
'ReactNativeApiRTC_RPK.m',
|
|
42
|
+
'reactNativeApiRTCExpo.entitlements'
|
|
43
|
+
];
|
|
44
|
+
const BRIDGING_FILES = [
|
|
45
|
+
'reactNativeApiRTCExpo-Bridging-Header.h',
|
|
46
|
+
];
|
|
47
|
+
const BRIDGING_HEADER = 'reactNativeApiRTCExpo-Bridging-Header.h';
|
|
48
|
+
function copyNativeFiles(projectRoot, iosProjectName) {
|
|
49
|
+
const nativeSrcPath = path.join(projectRoot, 'node_modules/@apizee/expo-apirtc-options-plugin-test/build/static');
|
|
50
|
+
//const nativeSrcPath = path.join('static');
|
|
51
|
+
const iosPath = path.join(projectRoot, 'ios', iosProjectName);
|
|
52
|
+
for (const file of NATIVE_FILES) {
|
|
53
|
+
const src = path.join(nativeSrcPath, file);
|
|
54
|
+
const dest = path.join(iosPath, file);
|
|
55
|
+
if (!fs.existsSync(src)) {
|
|
56
|
+
console.warn(`⚠️ Fichier manquant : ${src}`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
fs.copyFileSync(src, dest);
|
|
60
|
+
console.log(`📄 Copié : ${file}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function copyBridgingFiles(projectRoot, iosProjectName) {
|
|
64
|
+
const nativeSrcPath = path.join(projectRoot, 'node_modules/@apizee/expo-apirtc-options-plugin-test/build/static');
|
|
65
|
+
const iosPath = path.join(projectRoot, 'ios', iosProjectName);
|
|
66
|
+
for (const file of BRIDGING_FILES) {
|
|
67
|
+
const src = path.join(nativeSrcPath, file);
|
|
68
|
+
const dest = path.join(iosPath, file);
|
|
69
|
+
if (!fs.existsSync(src)) {
|
|
70
|
+
console.warn(`⚠️ Fichier manquant : ${src}`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
fs.copyFileSync(src, dest);
|
|
74
|
+
console.log(`📄 Copié : ${file}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const withNativeFilesPlugin = (config) => {
|
|
78
|
+
return (0, config_plugins_1.withXcodeProject)(config, (config) => {
|
|
79
|
+
const project = config.modResults;
|
|
80
|
+
const projectRoot = config.modRequest.projectRoot;
|
|
81
|
+
const iosProjectName = config.modRequest.projectName;
|
|
82
|
+
const iosDir = path.join(projectRoot, 'ios');
|
|
83
|
+
const nativeGroup = project.getFirstProject().firstProject.mainGroup;
|
|
84
|
+
copyNativeFiles(projectRoot, iosProjectName);
|
|
85
|
+
copyBridgingFiles(projectRoot, iosProjectName);
|
|
86
|
+
// Trouve la section Sources (PBXSourcesBuildPhase)
|
|
87
|
+
const buildPhases = project.hash.project.objects['PBXSourcesBuildPhase'];
|
|
88
|
+
const buildPhaseEntry = Object.entries(buildPhases).find(([key, val]) => key !== 'isa' && (val === null || val === void 0 ? void 0 : val.isa) === 'PBXSourcesBuildPhase');
|
|
89
|
+
if (!buildPhaseEntry) {
|
|
90
|
+
throw new Error('❌ PBXSourcesBuildPhase non trouvé dans le projet Xcode.');
|
|
91
|
+
}
|
|
92
|
+
const [sourcesBuildPhaseUuid, sourcesBuildPhase] = buildPhaseEntry;
|
|
93
|
+
sourcesBuildPhase.files = sourcesBuildPhase.files || [];
|
|
94
|
+
for (const fileName of NATIVE_FILES) {
|
|
95
|
+
const filePath = path.join(iosDir, iosProjectName, fileName);
|
|
96
|
+
if (!fs.existsSync(filePath)) {
|
|
97
|
+
console.warn(`⚠️ Fichier natif introuvable : ${filePath}`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const file = project.addFile(path.join(iosProjectName, fileName), nativeGroup);
|
|
101
|
+
if (!(file === null || file === void 0 ? void 0 : file.fileRef)) {
|
|
102
|
+
console.warn(`❌ Échec de l'ajout de : ${fileName}`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
// Vérifie s’il est déjà présent
|
|
106
|
+
const alreadyExists = sourcesBuildPhase.files.some((f) => (f === null || f === void 0 ? void 0 : f.comment) === `${fileName} in Sources`);
|
|
107
|
+
if (alreadyExists)
|
|
108
|
+
continue;
|
|
109
|
+
// Crée un buildFile
|
|
110
|
+
const buildFileUuid = project.generateUuid();
|
|
111
|
+
project.hash.project.objects['PBXBuildFile'][buildFileUuid] = {
|
|
112
|
+
isa: 'PBXBuildFile',
|
|
113
|
+
fileRef: file.fileRef,
|
|
114
|
+
//comment: `${fileName} in Sources`,
|
|
115
|
+
};
|
|
116
|
+
sourcesBuildPhase.files.push({
|
|
117
|
+
value: buildFileUuid,
|
|
118
|
+
comment: `${fileName} in Sources`,
|
|
119
|
+
});
|
|
120
|
+
console.log(`✅ Fichier ajouté : ${fileName}`);
|
|
121
|
+
}
|
|
122
|
+
// Swift config
|
|
123
|
+
const targetUuid = project.getFirstTarget().uuid;
|
|
124
|
+
project.addBuildProperty('SWIFT_OBJC_BRIDGING_HEADER', `${iosProjectName}/${BRIDGING_HEADER}`, `${BRIDGING_HEADER}`, targetUuid);
|
|
125
|
+
project.addBuildProperty('SWIFT_VERSION', '5.0', targetUuid);
|
|
126
|
+
return config;
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
exports.default = withNativeFilesPlugin;
|
package/build/withPlugin.d.ts
CHANGED
package/build/withPlugin.js
CHANGED
|
@@ -5,8 +5,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.withPlugin = void 0;
|
|
7
7
|
const withAndroidPlugin_1 = __importDefault(require("./withAndroidPlugin"));
|
|
8
|
-
const
|
|
9
|
-
|
|
8
|
+
const withIosBroadcastExtension_1 = __importDefault(require("./withIosBroadcastExtension"));
|
|
9
|
+
const withIosRPKFiles_1 = __importDefault(require("./withIosRPKFiles"));
|
|
10
|
+
const withPlugin = (config, props = {
|
|
11
|
+
enableMediaProjectionService: true,
|
|
12
|
+
appleTeamId: process.env.EXPO_APPLE_TEAM_ID || 'APPLE_TEAM_ID_NOT_SET',
|
|
13
|
+
}) => {
|
|
14
|
+
config = (0, withAndroidPlugin_1.default)(config, props);
|
|
15
|
+
config = (0, withIosBroadcastExtension_1.default)(config, props);
|
|
16
|
+
config = (0, withIosRPKFiles_1.default)(config, props);
|
|
17
|
+
return config;
|
|
10
18
|
};
|
|
11
19
|
exports.withPlugin = withPlugin;
|
|
12
20
|
exports.default = exports.withPlugin;
|