Share via


Embed Dragon Copilot in your React Native mobile app

When you integrate Dragon Copilot Embedded for Mobile with a React Native app, you need to write a middleware as a bridge between React Native and the SDK.

Part 1: Add dependencies

  1. Open xcworkspace: From XCode IDE, open your React Native iOS project or workspace (*.xcworkspace). It might be available in the ios folder in your project.

  2. Add the Dragon Copilot SPM as a package dependency: Open your React native iOS project settings, select Package Dependencies and enter the local path of the Dragon Copilot SPM.

  3. Add the framework/library to the target: Open your app's target, select General, and add the DragonCopilotTurnkey xcframework.

Part 2: Write a bridge module

  1. Create DRCBridgeModule.h and extend it from the RCTEventEmitter class. It should implement the RCTBridgeModule protocol.

  2. In the DRCBridgeModule.h file, create the TurnkeyFramework object.

    #ifndef DRCBridgeModule_h
    #define DRCBridgeModule_h
    #import <React/RCTBridgeModule.h>
    #import <React/RCTEventEmitter.h>
    #include <DragonCopilotTurnkey/DragonCopilotTurnkey.h>
    #import <DragonCopilotTurnkey/DragonCopilotTurnkey-Swift.h>
    @interface DRCBridgeModule : RCTEventEmitter <RCTBridgeModule>
    @property (nonatomic, strong) TurnkeyFramework *turnkeySdk;
    @end
    #endif /* DRCBridgeModule_h */
    
  3. In DRCBridgeModule.m, add the following methods:

    • initTurnkeySdk to start Dragon Copilot Embedded for Mobile. It should be called only once during the initialization.
    • openSession to display the Dragon Copilot UI on the screen. It should be called when a different session/encounter has selected.
    • closeSession to properly close an active patient session.
    • dispose to completely release all resources associated with the TurnkeyFramework class when the user signs out.
    // Start Dragon Copilot Embedded for Mobile
    RCT_EXPORT_METHOD(initTurnkeySdk) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.turnkeySdk = [TurnkeyFramework initializeWithDataProvider:self 
            delegate:self recordingDelegate:self dictationDelegate:self settingsDelegate:self];
        });
    }
    
    // Open a session
    RCT_EXPORT_METHOD(openSession) {
        dispatch_async(dispatch_get_main_queue(), ^{
            UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
            NSObject *webViewContainer = [self.turnkeySdk openSessionControllerWithSessionDataProvider:self];
            UIViewController *viewControllerToPresent = (UIViewController *) webViewContainer;
            [rootViewController presentViewController:viewControllerToPresent animated:YES completion:nil];
        });
    }
    
    // Close a session
    RCT_EXPORT_METHOD(closeSession) {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (self.turnkeySdk) {
                [self.turnkeySdk closeSession];
                NSLog(@"Turnkey session closed.");
            } else {
                NSLog(@"Turnkey SDK is not initialized. Cannot close session.");
            }
        });
    }
    
    // Dispose of the TurnkeyFramework class
    RCT_EXPORT_METHOD(disposeSdk) {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (self.turnkeySdk) {
                [TurnkeyFramework dispose];
                self.turnkeySdk = nil;
                NSLog(@"Turnkey SDK disposed.");
            } else {
                NSLog(@"Turnkey SDK is not initialized. Cannot dispose.");
            }
        });
    }
    
  4. If required, implement TDelegate, TRecordingDelegate, TDictationDelegate, and TSettingDelegate to receive callbacks from Dragon Copilot Embedded for Mobile.

    #pragma mark - TDelegate
    - (void)isTurnKeyWebViewLoaded:(BOOL)isLoadingDone {
      NSLog(@"[TDelegate] WebView loaded: %@", isLoadingDone ? @"YES" : @"NO");
    }
    
    - (void)logoutWith:(LogoutReason)logoutType {
      NSString *reason = logoutType == LogoutReasonUser ? @"User" : @"Inactivity";
      NSLog(@"[TDelegate] Logout occurred. Reason: %@", reason);
    }
    
    #pragma mark - TRecordingDelegate
    - (void)recordingStarted {
      NSLog(@"[TRecordingDelegate] Recording started");
    }
    
    - (void)recordingFailed {
      NSLog(@"[TRecordingDelegate] Recording failed");
    }
    
    - (void)recordingStopped {
      NSLog(@"[TRecordingDelegate] Recording stopped");
    }
    
    - (void)recordingInterruptedWithReason:(RecordingInterruptionReason)reason {
      NSLog(@"[TRecordingDelegate] Recording interrupted. Reason: %ld", (long)reason);
    }
    
    - (void)recordingNotificationWithNotification:(RecordingNotification)notification {
      NSLog(@"[TRecordingDelegate] Recording notification. Notification: %ld", (long)notification);
    }
    
    #pragma mark - TDictationDelegate
    - (void)dictationStarted {
      NSLog(@"[TDictationDelegate] Dictation started");
    }
    
    - (void)dictationStopped {
      NSLog(@"[TDictationDelegate] Dictation stopped");
    }
    
    #pragma mark - TSettingsDelegate
    - (void)isIdleTimerDisabledIsOn:(BOOL)screenOn {
      NSLog(@"[TSettingsDelegate] Idle timer disabled: %@", screenOn ? @"YES" : @"NO");
    }
    
    - (void)appearanceThemeChangedTo:(NSString *)uiTheme {
      NSLog(@"[TSettingsDelegate] Appearance theme changed to: %@", uiTheme);
    }
    
    - (void)changeApplicationLanguageTo:(NSString *)languageCode {
      NSLog(@"[TSettingsDelegate] Change application language to: %@", languageCode);
    }
    
  5. Implement TSessionDataProvider, TConfigurationProvider, and TAccessTokenProvider protocols with implementation for the methods.

    @interface DRCBridgeModule () <TSessionDataProvider, TConfigurationProvider>
    @end
    - (TConfiguration * _Nonnull)getTConfiguration {}
    - (TUser * _Nonnull)getTUser {}
    - (TPatient * _Nonnull)getTPatient {}
    - (TVisit * _Nonnull)getTVisit {}
    - (id<TAccessTokenProvider> _Nonnull)getTAccessTokenProvider {
        return  [[AuthProvider alloc] init];
    }
    
  6. Create AuthProvider.h for definitions and AuthProvider.m for implementation. Update the AuthProvider.m class to include your implementation for retrieving the JWT authentication token.

    //  AuthProvider.h
    #import <Foundation/Foundation.h>
    #import <DragonCopilotTurnkey/DragonCopilotTurnkey-Swift.h>
    NS_ASSUME_NONNULL_BEGIN
    
    @interface AuthProvider : NSObject <TAccessTokenProvider>
    /// Retrieves an access token for authentication.
    /// - Parameters:
    ///   - scope: Optional array of scopes for token generation.
    ///   - forceRefresh: When true, generate new token and provide
    ///   - onSuccess: The callback that returns a TAuthResponse object if the token is successfully retrieved.
    ///   - onFailure: The callback that returns an NSError object if token retrieval fails.
    - (void)accessTokenWithScope:(nullable NSArray<NSString *> *)scopes
    forceRefresh:(BOOL)forceRefresh
    onSuccess:(void (^)(TAuthResponse *authResponse))onSuccess
    onFailure:(void (^)(NSError *error))onFailure;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    // AuthProvider.m
    #import "AuthProvider.h"
    @implementation AuthProvider
    - (void)accessTokenWithScopes:(nullable NSArray<NSString *> *)scopes
    forceRefresh:(BOOL)forceRefresh
    onSuccess:(void (^)(TAuthResponse *authResponse))onSuccess
    onFailure:(void (^)(NSError *error))onFailure {
        // Example: Generate a sample token
        NSString *token = @"sampleJWTToken";
    
        TTokenResponse *tokenResponse = [[TTokenResponse alloc] initWithToken:token];
        TAuthResponse *authResponse = [[TAuthResponse alloc] initWithTokenResponse:tokenResponse];
    
        // Call the success callback
        onSuccess(tokenResponse);
    }
    
    @end
    
  7. Each of these methods is called synchronously from Dragon Copilot Embedded for Mobile when data is needed. You might need to have a mechanism to retrieve or construct data, for example using a semaphore, and provide information to the SDK.

    Once a two-way synchronous communication channel has been successfully established between the Objective-C and JavaScript components of the React Native application, the next step is to perform a clean build of the iOS application and conduct testing.