4

I'm trying to wrap the peerjs libs in Smart Mobile Studio.
However I'm not sure what the rules for wrapping are.
I've looked at the RTL units and tried to deduce some rules from that.

However some questions still remain.

(function(exports){
var binaryFeatures = {};
binaryFeatures.useBlobBuilder = (function(){
  ...
})();

Peer.js starts with what looks like an anonymous function, with a single parameter (exports).

Is the following wrapper for binaryFeatures correct?

type
  JBinaryFeatures = class external 'binaryFeatures'
  public
    function useBlobBuilder: boolean; external 'useBlobBuilder';
    function useArrayBufferView: boolean; external 'useArrayBufferView';
  end;

Next up is a function 'EventEmitter' which is used as a prototype for the connection classes (Peer/DataConnection etc).

How do I tell SMS that JPeer inherits from JEventEmitter?

I've tried:

JPeer = class{(TEventEmitter)} external 'Peer'

However this is not legal.

Finally some of the classes defined in Peer.js are never assigned to a variable, they are instead instantiated on the fly inside some class.
Do I use the external 'Object' construct for those classes, or do I name them according to the Peer.js file?

JNegotiator = class external 'Negotiator'  
JNegotiator = class external 'Object' 

Below are my attempt at writing a wrapper and the Peer.js file, that needs to be wrapped.

PeerWrapper.pas

unit PeerWrapper;

interface

{$R 'file:peer.js'}

uses 
  SmartCL.System;

type
  JBinaryFeatures = class external 'binaryFeatures'
  public
    function useBlobBuilder: boolean; external 'useBlobBuilder';
    function useArrayBufferView: boolean; external 'useArrayBufferView';
  end;

//  Parent class for the packing functions inside the connections.
//  TBinaryPack = class external 'binaryPack'
//  public
//    function unpack(data: variant): variant; external 'unpack';
//    function pack(data: variant): variant; external 'pack';
//  end;

  JReliable = class;
  JUtil = class;
  JPeer = class;
  JDataConnection = class;
  JMediaConnection = class;

//var
//  FBlobBuilder: variant; external 'BlobBuilder';
//  FBinaryFeatures: TBinaryFeatures; external 'binaryFeatures';
//  //FBinaryPack: TBinaryPack;
//  FReliable: TReliable; external 'reliable';
//  FUtil: TUtil; external 'util';
//  FPeer: TPeer; external 'Peer';
//  FRTCSessionDescription: variant;
//  FRTCPeerConnection: variant;
//  FRTCIceCandidate: variant;

type

//  Parent class for socket type connections.
  JEventEmitter = class external 'eventEmitter'
  public
    constructor Create; external 'EventEmitter';
    function addListener(&type, listener, scope, once: variant): TEventEmitter; external 'addListener';
    function &on(&type, listener, scope, once: variant): TEventEmitter; external 'on';
    function once(&type, listener, scope: variant): TEventEmitter; external 'once';
    function removeListener(&type, listener, scope: variant): TEventEmitter; external 'removeListener';
    function off(&type, listener, scope: variant): TEventEmitter; external 'off';
    function removeAllListeners(&type: variant): TEventEmitter; external 'removeAllListeners';
    function listeners(&type: variant): variant; external 'listeners';
    function emit(&type: variant): boolean; external 'emit';   //vararg function
  end;

  JReliable = class external 'reliable'
  public
    constructor Create(dc, debug: variant); external 'Reliable';
    procedure send(msg: variant); external 'send';
    function higherBandwidthSDP(sdp: string): string; external 'higherBandwidthSDP';
    function onmessage(msg: variant): variant; external 'onmessage';
  end;

  JUtil = class external 'util'
  private
    FCloud_host: string; external 'CLOUD_HOST';
    FCloud_port: string; external 'CLOUD_PORT';
    chunkedBrowsers: variant; external 'chunkedBrowsers';
    chunkedMTU: integer; external 'chunkedMTU';
    FLogLevel: integer; external 'logLevel';
    FDefaultConfig: variant; external 'defaultConfig';
    FDebug: boolean; external 'debug';
  public
    procedure setLogLevel(level: integer); external 'setLogLevel';
    procedure setLogFunction(fn: variant); external 'setLogFunction';
    function browser: string; external 'browser';
    function supports: variant; external 'supports';
    function validateId(id: string): boolean; external 'validateId';
    function validateKey(key: string): boolean; external 'validateKey';
    function unpack(data: variant): variant; external 'unpack';
    function pack(data: variant): variant; external 'pack';
    procedure log; external 'log';
    procedure setZeroTimeout(global: variant); external 'setZeroTimeout';
    function chunk(blob: variant): variant; external 'chunk';
    procedure blobToArrayBuffer(blob: variant; callback: variant); external 'blobToArrayBuffer';
    procedure blobToBinaryString(blob: variant; callback: variant); external 'blobToBinaryString';
    function binaryStringToArrayBuffer(binary: variant): variant; external 'binaryStringToArrayBuffer';
    function randomToken: string; external 'randomToken';
    function isSecure: boolean; external 'isSecure';

    property CloudHost: string read FCloud_host;
    property CloudPort: string read FCloud_port;
    property LogLevel: integer read FLogLevel;
    property DefaultConfig: variant read FDefaultConfig;
    property Debug: boolean read FDebug;
  end;

  JPeer = class{(TEventEmitter)} external 'Peer'
  public
    //inherited from EventEmitter
    function addListener(&type, listener, scope, once: variant): TEventEmitter; external 'addListener';
    function &on(&type, listener, scope, once: variant): TEventEmitter; external 'on';
    function once(&type, listener, scope: variant): TEventEmitter; external 'once';
    function removeListener(&type, listener, scope: variant): TEventEmitter; external 'removeListener';
    function off(&type, listener, scope: variant): TEventEmitter; external 'off';
    function removeAllListeners(&type: variant): TEventEmitter; external 'removeAllListeners';
    function listeners(&type: variant): variant; external 'listeners';
    function emit(&type: variant): boolean; external 'emit';   //vararg function
  public
    constructor Create(id: string; options: variant); external 'Peer';
    function connect(peer: TPeer; options: variant): TDataConnection; external 'connect';
    function call(peer: TPeer; stream: variant; options: variant): TMediaConnection; external 'call';
    function getConnection(peer: TPeer; id: string): TEventEmitter; external 'getConnection';
    procedure emitError(&type: variant; err: variant); external 'emitError';
    procedure destroy; external 'destroy';
    procedure disconnect; external 'disconnect';
    procedure reconnect; external 'reconnect';
    procedure listAllPeers(callback: variant); external 'listAllPeers';
  end;

  JDataConnection = class{(TEventEmitter)} external 'DataConnection'
  public
    //inherited from TEventEmitter
    function addListener(&type, listener, scope, once: variant): TEventEmitter; external 'addListener';
    function &on(&type, listener, scope, once: variant): TEventEmitter; external 'on';
    function once(&type, listener, scope: variant): TEventEmitter; external 'once';
    function removeListener(&type, listener, scope: variant): TEventEmitter; external 'removeListener';
    function off(&type, listener, scope: variant): TEventEmitter; external 'off';
    function removeAllListeners(&type: variant): TEventEmitter; external 'removeAllListeners';
    function listeners(&type: variant): variant; external 'listeners';
    function emit(&type: variant): boolean; external 'emit';   //vararg function
  public
    constructor Create(peer: TPeer; provider, options: variant); external 'Peer.connect.DataConnection';
    procedure initialize(DataChannel: variant); external 'initialize';
    procedure close; external 'close';
    procedure send(data: variant; chunked: boolean); external 'send';
    procedure handleMessage(message: variant); external 'handleMessage';
  end;

  JMediaConnection = class{(TEventEmitter)} external 'mediaConnection'
  public
    //inherited from EventEmitter
    function addListener(&type, listener, scope, once: variant): TEventEmitter; external 'addListener';
    function &on(&type, listener, scope, once: variant): TEventEmitter; external 'on';
    function once(&type, listener, scope: variant): TEventEmitter; external 'once';
    function removeListener(&type, listener, scope: variant): TEventEmitter; external 'removeListener';
    function off(&type, listener, scope: variant): TEventEmitter; external 'off';
    function removeAllListeners(&type: variant): TEventEmitter; external 'removeAllListeners';
    function listeners(&type: variant): variant; external 'listeners';
    function emit(&type: variant): boolean; external 'emit';   //vararg function
  public
    constructor Create(peer: TPeer; provider, options: variant); external 'Peer.call.MediaConnection';
    procedure addStream(remoteStream: variant); external 'addStream';
    procedure handleMessage(message: variant); external 'handleMessage';
    procedure answer(stream: variant); external 'answer';
    procedure close; external 'close';
  end;

  JNegotiator = class external 'Negotiator'
  public
    procedure startConnection(connection, options: variant); external 'startConnection';
    procedure cleanup(connection: variant); external 'cleanup';
    procedure handleSDP(&type, connection, sdp: variant); external 'handleSDP';
    procedure handleCandidate(connection, ice: variant); external 'handleCandidate';
  end;

  JSocket = class{(TEventEmitter)} external 'Socket'
  public
    //inherited from EventEmitter
    function addListener(&type, listener, scope, once: variant): TEventEmitter; external 'addListener';
    function &on(&type, listener, scope, once: variant): TEventEmitter; external 'on';
    function once(&type, listener, scope: variant): TEventEmitter; external 'once';
    function removeListener(&type, listener, scope: variant): TEventEmitter; external 'removeListener';
    function off(&type, listener, scope: variant): TEventEmitter; external 'off';
    function removeAllListeners(&type: variant): TEventEmitter; external 'removeAllListeners';
    function listeners(&type: variant): variant; external 'listeners';
    function emit(&type: variant): boolean; external 'emit';   //vararg function
  public
    constructor Create(secure: boolean; host: string; port: integer; path, key: string); external 'Socket';
    procedure start(id: string; token: string); external 'start';
    procedure send(data: variant); external 'send';
    procedure close; external 'close';
  end;

implementation


end.

Link to peer.js on github
https://github.com/peers/peerjs/blob/master/lib/peer.js

How do I properly wrap the classes in this file?

Johan
  • 74,508
  • 24
  • 191
  • 319
  • Note that the peer.js code is too long to put into the question, hence the link to github. – Johan Dec 14 '14 at 12:21

1 Answers1

3

The anonymous function in peerjs is used to create a private scope.

In Pascal terms, what is inside the anonymous function is like code inside an "implementation" section, and what is assigned to the "exports" is what will be exposed, sort of like an "interface" section.

So the binaryFeatures variable is not accessible to the outside (at least not from that snippet of code).

Rather than starting from the JS code, you should probably start from the documentation, and look at the code only to fill the holes/ambiguities in the documentation.

Also since JS does not have proper visibility management, "private" methods & fields are mimic'ed through a mix of obscurity and anonymous functions. For instance the methods prefixed with "_" in the peerjs source are intended as private/protected, so you should not expose them, even though they are accessible in JS.

So you can start from the Peer object, expose its methods, then expose the objects those methods use/need.

Other things to know:

  • parameters names are meaningless, so feel free to rename them and feel free to use Pascal conventions there, or use long meaningful names when the library used obscure/short names
  • prototypal class inheritence is supported, so "JSubClass = class (JParent)" is okay (the requirement however is that JParent should support the "new" operator, otherwise, the library will likeley be providing a custom constructor or factory)
  • the external directive for methods is only required if the name differs, f.i. if you made a friendly Pascal name

So

procedure start(id, token: string); external 'start';

and

procedure start(id, token: string);

are identical, but if you "Pascalized" the name, it becomes necessary:

procedure Start(id, token: string); external 'start';

as JS is case-sensitive

Eric Grange
  • 5,931
  • 1
  • 39
  • 61
  • Thanks Eric, it think it would be good to have an online resource detailing how the interface between SMS and java script works exactly. It's kind of hard to form a mental model at the moment. – Johan Dec 22 '14 at 15:35