1.0.9 • Published 3 years ago

cordova-plugin-apple-watch-link v1.0.9

Weekly downloads
82
License
Apache-2.0
Repository
github
Last release
3 years ago

Cordova Plugin to enable communication between a Cordova iOS app and an Apple WatchKit extension.

This plugin enables the addition of a companion Apple WatchKit extension to a Cordova iOS app. It provides a Cordova plugin interface and a Watchkit framework that provides iOS/watchOS app communication.

The following points should be noted.

  • An existing Cordova iOS app can be extended to include a companion WatchKit extension by opening the app project in Xcode and adding a "Watch App for iOS App" target.
  • The companion WatchKit app target must be coded in Swift, and use the WatchKit App Delegate lifecycle.
  • After the companion WatchKit app target has been added, you cannot use any functions of the Cordova CLI on the project since this will damage the project and make it unusable. All further additions or modifications to the project must be made via Xcode. Therefore, all required Cordova plugins must be installed before the WatchKit app target is added, and any changes to the HTML and Javascript code of the app (including plugins) must be applied directly to the Staging directory of the Xcode project.
  • As an alternative to making all Cordova iOS additions or modifications to the project via Xcode, a baseline project representing only the Cordova iOS app can be maintained, and updates to the Cordova iOS app can be applied to a separate project that contains the complete app (iOS and watchOS targets).
  • This plugin and companion Watchkit framework has been designed and tested for the latest versions of Cordova (10.0.0), Cordova iOS (6.2.0), iOS (14), watchOS (7), Xcode(12) and Swift (5). Compatibility with earlier versions of any of these components is not assured and will not be addressed.
  • This plugin includes a fully functional test app that illustrates the use of the watchLink messaging framework.

Design objectives

At minimum, this plugin allows an Apple Watch to be easily used as as a user interface device for a Cordova iOS app.

  • All iOS application logic can be provided via Javascript in the Cordova iOS app. No Swift code is required on the iOS side since all required communication functionality is provided through the plugin interface.
  • User interface logic must be coded for the Watch using Swift, with the watchLink messaging framework providing incoming and outgoing message communication with the iOS app.
  • Comprehensive logging is available which enables the iOS app and the watchOS app to be monitored from the Javascript console as well as the Xcode consoles for each.
  • The framework Javascript and Swift code guards against error conditions that would cause an app crash and abandons the requested operation after issuing console error messages.
  • There are no third-party software dependencies. The plugin and WatchOS framework are completely self-contained and rely only upon established Cordova iOS and WatchOS APIs. Of course, as much logic as desired can be included in the watchOS app. It is not limited to user interface functions and any of the WatchKit APIs can be accessed. Similarly, the iOS app may contain custom Swift modules to support the operation of the Cordova iOS app.

Communication framework

The Cordova plugin and Watchkit framework are built on the Apple iOS/watchOS WCSession class and WCSessionDelegate protocol.

All of the features of WCSession and WCSessionDelegate are supported with the exception of file transfer.

Table of Contents

Installation and Xcode project setup

The plugin creates the global window.cordova.plugins.watchLink which is abbreviated as watchLink in this document.

Latest published version on npm

cordova plugin add cordova-plugin-apple-watch-link

Latest version from GitHub

cordova plugin add
  https://github.com/nickredding/cordova-plugin-apple-watch-link.git

Xcode project setup

Add the watchLink plugin (and any other required plugins) to the Cordova project before creating the WatchKit app target.

After adding the WatchKit app target, add the file WatchLinkExtensionDelegate.swift to the watchOS app and modify the standard file ExtensionDelegate.swift as follows:

class ExtensionDelegate: WatchLinkExtensionDelegate {

    override func applicationDidFinishLaunching() {
        // Perform any final initialization of your application.
        super.applicationDidFinishLaunching()
        printAppLog("ExtensionDelegate.applicationDidFinishLaunching")

        // app-specific code ...
    }

    override func applicationDidBecomeActive() {
        super.applicationDidBecomeActive()

        // app-specific code ...
    }

    override func applicationWillResignActive() {
        super.applicationWillResignActive()

        // app-specific code ...
    }

    // app-specific code ...

The file WatchLinkExtensionDelegate.swift provides the framework for communication with the plugin, and the changes to ExtensionDelegate.swift enable the framework to process incoming messages from the plugin.

The following modifications to the Xcode project are necessary after creating the WatchKit app target:

  • Remove the bridging header reference under build settings for the watchOS app extension (but not for the watchOS app).
  • Set the bundle version and bundle version (short) for both the watchOS app and extension to match the iOS app.
  • Set the Swift Language Version to 5 under Build Settings for the iOS app (ignore the spurious warning message about the project containing Swift Version 3 code)
  • Accept Xcode recommendations to update project settings. Now add the watchOS app and extension files.

Migrating Watch target files from another Xcode project

You can drag Swift files and .xcassets graphic assets from the old Xcode project target to the new project. However, project settings should be set directly in the new project, and .storyboard files should be copied to the new project using the "File/Add Files to ..." Xcode command.

Initiate testing via Xcode

Use either the Xcode simulator or iPhone and Watch devices (preferred).

To launch using the Xcode simulator, select the Watch target and a Watch simulator, and run.

  • The simulator will load both a Watch simulator and an iPhone simulator.
  • The Xcode console will show only the Watch console (the iOS Xcode console is not available).
  • You can open a Web Inspector window from Safari to inspect the iOS Cordova app and view the console, which will show watchLink log messages from both the iOS app and the Watch.

To launch using iPhone and Watch devices, connect your iPhone to your Mac and run the watchOS app. Then run the iOS app.

  • This will result in both Xcode consoles being available (selected from the console selector).
  • You can open a Web Inspector window from Safari to inspect the iOS Cordova app and view the console, which will show watchLink log messages from both the iOS app and the Watch.

Although the iOS app is installed and launched when you run the watchOS app, installing the iOS app again makes its Xcode console available.

iOS platform and plugin maintenance

As an alternative to making all Cordova iOS additions or modifications to the project via Xcode, a baseline project representing only the the Cordova iOS app can be maintained, and updates to the Cordova iOS app can be applied to a separate project that contains the complete app (iOS and watchOS targets).

The plugin includes two shell scripts that will facilitate this. These scripts should be run from a terminal window with the current directory set to the baseline project directory.

