diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastAction.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastAction.java index 6f9f0b12c..bc0b25d83 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastAction.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastAction.java @@ -60,7 +60,8 @@ public class BroadcastAction { enum Type { SET_AUDIO_MUTED("org.jitsi.meet.SET_AUDIO_MUTED"), - HANG_UP("org.jitsi.meet.HANG_UP"); + HANG_UP("org.jitsi.meet.HANG_UP"), + SEND_ENDPOINT_TEXT_MESSAGE("org.jitsi.meet.SEND_ENDPOINT_TEXT_MESSAGE"); private final String action; diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastEvent.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastEvent.java index f6851fa2f..cab686303 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastEvent.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastEvent.java @@ -80,7 +80,8 @@ public class BroadcastEvent { CONFERENCE_WILL_JOIN("org.jitsi.meet.CONFERENCE_WILL_JOIN"), AUDIO_MUTED_CHANGED("org.jitsi.meet.AUDIO_MUTED_CHANGED"), PARTICIPANT_JOINED("org.jitsi.meet.PARTICIPANT_JOINED"), - PARTICIPANT_LEFT("org.jitsi.meet.PARTICIPANT_LEFT"); + PARTICIPANT_LEFT("org.jitsi.meet.PARTICIPANT_LEFT"), + ENDPOINT_TEXT_MESSAGE_RECEIVED("org.jitsi.meet.ENDPOINT_TEXT_MESSAGE_RECEIVED"); private static final String CONFERENCE_WILL_JOIN_NAME = "CONFERENCE_WILL_JOIN"; private static final String CONFERENCE_JOINED_NAME = "CONFERENCE_JOINED"; @@ -88,6 +89,7 @@ public class BroadcastEvent { private static final String AUDIO_MUTED_CHANGED_NAME = "AUDIO_MUTED_CHANGED"; private static final String PARTICIPANT_JOINED_NAME = "PARTICIPANT_JOINED"; private static final String PARTICIPANT_LEFT_NAME = "PARTICIPANT_LEFT"; + private static final String ENDPOINT_TEXT_MESSAGE_RECEIVED_NAME = "ENDPOINT_TEXT_MESSAGE_RECEIVED"; private final String action; @@ -122,6 +124,8 @@ public class BroadcastEvent { return PARTICIPANT_JOINED; case PARTICIPANT_LEFT_NAME: return PARTICIPANT_LEFT; + case ENDPOINT_TEXT_MESSAGE_RECEIVED_NAME: + return ENDPOINT_TEXT_MESSAGE_RECEIVED; } return null; diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastIntentHelper.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastIntentHelper.java index dd5ae9c15..d09f37fec 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastIntentHelper.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastIntentHelper.java @@ -12,4 +12,11 @@ public class BroadcastIntentHelper { public static Intent buildHangUpIntent() { return new Intent(BroadcastAction.Type.HANG_UP.getAction()); } + + public static Intent buildSendEndpointTextMessageIntent(String to, String message) { + Intent intent = new Intent(BroadcastAction.Type.SEND_ENDPOINT_TEXT_MESSAGE.getAction()); + intent.putExtra("to", to); + intent.putExtra("message", message); + return intent; + } } diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastReceiver.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastReceiver.java index 1d74208b7..f61199c21 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastReceiver.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/BroadcastReceiver.java @@ -18,6 +18,7 @@ public class BroadcastReceiver extends android.content.BroadcastReceiver { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BroadcastAction.Type.SET_AUDIO_MUTED.getAction()); intentFilter.addAction(BroadcastAction.Type.HANG_UP.getAction()); + intentFilter.addAction(BroadcastAction.Type.SEND_ENDPOINT_TEXT_MESSAGE.getAction()); localBroadcastManager.registerReceiver(this, intentFilter); } diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/ExternalAPIModule.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/ExternalAPIModule.java index 0312d25f8..caa60e9bf 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/ExternalAPIModule.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/ExternalAPIModule.java @@ -77,6 +77,7 @@ class ExternalAPIModule constants.put("SET_AUDIO_MUTED", BroadcastAction.Type.SET_AUDIO_MUTED.getAction()); constants.put("HANG_UP", BroadcastAction.Type.HANG_UP.getAction()); + constants.put("SEND_ENDPOINT_TEXT_MESSAGE", BroadcastAction.Type.SEND_ENDPOINT_TEXT_MESSAGE.getAction()); return constants; } diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivity.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivity.java index a76f170a3..1b443aa4b 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivity.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivity.java @@ -271,6 +271,7 @@ public class JitsiMeetActivity extends FragmentActivity intentFilter.addAction(BroadcastEvent.Type.CONFERENCE_TERMINATED.getAction()); intentFilter.addAction(BroadcastEvent.Type.PARTICIPANT_JOINED.getAction()); intentFilter.addAction(BroadcastEvent.Type.PARTICIPANT_LEFT.getAction()); + intentFilter.addAction(BroadcastEvent.Type.ENDPOINT_TEXT_MESSAGE_RECEIVED.getAction()); LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, intentFilter); } diff --git a/ios/app/src/ViewController.m b/ios/app/src/ViewController.m index 44db705d6..99205b48a 100644 --- a/ios/app/src/ViewController.m +++ b/ios/app/src/ViewController.m @@ -115,6 +115,10 @@ NSLog(@"%@%@", @"Audio muted changed: ", data[@"muted"]); } +- (void)endpointTextMessageReceived:(NSDictionary *)data; { + NSLog(@"%@%@", @"Endpoint text message received: ", data); +} + #pragma mark - Helpers - (void)terminate { diff --git a/ios/sdk/src/ExternalAPI.h b/ios/sdk/src/ExternalAPI.h index 558aa2113..66e5de9c0 100644 --- a/ios/sdk/src/ExternalAPI.h +++ b/ios/sdk/src/ExternalAPI.h @@ -20,5 +20,6 @@ - (void)sendHangUp; - (void)sendSetAudioMuted: (BOOL)muted; +- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message; @end diff --git a/ios/sdk/src/ExternalAPI.m b/ios/sdk/src/ExternalAPI.m index a9f9f3284..266484cee 100644 --- a/ios/sdk/src/ExternalAPI.m +++ b/ios/sdk/src/ExternalAPI.m @@ -18,8 +18,9 @@ #import "JitsiMeetView+Private.h" // Events -static NSString * const hangUpEvent = @"org.jitsi.meet.HANG_UP"; -static NSString * const setAudioMutedEvent = @"org.jitsi.meet.SET_AUDIO_MUTED"; +static NSString * const hangUpAction = @"org.jitsi.meet.HANG_UP"; +static NSString * const setAudioMutedAction = @"org.jitsi.meet.SET_AUDIO_MUTED"; +static NSString * const sendEndpointTextMessageAction = @"org.jitsi.meet.SEND_ENDPOINT_TEXT_MESSAGE"; @implementation ExternalAPI @@ -27,8 +28,9 @@ RCT_EXPORT_MODULE(); - (NSDictionary *)constantsToExport { return @{ - @"HANG_UP": hangUpEvent, - @"SET_AUDIO_MUTED" : setAudioMutedEvent + @"HANG_UP": hangUpAction, + @"SET_AUDIO_MUTED" : setAudioMutedAction, + @"SEND_ENDPOINT_TEXT_MESSAGE": sendEndpointTextMessageAction }; }; @@ -44,7 +46,7 @@ RCT_EXPORT_MODULE(); } - (NSArray *)supportedEvents { - return @[ hangUpEvent, setAudioMutedEvent ]; + return @[ hangUpAction, setAudioMutedAction, sendEndpointTextMessageAction ]; } /** @@ -103,13 +105,22 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name } - (void)sendHangUp { - [self sendEventWithName:hangUpEvent body:nil]; + [self sendEventWithName:hangUpAction body:nil]; } -- (void)sendSetAudioMuted: (BOOL)muted { +- (void)sendSetAudioMuted:(BOOL)muted { NSDictionary *data = @{ @"muted": [NSNumber numberWithBool:muted]}; - [self sendEventWithName:setAudioMutedEvent body:data]; + [self sendEventWithName:setAudioMutedAction body:data]; +} + +- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message { + NSDictionary *data = @{ + @"to": to, + @"message": message + }; + + [self sendEventWithName:sendEndpointTextMessageAction body:data]; } @end diff --git a/ios/sdk/src/JitsiMeet+Private.h b/ios/sdk/src/JitsiMeet+Private.h index 35206f9b0..3ba7ee0b2 100644 --- a/ios/sdk/src/JitsiMeet+Private.h +++ b/ios/sdk/src/JitsiMeet+Private.h @@ -16,11 +16,13 @@ #import +#import "ExternalAPI.h" #import "JitsiMeet.h" @interface JitsiMeet () - (NSDictionary *)getDefaultProps; - (RCTBridge *)getReactBridge; +- (ExternalAPI *)getExternalAPI; @end diff --git a/ios/sdk/src/JitsiMeet.m b/ios/sdk/src/JitsiMeet.m index a8a9104b2..8b8731f26 100644 --- a/ios/sdk/src/JitsiMeet.m +++ b/ios/sdk/src/JitsiMeet.m @@ -213,4 +213,8 @@ return _bridgeWrapper.bridge; } +- (ExternalAPI *)getExternalAPI { + return [_bridgeWrapper.bridge moduleForClass:ExternalAPI.class]; +} + @end diff --git a/ios/sdk/src/JitsiMeetView.h b/ios/sdk/src/JitsiMeetView.h index 850e91ba3..93cb676bf 100644 --- a/ios/sdk/src/JitsiMeetView.h +++ b/ios/sdk/src/JitsiMeetView.h @@ -41,4 +41,6 @@ - (void)setAudioMuted:(BOOL)muted; +- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message; + @end diff --git a/ios/sdk/src/JitsiMeetView.m b/ios/sdk/src/JitsiMeetView.m index 65aaa4133..12970aef9 100644 --- a/ios/sdk/src/JitsiMeetView.m +++ b/ios/sdk/src/JitsiMeetView.m @@ -116,13 +116,18 @@ static void initializeViewsMap() { } - (void)hangUp { - RCTBridge *bridge = [[JitsiMeet sharedInstance] getReactBridge]; - [[bridge moduleForClass:ExternalAPI.class] sendHangUp]; + ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI]; + [externalAPI sendHangUp]; } - (void)setAudioMuted:(BOOL)muted { - RCTBridge *bridge = [[JitsiMeet sharedInstance] getReactBridge]; - [[bridge moduleForClass:ExternalAPI.class] sendSetAudioMuted:muted]; + ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI]; + [externalAPI sendSetAudioMuted:muted]; +} + +- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message { + ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI]; + [externalAPI sendEndpointTextMessage:to :message]; } #pragma mark Private methods diff --git a/ios/sdk/src/JitsiMeetViewDelegate.h b/ios/sdk/src/JitsiMeetViewDelegate.h index 924c94efa..bed08e96b 100644 --- a/ios/sdk/src/JitsiMeetViewDelegate.h +++ b/ios/sdk/src/JitsiMeetViewDelegate.h @@ -75,4 +75,12 @@ * The `data` dictionary contains a `muted` key with state of the audioMuted for the localParticipant. */ - (void)audioMutedChanged:(NSDictionary *)data; + +/** + * Called when an endpoint text message is received. + * + * The `data` dictionary contains a `senderId` key with the participantId of the sender and a 'message' key with the content. + */ +- (void)endpointTextMessageReceived:(NSDictionary *)data; + @end diff --git a/react/features/mobile/external-api/logger.js b/react/features/mobile/external-api/logger.js new file mode 100644 index 000000000..c256dd848 --- /dev/null +++ b/react/features/mobile/external-api/logger.js @@ -0,0 +1,5 @@ +// @flow + +import { getLogger } from '../../base/logging/functions'; + +export default getLogger('features/mobile/external-api'); diff --git a/react/features/mobile/external-api/middleware.js b/react/features/mobile/external-api/middleware.js index 4f3593c23..43315f144 100644 --- a/react/features/mobile/external-api/middleware.js +++ b/react/features/mobile/external-api/middleware.js @@ -2,6 +2,7 @@ import { NativeEventEmitter, NativeModules } from 'react-native'; +import { ENDPOINT_TEXT_MESSAGE_NAME } from '../../../../modules/API/constants'; import { appNavigate } from '../../app/actions'; import { APP_WILL_MOUNT } from '../../base/app/actionTypes'; import { @@ -12,6 +13,7 @@ import { JITSI_CONFERENCE_URL_KEY, SET_ROOM, forEachConference, + getCurrentConference, isRoomValid } from '../../base/conference'; import { LOAD_CONFIG_ERROR } from '../../base/config'; @@ -22,6 +24,7 @@ import { JITSI_CONNECTION_URL_KEY, getURLWithoutParams } from '../../base/connection'; +import { JitsiConferenceEvents } from '../../base/lib-jitsi-meet'; import { SET_AUDIO_MUTED } from '../../base/media/actionTypes'; import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../../base/participants'; import { MiddlewareRegistry } from '../../base/redux'; @@ -29,6 +32,7 @@ import { muteLocal } from '../../remote-video-menu/actions'; import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture'; import { sendEvent } from './functions'; +import logger from './logger'; /** * Event which will be emitted on the native side to indicate the conference @@ -36,6 +40,12 @@ import { sendEvent } from './functions'; */ const CONFERENCE_TERMINATED = 'CONFERENCE_TERMINATED'; +/** + * Event which will be emitted on the native side to indicate a message was received + * through the channel. + */ +const ENDPOINT_TEXT_MESSAGE_RECEIVED = 'ENDPOINT_TEXT_MESSAGE_RECEIVED'; + const { ExternalAPI } = NativeModules; const eventEmitter = new NativeEventEmitter(ExternalAPI); @@ -52,7 +62,7 @@ MiddlewareRegistry.register(store => next => action => { switch (type) { case APP_WILL_MOUNT: - _registerForNativeEvents(store.dispatch); + _registerForNativeEvents(store); break; case CONFERENCE_FAILED: { const { error, ...data } = action; @@ -75,12 +85,16 @@ MiddlewareRegistry.register(store => next => action => { break; } - case CONFERENCE_JOINED: case CONFERENCE_LEFT: case CONFERENCE_WILL_JOIN: _sendConferenceEvent(store, action); break; + case CONFERENCE_JOINED: + _sendConferenceEvent(store, action); + _registerForEndpointTextMessages(store); + break; + case CONNECTION_DISCONNECTED: { // FIXME: This is a hack. See the description in the JITSI_CONNECTION_CONFERENCE_KEY constant definition. // Check if this connection was attached to any conference. If it wasn't, fake a CONFERENCE_TERMINATED event. @@ -160,11 +174,11 @@ MiddlewareRegistry.register(store => next => action => { /** * Registers for events sent from the native side via NativeEventEmitter. * - * @param {Dispatch} dispatch - The Redux dispatch function. + * @param {Store} store - The redux store. * @private * @returns {void} */ -function _registerForNativeEvents(dispatch) { +function _registerForNativeEvents({ getState, dispatch }) { eventEmitter.addListener(ExternalAPI.HANG_UP, () => { dispatch(appNavigate(undefined)); }); @@ -172,6 +186,48 @@ function _registerForNativeEvents(dispatch) { eventEmitter.addListener(ExternalAPI.SET_AUDIO_MUTED, ({ muted }) => { dispatch(muteLocal(muted === 'true')); }); + + eventEmitter.addListener(ExternalAPI.SEND_ENDPOINT_TEXT_MESSAGE, ({ to, message }) => { + const conference = getCurrentConference(getState()); + + try { + conference && conference.sendEndpointMessage(to, { + name: ENDPOINT_TEXT_MESSAGE_NAME, + text: message + }); + } catch (error) { + logger.warn('Cannot send endpointMessage', error); + } + }); +} + +/** + * Registers for endpoint messages sent on conference data channel. + * + * @param {Store} store - The redux store. + * @private + * @returns {void} + */ +function _registerForEndpointTextMessages(store) { + const conference = getCurrentConference(store.getState()); + + conference && conference.on( + JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, + (...args) => { + if (args && args.length >= 2) { + const [ sender, eventData ] = args; + + if (eventData.name === ENDPOINT_TEXT_MESSAGE_NAME) { + sendEvent( + store, + ENDPOINT_TEXT_MESSAGE_RECEIVED, + /* data */ { + message: eventData.text, + senderId: sender._id + }); + } + } + }); } /**