diff --git a/packages/nativescript-websockets/bridge.android.ts b/packages/nativescript-websockets/bridge.android.ts index 207e307..67d9b8c 100644 --- a/packages/nativescript-websockets/bridge.android.ts +++ b/packages/nativescript-websockets/bridge.android.ts @@ -1,5 +1,5 @@ import { HeaderType } from './common'; -import { NativeBridgeDefinition } from './websocket.definition'; +import { NativeBridgeDefinition, WebSocketBridgeConnectOptions } from './websocket.definition'; interface ExtendedArrayBuffer extends ArrayBuffer { from?(nativeBuffer: java.nio.ByteBuffer): ArrayBuffer; @@ -72,7 +72,7 @@ export class NativeBridge extends NativeBridgeDefinition { nativeWs!: okhttp3.WebSocket; startLooper?: android.os.Looper; handler?: android.os.Handler; - connect(url: string, protocols: string[], headers: HeaderType): void { + connect({ url, protocols, headers }: WebSocketBridgeConnectOptions): void { this.startLooper = android.os.Looper.myLooper(); this.handler = new android.os.Handler(this.startLooper); protocols = protocols || []; diff --git a/packages/nativescript-websockets/bridge.ios.ts b/packages/nativescript-websockets/bridge.ios.ts index 42cead0..67986e0 100644 --- a/packages/nativescript-websockets/bridge.ios.ts +++ b/packages/nativescript-websockets/bridge.ios.ts @@ -9,7 +9,7 @@ */ import { HeaderType } from './common'; -import { NativeBridgeDefinition } from './websocket.definition'; +import { NativeBridgeDefinition, WebSocketBridgeConnectOptions } from './websocket.definition'; @NativeClass class RCTSRWebSocketDelegateImpl extends NSObject implements RCTSRWebSocketDelegate { @@ -41,7 +41,7 @@ export class NativeBridge extends NativeBridgeDefinition { // store the delegate so it isn't garbage collected // TODO: fix the iOS runtime so we don't need this delegate!: RCTSRWebSocketDelegateImpl; - connect(url: string, protocols: string[], headers: HeaderType) { + connect({ url, protocols, headers, pinnedCertificates }: WebSocketBridgeConnectOptions): void { const nativeUrl = NSURL.URLWithString(url); const request = NSMutableURLRequest.requestWithURL(nativeUrl); // NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; @@ -63,6 +63,24 @@ export class NativeBridge extends NativeBridgeDefinition { for (const k of Object.keys(headers.headers)) { request.addValueForHTTPHeaderField(`${headers.headers[k]}`, k); } + if (pinnedCertificates) { + const sslArray = NSMutableArray.new(); + + for (const c of pinnedCertificates) { + // convert from pem to der (base64) + const der = c + .replace(/-----BEGIN CERTIFICATE-----/g, '') + .replace(/-----END CERTIFICATE-----/g, '') + .replace(/\r?\n/g, ''); + const cert = SecCertificateCreateWithData(null, NSData.alloc().initWithBase64EncodedStringOptions(der, NSDataBase64DecodingOptions.IgnoreUnknownCharacters)); + if (cert) { + sslArray.addObject(cert); + } else { + console.warn('Unable to create certificate from pem'); + } + } + request.RCTSR_SSLPinnedCertificates = sslArray; + } const webSocket = RCTSRWebSocket.alloc().initWithURLRequestProtocols(request, protocols); this.nativeSocket = webSocket; diff --git a/packages/nativescript-websockets/types/objc!NativeScriptWebSockets.d.ts b/packages/nativescript-websockets/types/objc!NativeScriptWebSockets.d.ts index 51755ab..7e8a63e 100644 --- a/packages/nativescript-websockets/types/objc!NativeScriptWebSockets.d.ts +++ b/packages/nativescript-websockets/types/objc!NativeScriptWebSockets.d.ts @@ -35,6 +35,10 @@ declare const enum RCTSRStatusCode { CodeMessageTooBig = 1009 } +interface NSMutableURLRequest { + RCTSR_SSLPinnedCertificates: NSArray; +} + declare class RCTSRWebSocket extends NSObject implements NSStreamDelegate { static alloc(): RCTSRWebSocket; // inherited from NSObject diff --git a/packages/nativescript-websockets/websocket.definition.ts b/packages/nativescript-websockets/websocket.definition.ts index d8c7fb4..0563bf7 100644 --- a/packages/nativescript-websockets/websocket.definition.ts +++ b/packages/nativescript-websockets/websocket.definition.ts @@ -7,10 +7,17 @@ export interface WebSocketPolyfill { _websocketFailed(message: string): void; } +export interface WebSocketBridgeConnectOptions { + url: string; + protocols: string[]; + headers: HeaderType; + pinnedCertificates?: string[]; +} + export abstract class NativeBridgeDefinition { public handleThreading = true; constructor(protected ws: WebSocketPolyfill) {} - abstract connect(url: string, protocols: string | string[], headers: HeaderType): void; + abstract connect(options: WebSocketBridgeConnectOptions): void; abstract send(data: string | ArrayBuffer | ArrayBufferView | Blob): void; abstract closeWithCodeReason(statusCode: number, closeReason: string): void; abstract sendPing(): void; diff --git a/packages/nativescript-websockets/websocket.ts b/packages/nativescript-websockets/websocket.ts index 3c5e944..7e80d54 100644 --- a/packages/nativescript-websockets/websocket.ts +++ b/packages/nativescript-websockets/websocket.ts @@ -37,6 +37,13 @@ const openWebsockets = new Set(); // websocketFailed: [{ id: number; message: string }]; // }; +interface ExtraWebsocketOptions { + headers: { origin?: string; [key: string]: unknown }; + nativescript: { handleThreading: boolean }; + pinnedCertificates?: string[]; + [key: string]: unknown; +} + /** * Browser-compatible WebSockets implementation. * @@ -81,7 +88,7 @@ export class WebSocket implements WebSocketPolyfill { message: new Map(), }; - constructor(url: string, protocols?: string | Array, options?: { headers: { origin?: string; [key: string]: unknown }; nativescript: { handleThreading: boolean }; [key: string]: unknown }) { + constructor(url: string, protocols?: string | Array, options?: ExtraWebsocketOptions) { this.nativeBridge = new NativeBridge(this); this.url = url; if (typeof protocols === 'string') { @@ -91,7 +98,7 @@ export class WebSocket implements WebSocketPolyfill { protocols = []; } - const { headers = {}, nativescript = { handleThreading: true }, ...unrecognized } = options || ({} as { headers: { origin?: string; [key: string]: unknown }; nativescript: { handleThreading: boolean }; [key: string]: unknown }); + const { headers = {}, nativescript = { handleThreading: true }, pinnedCertificates, ...unrecognized } = options || ({} as ExtraWebsocketOptions); this.nativeBridge.handleThreading = nativescript?.handleThreading ?? WebSocket.HANDLE_THREADING; this._binaryType = 'arraybuffer'; @@ -119,7 +126,7 @@ export class WebSocket implements WebSocketPolyfill { // Platform.OS !== 'ios' ? null : NativeWebSocketModule, // ); this._registerEvents(); - this.nativeBridge.connect(url, protocols, { headers }); + this.nativeBridge.connect({ url, protocols, headers: { headers }, pinnedCertificates }); openWebsockets.add(this); // NativeWebSocketModule.connect(url, protocols, {headers}, this._socketId); } diff --git a/references.d.ts b/references.d.ts index 61b401f..36d381b 100644 --- a/references.d.ts +++ b/references.d.ts @@ -1,2 +1,3 @@ /// +/// ///