  • The script clone.sh will perform cordova prepare ios, create a folder watchtarget and copy the iOS platform code to it.
  • The script update.sh will perform cordova prepare ios and update the iOS platform code in the watchtarget directory. The watchOS target development should take place in the watchtarget iOS platform directory. Subsequent changes to the Cordova iOS platform, plugins and application code can be made to the baseline project and applied to the watchtarget iOS platform via update.sh.
Inital project setup

After setting up the Cordova iOS project MyAppProject under the Documents directory, the plugin can be installed and the watchtarget directory can be created as illustrated by the following Terminal session.

$cd Documents/AppProject
cordova plugin add cordova-plugin-apple-watch-link
$cp plugins/cordova-plugin-apple-watch-link/*.sh .
$chmod 777 *.sh
$./clone.sh
$

This creates the watchtarget directory structure as illustrated below.

The watchOS target development can take place in the watchtarget iOS platform directory. Open this project from AppProject.xcodeproj in the watchtarget/platforms/ios directory.

Note: XCode will show the config.xml and www items in the project root as missing. This is not an error. These represent items in the baseline project which should be updated there as necessary.

After the Watch targets have been created, the file WatchLinkExtensionDelegate.swift should be added to the Extension and the standard file ExtensionDelegate.swift modified as specified above.

Cordova iOS updates

Changes to the Cordova iOS app code (platform, plugins and application logic) can be applied to the baseline project and then applied to the watchtarget iOS platform directory as illustrated by the following Terminal session (where AppIOS is the name of the iOS app target).

$cd Documents/AppProject
$.update.sh AppProject AppIOS

	Updating AppProject in watchtarget/platforms/ios

$

This updates the watchtarget directory structure as illustrated below.

Note: The update script does not copy any non-code resources to the watchtarget/platforms/ios. Graphic assets, storyboards and plist files (which should be developed from Xcode within the watchtarget/platforms/ios project) are not affected.

Watch session initialization

The Watch session at the Swift level might not have completed initialization by the time the plugin completes initialization. Therefore, the iOS and watchOS apps must ensure the Watch session is ready before attempting to query its status or send messages to the counterpart.

Watch session initialization status (iOS)

The variable watchLink.initialized is true if the Watch session has completed initialization. watchLink.ready adds a function to a list of app functions to invoke when the Watch session completes initialization. The function will be invoked immediately if the Watch session initialization has already been completed at the time watchLink.ready is invoked.

iOS Cordova app initialization can therefore be organized as follows:

document.addEventListener('deviceready', onDeviceReady, false);

function onDeviceReady() {
	// binding handlers and dealing with notifications can 
	// proceed immediately
	window.cordova.watchLink.bindMessageHandler(...);
	
	// communication must wait until Watch session initialization is complete
	window.cordova.watchLink.ready(
		function() {
			// initiate Watch communication
			window.cordova.watchLink.sendMessage(...)
		});

Watch session initialization status (watchOS)

The global watchInitialized is true if the Watch session has completed initialization, and the completion of initialization can be signaled to the watchOS app.

watchReady(_ f: (() -> Void)) 
//	sets a function f to invoke when the Watch session 
//	completes initialization.

The function will be invoked immediately if the Watch session initialization has already been completed at the time watchReady is invoked.

Session status

The status of the Watch can be queried by the iOS app, which can also bind handlers to be invoked when the status of the Watch changes.

Watch availability (iOS)

The availability of the watchOS app reflects whether the companion watchOS app is available, which is required for communication.

watchLink availability states
-----------------------------

watchLink.watchAvailable = true;
watchLink.watchUnavailable = false;
watchLink.watchNotPaired = "NOTPAIRED";
watchLink.watchNotInstalled = "NOTINSTALLED";

watchLink.available is the availability state.

	null: yet to be initialized
	watchLink.watchAvailable: Watch session is available, Watch is paired 
		and Watch companion app installed
	watchLink.watchUnavailable: Watch session is not available
	watchLink.watchNotPaired: Watch session is available but Watch is 
		not paired to phone
	watchLink.watchNotInstalled: Watch session is available but 
		Watch companion app has not been installed
		
Note: Use the === operator to test availability states since all states except
	watchLink.watchUnavailable would evaluate as true in a conditonal test.

watchLink.availability updates and returns the current availability state 
of the Watch session. It is not normally required to call this since 
watchLink.available is kept up to date as soon as the Watch session 
has completed initialization.
	
Using the traditional Cordova callback method:

watchLink.availability(callback, error)
//	success = function(availability)
//		availability is the current availability state 
//			of the Watch session

//	error = function(errorString)
//		If the Watch session has not completed initialization 
//			errorString will be "uninitialized"

	
Using the Promise construct:

watchLink.availability().then(callback).catch(error)

watchLink.availabilityChanged(callback) registers a callback to invoke when the 
availability state changes. The callback parameter represents the new 
availability state.

Supply null to deregister a previously set callback. Otherwise, 
this call will overwrite a previously set callback with a different callback.

Watch reachability (iOS)

The reachability of the watchOS app reflects whether the app is in the foreground (i.e., active) and able to process messages. The watchOS app can only be reachable if it is available.

watchLink.reachable is the reachability state.

null: yet to be initialized
true: watchOS app is reachable
false: watchOS app is not reachable

watchLink.reachability updates and returns the current reachability state 
of the Watch session. It is not normally required to call this since 
watchLink.reachable is kept up to date as soon as the Watch session 
has completed initialization.
	
Using the traditional Cordova callback method:

watchLink.reachability(callback, error)
//	success = function(reachability) 
//		reachability is the current reachability state of the 
//			Watch session

//	error = function(errorString) 
//		If the Watch session has not completed initialization 
//			errorString will be "uninitialized"


Using the Promise construct:

watchLink.reachability().then(callback).catch(error)

watchLink.reachabilityChanged(callback) registers a callback to invoke 
when the reachability state changes The callback parameter represents 
the new reachability state.

Supply null to deregister a previously set callback. Otherwise, this 
call will overwrite a previously set callback with a different callback.

Watch application state (iOS)

The application state of the watchOS app reflects whether the companion watchOS app is active, inactive or in background.

watchLink application states

watchLink.watchApplicationActive = "ACTIVE";
watchLink.watchApplicationInactive = "INACTIVE";
watchLink.watchAapplicationBackground = "BACKGROUND";

watchLink.applicationState is the full application state.
    
	null: yet to be initialized, otherwise
	watchLink.applicationState = 
		{ state: <state>, complication: <Boolean>, isPaired: <Boolean>, 
			isAppInstalled: <Boolean>, directoryURL: <string> }
	watchLink.applicationState.state
		false: Watch is not available, or the application is 
			not running or suspended
		watchLink.watchApplicationActive: The watchOS app is 
			running in foreground and responding to events
		watchLink.watchApplicationInactive: The watchOS app is 
		running in foreground but not yet responding to events
		watchLink.watchApplicationBackground: The watchOS app is 
			running in the background
	watchLink.applicationState.complication
		true: complication is enabled
		false: complication is not enabled
	watchLink.applicationState.isPaired
		true: Watch is paired
		false: Watch is not paired
	watchLink.applicationState.isAppInstalled
		true: watchOS app is installed
		false: watchOS app is not installed
	watchLink.applicationState.directoryURL
		The URL of a directory for storing information specific to the 
			currently paired and active Watch
        
Note that watchLink.applicationState.state is false if watchLink.available 
is not true.

watchLink.watchApplicationState updates and returns the current application
state of the watchOS app. It is not normally required to call this since
watchLink.applicationState is kept up to date as soon as the Watch session 
has completed initialization.
	
Using the traditional Cordova callback method:

watchLink.watchApplicationState(callback, error)
//	success = function(state)
//		state is the current application state

//	error = function(errorString)
//		If the Watch session has not completed initialization 
//			errorString will be "uninitialized"
	
Using the Promise construct:

watchLink.watchApplicationState().then(callback).catch(error)

watchLink.applicationStateChanged registers a callback to invoke 
when the application state changes. The callback parameter 
represents the new application state.

Supply null to deregister a previously set callback. Otherwise, this call 
will overwrite a previously set callback with a different callback.

Session status (watchOS)

To query the availability status of the session, examine the global variable phoneAvailable which maintains the availability state of the iPhone companion app.

To query the reachability status of the session, examine the global variable phoneReachable which maintains the reachability state of the iPhone companion app.

You can also examine the session directly. For example,

let watchSession = WCSession.default
watchSession.isReachable // is the iOS app reachable
	...

You can bind handlers to be notified of changes in companion app availability and reachability.

func bindAvailabilityHandler(_ handler: @escaping ((Bool) -> Void)) 

func bindReachabilityHandler(_ handler: @escaping ((Bool) -> Void))

See WCSession class for details.

iPhone companion app state considerations

If your watch extension does not support running without the iOS companion app installation (as specified in the Info.plist file), the watchOS session status will always be available since the watch app will be uninstalled if the iPhone app becomes unavailable.

If the watchOS session status is available it will always be reachable. Sending a message to the iOS companion app when it is not running will cause iOS to launch the app in background to process the message.

watchLink message session management

Cordova plugins are initialized at the Objective-C/Swift level upon initial launch of a Cordova app. However, a Cordova app can restart at the Javascript level (e.g., via window.reload) and this does not cause plugins at the Objective-C/Swift level to restart.

For this reason, watchLink uses a unique session ID to identify communication in the current messaging session. This key is created when the Javascript layer starts initially or restarts, and any previous session IDs become obsolete. Communications arriving with an obsolete session ID are discarded, and when a session starts any pending communications awaiting transmission with an obsolete session ID are also discarded.

The function watchLink.resetSession starts a new session, discarding any pending communications with an obsolete session ID and sending a message to the watchOS app providing the new session ID. Upon receipt of the reset message, the watchOS app flushes any pending communications with an obsolete key, and will also discard any communications arriving with an obsolete session ID.

watchLink.resetSession(completion)
//	completion = function(msg) is an optional callback to invoke when the 
//			session reset is complete (the reset message has been acknowledged
//				by the watchOS framework).
//		msg = true if successful, or "uninitialized" if the Watch 
//			session initialization is not complete

It is not normally necessary for the iOS app to invoke watchLink.resetSession since this is done by watchLink.js when it initializes or reinitializes. However, this function can be invoked to invalidate the current session and start a new one if the app needs to do that absent a restart at the Javascript level.

When the watchOS app initializes, and prior to receiving a reset message from the iOS app, communications are dispatched with session ID zero which is accepted by the iOS watchLink framework. Upon receipt of a reset message the watchOS app updates the session ID of pending communications that have a zero session ID to the new session ID.

Watch session reset callback (watchOS)

It may be useful for a reset issued by the iOS app to be signaled to the watchOS app. This can be accomplished by binding a reset handler.

watchReset(_ f: (() -> Void))
//	sets the function f to invoke when the Watch session is reset. 

Communication methods

There are six methods of communication provided:

  • Dictionary message passing: messages may be exchanged containing a dictionary of values. Messages will be queued until the companion app is reachable.
  • Data message passing: messages may be exchanged containing an untyped data object. Messages will be queued until the companion app is reachable.
  • User information transfers: a dictionary of values representing user information may be transmitted in either direction. These transfers can occur in background (when the companion app is available but not reachable).
  • Application context transfers: a dictionary of values representing application context may be transmitted in either direction. These transfers can occur in background (when the companion app is available but not reachable).
  • Complication data transfers: a dictionary of values representing complication user information may be transmitted from the iOS app to the watchOS app. These transfers can occur in background (when the watchOS app is not reachable, i.e. the watchOS app is not in foreground).
  • Scheduled local notifications: you can schedule a local notification to be presented at a specific time. The notification will be shown on the iOS device or Watch, depending on which one is active.

Dictionary format

A Dictionary is defined as a property list dictionary, which is a dictionary of property list values.

A property list value is defined as follows:

<property-list-value> = <Boolean (Bool)> | <Number (Int64)> | String (String)> | 
				<property-list-array> | 
				<property-list-dictionary>

<property-list-array> = [ <property-list-value>, ... ] 
				([ <property-list-value> ])
<property-list-dictionary> = { <String> : <property-list-value>, ... }
				([ <String> : <property-list-value> ])

In this definition, the types refer to Javascript types, with the corresponding Swift type in parentheses.

Untyped data object format

An untyped data object may be created via Javascript as an ArrayBuffer.

var buffer = new ArrayBuffer(<length>)

where <length> is the size of the data object in bytes. An ArrayBuffer can be accessed for reading and writing by creating a view in the desired format. For example,

// byteView is an array of 8 bit unsigned integers
var byteView = new Uint8Array(buffer); 

// wordView is an array of 32 bit integers
var wordView = new Int32Array(buffer); 

In Swift, a data object is declared as type Data and then cast to an appropriate type for access.

Dictionary message passing

Dictionary messages are accompanied by a message type which is used to determine how to route the message at the receiving end. Both the iOS app and watchOS app can bind a message handler function to a message type, and messages with that message type will be delivered to the handler that is bound.

Dictionary messages cannot be sent unless the companion app is reachable. If the companion app is available but not reachable, dictionary messages are queued until the companion app becomes reachable.

The following message types are reserved and may not be used:

ACK, DATA, SESSION, RESET, SETLOGLEVEL, SETPRINTLOGLEVEL, UPDATEDCONTEXT,
UPDATEDUSERINFO, WATCHLOG, WATCHERRORLOG, WATCHAPPLOG, WCSESSION, IOSINITIALIZED, IOSTERMINATED

The iOS app and watchOS app can bind a default message handler to receive messages with message types that are not bound.

Dictionary message transmission (iOS)

Dictionary messages are sent from the iOS app using a traditional Cordova plugin call, as in

watchLink.sendMessage(msgType, msgBody, success, error)
//	Upon return, msgBody.TIMESTAMP contains the unique numeric 
//		timestamp that can be used to refer to the message.
//	msgType = <String>
//	msgBody = <property-list-dictionary>
//	success = function(timestamp)
//		Invoked when the message has been delivered and acknowledged
//			timestamp is the string representation of 
//				msgBody.TIMESTAMP
//	error = function(errorString0
//		Invoked when an error occurred
//			errorString = string describing the error followed 
//				by ":<timestamp>"
//			<timestamp> is the value of msgBody.TIMESTAMP
//		If the Watch is unavailable errorString will 
//			be "unavailable:<timestamp>"
//		If the session was reset errorString will 
//			be "sessionreset:<timestamp>"
//		If the Watch session has not completed initialization errorString 
//			will be "uninitialized"
//		Errors are logged regardless of whether an error 
//			function is provided

Note: the msgBody sent to the Swift layer is a clone of the object submitted to the sendMessage function. Therefore, changes to msgBody made prior to transmission will not be reflected in the message payload.

Dictionary messages can also be sent from the iOS app using a Promise, as in

watchLink.sendMessage(msgType, msgBody).then(success).catch(error)
//	Upon return, msgBody.TIMESTAMP contains the unique numeric 
//	timestamp that can be used to refer to the message.

Dictionary messages are normally acknowledged by the receiving end, at which point the success function is invoked. However, if null is provided for the success parameter, the message will be sent without acknowledgement. For this behavior you must use the traditional Cordova plugin call.

A dictionary message that is sent with acknowledgement will block subsequent dictionary messages (acknowledged or not) until it is acknowledged or flushed due to an error or session reset.

Note that regardless of acknowledgement, messages are sent and delivered in the order in which they are dispatched.

Dictionary message transmission (watchOS)

Dictionary messages are sent from the watchOS app using a call to the function messageToPhone.

func messageToPhone(msgType: String, msg: [String: Any], ack: Bool = false,
			ackHandler: (Int64) -> Void)= nullHandler, 
			errHandler: ((String) -> Void)= nullHandler)
	-> Int64

Setting the ack parameter to true will send the message with acknowledgement. A dictionary message that is sent with acknowledgement will block subsequent dictionary messages (acknowledged or not) until it is acknowledged or flushed due to an error or session reset.

The errHandler function (if provided) will be invoked in the case of an error arising. If the session was reset errorString will be "sessionreset:". Errors are logged regardless of whether an error handler is provided.

This function returns the unique timestamp assigned to the message, and this value is provided to the ackHandler and errHandler functions.

Dictionary message flushing

Any outstanding messages can be flushed from the outgoing message queue using watchLink.flushMessages() on the iOS side and flushMessages() on the watchOS app side. The error handlers for flushed messages are not invoked. Note that any acknowledgements that arrive for flushed messages are ignored (the success handlers are not invoked).

Dictionary message receipt (iOS)

Handlers can be bound to specific message types, and are invoked to process incoming messages with the corresponding type.

watchLink.bindMessageHandler(expr, handler)
//	expr = <String> | <RegExp>
//	handler = function(msgType, msgBody)
//		Return false to terminate message processing
//		Otherwise processing will continue with additional 
//			matching handlers

To handle an incoming message, the message type is extracted and each handler with a matching expression is invoked with the message type and message body as parameters. The order of invocation is the order in which the handlers were bound.

  • Processing continues until all matching expressions have been checked, or until a handler returns false to halt processing.
  • The expression can be a string (requires an exact match) or a regular expression.
  • Supplying null for the handler will unbind a previously bound handler.
  • Supplying a handler for an existing match expression will overwrite the existing handler for that expression.
  • Supplying null for the match expression will set the default handler, unless it is null also in which case the default handler will be unbound.

Dictionary message receipt (watchOS)

Message handlers in the watchOS app are bound using bindMessageHandler and bindDefaultMessageHandler.

bindMessageHandler(msgType: String, 
	handler: @escaping ((String, [String: Any]) -> Bool))

bindMessageHandler(msgRegex: String, 
	handler: @escaping ((String, [String: Any]) -> Bool))

bindDefaultMessageHandler(handler: @escaping ((String, [String: Any]) -> Void))

Message processing in the watchOS app is handled in the same way as on the iOS app.

Direct dictionary message handling

It is possible to use the WCSession interface directly for sending messages to the counterpart. This will bypass the watchLink framework and these messages will be detected as direct and delivered to the default message handler (if configured) with msgType = "WCSESSION".

Note that if you access the WCSession interface directly your code is responsible for error handling and ensuring that the counterpart app is reachable before sending messages.

Dictionary messsages can be sent directly (bypassing the watchLink framework) from the iOS app using watchLink.wcSessionCommand. The Watch app must be reachable (otherwise the error callback will be invoked).

watchLink.wcSessionCommand('sendMessage', payload, error)
//	payload = <property-list-dictionary>
//	error = function(msg)
//		Invoked in the case of an error

Dictionary message transfer

Dictionary messages cannot be sent unless the companion app is reachable. However, the iOS message transfer function will send a message immediately if the watch companion app is reachable, but use user information transfer in background if the companion app is not reachable.

This enables the watch companion app to receive, acknowledge and process messages while in background.

Messages received via message transfer are processed by the watch companion app in the same way (using the same handlers) as messages receieved via message transmission. The fact that the mesage was sent via user information transfer is transparent to the watch companion app.

Dictionary message transfer (iOS)

Dictionary messages are transferred from the iOS app using a traditional Cordova plugin call, as in

watchLink.transferMessage(msgType, msgBody, success, error)
//	Upon return, msgBody.TIMESTAMP contains the unique numeric 
//		timestamp that can be used to refer to the message.
//	msgType = <String>
//	msgBody = <property-list-dictionary>
//	success = function(timestamp)
//		Invoked when the message has been delivered and acknowledged
//			timestamp is the string representation of 
//				msgBody.TIMESTAMP
//	error = function(errorString0
//		Invoked when an error occurred
//			errorString = string describing the error followed 
//				by ":<timestamp>"
//			<timestamp> is the value of msgBody.TIMESTAMP
//		If the Watch is unavailable errorString will 
//			be "unavailable:<timestamp>"
//		If the session was reset errorString will 
//			be "sessionreset:<timestamp>"
//		If the Watch session has not completed initialization errorString 
//			will be "uninitialized"
//		Errors are logged regardless of whether an error 
//			function is provided

Note: the msgBody sent to the Swift layer is a clone of the object submitted to the sendMessage function. Therefore, changes to msgBody made prior to transmission will not be reflected in the message payload.

Dictionary messages can also be sent from the iOS app using a Promise, as in

watchLink.transferMessage(msgType, msgBody).then(success).catch(error)
//	Upon return, msgBody.TIMESTAMP contains the unique numeric 
//	timestamp that can be used to refer to the message.

Dictionary messages are normally acknowledged by the receiving end, at which point the success function is invoked. However, if null is provided for the success parameter, the message will be sent without acknowledgement. For this behavior you must use the traditional Cordova plugin call.

A dictionary message that is sent with acknowledgement will block subsequent dictionary messages (acknowledged or not) until it is acknowledged or flushed due to an error or session reset.

Note that regardless of acknowledgement, messages are sent and delivered in the order in which they are dispatched.

Data message passing

Data messages are actually sent using dictionary messages. Both the iOS app and watchOS app can bind a message handler function to handle incoming data messages.

Data messages cannot be sent unless the watchOS app is reachable. If the Watch is available but not reachable, data messages are queued until the Watch becomes reachable.

Data message transmission (iOS)

Data messages are sent from the iOS app using a traditional Cordova plugin call, as in

watchLink.sendDataMessage(msgData, success, error);
//	msgData = <ArrayBuffer>
//	success = function(msgData)
//		Invoked when the message has been delivered and acknowledged
//			msgData is the message payload
//	error = function(errorString)
//		Invoked when an error occurred
//			errorString = string describing the error
//		If the Watch is unavailable errorString will be "unavailable"
//		If the session was reset errorString will be "sessionreset"
//		If the Watch session has not completed initialization errorString 
//			will be "uninitialized"
//		Errors are logged regardless of whether an error 
//			function is provided

Note: the msgData sent to the Swift layer and returned to the success function is a clone of the object submitted to the sendDataMessage function. Therefore, changes to msgData made prior to transmission will not be reflected in the message payload. Data messages can also be sent from the iOS app using a Promise, as in

watchLink.sendDataMessage(msgType, msgBody).then(success).catch(error)

Data messages are normally acknowledged by the receiving end, at which point the success function is invoked. However, if null is provided for the success parameter, the message will be sent without acknowledgement. For this behavior you must use the traditional Cordova plugin call.

A data message that is sent with acknowledgement will block subsequent data messages (acknowledged or not) until it is acknowledged or flushed due to an error or session reset.

Note that regardless of acknowledgement, messages are sent and delivered in the order in which they are dispatched.

Data message transmission (watchOS)

Data messages are sent from the watchOS app using a call to the function dataMessageToPhone

func dataMessageToPhone(msg: Data, ack: Bool = false,
			ackHandler: ((Int64) -> Void)= nullHandler, 
			errHandler: ((String) -> Void)= nullHandler) -> Int64

This function returns the unique timestamp assigned to the data message.

Setting the ack parameter to true will send the message with acknowledgement. A data message that is sent with acknowledgement will block subsequent data messages (acknowledged or not) until it is acknowledged or flushed due to an error or session reset.

The ackHandler function (if provided) receives the timestamp returned by dataMessageToPhone.

The errHandler function (if provided) will be invoked in the case of an error arising. If the message is cancelled errorString will be "sessionreset:". Errors are logged regardless of whether an error handler is provided.

Data message flushing (iOS)

Any outstanding data messages can be flushed from the outgoing message queue using watchLink.flushDataMessages() on the iOS side and flushDataMessages() on the watchOS app side. The error handlers for flushed messages are not invoked. Note that any acknowledgements that arrive for flushed messages are ignored (the success handlers are not invoked).

Data message receipt (iOS)

A single handler can be bound to process incoming data messages.

watchLink.bindDataMessageHandler(handler)
//	handler = function(msgData)
  • Supplying null for the handler will unbind a previously bound handler.
  • Binding a handler will overwrite any existing handler (only one handler can be invoked for an incoming data message).

Data message receipt (watchOS)

A data message handler in the watchOS app is bound using bindDataMessageHandler.

bindDataMessageHandler(handler: @escaping ((Data) -> Bool))

Direct data message handling

It is possible to use the WCSession interface directly for sending data messages to the counterpart. This will bypass the watchLink framework and these messages will be detected as direct and delivered to the data message handler (if configured).

Note that if you access the WCSession interface directly your code is responsible for error handling and ensuring that the counterpart app is reachable before sending messages.

Dictionary messsages can be sent directly (bypassing the watchLink framework) from the iOS app using watchLink.wcSessionCommand. The Watch app must be reachable (otherwise the error callback will be invoked).

watchLink.wcSessionCommand('sendDataMessage', payload, error)
//	payload = <ArrayBuffer>
//	error = function(msg)
//		Invoked in the case of an error

User information transfers

A dictionary of values representing user information may be transmitted in either direction. These transfers can occur in background (when the companion app is not reachable).

User information transfer (iOS)

User information transfers are sent from the iOS app using a traditional Cordova plugin call, as in

watchLink.sendUserInfo(userInfo, success, error)
//	Upon return, userInfo.TIMESTAMP contains the unique 
//		numeric timestamp that can be used to refer to the transfer.
//	userInfo = <property-list-dictionary>
//	success = function(timestamp)
//		Invoked when the message has been delivered and acknowledged
//			timestamp is the string representation of 
//				userInfo.TIMESTAMP
//	error = function(errorString)
//		Invoked when an error occurred
//			errorString = string describing the error 
//				followed by ":<timestamp>"
//			<timestamp> is the value of userInfo.TIMESTAMP
//		If the session was reset errorString will be "sessionreset"
//		If the Watch session has not completed initialization errorString 
//			will be "uninitialized"
//		Errors are logged regardless of whether an 
//			error function is provided

Note: the userInfo sent to the Swift layer is a clone of the object submitted to the sendUserInfo function.

User information transfers can also be sent from the iOS app using a Promise, as in

watchLink.sendUserInfo(userInfo).then(success).catch(error)
//	Upon return, userInfo.TIMESTAMP contains the unique numeric 
//	timestamp that can be used to refer to the transfer.

User information transfers are normally acknowledged by the receiving end, at which point the success function is invoked. However, if null is provided for the success parameter, the transfer will be sent without acknowledgement. For this behavior you must use the traditional Cordova plugin call.

A transfer that is sent with acknowledgement will block subsequent transfers (acknowledged or not) until it is acknowledged or flushed due to an error or session reset.

Note that regardless of acknowledgement, transfers are sent and delivered in the order in which they are dispatched.

watchLink.sendUserInfo adds the key TIMESTAMP to the userInfo object which contains a unique timestamp representing the transfer. Note that if userInfo contains an existing key TIMESTAMP it will be overwritten with the timestamp. This value can be used to query the status of the transfer, and cancel the transfer if desired. The value of this key can be retrieved from the supplied userInfo object immediately upon return from the function invocation.

The userInfo object received by the counterpart app will include keys TIMESTAMP, SESSION and ACK. If the original userInfo object contains any of these keys they will be overwritten.

User information transfer (watchOS)

User information transfers are sent from the watchOS app using a call to the function updateUserInfoToPhone which returns the Watch equivalent of TIMESTAMP.

func updateUserInfoToPhone(userInfo: [String: Any], 
			ackHandler: (([String: Any]) -> Void)? = nil, 
			errHandler: ((String) -> Void)? = nil) 
	-> Int64

Setting the ack parameter to true will send the information with acknowledgement. A transfer that is sent with acknowledgement will block subsequent transfers (acknowledged or not) until it is acknowledged or flushed due to an error or session reset.

The errHandler function (if provided) will be invoked in the case of an error arising. If the transfer is cancelled errorString will be "sessionreset". Errors are logged regardless of whether an error function is provided

User information transfer status and flushing (iOS)

A user information transfer can be queried and/or cancelled using the TIMESTAMP set by watchLink.sendUserInfo or updateUserInfoToPhone.

A user information transfer can be queried via watchLink.queryUserInfo from the iOS app using a traditional Cordova plugin call, as in

watchLink.queryUserInfo(timestamp, success, error)
//	success = function(transferInfo)
//		transferInfo = { timestamp: <Number>, 
				isComplication: <Boolean>, 
//				transmitComplete: <Boolean>, 
//				userInfo: <property-list-dictionary> 
//		transferInfo = false if the transfer status is no 
//			longer available
//	error = function(errorString)
//		If the Watch is unavailable errorString will be "unavailable"
//		If the Watch session has not completed initialization errorString 
//			will be "uninitialized"

The in-progress user information transfers can also be accessed from the iOS app using a Promise, as in

watchLink.queryUserInfo(timestamp).then(success).catch(error)

The status of a user information transfer will remain available until the transfer is complete and a subsequent user information transfer is initiated.

A user information transfer can be cancelled via watchLink.cancelUserInfo from the iOS app using a traditional Cordova plugin call, as in

watchLink.cancelUserInfo(timestamp, success, error)
//	success = function(cancelled
//		cancelled = <Boolean>, true if the transfer was cancelled, 
//			false otherwise
//	error = function(errorString
//		If the Watch is unavailable errorString will be "unavailable"
//		If the Watch session has not completed initialization errorString 
//			will be "uninitialized"

An information transfer can also be cancelled from the iOS app using a Promise, as in

watchLink.cancelUserInfo(timestamp).then(success).catch(error)

All outstanding user information transfers can be cancelled and flushed from the outgoing transfer queue using watchLink.flushUserInfoTransfers(). The error handlers for cancelled or flushed transfers are not invoked. Note that any acknowledgements that arrive for flushed transfers are ignored (the success handlers are not invoked).

User information transfer status and flushing (watchOS)

User information transfers from the watchOS app can be managed as follows:

func queryUserInfo(timestamp: Int64) -> 
		(timestamp: Int64, 
		isComplication: Bool, 
		transmitComplete: Bool, 
		userInfo: [String: Any])

func cancelUserInfo(timestamp: Int64) -> Bool

func flushUserInfoTransfers()

The error handlers for cancelled or flushed transfers are not invoked. Note that any acknowledgements that arrive for flushed transfers are ignored (the success handlers are not invoked).

User information transfers outstanding (iOS)

The in-progress user information transfers can be accessed via watchLink.outstandingUserInfoTransfers from the iOS app using a traditional Cordova plugin call, as in

watchLink.outstandingUserInfoTransfers(success, error)
//	success = function(outstandingUserInfo
//		outstandingUserInfo = [ 
//			{ userInfoID: <Number>, 
//			isComplication: <Boolean>, 
//			transmitComplete: <Boolean>, 
//			userInfo: <property-list-dictionary> } ]
//	error = function(errorString
//		If the Watch is unavailable errorString will be "unavailable"
//		If the Watch session has not completed initialization errorString 
//			will be "uninitialized"

The in-progress user information transfers can also be accessed from the iOS app using a Promise, as in

watchLink.outstandingUserInfoTransfers().then(success).catch(error)

User information transfers outstanding (watchOS)

The in-progress user information transfers can be accessed from the watchOS app as follows:

func outstandingUserInfoTransfers() -> [ [ 
//		timestamp: Int64, 
//		isComplication: Bool, 
//		transmitComplete: Bool, 
//		userInfo: [String: Any] ] ]

User information transfer receipt (iOS)

A single handler can be bound to process incoming user information transfers.

watchLink.bindUserInfoHandler(handler)
//	handler = function(userInfo)

Supplying null for the handler will unbind a previously bound handler.

Binding a handler will overwrite any existing handler (only one handler can be invoked for an incoming transfer).

User information transfer receipt (watchOS)

User information transfer handlers in the watchOS app are bound using bindUserInfoHandler.

bindUserInfoHandler(handler: @escaping (([String: Any]) -> Void))

Note: The dictionary passed to the handler will contain the key ISCOMPLICATION with value true if this is a complication update.

Direct user information transfer handling

It is possible to use the WCSession interface directly for transfering user information to the counterpart. This will bypass the watchLink framework and these transfers will be detected as direct and delivered to the user information handler (if configured).

Note that if you access the WCSession interface directly your code is responsible for error handling and ensuring that he counterpart app is available before sending user information transfers.

User information transfers can be sent directly (bypassing the watchLink framework) from the iOS app using watchLink.wcSessionCommand. The Watch app must be reachable. There is no provision for an error callback.

watchLink.wcSessionCommand('updateUserInfo', payload)
//	payload = <property-list-dictionary>

Application context transfers

A dictionary of values representing application context information may be transmitted in either direction. These transfers can occur in background (when the watchOS app is not reachable, i.e. the watchOS app is not in foreground).

Application context transmission (iOS)

Application context transfers are sent from the iOS app using a traditional Cordova plugin call, as in

watchLink.sendContext(context, success, error);
//	Upon return, context.TIMESTAMP contains the unique 
//		numeric timestamp that can be used to refer to the transfer
//	context = <property-list-dictionary>
//	success = function(timestamp)
//		Invoked when the message has been delivered and acknowledged
//		Context is the application context payload
//	error = function(errorString)
//		Invoked when an error occurred
//			errorString = string describing the error followed 
//				by ":<timestamp>"
//			<timestamp> is the value of userInfo.TIMESTAMP
//		If the transfer is superceded errorString 
//			will be "reset:<timestamp>"
//		If the transfer is flushed errorString 
//			will be "sessionreset:<timestamp>"
//		If the Watch session has not completed initialization 
//			errorString will be "uninitialized"
//		Errors are logged regardless of whether an error 
//			function is provided

Note: the context sent to the Swift layer is a clone of the object submitted to the sendContext function.

Application context transfers can also be sent from the iOS app using a Promise, as in

watchLink.sendContext(context).then(success).catch(error)
//	Upon return, context.TIMESTAMP contains the unique 
//	numeric timestamp that can be used to refer to the transfer

Application context transfers are normally acknowledged by the receiving end, at which point the success function is invoked. However, if null is provided for the success parameter, the transfer will be sent without acknowledgement. For this behavior you must use the traditional Cordova plugin call.

A transfer that is sent with acknowledgement will not block subsequent transfers (acknowledged or not). However, transfers requested before the completion of a previously requested transfer will overwrite that transfer. In this case, the error handler (if configured) for the previous transfer will be invoked.

The context object received by the counterpart app will include keys TIMESTAMP, SESSION and ACK. If the original userInfo object contains any of these keys they will be overwritten.

Application context transmission (watchOS)

Application context transfers are sent from the watchOS app using a call to the function updateUserInfoToPhone.

func updateContextToPhone(context: [String: Any], ack: Bool = false,
			ackHandler: ((Int64) -> Void)? = nil, 
			errHandler: ((String) -> Void)? = nil) -> Int64

Setting the ack parameter to true will send the information with acknowledgement. A transfer that is sent with acknowledgement will not block subsequent transfers (acknowledged or not). However, transfers requested before the completion of a previously requested transfer will overwrite that transfer. In this case, the error handler (if configured) for the previous transfer will be invoked.

The errHandler function (if provided) will be invoked in the case of an error arising. If the transfer is cancelled errorString will be "sessionreset". Errors are logged regardless of whether an error function is provided.

Application context transfer status (iOS)

Unlike user information transfers, the status of application context transfers cannot be queried.

However, the contents of the most recently transmitted and received application context transfers can be queried from the iOS app using a traditional Cordova plugin call, as in

watchLink.latestContextSent(success, error)
//	success = function(context
//		context = <property-list-dictionary>
//	error = function(errorString
//		If the Watch is unavailable errorString will be "unavailable"
//		If the Watch session has not completed initialization errorString 
//			will be "uninitialized"

watchLink.latestContextReceived(success, error
//	success = function(context) 
//		context = <property-list-dictionary>
//	error = function(errorString
//		If the Watch is unavailable errorString will be "unavailable"
//		If the Watch session has not completed initialization errorString 
//			will be "uninitialized"

The contents of the most recently transmitted and received application context transfers can also be accessed from the iOS app using a Promise, as in

watchLink.latestContextSent().then(success).catch(error)
	
watchLink.latestContextReceived().then(success).catch(error)

Application context transfer status (watchOS)

The contents of the most recently transmitted and received application context transfers can be accessed from the watchOS app as follows:

func latestContextSent() -> [String: Any]

func latestContextReceived() -> [String: Any]

Application context flushing (iOS)

All outstanding application context transfers can be flushed from the outgoing transfer queue using watchLink.flushContextTransfers(). The error handlers for flushed transfers are not invoked. Note that any acknowledgements that arrive for flushed transfers are ignored (the success handlers are not invoked).

Application context flushing (watchOS)

All outstanding application context transfers can be flushed from the outgoing transfer queue using flushContextTransfers(). The error handlers for flushed transfers are not invoked. Note that any acknowledgements that arrive for flushed transfers are ignored (the success handlers are not invoked).

Application context transfer receipt (iOS)

A single handler can be bound to process incoming application context transfers.

watchLink.bindContextHandler(handler)
//	handler = function(context)

Supplying null for the handler will unbind a previously bound handler.

Binding a handler will overwrite any existing handler (only one handler can be invoked for an incoming transfer).

Application context transfer receipt (watchOS)

Application context transfer handlers in the watchOS app are bound using bindUserInfoHandler.

bindContextHandler(handler: @escaping (([String: Any]) -> Void))

Direct application context transfer handling

It is possible to use the WCSession interface directly for transfering application contexts to the counterpart. This will bypass the watchLink framework and these transfers will be detected as direct and delivered to the user information handler (if configured).

Note that if you access the WCSession interface directly your code is responsible for error handling and ensuring that he counterpart app is available before sending context updates.

Dictionary messsages can be sent directly (bypassing the watchLink framework) from the iOS app using watchLink.wcSessionCommand. The Watch app must be reachable (otherwise the error callback will be invoked).

watchLink.wcSessionCommand('updateContext', payload, error)
//	payload = <property-list-dictionary>
//	error = function(msg)
//		Invoked in the case of an error

Complication data transfers

A dictionary of values representing complication data may be transmitted from the iOS to the watchOS app. These transfers can occur in background (when the watchOS app is not reachable, i.e. the watchOS app is not in foreground).

Complication data transfers are sent as high priority user information transfers. They are delivered to the watchOS app via the user information transfer handler that has been bound.

Complication data transmission (iOS)

Complication data transfers are sent from the iOS app using a traditional Cordova plugin call, as in

watchLink.sendComplicationInfo(complicationInfo);
//	Upon return, context.TIMESTAMP contains the unique 
//	numeric timestamp that can be used to refer to the transfer
//	complicationInfo = <property-list-dictionary>
//	returns: true if transmission was initiated, false if Watch 
//		is unavailable

watchLink.sendComplicationInfo adds the keys TIMESTAMP and ISCOMPLICATION to the complicationInfo object. TIMESTAMP contains a unique timestamp representing the transfer. Note that if complicationInfo contains existing keys TIMESTAMP or ISCOMPLICATION they will be overwritten. The TIMESTAMP value can be used to query the status of the transfer, and cancel the transfer if desired. The value of this key can be retrieved from the supplied complicationInfo object immediately upon return from the function invocation.

Note: the complicationInfo sent to the Swift layer is a clone of the object submitted to the sendComplicationInfo function.

Complication data transfer status and flushing (iOS)

A complication data transfer can be queried and/or cancelled using the TIMESTAMP set by watchLink.sendUserInfo or updateUserInfoToPhone.

A complication data transfer can be queried via watchLink.queryComplication from the iOS app using a traditional Cordova plugin call, as in

watchLink.queryComplication(timestamp, success, error)
//	success = function(transferInfo
//		transferInfo = { timestamp: <Number>, 
				isComplication: <Boolean>, 
				transmitComplete: <Boolean>,
//				complicationInfo: <property-list-dictionary> 
//		transferInfo = null if the transfer status is no longer available
//	error = function(errorString
//		If the Watch is unavailable errorString will be "unavailable"
//		If the Watch session has not completed initialization errorString 
//			will be "uninitialized"

The in-progress complication data transfers can also be accessed from the iOS app using a Promise, as in

watchLink.queryComplication(userInfoID).then(success).catch(error)

A complication data transfer can be cancelled via watchLink.cancelComplication from the iOS app using a traditional Cordova plugin call, as in

watchLink.cancelComplication(userInfoID, success, error)
//	success = function(cancelled
//		cancelled = <Boolean>, true if the transfer was cancelled, 
			false otherwise
//	error = function(errorString
//		If the Watch is unavailable errorString will be "unavailable"
//		If the Watch session has not completed initialization errorString 
//			will be "uninitialized"

A information transfer can also be cancelled from the iOS app using a Promise, as in

watchLink.cancelComplication(userInfoID).then(success).catch(error)

The status of a complication data transfer will remain available until the transfer is complete and a subsequent complication data transfer is initiated.

The number of daily complications remaining in quota can be retrieved from the iOS app using a traditional Cordova plugin call, as in

watchLink.queryComplicationQuota(success, error)
//	success = function(number
//		number = the number of complication transfers remaining
//	error = function(errorString
//		If the Watch is unavailable errorString will be "unavailable"
//		If the Watch session has not completed initialization errorString 
//			will be "uninitialized"

The number of daily complications remaining can also be queried from the iOS app using a Promise, as in

watchLink.queryComplicationQuota().then(success).catch(error)

Complication data transfers outstanding (iOS)

The in-progress complication data transfers can be accessed via watchLink.outstandingComplicationTransfers from the iOS app using a traditional Cordova plugin call, as in

watchLink.outstandingComplicationTransfers(success, error)
//	success = function(outstandingUserInfo
//		outstandingUserInfo = [ 
//			{ userInfoID: <Number>, 
//			isComplication: <Boolean>, 
//			transmitComplete: <Boolean>, 
//			complicationInfo: <property-list-dictionary> } ]
//	error = function(errorString
//		If the Watch is unavailable errorString will be "unavailable"
//		If the Watch session has not completed initialization errorString 
//			will be "uninitialized"

The in-progress complication data transfers can also be accessed from the iOS app using a Promise, as in

watchLink.outstandingComplicationTransfers().then(success).catch(error)

All outstanding complication transfers can be cancelled using watchLink.flushComplicationTransfers().

Scheduled local notifications

Local notifications can be scheduled by the iOS app and will be displayed on the iOS device and/or Watch, depending on which one is active. The following table describes the various possibilities.

iOS appwatchOS appNotification type
Foreground, screen onForegroundiOS delegate
Background/off, screen onForegroundiOS alert
Foreground, screen offForegroundiOS banner + watchOS delegate
Background/off, screen offForegroundiOS banner + watchOS delegate
Foreground, screen onBackgroundiOS delegate
Background/off, screen onBackgroundiOS