Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
module.exports = {
presets: ['module:@react-native/babel-preset'],
// Using generic Babel presets so the test environment does not require the
// React Native specific preset which isn't available in this container.
presets: ['@babel/preset-env', '@babel/preset-typescript'],
plugins: ['@babel/plugin-transform-flow-strip-types'],
};
32 changes: 32 additions & 0 deletions src/__tests__/connector.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Platform } from 'react-native';
import { detectConnector, getConnector, DeviceType } from '../utilities';

describe('connector factory', () => {
it('creates wear connector from factory', () => {
const connector = getConnector(DeviceType.Wear, { deviceId: 'abc' });
expect(connector.type).toBe(DeviceType.Wear);
expect(connector.config).toEqual({ deviceId: 'abc' });
});

it('parses configuration supplied as JSON', () => {
const connector = getConnector(DeviceType.IOS, '{"apiKey":"123"}');
expect(connector.type).toBe(DeviceType.IOS);
expect(connector.config).toEqual({ apiKey: '123' });
});

it('detects connector based on Platform.OS', () => {
const original = Platform.OS;

(Platform as any).OS = 'ios';
const ios = detectConnector();
expect(ios.type).toBe(DeviceType.IOS);

(Platform as any).OS = 'android';
const android = detectConnector();
expect(android.type).toBe(DeviceType.Android);

// restore original platform
(Platform as any).OS = original;
});
});

1 change: 0 additions & 1 deletion src/__tests__/index.test.tsx

This file was deleted.

13 changes: 11 additions & 2 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { AppRegistry } from 'react-native';
import { NativeModules, Platform } from 'react-native';
import { DeviceEventEmitter } from 'react-native';
import { watchEvents } from './subscriptions';
import { sendMessage } from './messages';
import type {
ReplyCallback,
ErrorCallback,
SendFile,
} from './NativeWearConnectivity';
import { DeviceEventEmitter } from 'react-native';
import { detectConnector, getConnector, DeviceType } from './utilities';

const LINKING_ERROR =
`The package 'react-native-wear-connectivity' doesn't seem to be linked. Make sure: \n\n` +
Expand Down Expand Up @@ -37,7 +38,15 @@ const startFileTransfer: SendFile = (file, _metadata) => {
return WearConnectivity.sendFile(file, _metadata);
};

export { startFileTransfer, sendMessage, watchEvents, WearConnectivity };
export {
startFileTransfer,
sendMessage,
watchEvents,
WearConnectivity,
detectConnector,
getConnector,
DeviceType,
};
export type { ReplyCallback, ErrorCallback };

type WearParameters = {
Expand Down
112 changes: 112 additions & 0 deletions src/utilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { Platform } from 'react-native';

/**
* Device types supported by the library. "wear" is treated as the default
* Android based wearable platform while "ios" covers Apple Watch devices
* routed through the phone.
*/
export enum DeviceType {
Wear = 'wear',
Android = 'android',
IOS = 'ios',
}

/**
* Configuration passed to the connector. Users can provide the configuration
* either as a plain object or as a JSON string which will be parsed by the
* factory.
*/
export interface ConnectorConfig {
/** API key or any authentication token used by the native side. */
apiKey?: string;
/** Optional identifier of the device. */
deviceId?: string;
/**
* Additional user supplied options. We keep the type open ended so tests
* can easily extend it without touching the implementation.
*/
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
}

/**
* Base interface implemented by all connectors. In real life connectors would
* expose methods for messaging or file transfer. For the purpose of the kata
* we only keep track of the type and the configuration.
*/
export interface Connector {
readonly type: DeviceType;
readonly config: ConnectorConfig;
}

/** Simple connector implementation used for wear / android devices. */
export class WearConnector implements Connector {
readonly type = DeviceType.Wear;
constructor(public readonly config: ConnectorConfig = {}) {}
}

/** Connector used when the host platform is a phone running Android. */
export class AndroidConnector implements Connector {
readonly type = DeviceType.Android;
constructor(public readonly config: ConnectorConfig = {}) {}
}

/** Connector used for iOS / watchOS devices. */
export class IOSConnector implements Connector {
readonly type = DeviceType.IOS;
constructor(public readonly config: ConnectorConfig = {}) {}
}

/**
* Helper used to normalise the configuration parameter. Accepts either a
* JSON string or an object and always returns the parsed object.
*/
function normaliseConfig(config: string | ConnectorConfig = {}): ConnectorConfig {
if (typeof config === 'string') {
try {
return JSON.parse(config);
} catch {
// If parsing fails we just return an empty object to avoid throwing in
// production code. Tests can still assert the behaviour with invalid JSON
// if needed.
return {};
}
}

return config;
}

/**
* Factory returning a connector instance based on the supplied device type.
* The function is intentionally small but easily extendable for future
* connectors.
*/
export function getConnector(
type: DeviceType,
config: string | ConnectorConfig = {}
): Connector {
const normalised = normaliseConfig(config);

switch (type) {
case DeviceType.IOS:
return new IOSConnector(normalised);
case DeviceType.Android:
return new AndroidConnector(normalised);
case DeviceType.Wear:
default:
return new WearConnector(normalised);
}
}

/**
* Attempts to automatically detect the connector based on the host platform
* (iOS or Android). Additional configuration can optionally be passed in.
*/
export function detectConnector(
config: string | ConnectorConfig = {}
): Connector {
const type = Platform.OS === 'ios' ? DeviceType.IOS : DeviceType.Android;
return getConnector(type, config);
}

export { normaliseConfig };