Skip to content

Instantly share code, notes, and snippets.

@dawsontoth
Last active January 3, 2016 04:58
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save dawsontoth/8412167 to your computer and use it in GitHub Desktop.
Save dawsontoth/8412167 to your computer and use it in GitHub Desktop.
Titanium client side simple JSON based socket communications. The NodeJS server side component to this is available here: https://gist.github.com/dawsontoth/8412318
var lox = require('lib/lox');
lox.start();
lox.exampleOfSendWithoutCallback();
lox.exampleOfSendWithCallback(function(result) {
console.log('Got data back! ' + result);
});
/*
Public API.
*/
exports.create = create;
/*
Implementation.
*/
function create(args) {
var socket,
reconnectTO = 1000,
decayedTO = 1,
listenSocketTimeout = 0,
failCount = 0,
retryPaused = false;
function listenSocket() {
listenSocketTimeout = 0;
socket = Ti.Network && Ti.Network.Socket && Ti.Network.Socket.createTCP({
host: args.host,
port: args.ports && args.ports[failCount % args.ports.length] || args.port,
connected: socketConnected,
error: disconnectAndRetry
});
socket && socket.connect();
}
function socketConnected(e) {
failCount = 0;
Ti.Stream.pump(e.socket, pumpSocket, 1024, true);
args.connected && args.connected();
}
function disconnect() {
if (listenSocketTimeout) {
clearTimeout(listenSocketTimeout);
listenSocketTimeout = 0;
}
if (socket) {
try {
socket.close();
}
catch (e) {
// Oh, well...
}
socket = null;
}
decayedTO = decayedTO * 2;
}
function retry() {
listenSocketTimeout = setTimeout(listenSocket, decayedTO);
}
function resume() {
decayedTO = reconnectTO;
retry();
}
function disconnectAndRetry() {
failCount += 1;
disconnect();
retry();
}
function isConnected() {
return socket && socket.state == Ti.Network.Socket.CONNECTED
}
function write(str) {
if (!isConnected()) {
return false;
}
else {
//noinspection JSValidateTypes
socket.write(Ti.createBuffer({ value: str }));
return true;
}
}
function pauseRetry() {
retryPaused = listenSocketTimeout > 0;
}
function resumeRetry() {
if (retryPaused) {
retryPaused = false;
retry();
}
}
function pumpSocket(e) {
// Has the remote socket closed its end?
if (e.bytesProcessed < 0) {
disconnectAndRetry();
return;
}
try {
e.buffer && args.read && args.read(e.buffer.toString());
} catch (ex) {
Ti.API.error('[LOX] Socket Error: ' + ex);
}
}
function clean() {
disconnect();
args = socket = decayedTO = retryPaused = listenSocketTimeout = failCount = null;
}
return {
start: retry,
isConnected: isConnected,
write: write,
pauseRetry: pauseRetry,
resumeRetry: resumeRetry,
resume: resume,
disconnect: disconnect,
clean: clean
};
}
var loxProtocol = require('lox.protocol'),
gracefulSocket = require('graceful.socket');
/*
State.
*/
var dataDir = Ti.Filesystem.applicationDataDirectory,
socket = gracefulSocket.create({
ports: [ 3001 ], // Multiple ports can be used; after a failure to connect, we'll try a different port.
host: '127.0.0.1',
connected: updateConfig,
read: handleSocketRead
}),
isPaused = false,
isResumed = false,
downloadClient = null,
downloadID = 0,
callbacks = {},
udp = {};
/*
Public API.
*/
exports.start = start;
exports.exampleOfSendWithoutCallback = exampleOfSendWithoutCallback;
exports.exampleOfSendWithCallback = exampleOfSendWithCallback;
/*
Implementation.
*/
function start() {
socket.start();
}
/*
Server interactions.
*/
function handleSocketRead(obj) {
var data = loxProtocol.fromPacket(obj);
if (!data) {
// We haven't received a full command yet; return until later.
return;
}
console.log('Received ' + data.command);
// For You TODO: Do something with data.command and data.result.
switch (data.command) {
case 'bar':
callbacks.bar && callbacks.bar(data.result);
break;
}
// Handle coalesced packets.
handleSocketRead();
}
function exampleOfSendWithoutCallback() {
writeSocket('foo', 'bar');
}
function exampleOfSendWithCallback(cb) {
callbacks.bar = cb;
writeSocket('bar', 'baz');
}
/*
Lifecycle methods.
*/
Ti.App.addEventListener('pause', pause);
Ti.App.addEventListener('paused', pause);
function pause() {
if (isPaused) { return; }
isResumed = false;
isPaused = true;
socket.disconnect();
}
Ti.App.addEventListener('resume', resume);
Ti.App.addEventListener('resumed', resume);
function resume() {
if (isResumed) { return; }
isPaused = false;
isResumed = true;
socket.resume();
}
function writeSocket(cmd, obj) {
console.log('Sending ' + cmd);
return socket.write(loxProtocol.toPacket(cmd, obj));
}
/**
* This is a simple JSON based socket protocol.
*
* Payloads follow the following format:
* command{new line}payloadLength{new line}JSON payload
* For example:
* echo\n30\n{ "message": "Hello, world!" }
*
* Version: 1.0.0
*/
var socketData = {};
/*
Public API.
*/
exports.toPacket = toPacket;
exports.fromPacket = fromPacket;
exports.safeParse = safeParse;
/*
Implementation.
*/
function safeParse(txt) {
try {
return JSON.parse(txt);
}
catch (err) {
console.error('[LOX] Parse failed: ' + err);
console.error(txt);
return false;
}
}
function toPacket(command, data) {
var str = '' + JSON.stringify(data);
return command + '\n' + str.length + '\n' + str;
}
function fromPacket(str, localData) {
if (!localData) {
localData = socketData;
}
if (localData.buffer === undefined) {
localData.remaining = 0;
localData.buffer = '';
localData.result = [];
}
str !== undefined && (localData.buffer += str);
var nextNewLine;
if (!localData.command) {
if (!localData.buffer.length) {
return false;
}
nextNewLine = localData.buffer.indexOf('\n');
if (nextNewLine <= 0) {
return false;
}
localData.command = localData.buffer.substr(0, nextNewLine);
localData.buffer = localData.buffer.substr(nextNewLine + 1);
}
if (!localData.remaining) {
nextNewLine = localData.buffer.indexOf('\n');
if (nextNewLine <= 0) {
return false;
}
localData.remaining = +localData.buffer.substr(0, nextNewLine);
localData.buffer = localData.buffer.substr(nextNewLine + 1);
}
var took = Math.min(localData.remaining, localData.buffer.length);
localData.result.push(localData.buffer.substr(0, took));
localData.buffer = localData.buffer.substr(took);
localData.remaining -= took;
if (localData.remaining > 0) {
return false;
}
if (localData.remaining < 0) {
throw 'Assertion failure: Negative buffered data.';
}
var retVal = {
command: localData.command,
result: safeParse(localData.result.join(''))
};
localData.command = false;
localData.result = [];
return retVal;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment