@apirtc/expo-apirtc-options-plugin 0.0.1 → 0.0.3

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 CHANGED
@@ -7,19 +7,22 @@ This plugin simplify the integration of following features :
7
7
  ## Installation
8
8
 
9
9
  ```bash
10
- npm install @apizee/expo-apirtc-options-plugin-test
10
+ npm install @apirtc/expo-apirtc-options-plugin
11
11
 
12
12
  ```
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
+ ['@apirtc/expo-apirtc-options-plugin', {
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', { enableMediaProjectionService: false }]
43
+ ['@apirtc/expo-apirtc-options-plugin', {
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,6 @@
1
+ //
2
+ // Use this file to import your target's public headers that you would like to expose to Swift.
3
+ //
4
+
5
+
6
+ #import "React/RCTEventEmitter.h"
@@ -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,7 @@
1
+ import { ConfigPlugin } from '@expo/config-plugins';
2
+ type PluginProps = {
3
+ enableMediaProjectionService?: boolean;
4
+ appleTeamId?: string;
5
+ };
6
+ declare const withIosBroadcastExtension: ConfigPlugin<PluginProps>;
7
+ export default withIosBroadcastExtension;
@@ -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,6 @@
1
+ import { ConfigPlugin } from '@expo/config-plugins';
2
+ type PluginProps = {
3
+ enableMediaProjectionService?: boolean;
4
+ };
5
+ declare const withNativeFilesPlugin: ConfigPlugin<PluginProps>;
6
+ export default withNativeFilesPlugin;
@@ -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/@apirtc/expo-apirtc-options-plugin/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/@apirtc/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;
@@ -1,6 +1,7 @@
1
1
  import { ConfigPlugin } from '@expo/config-plugins';
2
2
  type PluginProps = {
3
3
  enableMediaProjectionService?: boolean;
4
+ appleTeamId?: string;
4
5
  };
5
6
  export declare const withPlugin: ConfigPlugin<PluginProps>;
6
7
  export default withPlugin;
@@ -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 withPlugin = (config, props = { enableMediaProjectionService: true }) => {
9
- return (0, withAndroidPlugin_1.default)(config, props);
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apirtc/expo-apirtc-options-plugin",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Plugin Expo pour ajouter les features apiRTC dans une application React Native",
5
5
  "main": "index.js",
6
6
  "keywords": [