This documentation is for developers of the Feedme client library itself.
Clone the repo, install dependencies, and build the package:
git clone https://github.com/aarong/feedme-client
cd feedme-client
npm install
npm run build
The build procedure runs unit tests on the src
folder, assembles a transpiled
and publish-ready NPM package in the build
folder (including a Node module and
a browser bundle), and runs functional tests on the built Node module.
To enable debugging output set the debug
environment variable to
feedme-client*
.
-
build/
Created by
npm run build
. Contains files ready to be deployed as an NPM package. Includes an entrypoint for Node (index.js
) and a UMD module for browsers (bundle.js
has no sourcemaps and is used by applications, whilebundle.withmaps.js
has sourcemaps and is used for testing and debugging).LICENSE, README.md, and package.json are included.
(Gulp/Webpack)
-
coverage/
Created by
npm run coverage
. Coverage information is for unit tests only.(Jest)
-
docs/
Created by
npm run docs
. Source code documentation.(Documentation.js)
-
src/
Module source code.
-
src/main.node.js
Entrypoint for transpiling the Node NPM package, which includessource-map-support
. -
src/main.browser.js
Entrypoint for transpiling the browser bundle. No special functionality. -
src/main.js
Common entrypoint to the module for Node and the browser. -
src/__tests__
Unit tests (Jest).
-
-
tests/
Functional tests for the Node and and browser builds.
Functional tests are written for Jasmine, as Jest can not run in the browser.
-
tests/tests.js
The functional tests, run against Node and browser builds. -
tests/tests.node.js
Runs tests in Node. -
tests/tests.browsers.js
Runs tests in the browser. -
tests/webroot
A hosting root to run functional tests on Sauce. Derived from Jasmine-standalone.
-
Source code is written in ES6 and is transpiled on build for Node and the browser.
Eslint enforces Airbnb style and applies Prettier, which takes precence over some Airbnb rules. A lint check is performed before unit tests.
Errors are thrown, called back, and emitted in the form
new Error("ERROR_CODE: Some more descriptive text.")
. Altering the name
property of an error object breaks sourcemaps in the browser.
-
transport.wrapper.js
ensures that the transport object behaves as required and that tranport events are always deferred. -
session.sync.js
contains server-facing functionality. It enables a straightforward compliant conversation with the server and invokes callbacks and event handlers synchronously. -
session.wrapper.js
adds a deferral layer on top ofsession.sync.js
so that event handlers and callbacks are always invoked asynchronously. -
client.sync.js
contains app-facing functionality. It provides an enhanced experience oversession.js
with configurability and feed objects, and invokes callbacks and event handlers synchronously. -
client.wrapper.js
adds a deferral layer on top ofclient.sync.js
so that event handlers and callbacks are always invoked asynchronously. Also adds a promise API overclient.action()
. -
config.js
contains hard-coded configuration, mainly default options. -
main.js
is the common entrypoint for the module. It takes a transport object from the outside and returns a usable client. -
main.node.js
is the entrypoint for Node module transpilation. It injectssource-map-support
. -
main.browser.js
is the entrypoint for browser transpilation. No special functionality. -
defer.js
provides a common deferral mechanism for the three wrapper objects.
The intention is to support Node and NPM back as far as realistically possible.
For a development install, the binding dependency constraint is that Webpack and babel-loader require Node 8+. Also, package-lock.json is only supported by NPM 5+, which comes with Node 8+. So develop and build on Node 8+ and NPM 5+.
Although the library needs to be developed and built on Node 8+, its production dependencies are more lenient and can be run on Node 6+.
-
npm run docs
Generates source code documentation indocs
. -
npm run lint-src
Checks for linting errors insrc
. -
npm run lint-build-tests
Checks for linting errors intests
. -
npm run coverage
Displays Jest unit test coverage. -
npm run coveralls
Used by Travis to pass coverage information to Coveralls. -
npm run test-src
Runs linting and Jest unit tests on the source code. Aliased bynpm run test
. (Jest) -
npm run build
Runs the unit tests, builds a publishable NPM package inbuild
, and runs the Node functional tests on the build. Browser tests must be run explicitly. -
npm run test-build-node
Runs functional tests against the Node module in thebuild
folder. (Jasmine) -
npm run test-build-browsers -- <mode>
Runs functional tests against the browser bundle in thebuild
folder. The tests are built using Jasmine, which supports both Node and the browser (though sourcemaps only work in Node).Modes:
-
local
Launches a local webserver with the browser tests, which can then be accessed manually from a browser. -
sauce-live
Launches a local webserver with the browser tests and loads Sauce Connect the proxy. You can then log in to Sauce and run the browser tests live via the Sauce Connect tunnel. Useful for viewing console output. -
sauce-automatic
(Default) Launches a local server with the browser tests, loads the Sauce Connect proxy, instructs the Sauce REST API to run automated tests across the widest possible set of platforms, and reports results. Requires the environmental variablesSAUCE_USERNAME
andSAUCE_ACCESS_KEY
, otherwise the Sauce Connect proxy will fail. Runs on each Travis build. -
sauce-automatic-hanging
Many Sauce browser-platform combinations run the tests successfully but, frustratingly, do not actually return success. This option runs tests on those combinations, which can then be verified manually on the Sauce website. Requires Sauce credentials in environmental variables.
-
Commits to the master branch on Github are built and tested by Travis CI. If the NPM package version has been incremented, then Travis will deploy by publishing the build to NPM.
Contributors can fork the repo, make changes, and submit a pull request.
Significant new features should be developed in feature branches.
# Fork and clone the repo locally
git checkout -b my-new-feature
# Make changes
git commit -m "Added my new feature."
git push origin my-new-feature
# Submit a pull request
See previous draft
Transport objects abstract away the specifics of the messaging connection between the client and the server. A transport object is injected into the client library at initialization.
Transport objects must implement the following interface and behavior in order to function correctly with the library.
See the Feedme WebSocket Transport for a working example.
Transport objects are always in one of three states: disconnected
,
connecting
, or connected
. Once connected, transport objects must be able to
exchange string messages with the server. Messages must be received by the other
side in the order that they were sent.
Connection timeout functionality is controlled by the library and transports should generally not implement their own. If a transport fails to establish a connection to the server within the amount of time configured by the application, then the library will instruct the transport to abort its connection attempt and will inform the application that the connection attempt timed out.
Connection retry functionality is controlled by the library and must not be implemented by transports. If a transport's attempt to connect to a server fails, it must indicate to the library that it has failed and take no further action. The library may instruct the transport to initiate a subsequent connection attempt depending on the number of failed attempts and the configuration supplied by the application.
Reconnect functionality is controlled by the library and must not be implemented by transports. If a transport is connected to the server and disconnects due to a failure of its internal communication mechanism, then the library may instruct the transport to attempt to reconnect to the server depending on the configuration supplied by the application.
Transport objects must be traditional Javascript event emitters. The library must be able to subscribe event handlers using one of the following methods:
transport.on(eventName, eventHandler)
transport.addListener(eventName, eventHandler)
transport.addEventListener(eventName, eventHandler)
The library must also be able to unsubscribe event handlers using one of the following methods:
transport.off(eventName, eventHandler)
transport.removeListener(eventName, eventHandler)
transport.removeEventListener(eventName, eventHandler)
The library attaches event handlers to the following transport events:
-
connecting
Used to inform the library that the transport state changed from
disconnected
toconnecting
. -
connect
Used to inform the library that the transport state changed from
connecting
toconnected
. -
message(msg)
Used to inform the library that a string message
msg
has been received from the server. -
disconnect([err])
Used to inform the library that the transport state changed from
connecting
orconnected
todisconnected
.When the transport emits a
disconnect
event due to a library call totransport.disconnect()
, it must emit the event with no arguments. When the transport emits adisconnect
event because its internal connection to the server has failed, it must emit the event with anError
object argument describing the nature of the failure.
Transport objects must sequence event emissions as follows:
-
After library initialization, the transport must not emit any events until the library calls
transport.connect()
. -
After the library calls
transport.connect()
, the transport must emit aconnecting
event. -
A
connecting
event must be followed by aconnect
event or adisconnect
event. -
A
connect
event must be followed by amessage
event or adisconnect
event. -
A
message
event must be followed by anothermessage
event or adisconnect
event. -
A
disconnect
event must not be followed by any further events until the library has calledtransport.connect()
.
When the library invokes a transport method, the transport must emit any
resulting events asynchronously using a mechanism like process.nextTick()
or
setTimeout()
. Therefore, when the transport emits an event, the state returned
by transport.state()
is not required to align with the state suggested by the
event. When the library observes a transport event, it determines the transport
state using transport.state()
and proceeds accordingly.
Transport objects must implement the following methods:
-
transport.state()
- Returnsstring
Used by the library to determine the current transport state, and thus the set of operations that the library is permitted to perform on the transport. The transport must permit the library to call this method at any time and must return
"disconnected"
,"connecting"
, or"connected"
.-
disconnected
Indicates that the transport is not connected to the server and is not attempting to connect.
If
transport.state()
returnsdisconnected
, then the transport must accept a synchronous library call totransport.connect()
.Transport objects must be supplied to the library in a
disconnected
state when the library is initialized.When
disconnected
, transport objects must remaindisconnected
until the library callstransport.connect()
. -
connecting
Indicates that the transport is attempting to connect to the server but cannot yet transmit or receive messages.
If
transport.state()
returnsconnecting
, then the transport must accept a synchronous library call totransport.disconnect()
. -
connected
Indicates that the transport can transmit messages to the server and that it will emit any messages that it receives from the server.
If
transport.state()
returnsconnected
, then the transport must accept a synchronous library call totransport.send()
ortransport.disconnect()
.
The
transport.state()
return value must change only as described in thetransport.connect()
,transport.send()
, andtransport.disconnect()
sections below. -
-
transport.connect()
- Returnsundefined
Used by the library to instruct the transport to connect to the server. The library calls this method only after ensuring that
transport.state()
isdisconnected
.If the transport connection attempt fails synchronously within the call to
transport.connect()
, thentransport.state()
must returndisconnected
when the method exits and the transport must asynchronously emitconnecting
and thendisconnect
with a descriptive error argument. The call totransport.connect()
must exit successfully.If the transport is able to establish a connection to the server synchronously within the call to
transport.connect()
, thentransport.state()
must returnconnected
when the method exits and the transport must asynchronously emitconnecting
and thenconnect
. The call totransport.connect()
must exit successfully.If the transport is not able to synchronously determine whether the connection to the server has succeeded, then
transport.state()
must returnconnecting
when the method exits and the transport must asynchronously emitconnecting
. The call totransport.connect()
must exit successfully. Subsequently:-
If the transport connection attempt fails before the library calls
transport.disconnect()
, then the state reported bytransport.state()
must becomedisconnected
and the transport must then emitdisconnect
with a descriptive error argument. -
If the transport connection attempt succeeds before the library calls
transport.disconnect()
, then the state reported bytransport.state()
must becomeconnected
and the transport must then emitconnect
. Subsequently, if the transport's connection to the server fails before the library callstransport.disconnect()
, then the state reported bytransport.state()
must becomedisconnected
and the transport must then emitdisconnect
with a descriptive error argument.
-
-
transport.send(msg)
- Returnsundefined
Used by the library to instruct the transport to send a string message
msg
to the server. The library calls this method only after verifying that the state reported bytransport.state()
isconnected
.If the transmission attempt fails synchronously within the call to
transport.send()
thentransport.state()
must returndisconnected
when the method exits and the transport must asynchronously emitdisconnect
with a descriptive error argument. The call totransport.send()
must exit successfully.If the transmission attempt does not fail synchronously within the call to
transport.send()
thentransport.state()
must returnconnected
when the method exits and the call totransport.send()
must exit successfully. -
transport.disconnect()
- Returnsundefined
Used by the library to instruct the transport to disconnect from the server. The library calls this method only after verifying that the state reported by
transport.state()
isconnecting
orconnected
.The state reported by
transport.state()
must bedisconnected
after the method exits and the transport must asynchronously emitdisconnect
with no arguments. The call totransport.disconnect()
must exit successfully.
The library aims to detect transport structure and behavior that violates the above requirements.
-
If the transport violates a requirement when the application initializes the library, then the library throws an
Error
witherr.message === "TRANSPORT_ERROR: ..."
to the application. -
If the transport violates a requirement when the application invokes a library method, then the library throws an
Error
witherr.message === "TRANSPORT_ERROR: ..."
to the application. -
If the transport violates a requirement when the library invokes a transport method internally (i.e. not synchronously within a method called by the application), then the library throws an unhandled
Error
witherr.message === "TRANSPORT_ERROR: ..."
. -
If the transport emits an event that violates a requirement, or emits a valid event but violates a requirement during library event handler execution, then the library throws an
Error
witherr.message === "TRANSPORT_ERROR: ..."
to the transport. The transport must not attempt to catch and handle such errors.
If a transport method threw an unexpected error, then the error is exposed as
err.transportError
in all of the above cases.
When a transport error occurs, the library emits a transportError
event and
supplies the error as as an argument.
It is not generally possible to recover from transport errors, as they can create ambiguity as to the state of transport and Feedme conversation. For this reason, the library instance is destroyed if a transport error arises, as described in the user documentation.