Last active
October 11, 2018 22:34
-
-
Save muratcorlu/f5a9da629b3a727dec772844cc545fd8 to your computer and use it in GitHub Desktop.
GameSpark SDK for TypeScript (for using with NativeScript)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const OpCodes = { | |
LOGIN_RESULT: -1, | |
PING_RESULT: -3, | |
UDP_CONNECT_MESSAGE: -5, | |
PLAYER_READY_MESSAGE: -7, | |
PLAYER_CONNECT_MESSAGE: -101, | |
PLAYER_DISCONNECT_MESSAGE: -103, | |
} | |
export class Connection { | |
stopped: boolean; | |
constructor(public session) { | |
this.stopped = false; | |
} | |
stopInternal() { | |
} | |
stop() { | |
this.stopped = true; | |
this.stopInternal(); | |
} | |
onPacketReceived(p, packetSize) { | |
if (p.command) { | |
if (p.command["abstractResultType"]) { | |
var result = p.command; | |
result.configure(p, this.session); | |
if (result.executeAsync()) { | |
this.session.submitAction(p.command) | |
} else { | |
p.command.execute() | |
} | |
} else { | |
this.session.submitAction(p.command) | |
} | |
} else { | |
if (!p.hasPayload) { | |
var cmd; | |
if (p.sender) { | |
cmd = CustomCommand.pool.pop().configure(p.opCode, p.sender, null, p.data, 0, this.session, packetSize) | |
} else { | |
cmd = CustomCommand.pool.pop().configure(p.opCode, 0, null, p.data, 0, this.session, packetSize) | |
} | |
if (cmd) { | |
this.session.submitAction(cmd) | |
} | |
} | |
} | |
} | |
}; | |
export class ReliableConnection extends Connection { | |
client: WebSocket; | |
constructor(public remotehost, public remoteport, session) { | |
super(session); | |
console.log('ReliableConnection constructor'); | |
}; | |
connect() { | |
this.client = new WebSocket("wss://" + this.remotehost + ":" + this.remoteport); | |
this.client.addEventListener('open', (e) => { | |
this.onopen(e); | |
}); | |
this.client.addEventListener('message', (e) => {this.onmessage(e)}); | |
this.client.addEventListener('close', (e) => {this.onclose(e)}); | |
this.client.addEventListener('error', (e) => {this.onerror(e)}); | |
this.client.binaryType = "arraybuffer"; | |
// this.client.onopen = this.onopen.bind(this); | |
// this.client.onclose = this.onclose.bind(this); | |
// this.client.onerror = this.onerror.bind(this); | |
// this.client.onmessage = this.onmessage.bind(this) | |
}; | |
onopen(e) { | |
if (this.stopped || this.session == null) { | |
if (this.client != null) { | |
this.client.close(); | |
this.client = null | |
} | |
return | |
} | |
this.session.log("ReliableConnection", GameSparksRT.logLevel.DEBUG, " TCP Connection Established"); | |
var loginCmd = new LoginCommand(this.session.connectToken); | |
console.log(loginCmd); | |
this.send(loginCmd) | |
}; | |
onmessage(e) { | |
if (this.stopped) { | |
if (this.client != null) { | |
this.client.close(); | |
this.client = null | |
} | |
return | |
} | |
var data = e.data; | |
var read = data.byteLength; | |
var rss = new Stream; | |
var array = Array.from(new Uint8Array(data)); | |
rss.writeBytes(array, 0, read); | |
rss.setPosition(0); | |
while (rss.getPosition() < read) { | |
var p = PooledObjects.packetPool.pop(); | |
var bytesRead = this.read(rss, p); | |
this.onPacketReceived(p, bytesRead); | |
PooledObjects.packetPool.push(p); | |
p = PooledObjects.packetPool.pop() | |
} | |
}; | |
onclose(e) { | |
if (this.session != null) { | |
this.session.log("ReliableConnection", GameSparksRT.logLevel.DEBUG, " TCP Connection Closed") | |
} | |
if (!this.stopped && this.client != null) { } else { } | |
}; | |
onerror(e) { | |
if (this.session != null) { | |
this.session.log("ReliableConnection", GameSparksRT.logLevel.DEBUG, " TCP Connection Error") | |
} | |
console.log(e) | |
}; | |
send(request) { | |
if (this.client != null && this.client.readyState == WebSocket.OPEN) { | |
var stream = new Stream; | |
var p = request.toPacket(this.session, false); | |
var ret = Packet.serializeLengthDelimited(stream, p); | |
var buffer = new Uint8Array(stream.getBuffer()); | |
this.client.send(buffer); | |
PooledObjects.packetPool.push(p); | |
return ret | |
} else { | |
return -1 | |
} | |
}; | |
read(stream, p) { | |
if (this.stopped) { | |
return 0 | |
} | |
p.session = this.session; | |
p.reliable = true; | |
var ret = Packet.deserializeLengthDelimited(stream, p); | |
if (p.reliable == null) { | |
p.reliable = true | |
} | |
return ret | |
} | |
stopInternal() { | |
if (this.client != null && this.client.readyState == WebSocket.OPEN) { | |
this.client.close(); | |
this.client = null | |
} | |
} | |
} | |
const Wire = { | |
VARINT: 0, | |
FIXED64: 1, | |
LENGTH_DELIMITED: 2, | |
FIXED32: 5 | |
}; | |
export class Key { | |
constructor(public field, public wireType) { } | |
toString() { | |
return "[Key: " + this.field + ", " + this.wireType + "]" | |
} | |
}; | |
function assert(condition, message) { | |
if (!condition) { | |
message = message || "Assertion failed"; | |
if (typeof Error !== "undefined") { | |
throw new Error(message) | |
} | |
throw message | |
} | |
} | |
class Stream { | |
buffer = ""; | |
position = 0; | |
getPosition() { | |
return this.position | |
} | |
setPosition(position) { | |
this.position = position | |
} | |
getLength() { | |
return this.buffer.length | |
} | |
bytesAvailable() { | |
return this.buffer.length - this.position | |
} | |
readByte() { | |
var ret = this.readChar(); | |
if (typeof ret === "number" && ret == -1) { | |
return 0 | |
} else { | |
return (ret as string).charCodeAt(0) | |
} | |
} | |
readChar() { | |
var buffer = ""; | |
var totalBytes; | |
var ret; | |
ret = this.readChars(buffer, this.position, 1); | |
totalBytes = ret[0]; | |
buffer = ret[1]; | |
if (totalBytes == 1) { | |
return buffer.slice(0, 1) | |
} else { | |
return -1 | |
} | |
} | |
readChars(buffer, position, length) { | |
assert(typeof buffer === "string", "buffer must be string"); | |
if (this.bytesAvailable() <= 0) { | |
console.log("WARNING: Reached end of the stream"); | |
return [0, buffer] | |
} | |
if (length <= 0) { | |
console.log("WARNING: no characters read (length = 0)"); | |
return [0, buffer] | |
} | |
if (buffer.length > 0) { | |
assert(position >= 0 && position < buffer.length, "position out of range") | |
} else { | |
position = 0 | |
} | |
var subString; | |
var newBuffer = ""; | |
var startPosition = this.position; | |
var endPosition = startPosition + length; | |
var newLength; | |
if (endPosition > this.buffer.length) { | |
endPosition = this.buffer.length | |
} | |
newLength = endPosition - startPosition; | |
subString = this.buffer.slice(startPosition, endPosition); | |
this.position = endPosition; | |
if (position == 0) { | |
newBuffer = subString; | |
if (subString.length < buffer.length) { | |
newBuffer = newBuffer + buffer.slice(newLength) | |
} | |
} else { | |
newBuffer = buffer.slice(0, position) + subString; | |
if (position + newLength + 1 <= buffer.length) { | |
newBuffer = newBuffer + buffer.slice(position + newLength) | |
} | |
} | |
return [newLength, newBuffer] | |
} | |
read(buffer, position, length) { | |
assert(typeof buffer === "object" && Array.isArray(buffer), "buffer must be array"); | |
var ret; | |
var totalBytes; | |
var newBuffer = ""; | |
var newArray = []; | |
for (var i = 0; i < buffer.length; i++) { | |
newBuffer = newBuffer + String.fromCharCode(buffer[i]) | |
} | |
ret = this.readChars(newBuffer, position, length); | |
totalBytes = ret[0]; | |
newBuffer = ret[1]; | |
for (var i = 0; i < newBuffer.length; i++) { | |
newArray.push(newBuffer.charCodeAt(i)) | |
} | |
return [totalBytes, newArray] | |
} | |
writeByte(byte) { | |
assert(typeof byte === "number", "not valid byte"); | |
assert(byte >= 0 && byte <= 255, "not valid byte"); | |
this.writeChar(String.fromCharCode(byte)) | |
} | |
writeChar(char) { | |
this.writeChars(char, 0, 1) | |
} | |
writeChars(buffer, position, length) { | |
assert(typeof buffer === "string", "buffer must be string"); | |
assert(buffer && buffer.length > 0, "buffer must not be nil or empty"); | |
if (this.bytesAvailable() < 0) { | |
console.log("WARNING: Reached end of the stream"); | |
return | |
} | |
if (length <= 0) { | |
console.log("WARNING: no characters written (length = 0)"); | |
return [0, buffer] | |
} | |
assert(position >= 0 && position < buffer.length, "position out of range"); | |
var subString; | |
var newBuffer = ""; | |
var startPosition = position; | |
var endPosition = startPosition + length; | |
var newLength; | |
if (endPosition > buffer.length) { | |
endPosition = buffer.length | |
} | |
newLength = endPosition - startPosition; | |
subString = buffer.slice(startPosition, endPosition); | |
if (this.position == 0) { | |
newBuffer = subString; | |
if (this.buffer.length > subString.length) { | |
newBuffer = newBuffer + this.buffer.slice(newLength) | |
} | |
} else { | |
newBuffer = this.buffer.slice(0, this.position) + subString; | |
if (this.position + newLength + 1 <= this.buffer.length) { | |
newBuffer = newBuffer + this.buffer.slice(this.position + newLength) | |
} | |
} | |
this.buffer = newBuffer; | |
this.position = this.position + newLength | |
} | |
writeBytes(buffer, position, length) { | |
assert(typeof buffer === "object" && Array.isArray(buffer), "buffer must be array"); | |
var newBuffer = ""; | |
for (var i = position; i < position + length; i++) { | |
if (i >= buffer.length) { | |
break | |
} | |
newBuffer = newBuffer + String.fromCharCode(buffer[i]) | |
} | |
this.writeChars(newBuffer, 0, length) | |
} | |
seek(offset) { | |
this.position = this.position - offset; | |
if (this.position < 0) { | |
this.position = 0 | |
} else if (this.position >= this.buffer.length) { | |
this.position = this.buffer.length - 1 | |
} | |
} | |
toHex() { | |
var ret = ""; | |
for (var i = 0; i < Math.ceil(this.buffer.length / 16) * 16; i++) { | |
if (i % 16 == 0) { | |
var value = i.toString(16); | |
for (var a = value.length; a < 8; a++) { | |
ret = ret + "0" | |
} | |
ret = ret + value + " " | |
} | |
if (i >= this.buffer.length) { | |
ret = ret + "## " | |
} else { | |
var val = this.buffer.charCodeAt(i).toString(16); | |
for (var b = val.length; b < 2; b++) { | |
ret = ret + "0" | |
} | |
ret = ret + val + " " | |
} | |
if ((i + 1) % 8 == 0) { | |
ret = ret + " " | |
} | |
if ((i + 1) % 16 == 0) { | |
ret = ret + this.buffer.slice(i - 16 + 1, i + 1).replace("[^ -~]", ".") + "\n" | |
} | |
} | |
return ret | |
} | |
toString() { | |
return this.buffer | |
} | |
getBuffer() { | |
var buffer = []; | |
for (var i = 0; i < this.buffer.length; i++) { | |
buffer.push(this.buffer.charCodeAt(i)) | |
} | |
return buffer | |
} | |
}; | |
class PositionStream { | |
tempBuffer = []; | |
bytesRead = 0; | |
stream = null | |
wrap(baseStream, limit?) { | |
this.bytesRead = 0; | |
this.stream = baseStream | |
} | |
read(buffer, offset, count) { | |
var ret = this.stream.read(buffer, offset, count); | |
this.bytesRead = this.bytesRead + ret[0]; | |
return ret | |
} | |
readByte() { | |
var ret = this.read(this.tempBuffer, 0, 1); | |
if (ret[0] == 1) { | |
this.tempBuffer = ret[1]; | |
return this.tempBuffer[0] | |
} | |
return -1 | |
} | |
seek(offset) { | |
for (var i = 0; i < offset; i++) { | |
this.readByte() | |
} | |
return this.bytesRead | |
} | |
getLength() { | |
return this.stream.getLength() | |
} | |
getPosition() { | |
return this.bytesRead | |
} | |
}; | |
class LimitedPositionStream extends PositionStream { | |
limit = 0; | |
wrap(baseStream, limit) { | |
super.wrap(baseStream); | |
this.limit = limit | |
}; | |
read(buffer, offset, count) { | |
var toRead; | |
if (count > this.limit - this.bytesRead) { | |
toRead = this.limit - this.bytesRead | |
} else { | |
toRead = count | |
} | |
return super.read(buffer, offset, toRead) | |
}; | |
readByte() { | |
if (this.bytesRead >= this.limit) { | |
return -1 | |
} else { | |
return super.readByte() | |
} | |
}; | |
skipToEnd() { | |
if (this.bytesRead < this.limit) { | |
var discardBytes = PooledObjects.byteBufferPool.pop(); | |
while (this.read(discardBytes, this.bytesRead, 256)[0] == 256) { } | |
PooledObjects.byteBufferPool.push(discardBytes) | |
} | |
} | |
} | |
export class ProtocolParser { | |
static readBool(stream) { | |
var b = stream.readByte(); | |
if (b < 0) { | |
console.log("WARNING: Stream ended too early"); | |
return false | |
} else if (b == 1) { | |
return true | |
} else if (b == 0) { | |
return false | |
} | |
console.log("WARNING: Invalid boolean value"); | |
return false | |
}; | |
static readUInt32(stream) { | |
var b; | |
var val = 0; | |
for (var n = 0; n <= 4; n++) { | |
b = stream.readByte(); | |
if (b < 0) { | |
console.log("WARNING: Stream ended too early"); | |
return 0 | |
} | |
if (n == 4 && (b & 240) != 0) { | |
console.log("WARNING: Got larger VarInt than 32bit unsigned"); | |
return 0 | |
} | |
if ((b & 128) == 0) { | |
return val | b << 7 * n | |
} | |
val = val | (b & 127) << 7 * n | |
} | |
console.log("WARNING: Got larger VarInt than 32bit unsigned"); | |
return 0 | |
}; | |
static readZInt32(stream) { | |
var val = ProtocolParser.readUInt32(stream); | |
return val >> 1 ^ val << 31 >> 31 | |
}; | |
static readSingle(stream) { | |
var bytes = []; | |
var val = 0; | |
for (var n = 1; n <= 4; n++) { | |
bytes[4 - n] = stream.readByte() | |
} | |
val = bytes[0] << 24 | bytes[1] << 16 | (bytes[2] << 8 | bytes[3]); | |
var negative = (bytes[0] & 128) == 128; | |
var exponent = (val & 2139095040) >> 23; | |
var sign; | |
var mantissa = 0; | |
if (negative) { | |
sign = -1 | |
} else { | |
sign = 1 | |
} | |
if (exponent == 255) { | |
if (negative) { | |
value = Number.NEGATIVE_INFINITY | |
} else { | |
value = Number.POSITIVE_INFINITY | |
} | |
} else if (exponent == 0) { | |
value = 0 | |
} else { | |
exponent = exponent - 127 + 1; | |
for (var i = 3; i >= 2; i--) { | |
mantissa += bytes[i]; | |
mantissa /= 256 | |
} | |
mantissa += bytes[1] & 127; | |
mantissa = (mantissa / 128 + 1) / 2; | |
var value = sign * Math.pow(2, exponent) * mantissa | |
} | |
return value | |
}; | |
static readUInt64(stream) { | |
var b; | |
var val = 0; | |
for (var n = 0; n <= 9; n++) { | |
b = stream.readByte(); | |
if (b < 0) { | |
console.log("WARNING: Stream ended too early"); | |
return 0 | |
} | |
if (n == 9 && (b & 254) != 0) { | |
console.log("WARNING: Got larger VarInt than 64 bit unsigned"); | |
return 0 | |
} | |
if ((b & 128) == 0) { | |
return val + b * Math.pow(128, n) | |
} | |
val = val + (b & 127) * Math.pow(128, n) | |
} | |
console.log("WARNING: Got larger VarInt than 64 bit unsigned"); | |
return 0 | |
}; | |
static readZInt64(stream) { | |
var val = ProtocolParser.readUInt64(stream); | |
var sign = false; | |
if (val % 2 == 1) { | |
sign = true | |
} | |
val = Math.floor(val / 2); | |
if (sign == true) { | |
return -val | |
} else { | |
return val | |
} | |
}; | |
static readDouble(stream) { | |
var bytes = []; | |
var valh = 0; | |
for (var n = 1; n <= 8; n++) { | |
bytes[8 - n] = stream.readByte() | |
} | |
valh = bytes[0] << 24 | bytes[1] << 16; | |
var negative = (bytes[0] & 128) == 128; | |
var exponent = (valh & 2146435072) >> 20; | |
var mantissa = 0; | |
var sign; | |
if (negative) { | |
sign = -1 | |
} else { | |
sign = 1 | |
} | |
if (exponent == 2047) { | |
if (negative) { | |
value = Number.NEGATIVE_INFINITY | |
} else { | |
value = Number.POSITIVE_INFINITY | |
} | |
} else if (exponent == 0) { | |
value = 0 | |
} else { | |
exponent = exponent - 1023 + 1; | |
for (var i = 7; i >= 2; i--) { | |
mantissa += bytes[i]; | |
mantissa /= 256 | |
} | |
mantissa += bytes[1] & 15; | |
mantissa = (mantissa / 16 + 1) / 2; | |
var value = sign * Math.pow(2, exponent) * mantissa | |
} | |
return value | |
}; | |
static readString(stream) { | |
var length = ProtocolParser.readUInt32(stream); | |
var ms = PooledObjects.memoryStreamPool.pop(); | |
var buffer = PooledObjects.byteBufferPool.pop(); | |
var read = 0; | |
var ret; | |
var r; | |
while (read < length) { | |
ret = stream.read(buffer, 0, Math.min(length - read, buffer.length)); | |
r = ret[0]; | |
buffer = ret[1]; | |
if (r == 0) { | |
console.log("WARNING: Expected " + (length - read).toString() + " got " + read); | |
return 0 | |
} | |
ms.writeBytes(buffer, 0, r); | |
read = read + r | |
} | |
ret = ms.toString().slice(0, ms.getPosition()); | |
PooledObjects.byteBufferPool.push(buffer); | |
PooledObjects.memoryStreamPool.push(ms); | |
return ret | |
}; | |
static readKey(firstByte, stream) { | |
if (firstByte < 128) { | |
return new Key(firstByte >> 3, firstByte & 7) | |
} | |
var fieldID = ProtocolParser.readUInt32(stream) << 4 | firstByte >> 3 & 15; | |
return new Key(fieldID, firstByte & 7) | |
}; | |
static skipKey(stream, key) { | |
if (key.wireType == Wire.FIXED32) { | |
stream.seek(4) | |
} else if (key.wireType == Wire.FIXED64) { | |
stream.seek(8) | |
} else if (key.wireType == Wire.LENGTH_DELIMITED) { | |
stream.seek(ProtocolParser.readUInt32(stream)) | |
} else if (key.wireType == Wire.VARINT) { | |
ProtocolParser.readSkipVarInt(stream) | |
} else { | |
console.log("WARNING: Unknown wire type: " + key.wireType) | |
} | |
}; | |
static readSkipVarInt(stream) { | |
while (true) { | |
var b = stream.readByte(); | |
if (b < 0) { | |
console.log("WARNING: Stream ended too early"); | |
return | |
} | |
if ((b & 128) == 0) { | |
return | |
} | |
} | |
}; | |
static writeBool(stream, val) { | |
if (val) { | |
return stream.writeByte(1) | |
} else { | |
return stream.writeByte(0) | |
} | |
}; | |
static writeUInt32(stream, val) { | |
var b; | |
val = Math.abs(val); | |
while (true) { | |
b = val & 127; | |
val = val >>> 7; | |
if (val == 0) { | |
stream.writeByte(b); | |
break | |
} else { | |
b = b | 128; | |
stream.writeByte(b) | |
} | |
} | |
}; | |
static writeZInt32(stream, val) { | |
var val1 = val << 1; | |
var val2 = val >> 31; | |
ProtocolParser.writeUInt32(stream, val1 ^ val2) | |
}; | |
static writeUInt64(stream, val) { | |
var b; | |
val = Math.abs(val); | |
while (true) { | |
b = val & 127; | |
val = Math.floor(val / 128); | |
if (val == 0) { | |
stream.writeByte(b); | |
break | |
} else { | |
b = b | 128; | |
stream.writeByte(b) | |
} | |
} | |
}; | |
static writeZInt64(stream, val) { | |
var sign = false; | |
if (val < 0) { | |
val = -val; | |
sign = true | |
} | |
val = val * 2; | |
if (sign == true) { | |
val = val + 1 | |
} | |
ProtocolParser.writeUInt64(stream, val) | |
}; | |
static frexp(arg) { | |
arg = Number(arg); | |
const result = [arg, 0]; | |
if (arg !== 0 && Number.isFinite(arg)) { | |
const absArg = Math.abs(arg); | |
const log2 = Math.log2 || function log2(n) { | |
return Math.log(n) * Math.LOG2E | |
}; | |
let exp = Math.max(-1023, Math.floor(log2(absArg)) + 1); | |
let x = absArg * Math.pow(2, -exp); | |
while (x < .5) { | |
x *= 2; | |
exp-- | |
} | |
while (x >= 1) { | |
x *= .5; | |
exp++ | |
} | |
if (arg < 0) { | |
x = -x | |
} | |
result[0] = x; | |
result[1] = exp | |
} | |
return result | |
}; | |
static writeSingle(stream, val) { | |
var bytes = [0, 0, 0, 0]; | |
if (val != 0) { | |
var anum = Math.abs(val); | |
var ret = ProtocolParser.frexp(anum); | |
var mantissa = ret[0]; | |
var exponent = ret[1]; | |
var sign = val != anum && 128 || 0; | |
mantissa = mantissa * 2 - 1; | |
if (mantissa == Infinity) { | |
mantissa = 0; | |
exponent = 255 | |
} else { | |
exponent = exponent - 1; | |
exponent = exponent + 127; | |
if (exponent < 0 || exponent >= 255) { | |
mantissa = 0; | |
if (exponent < 0) { | |
exponent = 0 | |
} else { | |
exponent = 255 | |
} | |
} | |
} | |
bytes[0] = sign + (exponent >> 1); | |
mantissa *= 128; | |
var currentmantissa = Math.floor(mantissa); | |
mantissa -= currentmantissa; | |
bytes[1] = ((exponent & 1) << 7) + currentmantissa; | |
for (var i = 2; i < 4; i++) { | |
mantissa *= 256; | |
currentmantissa = Math.floor(mantissa); | |
mantissa -= currentmantissa; | |
bytes[i] = currentmantissa | |
} | |
} | |
for (var i = bytes.length - 1; i >= 0; i--) { | |
stream.writeByte(bytes[i]) | |
} | |
}; | |
static writeDouble(stream, val) { | |
var bytes = [0, 0, 0, 0, 0, 0, 0, 0]; | |
if (val != 0) { | |
var anum = Math.abs(val); | |
var ret = ProtocolParser.frexp(anum); | |
var mantissa = ret[0]; | |
var exponent = ret[1]; | |
var sign = val != anum && 128 || 0; | |
mantissa = mantissa * 2 - 1; | |
if (mantissa == Infinity) { | |
mantissa = 0; | |
exponent = 2047 | |
} else { | |
exponent = exponent - 1; | |
exponent = exponent + 1023; | |
if (exponent < 0 || exponent >= 2047) { | |
mantissa = 0; | |
if (exponent < 0) { | |
exponent = 0 | |
} else { | |
exponent = 2047 | |
} | |
} | |
} | |
bytes[0] = sign + (exponent >> 4); | |
mantissa *= 16; | |
var currentmantissa = Math.floor(mantissa); | |
mantissa -= currentmantissa; | |
bytes[1] = ((exponent & 15) << 4) + currentmantissa; | |
for (var i = 2; i < 8; i++) { | |
mantissa *= 256; | |
currentmantissa = Math.floor(mantissa); | |
mantissa -= currentmantissa; | |
bytes[i] = currentmantissa | |
} | |
} | |
for (var i = bytes.length - 1; i >= 0; i--) { | |
stream.writeByte(bytes[i]) | |
} | |
}; | |
static writeBytes(stream, val, len) { | |
ProtocolParser.writeUInt32(stream, len); | |
stream.writeBytes(val, 0, len) | |
}; | |
static writeString(stream, val) { | |
var array = []; | |
for (var i = 0; i < val.length; i++) { | |
array.push(val.charCodeAt(i)) | |
} | |
ProtocolParser.writeBytes(stream, array, val.length) | |
}; | |
} | |
export class ObjectPool { | |
stack = []; | |
constructor(public creator, public refresher, public maxSize) { } | |
pop() { | |
if (this.stack.length == 0) { | |
return this.creator() | |
} else { | |
return this.stack.shift() | |
} | |
} | |
push(item) { | |
if (item) { | |
if (this.stack.indexOf(item) >= 0) { | |
return | |
} | |
if (this.stack.length < this.maxSize) { | |
if (this.refresher) { | |
this.refresher(item) | |
} | |
this.stack.push(item) | |
} | |
} | |
} | |
dispose() { | |
this.stack = [] | |
} | |
}; | |
export class LogCommand { | |
tag; | |
msg; | |
level; | |
session; | |
static pool = new ObjectPool(function () { | |
return new LogCommand | |
}, null, 5); | |
configure(session, tag, level, msg) { | |
this.tag = tag; | |
this.msg = msg; | |
this.level = level; | |
this.session = session; | |
return this | |
} | |
execute() { | |
if (GameSparksRT.shouldLog(this.tag, this.level)) { | |
if (this.session.peerId) { | |
GameSparksRT.logger(this.session.peerId + " " + this.tag + ":" + this.msg) | |
} else { | |
GameSparksRT.logger(" " + this.tag + ":" + this.msg) | |
} | |
} | |
LogCommand.pool.push(this) | |
} | |
}; | |
export class CustomCommand { | |
session = null; | |
opCode = 0; | |
sender = 0; | |
limit = 0; | |
packetSize = 0; | |
ms = null; | |
limitedStream = null; | |
data = null | |
static pool = new ObjectPool(function () { | |
return new CustomCommand | |
}, null, 5); | |
configure(opCode, sender, lps, data, limit, session, packetSize) { | |
this.ms = PooledObjects.memoryStreamPool.pop(); | |
this.packetSize = packetSize; | |
this.opCode = opCode; | |
this.sender = sender; | |
this.data = data; | |
this.session = session; | |
this.limit = limit; | |
this.limitedStream = null; | |
if (lps != null) { | |
this.limitedStream = PooledObjects.limitedPositionStreamPool.pop(); | |
for (var i = 1; i <= limit; i++) { | |
var read = lps.readByte(); | |
this.ms.writeByte(read) | |
} | |
this.ms.setPosition(0); | |
this.limitedStream.wrap(this.ms, limit) | |
} | |
return this | |
} | |
execute() { | |
this.session.onPacket(new RTPacket(this.opCode, this.sender, this.limitedStream, this.limit, this.data, this.packetSize)); | |
PooledObjects.memoryStreamPool.push(this.ms); | |
PooledObjects.limitedPositionStreamPool.push(this.limitedStream); | |
CustomCommand.pool.push(this) | |
} | |
}; | |
export class ActionCommand { | |
action; | |
static pool = new ObjectPool(function () { | |
return new ActionCommand | |
}, null, 5); | |
configure(action) { | |
this.action = action; | |
return this | |
} | |
execute() { | |
this.action(); | |
ActionCommand.pool.push(this) | |
} | |
}; | |
export class CommandFactory { | |
static getCommand(opCode, sender, sequence, stream, session, data, packetSize) { | |
var ret = null; | |
var limit = ProtocolParser.readUInt32(stream); | |
var lps = PooledObjects.limitedPositionStreamPool.pop(); | |
lps.wrap(stream, limit); | |
if (opCode == OpCodes.LOGIN_RESULT) { | |
ret = LoginResult.deserialize(lps, LoginResult.pool.pop()) | |
} else if (opCode == OpCodes.PING_RESULT) { | |
ret = PingResult.deserialize(lps, PingResult.pool.pop()) | |
} else if (opCode == OpCodes.UDP_CONNECT_MESSAGE) { | |
ret = UDPConnectMessage.deserialize(lps, UDPConnectMessage.pool.pop()) | |
} else if (opCode == OpCodes.PLAYER_CONNECT_MESSAGE) { | |
ret = PlayerConnectMessage.deserialize(lps, PlayerConnectMessage.pool.pop()) | |
} else if (opCode == OpCodes.PLAYER_DISCONNECT_MESSAGE) { | |
ret = PlayerDisconnectMessage.deserialize(lps, PlayerDisconnectMessage.pool.pop()) | |
} else { | |
if (session.shouldExecute(sender, sequence)) { | |
ret = CustomCommand.pool.pop().configure(opCode, sender, lps, data, limit, session, packetSize) | |
} | |
} | |
lps.skipToEnd(); | |
PooledObjects.limitedPositionStreamPool.push(lps); | |
return ret | |
}; | |
} | |
export abstract class AbstractResult { | |
abstractResultType = true; | |
packet = null; | |
session = null | |
configure(packet, session) { | |
this.packet = packet; | |
this.session = session | |
} | |
executeAsync() { | |
return true | |
} | |
}; | |
export class LoginResult extends AbstractResult { | |
success = false; | |
reconnectToken = null; | |
peerId = null; | |
activePeers = []; | |
fastPort = null | |
static pool = new ObjectPool(function () { | |
return new LoginResult | |
}, function (instance) { | |
instance.activePeers = []; | |
instance.fastPort = null; | |
instance.reconnectToken = null; | |
instance.peerId = null | |
}, 5); | |
execute() { | |
this.session.connectToken = this.reconnectToken; | |
this.session.peerId = this.peerId; | |
if (this.packet.reliable == null || this.packet.reliable == true) { | |
if (this.fastPort != null && this.fastPort) { | |
this.session.fastPort = this.fastPort | |
} | |
this.session.activePeers = this.activePeers.slice(); | |
this.session.setConnectState(GameSparksRT.connectState.RELIABLE_ONLY); | |
this.session.connectFast(); | |
this.session.log("LoginResult", GameSparksRT.logLevel.DEBUG, this.session.peerId + " TCP LoginResult, ActivePeers " + this.session.activePeers.length) | |
} else { | |
this.session.setConnectState(GameSparksRT.connectState.RELIABLE_AND_FAST_SEND) | |
} | |
LoginResult.pool.push(this) | |
}; | |
executeAsync() { | |
return false | |
}; | |
static deserialize(stream, instance) { | |
if (instance.activePeers == null) { | |
instance.activePeers = [] | |
} | |
while (true) { | |
var keyByte = stream.readByte(); | |
if (keyByte == -1) { | |
break | |
} | |
var _continue = false; | |
if (keyByte == 8) { | |
instance.success = ProtocolParser.readBool(stream); | |
_continue = true | |
} else if (keyByte == 18) { | |
instance.reconnectToken = ProtocolParser.readString(stream); | |
_continue = true | |
} else if (keyByte == 24) { | |
instance.peerId = ProtocolParser.readUInt64(stream); | |
_continue = true | |
} else if (keyByte == 32) { | |
instance.activePeers.push(ProtocolParser.readUInt64(stream)); | |
_continue = true | |
} else if (keyByte == 40) { | |
instance.fastPort = ProtocolParser.readUInt64(stream); | |
_continue = true | |
} | |
if (!_continue) { | |
var key = ProtocolParser.readKey(keyByte, stream); | |
if (key.field == 0) { | |
console.log("WARNING: Invalid field id: 0, something went wrong in the stream"); | |
return null | |
} else { | |
ProtocolParser.skipKey(stream, key) | |
} | |
} | |
} | |
return instance | |
}; | |
} | |
export class PingResult extends AbstractResult { | |
static pool = new ObjectPool(function () { | |
return new PingResult | |
}, null, 5); | |
execute() { | |
this.session.log("PingResult", GameSparksRT.logLevel.DEBUG, ""); | |
PingResult.pool.push(this) | |
}; | |
executeAsync() { | |
return false | |
}; | |
static deserialize(stream, instance) { | |
while (true) { | |
var keyByte = stream.readByte(); | |
if (keyByte == -1) { | |
break | |
} | |
var key = ProtocolParser.readKey(keyByte, stream); | |
if (key.field == 0) { | |
console.log("WARNING: Invalid field id: 0, something went wrong in the stream"); | |
return null | |
} else { | |
ProtocolParser.skipKey(stream, key) | |
} | |
} | |
return instance | |
}; | |
} | |
export class PlayerConnectMessage extends AbstractResult { | |
peerId = 0; | |
activePeers = []; | |
static pool = new ObjectPool(function () { | |
return new PlayerConnectMessage | |
}, function (instance) { | |
instance.activePeers = [] | |
}, 5); | |
execute() { | |
this.session.activePeers = []; | |
this.session.activePeers = this.activePeers.slice(); | |
this.session.log("PlayerConnectMessage", GameSparksRT.logLevel.DEBUG, "PeerId=" + this.peerId + ", ActivePeers " + this.session.activePeers.length); | |
this.session.onPlayerConnect(this.peerId); | |
PlayerConnectMessage.pool.push(this) | |
}; | |
static deserialize(stream, instance) { | |
if (instance.activePeers == null) { | |
instance.activePeers = [] | |
} | |
while (true) { | |
var keyByte = stream.readByte(); | |
if (keyByte == -1) { | |
break | |
} | |
var _continue = false; | |
if (keyByte == 8) { | |
instance.peerId = ProtocolParser.readUInt64(stream); | |
_continue = true | |
} else if (keyByte == 32) { | |
instance.activePeers.push(ProtocolParser.readUInt64(stream)); | |
_continue = true | |
} | |
if (!_continue) { | |
var key = ProtocolParser.readKey(keyByte, stream); | |
if (key.field == 0) { | |
console.log("WARNING: Invalid field id: 0, something went wrong in the stream"); | |
return null | |
} else { | |
ProtocolParser.skipKey(stream, key) | |
} | |
} | |
} | |
return instance | |
}; | |
} | |
export class PlayerDisconnectMessage extends AbstractResult { | |
peerId = 0; | |
activePeers = []; | |
static pool = new ObjectPool(function () { | |
return new PlayerDisconnectMessage | |
}, function (instance) { | |
instance.activePeers = [] | |
}, 5); | |
execute() { | |
this.session.activePeers = []; | |
this.session.activePeers = this.activePeers.slice(); | |
this.session.log("PlayerDisconnectMessage", GameSparksRT.logLevel.DEBUG, "PeerId=" + this.peerId + ", ActivePeers " + this.session.activePeers.length); | |
this.session.onPlayerDisconnect(this.peerId); | |
PlayerDisconnectMessage.pool.push(this) | |
}; | |
static deserialize(stream, instance) { | |
if (instance.activePeers == null) { | |
instance.activePeers = [] | |
} | |
while (true) { | |
var keyByte = stream.readByte(); | |
if (keyByte == -1) { | |
break | |
} | |
var _continue = false; | |
if (keyByte == 8) { | |
instance.peerId = ProtocolParser.readUInt64(stream); | |
_continue = true | |
} else if (keyByte == 32) { | |
instance.activePeers.push(ProtocolParser.readUInt64(stream)); | |
_continue = true | |
} | |
if (!_continue) { | |
var key = ProtocolParser.readKey(keyByte, stream); | |
if (key.field == 0) { | |
console.log("WARNING: Invalid field id: 0, something went wrong in the stream"); | |
return null | |
} else { | |
ProtocolParser.skipKey(stream, key) | |
} | |
} | |
} | |
return instance | |
}; | |
} | |
export class UDPConnectMessage extends AbstractResult { | |
static pool = new ObjectPool(function () { | |
return new UDPConnectMessage | |
}, null, 5); | |
execute() { | |
var reliable; | |
if (this.packet.reliable != null) { | |
reliable = this.packet.reliable | |
} else { | |
reliable = false | |
} | |
this.session.log("UDPConnectMessage", GameSparksRT.logLevel.DEBUG, "(UDP) reliable=" + reliable.toString() + ", ActivePeers " + this.session.activePeers.length); | |
if (!reliable) { | |
this.session.setConnectState(GameSparksRT.connectState.RELIABLE_AND_FAST); | |
this.session.sendData(-5, GameSparksRT.deliveryIntent.RELIABLE, null, null) | |
} else { | |
this.session.log("UDPConnectMessage", GameSparksRT.logLevel.DEBUG, "TCP (Unexpected) UDPConnectMessage") | |
} | |
UDPConnectMessage.pool.push(this) | |
}; | |
executeAsync() { | |
return false | |
}; | |
static deserialize(stream, instance) { | |
while (true) { | |
var keyByte = stream.readByte(); | |
if (keyByte == -1) { | |
break | |
} | |
var key = ProtocolParser.readKey(keyByte, stream); | |
if (key.field == 0) { | |
console.log("WARNING: Invalid field id: 0, something went wrong in the stream"); | |
return null | |
} else { | |
ProtocolParser.skipKey(stream, key) | |
} | |
} | |
return instance | |
}; | |
} | |
export class RTRequest { | |
data = null; | |
opCode = 0; | |
targetPlayers = []; | |
intent = GameSparksRT.deliveryIntent.RELIABLE | |
toPacket(session, fast) { | |
return null | |
} | |
reset() { | |
this.targetPlayers = [] | |
} | |
serialize(stream) { } | |
}; | |
export class LoginCommand extends RTRequest { | |
opCode = 0; | |
clientVersion = 2 | |
constructor(public token) { | |
super(); | |
} | |
toPacket(session, fast) { | |
var p = PooledObjects.packetPool.pop(); | |
p.opCode = this.opCode; | |
p.data = this.data; | |
p.session = session; | |
if (!fast && this.intent != GameSparksRT.deliveryIntent.RELIABLE) { | |
p.reliable = false | |
} | |
if (this.intent == GameSparksRT.deliveryIntent.UNRELIABLE_SEQUENCED) { | |
p.sequenceNumber = session.nextSequenceNumber() | |
} | |
if (this.targetPlayers.length > 0) { | |
p.targetPlayers = this.targetPlayers | |
} | |
p.request = this; | |
return p | |
}; | |
serialize(stream) { | |
if (this.token != null) { | |
stream.writeByte(10); | |
ProtocolParser.writeString(stream, this.token) | |
} | |
if (this.clientVersion != null) { | |
stream.writeByte(16); | |
ProtocolParser.writeUInt64(stream, this.clientVersion) | |
} | |
}; | |
} | |
export class PingCommand extends RTRequest { | |
opCode = -2; | |
toPacket(session, fast) { | |
var p = PooledObjects.packetPool.pop(); | |
p.opCode = this.opCode; | |
p.data = this.data; | |
p.session = session; | |
if (!fast && this.intent != GameSparksRT.deliveryIntent.RELIABLE) { | |
p.reliable = false | |
} | |
if (this.intent == GameSparksRT.deliveryIntent.UNRELIABLE_SEQUENCED) { | |
p.sequenceNumber = session.nextSequenceNumber() | |
} | |
if (this.targetPlayers.length > 0) { | |
p.targetPlayers = this.targetPlayers | |
} | |
p.request = this; | |
return p | |
} | |
serialize(stream) { }; | |
} | |
export class CustomRequest extends RTRequest { | |
payload = null; | |
configure(opCode, intent, payload, data, targetPlayers) { | |
this.opCode = opCode; | |
this.payload = payload; | |
this.intent = intent; | |
this.data = data; | |
if (targetPlayers != null) { | |
for (var i in targetPlayers) { | |
this.targetPlayers.push(targetPlayers[i]) | |
} | |
} | |
} | |
toPacket(session, fast) { | |
var p = PooledObjects.packetPool.pop(); | |
p.opCode = this.opCode; | |
p.data = this.data; | |
p.session = session; | |
if (!fast && this.intent != GameSparksRT.deliveryIntent.RELIABLE) { | |
p.reliable = false | |
} | |
if (this.intent == GameSparksRT.deliveryIntent.UNRELIABLE_SEQUENCED) { | |
p.sequenceNumber = session.nextSequenceNumber() | |
} | |
if (this.targetPlayers.length > 0) { | |
p.targetPlayers = this.targetPlayers | |
} | |
p.request = this; | |
return p | |
} | |
serialize(stream) { | |
if (this.payload) { | |
stream.writeBytes(this.payload, 0, this.payload.length) | |
} | |
} | |
reset() { | |
this.payload = null; | |
super.reset(); | |
}; | |
} | |
export class Packet { | |
opCode = 0; | |
sequenceNumber = null; | |
requestId = null; | |
targetPlayers = null; | |
sender = null; | |
reliable = null; | |
data = null; | |
payload = null; | |
request = null; | |
hasPayload = false; | |
command = null; | |
session = null | |
reset() { | |
this.opCode = 0; | |
this.sequenceNumber = null; | |
this.requestId = null; | |
this.targetPlayers = null; | |
this.sender = null; | |
this.reliable = null; | |
this.payload = null; | |
this.command = null; | |
this.request = null; | |
this.hasPayload = false; | |
this.data = null | |
} | |
toString() { | |
return "{OpCode:" + this.opCode + ",TargetPlayers:" + this.targetToString() + "}" | |
} | |
targetToString() { | |
var s = "["; | |
if (this.targetPlayers != null) { | |
for (var i = 0; i < this.targetPlayers.length; i++) { | |
s = s + this.targetPlayers[i] + " " | |
} | |
} | |
return s + "]" | |
} | |
readPayload(stream, packetSize) { | |
this.hasPayload = true; | |
if (this.sender != null) { | |
this.command = CommandFactory.getCommand(this.opCode, this.sender, this.sequenceNumber, stream, this.session, this.data, packetSize) | |
} else { | |
this.command = CommandFactory.getCommand(this.opCode, 0, this.sequenceNumber, stream, this.session, this.data, packetSize) | |
} | |
return null | |
} | |
writePayload(stream) { | |
if (this.request != null) { | |
var ms = PooledObjects.memoryStreamPool.pop(); | |
this.request.serialize(ms); | |
var written = ms.getBuffer(); | |
if (ms.getPosition() > 0) { | |
stream.writeByte(122); | |
ProtocolParser.writeBytes(stream, written, ms.getPosition()) | |
} | |
PooledObjects.memoryStreamPool.push(ms) | |
} else if (this.payload != null) { | |
stream.writeByte(122); | |
ProtocolParser.writeBytes(stream, this.payload, this.payload.length) | |
} | |
} | |
static serialize(stream, instance) { | |
stream.writeByte(8); | |
ProtocolParser.writeZInt32(stream, instance.opCode); | |
if (instance.sequenceNumber != null) { | |
stream.writeByte(16); | |
ProtocolParser.writeUInt64(stream, instance.sequenceNumber) | |
} | |
if (instance.requestId != null) { | |
stream.writeByte(24); | |
ProtocolParser.writeUInt64(stream, instance.requestId) | |
} | |
if (instance.targetPlayers != null) { | |
for (var i = 0; i < instance.targetPlayers.length; i++) { | |
stream.writeByte(32); | |
ProtocolParser.writeUInt64(stream, instance.targetPlayers[i]) | |
} | |
} | |
if (instance.sender != null) { | |
stream.writeByte(40); | |
ProtocolParser.writeUInt64(stream, instance.sender) | |
} | |
if (instance.reliable != null) { | |
stream.writeByte(48); | |
ProtocolParser.writeBool(stream, instance.reliable) | |
} | |
if (instance.data != null) { | |
stream.writeByte(114); | |
RTData.writeRTData(stream, instance.data) | |
} | |
instance.writePayload(stream); | |
return stream | |
}; | |
static serializeLengthDelimited(stream, instance) { | |
var ms = PooledObjects.memoryStreamPool.pop(); | |
var ret; | |
Packet.serialize(ms, instance); | |
var data = ms.getBuffer(); | |
ProtocolParser.writeBytes(stream, data, ms.getPosition()); | |
ret = ms.getPosition(); | |
PooledObjects.memoryStreamPool.push(ms); | |
return ret | |
}; | |
static deserializeLengthDelimited(stream, instance) { | |
var limit = ProtocolParser.readUInt32(stream); | |
var origLimit = limit; | |
limit = limit + stream.getPosition(); | |
while (true) { | |
if (stream.getPosition() >= limit) { | |
if (stream.getPosition() == limit) { | |
break | |
} else { | |
console.log("WARNING: Read past max limit"); | |
return 0 | |
} | |
} | |
var keyByte = stream.readByte(); | |
if (keyByte == -1) { | |
console.log("WARNING: End of stream"); | |
return 0 | |
} | |
var _continue = false; | |
if (keyByte == 8) { | |
instance.opCode = ProtocolParser.readZInt32(stream); | |
_continue = true | |
} else if (keyByte == 16) { | |
instance.sequenceNumber = ProtocolParser.readUInt64(stream); | |
_continue = true | |
} else if (keyByte == 24) { | |
instance.requestId = ProtocolParser.readUInt64(stream); | |
_continue = true | |
} else if (keyByte == 40) { | |
instance.sender = ProtocolParser.readUInt64(stream); | |
_continue = true | |
} else if (keyByte == 48) { | |
instance.reliable = ProtocolParser.readBool(stream); | |
_continue = true | |
} else if (keyByte == 114) { | |
if (instance.data == null) { | |
instance.data = RTData.readRTData(stream, instance.data) | |
} else { | |
RTData.readRTData(stream, instance.data) | |
} | |
_continue = true | |
} else if (keyByte == 122) { | |
instance.payload = instance.readPayload(stream, origLimit); | |
_continue = true | |
} | |
if (!_continue) { | |
var key = ProtocolParser.readKey(keyByte, stream); | |
if (key.field == 0) { | |
console.log("WARNING: Invalid field id: 0, something went wrong in the stream"); | |
return 0 | |
} else { | |
ProtocolParser.skipKey(stream, key) | |
} | |
} | |
} | |
return origLimit | |
}; | |
} | |
export class PooledObjects { | |
static packetPool = new ObjectPool(function () { | |
return new Packet | |
}, function (packet) { | |
packet.reset() | |
}, 5); | |
static memoryStreamPool = new ObjectPool(function () { | |
return new Stream | |
}, function (stream) { | |
stream.setPosition(0) | |
}, 5); | |
static positionStreamPool = {}; | |
static limitedPositionStreamPool = new ObjectPool(function () { | |
return new LimitedPositionStream | |
}, null, 5); | |
static byteBufferPool = new ObjectPool(function () { | |
var array = []; | |
for (var i = 0; i < 256; i++) { | |
array.push(0) | |
} | |
return array | |
}, null, 5); | |
static customRequestPool = new ObjectPool(function () { | |
return new CustomRequest | |
}, function (cr) { | |
cr.reset() | |
}, 5); | |
} | |
export class RTDataSerializer { | |
static cache = new ObjectPool(function () { | |
return new RTData | |
}, function (rtData) { | |
for (var i = 0; i < rtData.data.length; i++) { | |
if (rtData.data[i].data_val) { | |
rtData.data[i].data_val.dispose() | |
} | |
rtData.data[i].reset() | |
} | |
}, 5); | |
static get() { | |
return RTDataSerializer.cache.pop() | |
}; | |
static readRTData(stream, instance) { | |
if (instance == null) { | |
instance = RTDataSerializer.cache.pop() | |
} | |
var limit = ProtocolParser.readUInt32(stream); | |
limit = limit + stream.getPosition(); | |
while (true) { | |
if (stream.getPosition() >= limit) { | |
if (stream.getPosition() == limit) { | |
break | |
} else { | |
console.log("WARNING: Read past max limit"); | |
return null | |
} | |
} | |
var keyByte = stream.readByte(); | |
if (keyByte == -1) { | |
break | |
} | |
var key = ProtocolParser.readKey(keyByte, stream); | |
if (key.wireType == Wire.VARINT) { | |
instance.data[key.field] = RTVal.newLong(ProtocolParser.readZInt64(stream)) | |
} else if (key.wireType == Wire.FIXED32) { | |
instance.data[key.field] = RTVal.newFloat(ProtocolParser.readSingle(stream)) | |
} else if (key.wireType == Wire.FIXED64) { | |
instance.data[key.field] = RTVal.newDouble(ProtocolParser.readDouble(stream)) | |
} else if (key.wireType == Wire.LENGTH_DELIMITED) { | |
instance.data[key.field] = RTVal.deserializeLengthDelimited(stream) | |
} | |
if (key.field == 0) { | |
console.log("WARNING: Invalid field id: 0, something went wrong in the stream"); | |
return null | |
} | |
} | |
return instance | |
}; | |
static writeRTData(stream, instance) { | |
var ms = PooledObjects.memoryStreamPool.pop(); | |
for (var index = 1; index < instance.data.length; index++) { | |
var entry = instance.data[index]; | |
if (entry.long_val != null) { | |
ProtocolParser.writeUInt32(ms, index << 3); | |
ProtocolParser.writeZInt64(ms, entry.long_val) | |
} else if (entry.float_val != null) { | |
ProtocolParser.writeUInt32(ms, index << 3 | 5); | |
ProtocolParser.writeSingle(ms, entry.float_val) | |
} else if (entry.double_val != null) { | |
ProtocolParser.writeUInt32(ms, index << 3 | 1); | |
ProtocolParser.writeDouble(ms, entry.double_val) | |
} else if (entry.data_val || entry.string_val || entry.vec_val) { | |
ProtocolParser.writeUInt32(ms, index << 3 | 2); | |
entry.serializeLengthDelimited(ms) | |
} | |
} | |
var buffer = ms.getBuffer(); | |
ProtocolParser.writeBytes(stream, buffer, ms.getPosition()); | |
PooledObjects.memoryStreamPool.push(ms) | |
}; | |
} | |
export class RTVector { | |
x; | |
y; | |
z; | |
w; | |
} | |
export class RTVal { | |
long_val = null; | |
float_val = null; | |
double_val = null; | |
data_val = null; | |
string_val = null; | |
vec_val = null; | |
serializeLengthDelimited(stream) { | |
var ms = PooledObjects.memoryStreamPool.pop(); | |
if (this.string_val) { | |
ms.writeByte(10); | |
ProtocolParser.writeString(ms, this.string_val) | |
} else if (this.data_val) { | |
ms.writeByte(114); | |
RTData.writeRTData(ms, this.data_val) | |
} else if (this.vec_val) { | |
var vec_value = this.vec_val; | |
var numberOfFloatsSet = 0; | |
ms.writeByte(18); | |
if (vec_value.x != null) { | |
numberOfFloatsSet = numberOfFloatsSet + 1 | |
} | |
if (vec_value.y != null) { | |
numberOfFloatsSet = numberOfFloatsSet + 1 | |
} | |
if (vec_value.z != null) { | |
numberOfFloatsSet = numberOfFloatsSet + 1 | |
} | |
if (vec_value.w != null) { | |
numberOfFloatsSet = numberOfFloatsSet + 1 | |
} | |
ProtocolParser.writeUInt32(ms, 4 * numberOfFloatsSet); | |
for (var i = 1; i <= numberOfFloatsSet; i++) { | |
if (i == 1) { | |
ProtocolParser.writeSingle(ms, vec_value.x) | |
} else if (i == 2) { | |
ProtocolParser.writeSingle(ms, vec_value.y) | |
} else if (i == 3) { | |
ProtocolParser.writeSingle(ms, vec_value.z) | |
} else if (i == 4) { | |
ProtocolParser.writeSingle(ms, vec_value.w) | |
} | |
} | |
} | |
var data = ms.getBuffer(); | |
ProtocolParser.writeBytes(stream, data, ms.getPosition()); | |
PooledObjects.memoryStreamPool.push(ms) | |
} | |
reset() { | |
if (this.data_val) { | |
this.data_val.dispose() | |
} | |
this.long_val = null; | |
this.float_val = null; | |
this.double_val = null; | |
this.data_val = null; | |
this.string_val = null; | |
this.vec_val = null | |
} | |
dirty() { | |
if (this.long_val != null) { | |
return true | |
} else if (this.float_val != null) { | |
return true | |
} else if (this.double_val != null) { | |
return true | |
} else if (this.data_val) { | |
return true | |
} else if (this.string_val) { | |
return true | |
} else if (this.vec_val) { | |
return true | |
} | |
return false | |
} | |
asString() { | |
if (this.long_val != null) { | |
return this.long_val.toString() | |
} else if (this.float_val != null) { | |
return this.float_val.toString() | |
} else if (this.double_val != null) { | |
return this.double_val.toString() | |
} else if (this.data_val) { | |
return this.data_val.toString() | |
} else if (this.string_val) { | |
return '"' + this.string_val + '"' | |
} else if (this.vec_val) { | |
var ret = "|"; | |
if (this.vec_val.x != null) { | |
ret = ret + this.vec_val.x.toString() + "|" | |
} | |
if (this.vec_val.y != null) { | |
ret = ret + this.vec_val.y.toString() + "|" | |
} | |
if (this.vec_val.z != null) { | |
ret = ret + this.vec_val.z.toString() + "|" | |
} | |
if (this.vec_val.w != null) { | |
ret = ret + this.vec_val.w.toString() + "|" | |
} | |
return ret | |
} | |
return null | |
} | |
static newLong(value) { | |
var instance = new RTVal; | |
instance.long_val = value; | |
return instance | |
}; | |
static newFloat(value) { | |
var instance = new RTVal; | |
instance.float_val = value; | |
return instance | |
}; | |
static newDouble(value) { | |
var instance = new RTVal; | |
instance.double_val = value; | |
return instance | |
}; | |
static newRTData(value) { | |
var instance = new RTVal; | |
instance.data_val = value; | |
return instance | |
}; | |
static newString(value) { | |
var instance = new RTVal; | |
instance.string_val = value; | |
return instance | |
}; | |
static newRTVector(value) { | |
var instance = new RTVal; | |
instance.vec_val = value; | |
return instance | |
}; | |
static deserializeLengthDelimited(stream) { | |
var instance = new RTVal; | |
var limit = ProtocolParser.readUInt32(stream); | |
limit = limit + stream.getPosition(); | |
while (true) { | |
if (stream.getPosition() >= limit) { | |
if (stream.getPosition() == limit) { | |
break | |
} else { | |
console.log("WARNING: Read past max limit"); | |
return 0 | |
} | |
} | |
var keyByte = stream.readByte(); | |
if (keyByte == -1) { | |
console.log("WARNING: End of stream"); | |
return 0 | |
} | |
var _continue = false; | |
if (keyByte == 10) { | |
instance.string_val = ProtocolParser.readString(stream); | |
_continue = true | |
} else if (keyByte == 18) { | |
var end2 = ProtocolParser.readUInt32(stream); | |
end2 = end2 + stream.getPosition(); | |
var v = new RTVector; | |
var i = 0; | |
while (stream.getPosition() < end2) { | |
var read = ProtocolParser.readSingle(stream); | |
if (i == 0) { | |
v.x = read | |
} else if (i == 1) { | |
v.y = read | |
} else if (i == 2) { | |
v.z = read | |
} else if (i == 3) { | |
v.w = read | |
} | |
i = i + 1 | |
} | |
instance.vec_val = v; | |
if (stream.getPosition() != end2) { | |
console.log("WARNING: Read too many bytes in packed data"); | |
return null | |
} | |
_continue = true | |
} else if (keyByte == 114) { | |
if (instance.data_val == null) { | |
instance.data_val = RTDataSerializer.cache.pop() | |
} | |
RTData.readRTData(stream, instance.data_val); | |
_continue = true | |
} | |
if (!_continue) { | |
var key = ProtocolParser.readKey(keyByte, stream); | |
if (key.field == 0) { | |
console.log("WARNING: Invalid field id: 0, something went wrong in the stream"); | |
return null | |
} else { | |
ProtocolParser.skipKey(stream, key) | |
} | |
} | |
} | |
return instance | |
} | |
} | |
export class RTData { | |
data: RTVal[] = []; | |
constructor() { | |
for (var i = 0; i < GameSparksRT.MAX_RTDATA_SLOTS; i++) { | |
this.data.push(new RTVal) | |
} | |
}; | |
static get() { | |
return RTDataSerializer.cache.pop() | |
}; | |
static readRTData(stream, instance) { | |
return RTDataSerializer.readRTData(stream, instance) | |
}; | |
static writeRTData(stream, instance) { | |
RTDataSerializer.writeRTData(stream, instance) | |
}; | |
dispose() { | |
for (var i = 0; i < this.data.length; i++) { | |
if (this.data[i].dirty()) { | |
this.data[i] = new RTVal | |
} | |
} | |
RTDataSerializer.cache.push(this) | |
} | |
getRTVector(index) { | |
return this.data[index].vec_val | |
} | |
getLong(index) { | |
return this.data[index].long_val | |
} | |
getFloat(index) { | |
return this.data[index].float_val | |
} | |
getDouble(index) { | |
return this.data[index].double_val | |
} | |
getString(index) { | |
return this.data[index].string_val | |
} | |
getData(index) { | |
return this.data[index].data_val | |
} | |
setRTVector(index, value) { | |
this.data[index] = RTVal.newRTVector(value); | |
return this | |
} | |
setLong(index, value) { | |
if (isFinite(value)) { | |
this.data[index] = RTVal.newLong(value) | |
} else { | |
console.error("Not a valid number error") | |
} | |
return this | |
} | |
setFloat(index, value) { | |
if (isFinite(value)) { | |
this.data[index] = RTVal.newFloat(value) | |
} else { | |
console.error("Not a valid number error") | |
} | |
return this | |
} | |
setDouble(index, value) { | |
if (isFinite(value)) { | |
this.data[index] = RTVal.newDouble(value) | |
} else { | |
console.error("Not a valid number error") | |
} | |
return this | |
} | |
setString(index, value) { | |
this.data[index] = RTVal.newString(value); | |
return this | |
} | |
setData(index, value) { | |
this.data[index] = RTVal.newRTData(value); | |
return this | |
} | |
toString() { | |
return this.asString() | |
} | |
asString() { | |
var builder = " {"; | |
for (var i = 0; i < GameSparksRT.MAX_RTDATA_SLOTS; i++) { | |
var val = this.data[i].asString(); | |
if (val != null) { | |
builder = builder + " [" + i.toString() + " " + val + "] " | |
} | |
} | |
builder = builder + "} "; | |
return builder | |
} | |
}; | |
export class RTPacket { | |
constructor(public opCode, public sender, public stream, public streamLength, public data, public packetSize) { } | |
toString() { | |
var string = "OpCode=" + this.opCode + ",Sender=" + this.sender + ",streamExists="; | |
if (this.stream != null) { | |
string = string + "true,StreamLength=" + this.streamLength | |
} else { | |
string = string + "false" | |
} | |
string = string + ",Data="; | |
if (this.data != null) { | |
string = string + this.data.toString() | |
} else { | |
string = string + ".PacketSize=" + this.packetSize | |
} | |
return string | |
} | |
}; | |
export class RTSessionImpl { | |
fastPort; | |
activePeers = []; | |
running = false; | |
ready = false; | |
actionQueue = []; | |
connectState = GameSparksRT.connectState.DISCONNECTED; | |
mustConnnectBy = (new Date).getTime(); | |
reliableConnection = null; | |
sequenceNumber = 0; | |
peerId = null; | |
peerMaxSequenceNumbers = []; | |
constructor(public connectToken, public hostName, public tcpPort, public listener) { | |
this.fastPort = tcpPort; | |
} | |
start() { | |
this.running = true | |
} | |
stop() { | |
this.log("IRTSession", GameSparksRT.logLevel.DEBUG, "Stopped"); | |
this.running = false; | |
this.ready = false; | |
if (this.reliableConnection) { | |
this.reliableConnection.stop() | |
} | |
this.setConnectState(GameSparksRT.connectState.DISCONNECTED) | |
} | |
update() { | |
if (this.running) { | |
this.checkConnection() | |
} | |
var toExecute = null; | |
while (true) { | |
toExecute = this.getNextAction(); | |
if (toExecute) { | |
toExecute.execute() | |
} else { | |
break | |
} | |
} | |
} | |
getNextAction() { | |
if (this.actionQueue.length > 0) { | |
return this.actionQueue.shift() | |
} | |
return null | |
} | |
submitAction(action) { | |
this.actionQueue.push(action) | |
} | |
checkConnection() { | |
if (this.connectState == GameSparksRT.connectState.DISCONNECTED) { | |
this.log("IRTSession", GameSparksRT.logLevel.INFO, "Disconnected, trying to connect"); | |
this.setConnectState(GameSparksRT.connectState.CONNECTING); | |
this.connectReliable() | |
} else if (this.connectState == GameSparksRT.connectState.CONNECTING && (new Date).getTime() > this.mustConnnectBy) { | |
this.setConnectState(GameSparksRT.connectState.DISCONNECTED); | |
this.log("IRTSession", GameSparksRT.logLevel.INFO, "Not connected in time, retrying"); | |
if (this.reliableConnection) { | |
this.reliableConnection.stopInternal(); | |
this.reliableConnection = null | |
} | |
} | |
} | |
setConnectState(value) { | |
if (value == GameSparksRT.connectState.RELIABLE_AND_FAST_SEND || value == GameSparksRT.connectState.RELIABLE_AND_FAST) { | |
value = GameSparksRT.connectState.RELIABLE_ONLY | |
} | |
if (value != this.connectState) { | |
if (this.connectState < value || value == GameSparksRT.connectState.DISCONNECTED) { | |
this.log("IRTSession", GameSparksRT.logLevel.DEBUG, "State Change : from " + this.connectState + " to " + value + ", ActivePeers " + this.activePeers.length); | |
this.connectState = value | |
} | |
if (value == GameSparksRT.connectState.RELIABLE_ONLY) { | |
this.onReady(true) | |
} | |
} | |
} | |
connectFast() { } | |
connectReliable() { | |
this.mustConnnectBy = (new Date).getTime() + GameSparksRT.TCP_CONNECT_TIMEOUT_SECONDS * 1e3; | |
this.reliableConnection = new ReliableConnection(this.hostName, this.tcpPort, this); | |
this.reliableConnection.connect() | |
} | |
nextSequenceNumber() { | |
var sequenceNumber = this.sequenceNumber; | |
this.sequenceNumber = this.sequenceNumber + 1; | |
return sequenceNumber | |
} | |
shouldExecute(peerId, sequence) { | |
if (sequence == null) { | |
return true | |
} else if (this.peerMaxSequenceNumbers[peerId] == null) { | |
this.peerMaxSequenceNumbers[peerId] = 0 | |
} | |
if (this.peerMaxSequenceNumbers[peerId] > sequence) { | |
this.log("IRTSession", GameSparksRT.logLevel.DEBUG, "Discarding sequence id " + sequence + " from peer " + peerId.toString()); | |
return false | |
} else { | |
this.peerMaxSequenceNumbers[peerId] = sequence; | |
return true | |
} | |
} | |
resetSequenceForPeer(peerId) { | |
if (this.peerMaxSequenceNumbers[peerId]) { | |
this.peerMaxSequenceNumbers[peerId] = 0 | |
} | |
} | |
onPlayerConnect(peerId) { | |
this.resetSequenceForPeer(peerId); | |
if (this.listener) { | |
if (this.ready) { | |
this.listener.onPlayerConnect(peerId) | |
} | |
} | |
} | |
onPlayerDisconnect(peerId) { | |
if (this.listener) { | |
if (this.ready) { | |
this.listener.onPlayerDisconnect(peerId) | |
} | |
} | |
} | |
onReady(ready) { | |
if (!this.ready && ready) { | |
this.sendData(OpCodes.PLAYER_READY_MESSAGE, GameSparksRT.deliveryIntent.RELIABLE, null, null, null); | |
if (this.peerId) { | |
var ok = false; | |
for (var k in this.activePeers) { | |
if (this.activePeers[k] == this.peerId) { | |
ok = true; | |
break | |
} | |
} | |
if (!ok) { | |
this.activePeers.push(this.peerId) | |
} | |
} | |
} | |
this.ready = ready; | |
if (!this.ready) { | |
this.setConnectState(GameSparksRT.connectState.DISCONNECTED) | |
} | |
if (this.listener) { | |
var myListener = this.listener; | |
this.submitAction(ActionCommand.pool.pop().configure(function () { | |
myListener.onReady(ready) | |
})) | |
} | |
} | |
onPacket(packet) { | |
if (this.listener) { | |
this.listener.onPacket(packet) | |
} else { | |
throw new Error("AccessViolationException") | |
} | |
} | |
sendData(opCode, intent, payload, data, targetPlayers) { | |
return this.sendRTDataAndBytes(opCode, intent, payload, data, targetPlayers) | |
} | |
sendRTData(opCode, intent, data, targetPlayers) { | |
return this.sendRTDataAndBytes(opCode, intent, null, data, targetPlayers) | |
} | |
sendBytes(opCode, intent, payload, targetPlayers) { | |
return this.sendRTDataAndBytes(opCode, intent, payload, null, targetPlayers) | |
} | |
sendRTDataAndBytes(opCode, intent, payload, data, targetPlayers) { | |
var csr = PooledObjects.customRequestPool.pop(); | |
var ret; | |
csr.configure(opCode, intent, payload, data, targetPlayers); | |
if (this.connectState >= GameSparksRT.connectState.RELIABLE_ONLY) { | |
ret = this.reliableConnection.send(csr); | |
PooledObjects.customRequestPool.push(csr); | |
return ret | |
} | |
PooledObjects.customRequestPool.push(csr); | |
return 0 | |
} | |
log(tag, level, msg) { | |
if (GameSparksRT.shouldLog(tag, level)) { | |
this.submitAction(LogCommand.pool.pop().configure(this, tag, level, msg)) | |
} | |
} | |
}; | |
export class GameSparksRT { | |
static MAX_RTDATA_SLOTS = 128; | |
static TCP_CONNECT_TIMEOUT_SECONDS = 5; | |
static logLevel = { | |
DEBUG: 0, | |
INFO: 1, | |
WARN: 2, | |
ERROR: 3 | |
}; | |
static connectState = { | |
DISCONNECTED: 0, | |
CONNECTING: 1, | |
RELIABLE_ONLY: 2, | |
RELIABLE_AND_FAST_SEND: 3, | |
RELIABLE_AND_FAST: 4 | |
}; | |
static deliveryIntent = { | |
RELIABLE: 0, | |
UNRELIABLE: 1, | |
UNRELIABLE_SEQUENCED: 2 | |
}; | |
static currLogLevel = GameSparksRT.logLevel.INFO; | |
static tagLevels = {}; | |
static logger(msg) { | |
console.log(msg) | |
}; | |
static getSession(connectToken, hostName, tcpPort, listener) { | |
return new RTSessionImpl(connectToken, hostName, tcpPort, listener) | |
}; | |
static setRootLogLevel(level) { | |
GameSparksRT.currLogLevel = level | |
}; | |
static setLogLevel(tag, level) { | |
GameSparksRT.tagLevels[tag] = level | |
}; | |
static shouldLog(tag, level) { | |
for (var key in GameSparksRT.tagLevels) { | |
var value = GameSparksRT.tagLevels[key]; | |
if (key == tag) { | |
return value >= level | |
} | |
} | |
return GameSparksRT.currLogLevel <= level | |
}; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { isIOS } from "tns-core-modules/ui/page/page"; | |
import { GameSparksRT, RTSessionImpl } from "~/gamesparks/gamesparks-realtime"; | |
require('nativescript-websockets'); | |
export interface GenericResponse { | |
/** A JSON Map of any data added either to the Request or the Response by your Cloud Code */ | |
scriptData: Object; | |
}; | |
export interface Player { | |
/** The achievements of the Player */ | |
achievements: string[]; | |
/** The display name of the Player */ | |
displayName: string; | |
/** The external Id’s of the Player */ | |
externalIds: Object; | |
/** The Id of the Player */ | |
id: string; | |
/** The online status of the Player */ | |
online: boolean; | |
/** The script data of the Player */ | |
scriptData: Object; | |
/** The virtual goods of the Player */ | |
virtualGoods: string[]; | |
}; | |
export interface MatchDetailsResponse { | |
/** The accessToken used to authenticate this player for this match */ | |
accessToken: string; | |
/** The host to connect to for this match */ | |
host: string; | |
/** MatchData is arbitrary data that can be stored in a Match instance by a Cloud Code script. */ | |
matchData: JSON; | |
/** The id for this match instance */ | |
matchId: string; | |
/** The opponents this player has been matched against */ | |
opponents: Player[]; | |
/** The peerId of this player within the match */ | |
peerId: number; | |
/** The id of the current player */ | |
playerId: string; | |
/** The port to connect to for this match */ | |
port: number; | |
/** A JSON Map of any data added either to the Request or the Response by your Cloud Code */ | |
scriptData: Object; | |
} | |
export interface ChallengeRequest { | |
/** Who can join this challenge */ | |
accessType?: 'PUBLIC' | 'PRIVATE' | 'FRIENDS' | |
/** An optional message to include with the challenge */ | |
challengeMessage?: string | |
/** The ammount of currency type 1 that the player is wagering on this challenge */ | |
currency1Wager? | |
currency2Wager? | |
currency3Wager? | |
currency4Wager? | |
currency5Wager? | |
currency6Wager? | |
/** The time at which this challenge will end. This is required when the challenge is not linked to an achievement */ | |
endTime?: string; | |
/** The latest time that players can join this challenge */ | |
expiryTime?: string; | |
/** The maximum number of attempts */ | |
maxAttempts?: number; | |
/** The maximum number of players that are allowed to join this challenge */ | |
maxPlayers?: number; | |
/** The minimum number of players that are allowed to join this challenge */ | |
minPlayers?: number; | |
/** If True no messaging is triggered */ | |
silent?: boolean; | |
/** The time at which this challenge will begin */ | |
startTime?: string; | |
/** A player id or an array of player ids who will recieve this challenge */ | |
usersToChallenge?: string[] | |
} | |
export interface Participant { | |
/** The achievements of the Player */ | |
achievements: string[]; | |
/** The display name of the Player */ | |
displayName: string; | |
/** The external Id’s of the Player */ | |
externalIds: Object; | |
/** The Id of the Player */ | |
id: string; | |
/** The online status of the Player */ | |
online: boolean; | |
/** A JSON Map of any data that was associated to this user */ | |
participantData: Object; | |
/** The peerId of this participant within the match */ | |
peerId: number; | |
/** The script data of the Player */ | |
scriptData: Object; | |
/** The virtual goods of the Player */ | |
virtualGoods: string[]; | |
} | |
export interface MatchFoundMessage { | |
/** The accessToken used to authenticate this player for this match */ | |
accessToken: string; | |
/** The host to connect to for this match */ | |
host: string; | |
/** The arbitrary data that is stored on a match instance in the matchData field */ | |
matchData: JSON; | |
/** The group the player was assigned in the matchmaking request */ | |
matchGroup: string; | |
/** The id for this match instance */ | |
matchId: string; | |
/** The shortCode of the match type this message for */ | |
matchShortCode: string; | |
/** A unique identifier for this message. */ | |
messageId: string; | |
/** Flag indicating whether this message could be sent as a push notification or not. */ | |
notification: boolean; | |
/** ] The participants in this match */ | |
participants: Participant[]; | |
/** The arbitrary data that is stored on a pendingMatch in the matchData field */ | |
pendingMatchData: JSON; | |
/** The port to connect to for this match */ | |
port: number; | |
/** ] ScriptData is arbitrary data that can be stored in a message by a Cloud Code script. */ | |
scriptData: Object; | |
/** A textual title for the message. */ | |
subTitle: string; | |
/** A textual summary describing the message’s purpose. */ | |
summary: string; | |
/** A textual title for the message. */ | |
title: string; | |
} | |
export class RealtimeSession { | |
static started = false; | |
static onPlayerConnectCB = null; | |
static onPlayerDisconnectCB = null; | |
static onReadyCB = null; | |
static onPacketCB = null; | |
static session: RTSessionImpl; | |
static start(connectToken, host, port) { | |
var index = host.indexOf(":"); | |
var theHost; | |
if (index > 0) { | |
theHost = host.slice(0, index); | |
} else { | |
theHost = host; | |
} | |
console.log(theHost + " : " + port); | |
RealtimeSession.session = GameSparksRT.getSession(connectToken, theHost, port, RealtimeSession); | |
if (RealtimeSession.session != null) { | |
RealtimeSession.started = true; | |
RealtimeSession.session.start(); | |
} else { | |
RealtimeSession.started = false; | |
} | |
}; | |
static stop() { | |
RealtimeSession.started = false; | |
if (RealtimeSession.session != null) { | |
RealtimeSession.session.stop(); | |
} | |
}; | |
static log(message) { | |
var peers = "|"; | |
for (var k in RealtimeSession.session.activePeers) { | |
peers = peers + RealtimeSession.session.activePeers[k] + "|"; | |
} | |
console.log(RealtimeSession.session.peerId + ": " + message + " peers:" + peers); | |
}; | |
static onPlayerConnect(peerId) { | |
RealtimeSession.log(" OnPlayerConnect:" + peerId); | |
if (RealtimeSession.onPlayerConnectCB != null) { | |
RealtimeSession.onPlayerConnectCB(peerId); | |
} | |
}; | |
static onPlayerDisconnect(peerId) { | |
RealtimeSession.log(" OnPlayerDisconnect:" + peerId); | |
if (RealtimeSession.onPlayerDisconnectCB != null) { | |
RealtimeSession.onPlayerDisconnectCB(peerId); | |
} | |
}; | |
static onReady(ready) { | |
RealtimeSession.log(" OnReady:" + ready.toString()); | |
if (RealtimeSession.onReadyCB != null) { | |
RealtimeSession.onReadyCB(ready); | |
} | |
}; | |
static onPacket(packet) { | |
RealtimeSession.log(" OnPacket:" + packet.toString()); | |
if (RealtimeSession.onPacketCB != null) { | |
RealtimeSession.onPacketCB(packet); | |
} | |
}; | |
} | |
export class GameSparks { | |
options; | |
socketUrl; | |
pendingRequests; | |
requestCounter; | |
init(options) { | |
this.options = options; | |
this.socketUrl = options.url; | |
this.pendingRequests = {}; | |
this.requestCounter = 0; | |
this.connect(); | |
} | |
buildServiceUrl(live, options) { | |
var stage; | |
var urlAddition = options.key; | |
var credential; | |
var index; | |
if (live) { | |
stage = "live"; | |
} else { | |
stage = "preview"; | |
} | |
if (!options.credential || options.credential.length === 0) { | |
credential = "device"; | |
} else { | |
credential = options.credential; | |
} | |
if (options.secret) { | |
index = options.secret.indexOf(":"); | |
if (index > 0) { | |
credential = "secure"; | |
urlAddition = options.secret.substr(0, index) + "/" + urlAddition; | |
} | |
} | |
return "wss://" + stage + "-" + urlAddition + ".ws.gamesparks.net/ws/" + credential + "/" + urlAddition; | |
} | |
initPreview(options) { | |
options.url = this.buildServiceUrl(false, options); | |
this.init(options); | |
} | |
initLive(options) { | |
options.url = this.buildServiceUrl(true, options); | |
this.init(options); | |
} | |
initialised = false; | |
connected = false; | |
error = false; | |
disconnected = false; | |
webSocket; | |
reset() { | |
this.initialised = false; | |
this.connected = false; | |
this.error = false; | |
this.disconnected = false; | |
if (this.webSocket != null) { | |
this.webSocket.onclose = null; | |
this.webSocket.close(); | |
} | |
} | |
connect() { | |
this.reset(); | |
try { | |
this.webSocket = new WebSocket(this.socketUrl); | |
this.webSocket.onopen = this.onWebSocketOpen.bind(this); | |
this.webSocket.onclose = this.onWebSocketClose.bind(this); | |
this.webSocket.onerror = this.onWebSocketError.bind(this); | |
this.webSocket.onmessage = this.onWebSocketMessage.bind(this); | |
} catch (e) { | |
this.log(e.message); | |
} | |
} | |
disconnect() { | |
if (this.webSocket && this.connected) { | |
this.disconnected = true; | |
this.webSocket.close(); | |
} | |
} | |
onWebSocketOpen(ev) { | |
this.log('WebSocket onOpen'); | |
if (this.options.onOpen) { | |
this.options.onOpen(ev); | |
} | |
this.connected = true; | |
} | |
onWebSocketClose(ev) { | |
this.log('WebSocket onClose'); | |
if (this.options.onClose) { | |
this.options.onClose(ev); | |
} | |
this.connected = false; | |
// Attemp a re-connection if not in error state or deliberately disconnected. | |
if (!this.error && !this.disconnected) { | |
this.connect(); | |
} | |
} | |
onWebSocketError(ev) { | |
this.log('WebSocket onError: Sorry, but there is some problem with your socket or the server is down'); | |
if (this.options.onError) { | |
this.options.onError(ev); | |
} | |
// Reset the socketUrl to the original. | |
this.socketUrl = this.options.url; | |
this.error = true; | |
} | |
authToken; | |
onWebSocketMessage(message) { | |
this.log('WebSocket onMessage: ' + message.data); | |
var result; | |
try { | |
result = JSON.parse(message.data); | |
} catch (e) { | |
this.log('An error ocurred while parsing the JSON Data: ' + message + '; Error: ' + e); | |
return; | |
} | |
if (this.options.onMessage) { | |
this.options.onMessage(result); | |
} | |
// Extract any auth token. | |
if (result['authToken']) { | |
this.authToken = result['authToken']; | |
delete result['authToken']; | |
} | |
if (result['connectUrl']) { | |
// Any time a connectUrl is in the response we should update and reconnect. | |
this.socketUrl = result['connectUrl']; | |
this.connect(); | |
} | |
var resultType = result['@class']; | |
if (resultType === '.AuthenticatedConnectResponse') { | |
this.handshake(result); | |
} else if (resultType.match(/Response$/)) { | |
if (result['requestId']) { | |
var requestId = result['requestId']; | |
delete result['requestId']; | |
if (this.pendingRequests[requestId]) { | |
this.pendingRequests[requestId](result); | |
this.pendingRequests[requestId] = null; | |
} | |
} | |
} | |
} | |
sessionId; | |
keepAliveInterval; | |
handshake(result) { | |
if (result['nonce']) { | |
var hmac; | |
if (this.options.onNonce) { | |
hmac = this.options.onNonce(result['nonce']); | |
} else if (this.options.secret) { | |
var SHA256 = require('nativescript-toolbox/crypto-js/hmac-sha256'); | |
var Base64 = require('nativescript-toolbox/crypto-js/enc-base64'); | |
hmac = Base64.stringify(SHA256(result['nonce'], this.options.secret)); | |
} | |
var toSend = { | |
'@class': '.AuthenticatedConnectRequest', | |
hmac: hmac | |
} as any; | |
if (this.authToken) { | |
toSend.authToken = this.authToken; | |
} | |
if (this.sessionId) { | |
toSend.sessionId = this.sessionId; | |
} | |
toSend.platform = 'mobile'; | |
toSend.os = | |
? 'iOS' : 'ANDROID'; | |
this.webSocketSend(toSend); | |
} else if (result['sessionId']) { | |
this.sessionId = result['sessionId']; | |
this.initialised = true; | |
if (this.options.onInit) { | |
this.options.onInit(); | |
} | |
this.keepAliveInterval = setInterval(this.keepAlive.bind(this), 30000); | |
} | |
} | |
keepAlive() { | |
if (this.initialised && this.connected) { | |
this.webSocket.send(' '); | |
} | |
} | |
send(requestType, onResponse) { | |
this.sendWithData(requestType, {}, onResponse); | |
} | |
sendWithData(requestType, json, onResponse?) { | |
if (!this.initialised) { | |
if (onResponse) { | |
onResponse({ error: 'NOT_INITIALISED' }); | |
} | |
return; | |
} | |
// Ensure requestType starts with a dot. | |
if (requestType.indexOf('.') !== 0) { | |
requestType = '.' + requestType; | |
} | |
json['@class'] = requestType; | |
json.requestId = (new Date()).getTime() + "_" + (++this.requestCounter); | |
if (onResponse != null) { | |
this.pendingRequests[json.requestId] = onResponse; | |
// Time out handler. | |
setTimeout((() => { | |
if (this.pendingRequests[json.requestId]) { | |
this.pendingRequests[json.requestId]({ error: 'NO_RESPONSE' }); | |
} | |
}).bind(this), 32000); | |
} | |
this.webSocketSend(json); | |
} | |
webSocketSend(data) { | |
if (this.options.onSend) { | |
this.options.onSend(data); | |
} | |
var requestString = JSON.stringify(data); | |
this.log('WebSocket send: ' + requestString); | |
this.webSocket.send(requestString); | |
} | |
getSocketUrl() { | |
return this.socketUrl; | |
} | |
getSessionId() { | |
return this.sessionId; | |
} | |
getAuthToken() { | |
return this.authToken; | |
} | |
setAuthToken(authToken) { | |
this.authToken = authToken; | |
} | |
isConnected() { | |
return this.connected; | |
} | |
log(message) { | |
if (this.options.logger) { | |
this.options.logger(message); | |
} | |
} | |
acceptChallengeRequest(challengeInstanceId, message, onResponse) { | |
var request = {}; | |
request["challengeInstanceId"] = challengeInstanceId; | |
request["message"] = message; | |
this.sendWithData("AcceptChallengeRequest", request, onResponse); | |
} | |
accountDetailsRequest(onResponse) { | |
var request = {}; | |
this.sendWithData("AccountDetailsRequest", request, onResponse); | |
} | |
analyticsRequest(data, end, key, start, onResponse) { | |
var request = {}; | |
request["data"] = data; | |
request["end"] = end; | |
request["key"] = key; | |
request["start"] = start; | |
this.sendWithData("AnalyticsRequest", request, onResponse); | |
} | |
aroundMeLeaderboardRequest(count, friendIds, leaderboardShortCode, social, onResponse) { | |
var request = {}; | |
request["count"] = count; | |
request["friendIds"] = friendIds; | |
request["leaderboardShortCode"] = leaderboardShortCode; | |
request["social"] = social; | |
this.sendWithData("AroundMeLeaderboardRequest", request, onResponse); | |
} | |
authenticationRequest(password, userName, onResponse) { | |
var request = {}; | |
request["password"] = password; | |
request["userName"] = userName; | |
this.sendWithData("AuthenticationRequest", request, onResponse); | |
} | |
buyVirtualGoodsRequest(currencyType, quantity, shortCode, onResponse) { | |
var request = {}; | |
request["currencyType"] = currencyType; | |
request["quantity"] = quantity; | |
request["shortCode"] = shortCode; | |
this.sendWithData("BuyVirtualGoodsRequest", request, onResponse); | |
} | |
changeUserDetailsRequest(displayName, onResponse) { | |
var request = {}; | |
request["displayName"] = displayName; | |
this.sendWithData("ChangeUserDetailsRequest", request, onResponse); | |
} | |
chatOnChallengeRequest(challengeInstanceId, message, onResponse) { | |
var request = {}; | |
request["challengeInstanceId"] = challengeInstanceId; | |
request["message"] = message; | |
this.sendWithData("ChatOnChallengeRequest", request, onResponse); | |
} | |
consumeVirtualGoodRequest(quantity, shortCode, onResponse) { | |
var request = {}; | |
request["quantity"] = quantity; | |
request["shortCode"] = shortCode; | |
this.sendWithData("ConsumeVirtualGoodRequest", request, onResponse); | |
} | |
matchmakingRequest(matchShortCode: string, options: { | |
/** The skill of the player looking for a match */ | |
skill?: number, | |
/** A JSON Map of any data that will be associated to the pending match */ | |
matchData?: Object, | |
/** Players will be grouped based on the distinct value passed in here, only players in the same group can be matched together */ | |
matchGroup?: string, | |
/** The action to take on the already in-flight request for this match. Currently supported actions are: 'cancel’ */ | |
action?: 'cancel', | |
/** The query that will be applied to the PendingMatch collection */ | |
customQuery?: Object, | |
/** A JSON Map of any data that will be associated to this user in a pending match */ | |
participantData?: Object | |
} = {}): Promise<GenericResponse> { | |
return new Promise((resolve, reject) => { | |
var request = Object.assign({ | |
matchShortCode: matchShortCode | |
}, options); | |
this.sendWithData("MatchmakingRequest", request, (response) => { | |
resolve(response); | |
}); | |
}) | |
} | |
matchDetailsRequest(matchId: string, options: { | |
/** Adds realtime server details if the match has been created using Cloud Code and it has not been realtime enabled */ | |
realtimeEnabled?: boolean | |
}): Promise<MatchDetailsResponse> { | |
return new Promise((resolve, reject) => { | |
var request = Object.assign({ | |
matchId: matchId | |
}); | |
this.sendWithData("MatchDetailsRequest", request, (response) => { | |
resolve(response); | |
}); | |
}) | |
} | |
createChallengeRequest( | |
challengeShortCode: string, | |
options: ChallengeRequest = {} | |
): Promise<{ challengeInstanceId: string }> { | |
var request = Object.assign({ | |
challengeShortCode: challengeShortCode | |
}, options); | |
return new Promise((resolve, reject) => { | |
this.sendWithData("CreateChallengeRequest", request, (response) => { | |
resolve(response); | |
}); | |
}) | |
} | |
declineChallengeRequest(challengeInstanceId, message, onResponse) { | |
var request = {}; | |
request["challengeInstanceId"] = challengeInstanceId; | |
request["message"] = message; | |
this.sendWithData("DeclineChallengeRequest", request, onResponse); | |
} | |
deviceAuthenticationRequest(deviceId, deviceModel, deviceName, deviceOS, deviceType, operatingSystem, onResponse) { | |
var request = {}; | |
request["deviceId"] = deviceId; | |
request["deviceModel"] = deviceModel; | |
request["deviceName"] = deviceName; | |
request["deviceOS"] = deviceOS; | |
request["deviceType"] = deviceType; | |
request["operatingSystem"] = operatingSystem; | |
this.sendWithData("DeviceAuthenticationRequest", request, onResponse); | |
} | |
dismissMessageRequest(messageId, onResponse) { | |
var request = {}; | |
request["messageId"] = messageId; | |
this.sendWithData("DismissMessageRequest", request, onResponse); | |
} | |
endSessionRequest(onResponse) { | |
var request = {}; | |
this.sendWithData("EndSessionRequest", request, onResponse); | |
} | |
facebookConnectRequest(accessToken, code, onResponse) { | |
var request = {}; | |
request["accessToken"] = accessToken; | |
request["code"] = code; | |
this.sendWithData("FacebookConnectRequest", request, onResponse); | |
} | |
findChallengeRequest(accessType, count, offset, onResponse) { | |
var request = {}; | |
request["accessType"] = accessType; | |
request["count"] = count; | |
request["offset"] = offset; | |
this.sendWithData("FindChallengeRequest", request, onResponse); | |
} | |
getChallengeRequest(challengeInstanceId, message, onResponse) { | |
var request = {}; | |
request["challengeInstanceId"] = challengeInstanceId; | |
request["message"] = message; | |
this.sendWithData("GetChallengeRequest", request, onResponse); | |
} | |
getDownloadableRequest(shortCode, onResponse) { | |
var request = {}; | |
request["shortCode"] = shortCode; | |
this.sendWithData("GetDownloadableRequest", request, onResponse); | |
} | |
getMessageRequest(messageId, onResponse) { | |
var request = {}; | |
request["messageId"] = messageId; | |
this.sendWithData("GetMessageRequest", request, onResponse); | |
} | |
getRunningTotalsRequest(friendIds, shortCode, onResponse) { | |
var request = {}; | |
request["friendIds"] = friendIds; | |
request["shortCode"] = shortCode; | |
this.sendWithData("GetRunningTotalsRequest", request, onResponse); | |
} | |
getUploadUrlRequest(uploadData, onResponse) { | |
var request = {}; | |
request["uploadData"] = uploadData; | |
this.sendWithData("GetUploadUrlRequest", request, onResponse); | |
} | |
getUploadedRequest(uploadId, onResponse) { | |
var request = {}; | |
request["uploadId"] = uploadId; | |
this.sendWithData("GetUploadedRequest", request, onResponse); | |
} | |
googlePlayBuyGoodsRequest(currencyCode, signature, signedData, subUnitPrice, onResponse) { | |
var request = {}; | |
request["currencyCode"] = currencyCode; | |
request["signature"] = signature; | |
request["signedData"] = signedData; | |
request["subUnitPrice"] = subUnitPrice; | |
this.sendWithData("GooglePlayBuyGoodsRequest", request, onResponse); | |
} | |
iOSBuyGoodsRequest(currencyCode, receipt, sandbox, subUnitPrice, onResponse) { | |
var request = {}; | |
request["currencyCode"] = currencyCode; | |
request["receipt"] = receipt; | |
request["sandbox"] = sandbox; | |
request["subUnitPrice"] = subUnitPrice; | |
this.sendWithData("IOSBuyGoodsRequest", request, onResponse); | |
} | |
joinChallengeRequest(challengeInstanceId, message, onResponse) { | |
var request = {}; | |
request["challengeInstanceId"] = challengeInstanceId; | |
request["message"] = message; | |
this.sendWithData("JoinChallengeRequest", request, onResponse); | |
} | |
leaderboardDataRequest(leaderboardShortCode, entryCount: number, onResponse) { | |
var request = {}; | |
request["leaderboardShortCode"] = leaderboardShortCode; | |
request["entryCount"] = entryCount; | |
this.sendWithData("LeaderboardDataRequest", request, onResponse); | |
} | |
leaderboardDataRequestForChallenge(challengeInstanceId, entryCount, friendIds, leaderboardShortCode, offset, social, onResponse) { | |
var request = {}; | |
request["challengeInstanceId"] = challengeInstanceId; | |
request["entryCount"] = entryCount; | |
request["friendIds"] = friendIds; | |
request["leaderboardShortCode"] = leaderboardShortCode; | |
request["offset"] = offset; | |
request["social"] = social; | |
this.sendWithData("LeaderboardDataRequest", request, onResponse); | |
} | |
listAchievementsRequest(onResponse) { | |
var request = {}; | |
this.sendWithData("ListAchievementsRequest", request, onResponse); | |
} | |
listChallengeRequest(entryCount, offset, shortCode, state, onResponse) { | |
var request = {}; | |
request["entryCount"] = entryCount; | |
request["offset"] = offset; | |
request["shortCode"] = shortCode; | |
request["state"] = state; | |
this.sendWithData("ListChallengeRequest", request, onResponse); | |
} | |
listChallengeTypeRequest(onResponse) { | |
var request = {}; | |
this.sendWithData("ListChallengeTypeRequest", request, onResponse); | |
} | |
listGameFriendsRequest(onResponse) { | |
var request = {}; | |
this.sendWithData("ListGameFriendsRequest", request, onResponse); | |
} | |
listInviteFriendsRequest(onResponse) { | |
var request = {}; | |
this.sendWithData("ListInviteFriendsRequest", request, onResponse); | |
} | |
listLeaderboardsRequest(onResponse) { | |
var request = {}; | |
this.sendWithData("ListLeaderboardsRequest", request, onResponse); | |
} | |
listMessageRequest(entryCount, offset, onResponse) { | |
var request = {}; | |
request["entryCount"] = entryCount; | |
request["offset"] = offset; | |
this.sendWithData("ListMessageRequest", request, onResponse); | |
} | |
listMessageSummaryRequest(entryCount, offset, onResponse) { | |
var request = {}; | |
request["entryCount"] = entryCount; | |
request["offset"] = offset; | |
this.sendWithData("ListMessageSummaryRequest", request, onResponse); | |
} | |
listVirtualGoodsRequest(onResponse) { | |
var request = {}; | |
this.sendWithData("ListVirtualGoodsRequest", request, onResponse); | |
} | |
logChallengeEventRequest(challengeInstanceId, eventKey, onResponse) { | |
var request = {}; | |
request["challengeInstanceId"] = challengeInstanceId; | |
request["eventKey"] = eventKey; | |
this.sendWithData("LogChallengeEventRequest", request, onResponse); | |
} | |
logEventRequest(eventKey: string, eventData: any = {}): Promise<any> { | |
var request = Object.assign({ | |
eventKey: eventKey | |
}, eventData); | |
return new Promise((resolve, reject) => { | |
this.sendWithData("LogEventRequest", request, (response) => { | |
resolve(response); | |
}); | |
}) | |
} | |
pushRegistrationRequest(deviceOS, pushId, onResponse) { | |
var request = {}; | |
request["deviceOS"] = deviceOS; | |
request["pushId"] = pushId; | |
this.sendWithData("PushRegistrationRequest", request, onResponse); | |
} | |
registrationRequest(displayName, password, userName, onResponse) { | |
var request = {}; | |
request["displayName"] = displayName; | |
request["password"] = password; | |
request["userName"] = userName; | |
this.sendWithData("RegistrationRequest", request, onResponse); | |
} | |
sendFriendMessageRequest(friendIds, message, onResponse) { | |
var request = {}; | |
request["friendIds"] = friendIds; | |
request["message"] = message; | |
this.sendWithData("SendFriendMessageRequest", request, onResponse); | |
} | |
socialLeaderboardDataRequest(challengeInstanceId, entryCount, friendIds, leaderboardShortCode, offset, social, onResponse) { | |
var request = {}; | |
request["challengeInstanceId"] = challengeInstanceId; | |
request["entryCount"] = entryCount; | |
request["friendIds"] = friendIds; | |
request["leaderboardShortCode"] = leaderboardShortCode; | |
request["offset"] = offset; | |
request["social"] = social; | |
this.sendWithData("SocialLeaderboardDataRequest", request, onResponse); | |
} | |
twitterConnectRequest(accessSecret, accessToken, onResponse) { | |
var request = {}; | |
request["accessSecret"] = accessSecret; | |
request["accessToken"] = accessToken; | |
this.sendWithData("TwitterConnectRequest", request, onResponse); | |
} | |
windowsBuyGoodsRequest(currencyCode, receipt, subUnitPrice, onResponse) { | |
var request = {}; | |
request["currencyCode"] = currencyCode; | |
request["receipt"] = receipt; | |
request["subUnitPrice"] = subUnitPrice; | |
this.sendWithData("WindowsBuyGoodsRequest", request, onResponse); | |
} | |
withdrawChallengeRequest(challengeInstanceId, message, onResponse) { | |
var request = {}; | |
request["challengeInstanceId"] = challengeInstanceId; | |
request["message"] = message; | |
this.sendWithData("WithdrawChallengeRequest", request, onResponse); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
My main purpose for these was using GameSparks services with NativeScript. To use Websockets and CryptoJS in NativeScript, you'll need to add nativescript-websockets and nativescript-toolbox.