Skip to content

Instantly share code, notes, and snippets.

@Ephellon
Last active January 4, 2019 07:30
Show Gist options
  • Save Ephellon/bdb6be9368e6a120ce35c0a83be42782 to your computer and use it in GitHub Desktop.
Save Ephellon/bdb6be9368e6a120ce35c0a83be42782 to your computer and use it in GitHub Desktop.
Documentation about SynQ.js

SynQ.js Quick Start

Objects, Functions & Methods

Data Oriented Items

Function SynQ.set -> String

arguments: String: Key Name, String: Data[, String: Password]

Creates (or updates) Key Name in SynQ.

Returns Key Name.

Function SynQ.upload -> String

arguments: String: Key Name[, String: Data[, String: Password]]

Creates (or updates) Global:Key Name in SynQ.

Returns a UUID of Global:Key Name, even if the data is empty.

Function SynQ.get -> String

arguments: String: Key Name[, String: Password]

Returns the data from Key Name.

Function SynQ.download -> String

arguments: String: Key Name[, String: Password]

Returns the data from Global:Key Name

Function SynQ.push -> String

arguments: String: Array Name, String: Data[, String: Password]

Returns the name of the array (Array Name).

Function SynQ.append -> String

arguments: String: Array Name, String: Data[, String: Password[, String: Delimeter]]

Returns the name of the array (Global:Array Name).

Function SynQ.pull -> String

arguments: String: Array Name[, String: Password[, String: delimeter]]

Returns data from the array Array Name.

Function SynQ.recall -> String

arguments: String: Array Name[, String: Password[, String: delimeter]]

Returns data from the array Global:Array Name.

Function SynQ.pop -> String

arguments: String: Key Name[, String: Password]

Removes, and returns the data from Key Name, similar to Array.prototype.pop.

Function SynQ.snip -> String

arguments: String: Key Name[, String: Password]

Removes, and returns the data from Global:Key Name, similar to SynQ.pop.

Function SynQ.list -> Object

arguments: [Boolean: Show Private Items]

Returns an object containing all owned items of the current SynQ instance.

Please note, if you don't specify use_global_synq_token as a defined value (null counts), SynQ will create a new token for each page under your domain, i.e. http://example.com/page-1 will not share data with http://example.com/page-2.

Function SynQ.clear -> Undefined

arguments: [Boolean: Private Items]

Removes all entries for the current SynQ instance.

If Private Items is true, then those items will be removed as well.

Function SynQ.find -> Array

arguments: (Array|RegExp|String): Query[, Boolean: Search Private Items[, Array: Previous Finds]]

  • String: Query: a simple string containing a search term.
  • String: Query: a SynQ-URL containing options and search terms synq://{ UUID }/?auth={ Base64 {username}:{password} }&all={ Search Private Items }&query={ Query }&query=....
    • Where password is the entry's original name before signing, e.g. "test-page" (before it became "xyzabc123" after signing)

Returns an array of results from the search.

Function SynQ.deflate -> String:JSON

arguments: ANY: Object[, Function: Filter[, Number: Spaces]]

Returns a serialized JSON Object (a String). This is a Polyfill of JSON.stringify.

Function SynQ.inflate -> Array|Boolean|Object|Number|String|null

arguments: String: Data[, Function: Mutator]

Returns a parsed JSON Object, or literal.

Array SynQ.last

Contains the names of each array created (from SynQ.push and SynQ.append) in order of creation.

Function SynQ.size -> Number | String

  1. arguments: EMPTY

Returns the maximum amount of storage supported by the device (in bytes [1Byte = 8bits]).

  1. arguments: Number: X[, Number: Base[, String: Symbol]]

Returns the SI formatted version of the given number N (doesn't support the thousand's place).


"Security" Items

Function SynQ.lock -> String

arguments: String: Data, String: Password

Returns a salted string from Data using the Password.

Function SynQ.unlock -> String

arguments: String: Data, String: Password

Returns an unsalted string from Data (the same as calling SynQ.lock(SynQ.lock( data, password ), password)).

Function SynQ.salt -> String

arguments: String: Data, String: Salt

Returns a salted string.

Function SynQ.sign -> String

arguments: String: Data[, Float Fidelity]

Returns a hashed string from Data. Hashes vary in length depending on Fidelity, with 0 producing longer hashes; 1 generally producing shorter hashes.


URL Oriented Items

Function SynQ.decodeURL -> String

arguments: String: URL

Decodes a URL string, i.e. the %20 in a URL become (space character).

Function SynQ.encodeURL -> String

arguments: String: URL

Encodes a URL string, i.e. the (space character) in a URL becomes %20.

Function SynQ.parseURL -> Object

arguments: String: URL

Returns a URL-like object as the following: { href, origin, protocol, scheme, username, password, host, hostname, port, path, search, searchParameters, hash }


Miscellaneous Items

Function SynQ.help

For all other inquiries, SynQ comes with a built-in help function, simply call SynQ.help('*').

Object localStorage

If the localStorage object doesn't exist, SynQ will use a polyfill (localStorage) utilizing cookies.

Object sessionStorage

Same as localStorage, but only used when the use_vpn_synq_token is enabled.

Object storage

Either localStorage or sessionStorage, depending on the running configuration.

Object connection

Either navigator.connection or a polyfill using NetworkInformation.

Class Storage

If the Storage class doesn't exist, SynQ will use a polyfill (StorageObject).

Class NetworkInformation

If the NetworkInformation class doesn't exist, SynQ will use a polyfill (NetworkInformation).

Function SynQ -> Undefined

arguments: [String: Attribute Name]

This is the main function, which handles all of the synchronizing and storage functions.

If you don't specify an AttributeName, SynQ will assume each name from SynQ.syn; otherwise SynQ will only synchronize HTML elements with AttributeName.

String SynQ.esc

The list delimter used when SynQ can't determine an element's UUID. Refer to Array.prototype.join for references on use.

Function SynQ.prevent -> Undefined

arguments: Object: Variable, Array: Errors, String: Error Message[, String: Help Keyword]

Determines if Variable matches any Errors, and if so, throws a new error with Message.

Array SynQ.syn

The list of default attribute names to synchronize: ["synq-attr", "synq-data", "synq-text", "synq-html", "synq-host"].

Function SynQ.eventlistener -> Undefined

arguments: [Object: Event]

The event listener for storage changes (how SynQ works).

Function SynQ.pack -> String:UTF16

See SynQ.help('pack').

Function SynQ.unpack -> String:UTF8

See SynQ.help('unpack')

Function SynQ.parseFunction -> Function

See SynQ.help('parseFunction').

Function SynQ.parseSize -> Function

See SynQ.help('parseSize').

Function SynQ.addEventListener -> Event

See SynQ.help('addEventListener').

Function SynQ.removeEventListener -> Event

See SynQ.help('removeEventListener').

Function SynQ.triggerEvent -> ?

See SynQ.help('triggerEvent').

Function ping -> Object

See SynQ.help('ping')


Possible uses of SynQ

  • Multiplayer games:

    • Battleship on multiple screens/tabs
    • Competitive card games with multiple screens/tabs
  • Online shopping:

    • No more "we didn't get that request" due to multiple tabs
  • In-browser OS:

    • Now you can easily control an I-Frame's shadow DOM from the parent DOM
/** Ephellon Dantzler - 2015, 2018
* How to use ([...] is optional):
* Simply call on SynQ([attribute-name]), and that's it
* For in depth help, use SynQ.help([string:item-name])
* FAQ:
* Q0) How can I get help?
* A0) Use `SynQ.help([string:item-name])`
* Q1) What if I just want to update some storage data?
* A2) Use `SynQ.set(string:name, string:data[, string:password])`
`SynQ.get(string:name[, string:password])`
`SynQ.pop(string:name[, string:password])`
* Q2) What if I want to get rid of all of my data (in SynQ)?
* A2) Use `SynQ.clear([boolean:clear-all])`
+ See note #1
* Q3) What if I want to synchronize data across my entire domain?
* A3) Set the `use_global_synq_token` variable to a defined value (`null` counts as defined)
* Q4) How do I check on the status of my data?
* A4) You can use `SynQ.list([boolean:show-private])` to show your data
* Q5) What about the other things I see under SynQ?
* A5) Those are for future technologies, but you can use them as you see fit
* Q6) How much space do I have?
* A6) It depends on the browser but you can use `SynQ.size()` to find the current maximum.
The highest is 5 MiB (5,242,880 b, with UTF-16 enabled) because JS uses UTF-16 characters by default.
+ See note #3, #4
`SynQ.size([number:value[, number:base[, string:symbol]]])` also converts `value` into an SI foramatted string,
e.g. `SynQ.size(8214, 1000, "m")` returns "8.214km"
* Q7) What if I want more space?
* A7) Set the `use_utf16_synq_token` variable to a defined value; SynQ will then force UTF-8 data strings;
the space will be doubled, max being 10 MiB (10,485,760 b, with UTF-16 disabled/UTF-8 enabled).
+ See note #3
* Q8) How can I see how much space I am using?
* A8) Use `SynQ.used([boolean:synq-data-only])`
*
* Notes:
* 1) `SynQ.clear` will only remove "local" (see note #2a) items if the `use_global_synq_token` isn't set
* 2a) By "local" I mean per the unique page identifier (URL),
- i.e. "https://example.com/page-1" NEQ "https://example.com/page-2" (pathname: page-1/page-2)
- etc. "https://example.com/page-1" NEQ "http://example.com/page-1" (protocol: https/http)
* 2b) By "global" I mean per unique domain name (host),
+ i.e. "https://example.com/page-1" EQU "https://example.com/page-2" (domain: https://example.com)
- etc. "https://example.com/page-1" NEQ "http://exmple.com/page-1" (protocol: https/http)
* 3) Data units are given in bits and bytes (1B = 8b [0000 0000]), with no regard to encoding.
e.g. if `SynQ.used()` returns "16" it means 16 bits/2 Bytes (i.e. 1 UTF-16 character, or 2 UTF-8 characters)
* 4) According to [Mozilla](https://developer.mozilla.org/en-US/docs/Web/API/Storage/LocalStorage),
pretty sure they know what they're talking about
*/
var NO_NAME_ERROR = "The resource name is missing",
UTF8_ONLY_ERROR = "Only UTF-8 characters are allowed.",
NULL_KEY_ERROR = "The [use_vpn_synq_token] flag is set, therefore all entries require a password.",
IE = {
"addEventListener": "attachEvent",
"dispatchEvent": "fireEvent",
"Event": "CustomEvent"
}, SA = false,
window, global, self, top,
document, navigator,
localStorage, sessionStorage, storage, connection,
// SynQ Tokens
use_global_synq_token, // use a global state
use_utf16_synq_token, // use UTF16 character pairs
use_cookie_synq_token, // use cookies instead of localStorage
use_uuid_synq_token, // use a static UUID
use_vpn_synq_token, // use a VPN-like setup
internal = '[[InternalAccess]]';
window = window || global || top || self || this || {};
document = window.document || {};
navigator = window.navigator || {};
var CustomEvent;
// IE and Edge?
if(typeof CustomEvent == 'object') {
for(var property in IE)
window[ property ] = window[ IE[property] ] || window[ property ];
(function() {
function CustomEvent(event, parameters) {
var E = document.createEvent('CustomEvent');
parameters = parameters || { bubbles: false, cancelable: false, detail: undefined };
E.initCustomEvent(event, parameters.bubbles, parameters.cancelable, parameters.detail);
return E;
}
CustomEvent.prototype = Event.prototype;
window.CustomEvent = CustomEvent;
})();
IE = true;
} else if(CustomEvent == undefined) {
(function() {
function CustomEvent(event, parameters) {
addEventListener(event, parameters || { bubbles: false, cancelable: false, detail: undefined });
}
CustomEvent.prototype = Event.prototype;
window.CustomEvent = CustomEvent;
})();
SA = true; IE = false;
} else {
IE = false;
}
/* Helper functions not under SynQ */
// repeat a string
String.prototype.repeat = String.prototype.repeat || function repeat(number) {
for(var string = this.toString(); number-- > 0;)
string += string;
return string;
};
// map values
Array.prototype.map = function(callback, self) {
for(var index = 0, length = this.length, array = [], self = self || null; index < length; index++)
array.push(callback.call(self, this[index], index, this));
return array;
};
// ping an address
// https://stackoverflow.com/questions/4282151/is-it-possible-to-ping-a-server-from-javascript
// Powered by: Lorem Picsum
function ping(address, options) {
if(ping.inUse)
return 'Currently pinging an address, please wait.';
var fallback = {
callback: function(trip) { return trip },
pass: function() {},
fail: function() {},
timeout: 10000
},
x = ((Math.random() * 638) | 0) + 126,
y = ((Math.random() * 638) | 0) + 126;
address = address || "picsum.photos/" + y + "/" + x + "/?random";
options = options || fallback;
ping.inUse = true;
ping.address = address;
ping.callback = options.callback || fallback.callback;
ping.pass = options.pass || fallback.pass;
ping.fail = options.fail || fallback.fail;
ping.ping = new Image();
ping.clear = function(status) {
clearTimeout(ping.timeout);
ping.inUse = false;
var time = +(new Date) - ping.start,
size = (ping.ping.height * ping.ping.width * 64) || 1;
return ping.callback.call(null, ping.trip = {
time: time,
status: (status? "pass": "fail"),
address: ping.address,
resolve: ping.ping.src,
size: size,
speed: ((size / time) | 0),
uplink: ((size / time / 2) | 0),
removed: delete ping.ping
});
};
ping.ping.onload = function() { return ping.clear(!0), ping.pass.call(null, arguments); };
ping.ping.onerror = function() { return ping.clear(!1), ping.fail.call(null, arguments); };
ping.start = +(new Date);
ping.ping.src = 'https://' + address;
ping.timeout = setTimeout(ping.fail, options.timeout || fallback.timeout);
return ping.trip = ping.trip || { time: 0, status: 'fail', address: address, resolve: address, size: 0, speed: 0, uplink: 0 };
}
// The main function
function SynQ(name) {
++SynQ[internal];
name = name || null;
if(name == null)
name = SynQ.syn;
if(name instanceof Array)
name = name.join('],[');
var messages = [],
uuids = SynQ.get('.uuids'),
copies = {},
query = document.querySelectorAll('[' + name + ']');
uuids = (uuids || "").split(',');
function fetch(e) {
var a = e.attributes,
n =
("synq-attr" in a)?
"#ATTR":
("synq-data" in a)?
("value" in e)?
"value":
"innerText":
("synq-text" in a)?
"innerHTML":
("synq-html" in a)?
"outerHTML":
"";
if(n == "")
return n;
switch(n) {
case '#ATTR':
var o = {},
l = a['synq-attr'].value,
f = function(v, i, r) {o[v] = a[v].value};
if(!l.length)
[].slice.call(a).map(f);
else
l.split(/\s+/).map(f);
return o;
break;
}
return e[n];
}
for(var index = 0, element, uuid, name, value, info, id, attr, host, obj = false; index < query.length; obj = false, index++) {
// Element information
element = query[index];
info = element.tagName;
attr = element.attributes;
id = ('id' in attr)? attr.id.value: null;
name = ('name' in attr)? attr.name.value: null;
uuid = ('synq-uuid' in attr)? attr['synq-uuid'].value: null;
host = ('synq-host' in attr)? attr['synq-host'].value: null;
copies[info] |= 0;
// Setup hosts
if(host != null)
element.setAttribute('name', SynQ.host = host || id || uuid || name);
// Add copies of elements for the UUID to work properly
info += (id != null || uuid != null)?
"#" + (id || uuid):
":nth-child(" + (++copies[info]) + ")";
// Get, and test value
value = fetch(element);
if(obj = typeof value == 'object')
value = SynQ.deflate(value);
// Get/Set the element's UUID
if(uuid == null)
uuid = (obj? '!': '') + SynQ.sign(info);
else if(obj)
uuid = /^!/.test(uuid)? uuid: '!' + uuid;
// Set the element's UUID for future references
element.setAttribute('synq-uuid', uuid);
// Push the UUID
if(!~uuids.indexOf(uuid))
uuids.push(uuid);
// Push the messages
uuid = ".values/#" + uuid;
messages.push( SynQ.encodeURL(value) );
SynQ.set(uuid, value);
}
SynQ.set('.values', messages.join(SynQ.esc));
SynQ.set('.uuids', uuids.join(',').replace(/^,+|,+$/g, ''));
--SynQ[internal];
}
SynQ.syn = "synq-attr synq-data synq-text synq-html synq-host".split(" ");
// change this accordingly; this is the syncronizing attribute for your elements
// synq-data: .innerText OR .value
// synq-text: .innerHTML
// synq-html: .outerHTML
SynQ.esc = "%%";
// change to your liking; this is the list delimeter
/* The event-listeners */
// eventlistener target
SynQ.eventlistener = function(event) {
++SynQ[internal];
var messages = SynQ.get('.values'),
uuids = SynQ.get('.uuids'),
copies = {},
query = document.querySelectorAll('[' + SynQ.syn.join('],[') + ']');
messages = messages || "";
messages = (messages.length > 0)? messages.split(SynQ.esc): [];
uuids = uuids || "";
uuids = (uuids.length > 0)? uuids.split(','): [];
function write(e, m) {
var a = e.attributes,
n =
("synq-attr" in a)?
"#ATTR":
("synq-data" in a)?
("value" in e)?
"value":
"innerText":
("synq-text" in a)?
"innerHTML":
("synq-html" in a)?
"outerHTML":
"";
if(n == "")
return n;
switch(n) {
case '#ATTR':
for(var p in m)
e.setAttribute(p, m[p]);
return a;
break;
}
e[n] = m? m: e[n];
}
updating:
for(var index = 0, length = query.length, element, uuid, name, value, info, id, attr, host, obj = false, skip; index < length; obj = false, index++) {
// Element information
element = query[index];
info = element.tagName;
attr = element.attributes;
id = ('id' in attr)? attr.id.value: null;
name = ('name' in attr)? attr.name.value: null;
uuid = ('synq-uuid' in attr)? attr['synq-uuid'].value: null;
host = ('synq-host' in attr)? attr['synq-host'].value: null;
copies[info] |= 0;
// Don't overwrite skip elements (higher number = higher priority)
// synq-skip = number of times to ignore SynQ-ing
if('synq-skip' in attr) {
skip = +attr['synq-skip'].value | 0;
if(skip > 0) {
element.setAttribute('synq-skip', --skip);
continue updating;
} else {
element.removeAttribute('synq-skip');
}
}
if(host != null)
element.setAttribute('name', SynQ.host = SynQ.host || host || uuid);
info += (id != null || uuid != null)?
"#" + (id || uuid):
":nth-child(" + (++copies[info]) + ")";
// Get the element's UUID
if(uuid == null)
uuid = SynQ.sign(info);
// UUID is set
// Write confidently, even if the HTML document has changed
if(uuid != null) {
value = SynQ.get('.values/#!' + uuid);
value = (obj = value != null)?
value:
SynQ.get('.values/#' + uuid);
value = value != null?
value:
"";
}
// UUID isn't set
// Write, assuming the HTML document hasn't changed
else {
value = messages[index] || "";
}
// Set the element's UUID for future references
element.setAttribute('synq-uuid', uuid = (obj? '!': '') + uuid);
value = SynQ.decodeURL(value);
if(obj || /^!/.test(uuid))
value = SynQ.inflate(value);
write(element, value);
}
--SynQ[internal];
};
// add listener
SynQ.addEventListener = function(event, action) {
SynQ.prevent([event, action], [undefined, null, ""], "Failed to add event listener '" + event + "'", "addEventListener");
if(SynQ[internal])
return action;
else
++SynQ[internal];
var event = SynQ.eventName + '/' + event + '/#',
events = SynQ.get(event + '!'),
fn = SynQ.parseFunction(action),
name = fn[2] || events.length;
events = events || "";
events = (events.length > 0)? events.split(SynQ.esc): [name];
if(events.indexOf(name) < 0)
events.push(name);
SynQ.set(event + '!', events);
SynQ.set(event + name, action + "");
return --SynQ[internal], events.length - 1;
};
// remove listener
SynQ.removeEventListener = function(name, parent) {
SynQ.prevent(name, [undefined, null, ""], "Failed to remove event listener '" + (parent || '*') + "." + name + "'", "removeEventListener");
if(SynQ[internal])
return parent;
else
++SynQ[internal];
if(parent)
return --SynQ[internal], SynQ.pop(SynQ.eventName + '/' + parent + '/#' + name);
var events = SynQ.events.split(' '),
popped = [];
for(var index = 0, listener, values; index < events.length; index++) {
parent = SynQ.eventName + '/' + events[index] + '/#';
listener = SynQ.pop(parent + name);
if(listener) {
values = SynQ.pop(parent += '!').split(',');
values.splice(values.indexOf(name) - 1, 1);
SynQ.set(parent, values + "");
popped.push(listener);
}
}
return --SynQ[internal], popped;
};
// trigger listener
SynQ.triggerEvent = function(event, data) {
SynQ.prevent(event, [undefined, null, ""], "Failed to trigger event '" + event + "'", "triggerEvent");
if(SynQ[internal])
return data;
else
++SynQ[internal];
var event = SynQ.eventName + '/' + event + '/#',
events = SynQ.get(event + '!');
events = events || "";
events = (events.length > 0)? events.split(SynQ.esc): [];
for(var index = 0, head, body, fn, host = SynQ.host; index < events.length; index++) {
fn = SynQ.parseFunction(SynQ.get(event + events[index]));
head = fn[0];
body = fn[1];
--SynQ[internal];
new Function(head, body).call(null, data);
++SynQ[internal];
}
return --SynQ[internal], data;
}
/* The URL-oriented functions */
// URI encoder
SynQ.encodeURL = function(url) {
for(var index = 0, characters = "% \"<>[\\]^`{|}", url = url.toString(); index < characters.length; index++)
url = url.replace(RegExp( "\\" + characters[index], "g" ), "%" + characters.charCodeAt(index).toString(16));
return url;
};
// URI decoder
SynQ.decodeURL = function(url) {
for(var regexp = /%(\d{2})/, url = url.toString(); regexp.test(url);)
url = url.replace(regexp, function($0, $1, $_) {
return String.fromCharCode(+("0x" + $1)) + "\r\f"; // prevent intentional %code from being removed
});
return url.replace(/(% "<>\[\\\]\^`\{\|\})\r\f/g, "$1");
};
/* The storage-oriented functions */
// List all of the current SynQ's data (paths)
SynQ.list = function(all) {
var signature = SynQ.shadow || SynQ.signature,
regexp = RegExp("^(" +
(use_global_synq_token != undefined || all? "synq://localhost:(443|80)\/|": "") +
signature.replace(/(\W)/g, "\\$1") +
")" + (all? ".+": "[^\\.].+") + "$"),
array = {};
for(var item in storage)
if(regexp.test(item))
array[item] = storage[item];
return array;
};
// The "clear all" option
SynQ.clear = function(all) {
var signature = SynQ.shadow || SynQ.signature,
regexp = RegExp("^(" +
signature.replace(/(\W)/g, "\\$1") +
")" + (all? ".+": "(.*\/)?[^\\.].+") + "$");
for(var item in storage)
if(regexp.test(item))
storage.removeItem(item);
return SynQ.triggerEvent('clear');
};
// Push (set) a resource
SynQ.set = function(name, data, key) {
SynQ.prevent(name, [undefined, null], NO_NAME_ERROR, 'set');
data = data.toString();
key = key || use_vpn_synq_token;
var UTF16 = use_utf16_synq_token != undefined;
if(UTF16)
SynQ.prevent(data, /[^\u0000-\u00ff]/, UTF8_ONLY_ERROR);
if(key != undefined && key != null)
data = SynQ.lock(data, key),
key = /^\./.test(name)? '': '.';
else
key = "";
if(UTF16 && data)
data = SynQ.pack(data);
storage.setItem(SynQ.signature + key + name, data);
SynQ.last.push(name);
return SynQ.triggerEvent('set', name);
};
// Pull (get) a resource
SynQ.get = function(name, key) {
SynQ.prevent(name, [undefined, null], NO_NAME_ERROR, 'get');
var data, UTF16 = use_utf16_synq_token != undefined;
key = key || use_vpn_synq_token;
if(UTF16)
SynQ.prevent(data, /[^\u0000-\u00ff]/, UTF8_ONLY_ERROR);
if(key != undefined && key != null)
data = storage.getItem(SynQ.signature + "." + name);
else
data = storage.getItem(SynQ.signature + name);
if(UTF16 && data)
data = SynQ.unpack(data);
if(key != undefined && key != null)
data = SynQ.unlock(data, key);
return SynQ.triggerEvent('get', data);
};
// Append (push) a resource
SynQ.push = function(name, data, key, delimiter) {
SynQ.prevent(name, [undefined, null, ""], NO_NAME_ERROR, 'push');
++SynQ[internal];
key = key || use_vpn_synq_token;
var last = SynQ.get(name, key) || "";
delimiter = delimiter || SynQ.esc;
data = (last.length > 0)? last.split(delimiter).concat(data): [data];
data = SynQ.set(name, data.join(delimiter), key);
return --SynQ[internal], SynQ.triggerEvent('push', data);
};
// Pull a resource
SynQ.pull = function(name, key, delimiter) {
++SynQ[internal];
delimiter = delimiter || SynQ.esc;
key = key || use_vpn_synq_token;
var data = SynQ.get(name, key);
data = data || "";
data = (data.length > 0)? data.split(delimiter): [data];
return --SynQ[internal], SynQ.triggerEvent('pull', data);
}
// Remove a resource
SynQ.pop = function(name, key) {
++SynQ[internal];
name = name || SynQ.last.pop();
key = key || use_vpn_synq_token;
SynQ.prevent(name, [undefined, null, ""], NO_NAME_ERROR, 'pop');
var data = SynQ.get(name, key);
if(key != undefined && key != null)
storage.removeItem(SynQ.signature + "." + name);
else
storage.removeItem(SynQ.signature + name);
return --SynQ[internal], SynQ.triggerEvent('pop', data);
};
// Broadcast to a global storage
SynQ.upload = function(name, data, key) {
SynQ.prevent(name, [undefined, null], NO_NAME_ERROR, 'upload');
if(data == undefined || data == null) {
key = (key? '.': '');
return key + SynQ.sign(key + name)
.replace(/(.{1,4})(.{1,2})?(.{1,2})?(.{1,2})?(.{1,6})?(.{1,12})?/, "$1-$2-$3-$4-$5:$6")
.replace(/[\-\:]+$/, "");
}
data = data.toString();
key = key || use_vpn_synq_token;
var UTF16 = use_utf16_synq_token != undefined;
if(UTF16)
SynQ.prevent(data, /[^\u0000-\u00ff]/, UTF8_ONLY_ERROR);
if(key != undefined && key != null)
data = SynQ.lock(data, key),
key = /^\./.test(name)? '': '.';
else
key = "";
if(UTF16 && data)
data = SynQ.pack(data);
// ".name" and "name" won't be interchangeable
name = SynQ.sign(key + name)
.replace(/(.{1,4})(.{1,2})?(.{1,2})?(.{1,2})?(.{1,6})?(.{1,12})?/, "$1-$2-$3-$4-$5:$6")
.replace(/[\-\:]+$/, "");
storage.setItem('synq://localhost:' + (key.length? 443: 80) + '/' + name, data);
SynQ.last_upload.push(name);
return SynQ.triggerEvent('upload', name);
};
// Retrieve a global item
SynQ.download = function(name, key) {
name = name || SynQ.last_upload[SynQ.last_upload.length - 1];
key = key || use_vpn_synq_token;
SynQ.prevent(name, [undefined, null], NO_NAME_ERROR, 'download');
var data, UTF16 = use_utf16_synq_token != undefined;
if(UTF16)
SynQ.prevent(data, /[^\u0000-\u00ff]/, UTF8_ONLY_ERROR);
// ".name" and "name" won't be interchangeable
name = SynQ.sign((key? ".": "") + name)
.replace(/(.{1,4})(.{1,2})?(.{1,2})?(.{1,2})?(.{1,6})?(.{1,12})?/, "$1-$2-$3-$4-$5:$6")
.replace(/[\-\:]+$/, "");
if(key != undefined && key != null)
data = storage.getItem('synq://localhost:443/' + name);
else
data = storage.getItem('synq://localhost:80/' + name);
if(UTF16 && data)
data = SynQ.unpack(data);
if(key != undefined && key != null)
data = SynQ.unlock(data, key);
return SynQ.triggerEvent('download', data);
};
// Append (push) a global resource
SynQ.append = function(name, data, key, delimiter) {
++SynQ[internal];
SynQ.prevent(name, [undefined, null, ""], NO_NAME_ERROR, 'append');
delimiter = delimiter || SynQ.esc;
key = key || use_vpn_synq_token;
data = SynQ.download(name, key).split(SynQ.esc).concat(data);
data = SynQ.upload(name, data.join(delimiter), key);
return --SynQ[internal], SynQ.triggerEvent('append', data);
};
// Pull a global resource
SynQ.recall = function(name, key, delimiter) {
++SynQ[internal];
delimiter = delimiter || SynQ.esc;
key = key || use_vpn_synq_token;
var data = SynQ.download(name, key);
data = data || "";
data = (data.length > 0)? data.split(delimiter): [data];
return --SynQ[internal], SynQ.triggerEvent('recall', data);
};
// Remove a global resource
SynQ.snip = function(name, key) {
name = name || SynQ.last_upload.pop();
key = key || use_vpn_synq_token;
++SynQ[internal];
SynQ.prevent(name, [undefined, null, ""], NO_NAME_ERROR, 'snip');
var data = SynQ.get(name, key),
name = SynQ.set(name, null, key);
if(key != undefined && key != null)
storage.removeItem('synq://localhost:443/' + name);
else
storage.removeItem('synq://localhost:80/' + name);
return --SynQ[internal], SynQ.triggerEvent('snip', data);
};
/* Search, and like-wise helpers */
// Search for entries
SynQ.find = function(query, all, results) {
results = results || [];
if(/^synq:\/\/([^\n\r\f\v]+)\/\?/.test(query)) {
var url = SynQ.parseURL(query),
original = SynQ.signature,
shadow = url.origin + '/',
params = url.searchParameters;
SynQ.shadow = shadow;
results = SynQ.find(params.query, params.all);
SynQ.shadow = null;
return results;
}
if(query instanceof Array) {
query.forEach(function(entry, index, array) {
results.concat(SynQ.find(entry, all, results));
});
return results;
}
var owned = SynQ.list(all), test;
if(query instanceof RegExp)
test = function(string) { return query.test(string) };
else
test = function(string) { return string == (query += "") || !!~string.indexOf(query) };
for(var entry in owned)
if(!~results.indexOf(entry) && (test(entry) || test(owned[entry] + "")))
results.push(entry);
return results;
};
/* The space-oriented functions */
// Test data limits (in Bytes -> iB) or return an SI formated value
SynQ.size = function(number, base, symbol) {
var backup = {},
size = function(n, b, s) {
b = b || 1024;
s = s || (n < 1024? 'b': 'iB');
if(n < b && n > -b)
for(var k = "m\u00b5npfaz", x = 0, g = k.length; (x < g) && (n < 1); x++)
n *= b;
else
for(var k = "kMGTPEZY", x = 0, g = k.length; (x < g) && (n >= b); x++)
n /= b;
return n + (k[x - 1] || "") + s;
},
found;
number = number || null;
if((number == null || number == undefined))
if(IE && use_cookie_synq_token == undefined) {
for(var n in storage)
backup[n] = storage[n];
storage.clear();
found = storage.remainingSpace | 0;
for(var n in backup)
storage[n] = backup[n];
} else {
for(var n in storage)
backup[n] = storage[n];
storage.clear();
_1kiB: // 0 b - 1 kiB
for(var s = "_", i = 1, j, l = 1024, p = true; p && i < l; i *= 2)
try {
storage.setItem("SizeTest", s.repeat(i));
} catch(e) {
p = false;
break _1kiB;
};
_1MiB: // 1 kiB - 1 MiB
for(j = l, l *= 1024; p && i < l; i *= 2)
try {
storage.setItem("SizeTest", s.repeat(i));
} catch(e) {
p = false;
break _1MiB;
};
_1GiB: // 1 MiB - 1 GiB
for(j = l, l *= 1024; p && i < l; i += j)
try {
storage.setItem("SizeTest", s.repeat(i));
} catch(e) {
p = false;
break _1GiB;
};
storage.clear();
for(var n in backup)
storage[n] = backup[n];
found = i;
} // :defined number
SynQ.size = function(number, base, symbol) {
return (number == undefined || number == null)?
SynQ.size.max | 0:
SynQ.size.convert(+number, base, symbol)
};
SynQ.size.convert = size;
SynQ.size.max = found *= 2;
if(number != null)
return SynQ.size(number, base, symbol);
return found;
};
// How much space is in use?
SynQ.used = function(exclusive) {
var size = 0,
signature = SynQ.shadow || SynQ.signature;
for(var item in storage)
if(storage.hasOwnProperty(item))
if(exclusive && RegExp(signature.replace(/(\W)/g, "\\$1")).test(item))
size += storage[item].length | 0;
else if(!exclusive)
size += storage[item].length | 0;
return size * 2;
};
// Return a number from an SI formatted string
SynQ.parseSize = function(size, base, symbol) {
var e = "";
base = base || 1024;
symbol = symbol || (+size < 1024? 'b': 'iB');
size = (size || e)
.replace(/^\s*([\d\. ]+[zafpn\u00b5mkMGTPEZY]|[\d\., ]+).*/, "$1")
.replace(/[^\w\.]/g, e)
.replace(symbol, e)
.replace(/(\d+)?(\.\d+)?/, function($0, $1, $2, $_) {
$1 = $1 || e; $2 = $2 || e;
return $1 + $2.replace(/\./g, e);
});
var sizes = "zafpn\u00b5m kMGTPEZY",
tail = size.replace((size = parseInt(size)), e);
return size * Math.pow(base, sizes.indexOf(tail) - 7);
};
/* The "security" oritned functions */
// Lock and unlock data (weak security)
SynQ.lock = function(data, key) {
data = data || "";
key = SynQ.salt(SynQ.sign(key, 1));
for(var index = 0, salted = []; index < data.length; index++)
salted.push(String.fromCharCode(
data.charCodeAt(index) ^ key.charCodeAt(index % 256)
));
return salted.join("");
};
SynQ.unlock = SynQ.lock;
// KSA algorithm
SynQ.salt = function(text) {
for(var index = 0, exdex = 0, swap = 0, salted = [], text = text || ""; index < 256; index++)
salted[index] = index;
for(index = 0; index < 256; index++)
exdex = (exdex + salted[index] + text.charCodeAt(index % text.length)) % 256,
salted[swap] = salted[index],
salted[index] = salted[exdex],
salted[exdex] = salted[swap];
for(index = 0; index < 256; index++)
salted[index] = String.fromCharCode( salted[index] );
return salted.join("");
};
// The hash/signature function (from Mi/o, by me)
SynQ.sign =
/** Siphun - hashing algorithm
* @author Ephellon Dantzler (Cliffton E.D. Tucker)
*/
function Siphun(string, fidelity) {
var array = (string += '').split(/([^]{256})/),
gamma = 0,
result = [],
method,
base;
fidelity = +fidelity || 0;
var R = function(t) {
var s = [], S = [], N = 256, u = '', l = (t = (t || u) + u).length;
if(!l)
return u;
else
t = ' ' + t;
for(var n = 0; n < N;)
s.push(n++);
for(var n = 0, x = n, w = x; n < N; n++) {
x = (x + s[n] + t.charCodeAt(n % l)) % N;
s[w] = s[n];
s[n] = s[x];
s[x] = s[w];
}
for(var n = -1; n < N; n++)
S.push(t[s[n]] || u);
return S.join(u);
};
fidelity = 2 + ((fidelity * 14) | 0);
method = function(s) { return s? s.charCodeAt(0): s };
array.forEach(function(value, index, self) {
return (value == '')?
(self = self || this).splice(index, 1):
R(value).split('').forEach(function(character, position) { gamma += +R(character.charCodeAt(0) + index) * (position | 1) })
});
gamma = +R(gamma);
for(var index = 0, length = array.length, last = length - 1; index < length; index++)
for(var self = array[index], next = (array[index + 1] || ""), mirror = array[last], a, b, c, d, e, f, g = gamma, i = 0, j = self.length, k = mirror.length, l = length, m = k - 1, q = fidelity; i < j; ++i, --m, gamma = g += a + b + c + d + e + f)
a = (method(self[i]) | q) | 0,
b = (method(self[j - i - 1]) - a) | 0,
c = (method(mirror[m]) + b) | 0,
d = (method(mirror[k - m]) ^ c) | 0,
e = (method(next[i]) | d) | 0,
f = (method(next[m]) ^ e) | 0,
result.push(
(((a ^ ~b) << (i + k)) | (j & e) | g) ^
(((b | -c) ^ (m + j)) | (j & f) | g) ^
(((c & ~d) << (e - k)) >> (k ^ q) + g) ^
(((d << a) ^ (f - j)) >> (k ^ q) + g) ^
((a & b | c ^ d) ^ e - f) << (q & e & f)
);
result.splice(fidelity, result.length - fidelity);
base = ((gamma % 20) + (fidelity % 16)) | 16;
result.forEach(function(value, index, self) { return self.splice(index, 1, Math.abs(value ^ gamma).toString(base)) });
result = result.join('').slice(0, 256);
gamma = gamma.toString(base);
base = (base * (fidelity || 1)).toString(base);
return R(result) + base + gamma;
};
/* Helpers under SynQ */
// Parse a function (returns a "head" and "body")
SynQ.parseFunction = function(fn) {
SynQ.prevent(fn, [undefined, null, ""], "Failed to parse empty function", "parseFunction");
fn = fn.toString().replace(/(?:\bfunction(\s+[a-zA-Z\$_]\w*)?)?\s*(\(.*?\))\s*(\{[^]*\})/, "$2 => $3");
var name = (RegExp.$1 || "").replace(/\s+/g, "");
fn = fn.split(/\s*\=>\s*/, 2);
return [fn[0].replace(/^\(|\)$/g, ""), fn[1].replace(/^\{|\}$/g, ""), name];
};
// Parse all URL types (even unconventional ones)
SynQ.parseURL = function(url) {
if(url == undefined || url == null)
return {};
var url = url.toString(),
data = url.match(/^((([^:\/?#]+):)?(?:\/{2})?)(?:([^:]+):([^@]+)@)?(([^:\/?#]*)?(?:\:(\d+))?)?([^?#]*)(\?[^#]*)?(#.*)?$/),
i = 0,
e = "";
data = data || e;
return {
href: data[i++] || e,
origin: (data[i++] + data[i + 4]) || e,
protocol: data[i++] || e,
scheme: data[i++] || e,
username: data[i++] || e,
password: data[i++] || e,
host: data[i++] || e,
hostname: data[i++] || e,
port: data[i++] || e,
pathname: data[i++] || e,
search: data[i] || e,
searchParameters: (function(sd) {
parsing:
for(var i = 0, s = {}, e = "", d = sd.slice(1, sd.length).split('&'), n, p, c; sd != e && i < d.length; i++) {
c = d[i].split('=');
n = c[0] || e;
p = c.slice(1, c.length).join('=');
s[n] = (s[n] != undefined)?
s[n] instanceof Array?
s[n].concat(p):
[s[n], p]:
p;
}
return s;
})(data[i++] || e),
hash: data[i++] || e
};
};
// Handle errors
SynQ.prevent = function(variable, failures, message, helper) {
function prevent(a, b, c, d) {
if(a == b)
throw new Error(c + (d? ", please see SynQ.help('" + d + "').": "."));
};
// Array, *, *
if(variable instanceof Array)
for(var index = 0, length = variable.length; index < length; index++)
SynQ.prevent(variable[index], failures, message, helper);
// *, Function, *
else if(failures instanceof RegExp)
prevent(failures.test(variable), true, message, helper);
// *, Array, *
else if(failures instanceof Array)
for(var index = 0, length = failures.length; index < length; index++)
prevent(variable, failures[index], message, helper);
};
/* Data oriented functions */
// Pack UTF8 characters
SynQ.pack = function(string) {
for(var index = 0, length = string.length, array = []; index < length;)
array.push(SynQ.pack16(string[index++], string[index++]));
return array.join("");
};
// Unpack UTF16 characters
SynQ.unpack = function(string) {
for(var index = 0, length = string.length, array = [], data = []; index < length;)
array.push(SynQ.unpack16(string[index++]));
for(array = array.join("").split(/([01]{8})/), index = 0, length = array.length; index < length; index++)
if(array[index] != "")
data.push(String.fromCharCode(+("0b" + array[index])));
return data.join("");
};
// Pack UTF8 characters (helper)
SynQ.pack16 = function(a, b) {
var s = function(c) {return ("00000000" + c.charCodeAt(0).toString(2)).slice(-8)};
return String.fromCharCode(+("0b" + s(a) + (b? s(b): "")));
};
// Unpack UTF16 characters (helper)
SynQ.unpack16 = function(a) {
var b, s = function(c) {return (((b = c.charCodeAt(0)) < 0xff? "": "00000000") + b.toString(2)).slice(-16)};
return s(a);
};
// Stringify objects
var JSON, Symbol,
Map, WeakMap, Set, WeakSet,
Int8Array, Int16Array, Int32Array,
Uint8Array, Uint8ClampedArray, Uint16Array, Uint32Array,
Float32Array, Float64Array;
SynQ.deflate =
// (JSON && JSON.stringify)?
// JSON.stringify:
function(object, filter, space, indent) {
var pack = SynQ.deflate,
json = [],
blacklist = [],
list = [],
max = 10,
filterfn,
ltypes = [Boolean, Number, String],
atypes = [Array, Map, Set, WeakMap, WeakSet],
otypes = [Object, Int8Array, Int16Array, Int32Array, Uint8Array, Uint8ClampedArray, Uint16Array, Uint32Array, Float32Array, Float64Array],
constructor = ((object && object.constructor)? object.constructor: null),
type =
(!!~atypes.indexOf(constructor))?
'[]':
(!!~otypes.indexOf(constructor))?
'{}':
(!!~ltypes.indexOf(constructor))?
'':
(object && typeof object == 'object' && 'toJSON' in object)?
'""':
(object !== undefined && object !== null)?
'{}':
'';
if(object === undefined || object === null || !!~ltypes.indexOf(object.constructor))
return ((object && object.constructor == String)?
'"' + object.replace(/\\/g, '\\\\') + '"':
(object !== undefined && object !== null && (object.constructor == Boolean || (object.constructor == Number && (object.valueOf() < Infinity && object.valueOf() > -Infinity))))?
object:
null) + '';
filter = filter || function(key, value) { return value };
space = space || '';
indent = indent || space.length;
space = ((typeof space == 'number')?
'\t'.repeat(space):
space).slice(0, max);
function format(string, data) {
if(!filterfn(data.key, data.value))
return '';
for(var regexp = /#\{(\w+)\}/; regexp.test(string);)
string = string.replace(regexp, function($0, $1, $$, $_) {
if(data.string && data[$1].constructor == String)
return data[$1].replace(/[\\"]/g, '\\$&').replace(/\n/g, '\\n').replace(/\r/g, '\\r');
return data[$1];
});
return space + string.replace(/\:\s+/, space? ': ': ':');
}
if(!!~atypes.indexOf(filter.constructor))
filterfn = function(key, value) { return !!~filter.indexOf(key) };
else if(filter instanceof Function)
filterfn = filter;
else
filterfn = function(key, value) { return value };
if(!!~atypes.indexOf(object.constructor))
for(var data, index = 0, length = object.length | 0; index < length; index++)
json.push(format('#{value}', { value: pack(object[index], filter, space) }));
else if('toJSON' in object)
json.push(format('#{value}', { key: 'toJSON', value: object.toJSON() })),
type = '';
else serializing:
for(var key in object) {
var value = object[key];
if(value == object || !!~blacklist.indexOf(key)) {
blacklist.push(key);
continue serializing;
}
if(value === undefined || value === null || value.constructor == Function || value.constructor == Symbol)
json.push(format('"#{key}": null', { key: key, value: value }));
else if(value.constructor == RegExp)
json.push(format('"#{key}": #{value}', { key: key, value: pack(value.source + '$=' + value.flags, filter, space) }));
else if(value.constructor == String)
json.push(format('"#{key}": "#{value}"', { key: key, value: value, string: true }));
else if(!!~ltypes.indexOf(value.constructor))
json.push(format('"#{key}": #{value}', { key: key, value: value }));
else if(!!~atypes.indexOf(value.constructor))
json.push(format('"#{key}": #{value}', { key: key, value: pack(value, filter, space) }));
else if(!!~otypes.indexOf(value.constructor))
json.push(format('"#{key}": #{value}', { key: key, value: pack(value, filter, space + space, space.length) }));
else if(/^synq\:\/\//.test(key))
if(!key.indexOf(SynQ.signature))
json.push(format('"#{key}": "#{value}"', { key: key, value: value, string: true }));
else
/* foreign SynQ token */;
else if(value instanceof Object && !value instanceof Function)
json.push(format('"#{key}": #{value}', { key: key, value: pack(value = Object.assign(value, {constructor: Object}), filter, space, space.length) }));
else if(value instanceof Object)
json.push(format('"#{key}": #{value}', { key: key, value: '{}' }));
else
json.push(format('"#{key}": "#{value}"', { key: key, value: value, string: true }));
}
var head = (type[0] || '') + (space? '\n': ''),
tail = (space? '\n' + space.slice(indent, space.length): '') + (type[1] || '');
function trim(array) {
for(var index = 0, length = json.length; index < length; index++)
if(array[index] == '' || array[index] == undefined)
array.splice(index, 1);
return array;
}
for(var index = 0; index < max && !~json.indexOf(undefined); index++)
trim(json);
return (head + json.join(',' + (space? '\n': '')) + tail).replace(/\{\s*\}/g, '{}').replace(/(".+?"\:\s*)undefined\b/g, '$1null');
};
// Parse JSON strings
SynQ.inflate = function(string, mutator) {
var object, R = RegExp, regexp,
parse = SynQ.inflate,
type =
/^\s*\{[^]*\}\s*$/.test(string)?
'object':
/^\s*\[[^]*\]\s*$/.test(string)?
'array':
'literal';
mutator = mutator || function(key, value) { return value };
switch(type) {
case 'object':
object = {};
if(/^\s*\{(?:[,\s]*)?\}\s*$/.test(string))
break;
else
for(var regexp = /\s*\{([^]*)\}\s*/, string = string.replace(/^\s*\{/, '^').replace(/\}\s*$/, '$'), dummy; regexp.test(string);)
string = string.replace(regexp, function($0, $1, $$, $_) {
for(var layer = 0, index = 0; layer >= 0 && index < $1.length; index++)
layer += $1[index] == '{'? 1: $1[index] == '}'? -1: 0;
index -= +(layer < 0);
return (dummy = $1.split('')).splice(index, 1, ')@'), dummy = dummy.join(''),
'@(' + dummy.slice(0, index).replace(/\{/g, '#(').replace(/\}/g, ')#') + dummy.slice(index, dummy.length) + (layer < 0? '}': '');
});
string = string/* All */.replace(/#\(/g, '{').replace(/\)#/g, '}')/* First */.replace(/@\(/, '{').replace(/\)@/, '}');
string
.split(/\s*("(?:[^\\"]|\\.)*?"\s*(?:\:\s*(?:null|true|false|\d+|"(?:[^\\"]|\\.)*?"|\[[^]*\]|\{[^]*\}|@\([^]*?\)@)(?=\s*,|(?:\s*,)?\s*\$\s*$))?)/)
.map(function(value) {
if(/^([\{\s,\}]+|[\^\$])$/.test(value = value.replace(/@\(/g, '{').replace(/\)@/g, '}')) || /^\^|\$$/.test(value)) return;
value.replace(/\s*"((?:[^\\"]|\\.)*?)"\s*(?:\:\s*(null|true|false|\d+|"(?:[^\\"]|\\.)*?"|\[[^]*\]|\{[^]*\}))?/);
return (R.$2 && R.$2.length)?
object[R.$1] = mutator(R.$1, parse(R.$2.replace(/^\^|\$$/g, ''), mutator)):
object[R.$1] = mutator(R.$1, R.$1.replace(/^\^|\$$/g, ''), mutator);
});
break;
case 'array':
object = [];
if(/^\s*\[\s*(?:,\s*)?\]\s*$/.test(string))
break;
else
for(var regexp = /\s*\[([^]*)\]\s*/, string = string.replace(/^\[/, '^').replace(/\]$/, '$'), dummy; regexp.test(string);)
string = string.replace(regexp, function($0, $1, $$, $_) {
for(var layer = 0, index = 0; layer >= 0 && index < $1.length; index++)
layer += $1[index] == '['? 1: $1[index] == ']'? -1: 0;
index -= +(layer < 0);
return (dummy = $1.split('')).splice(index, 1, ')@'), dummy = dummy.join(''),
'@(' + dummy.slice(0, index).replace(/\[/g, '#(').replace(/\]/g, ')#') + dummy.slice(index, dummy.length) + (layer < 0? ']': '');
});
string = string/* All */.replace(/#\(/g, '[').replace(/\)#/g, ']')/* First */.replace(/@\(/, '[').replace(/\)@/, ']');
var index = 0;
string
.split(/\s*(null|true|false|\d+|"(?:[^\\"]|\\.)*?"|\[[^]*\]|\{[^]*\}|@\([^]*?\)@|\s*)(?=\s*,|(?:\s*,)?\s*\$\s*$)/)
.map(function(value) {
if(/^([\[\s,\]]+|[\^\$])$/.test(value = value.replace(/@\(/g, '[').replace(/\)@/g, ']')) || /^\^|\$$/.test(value)) return;
object.push(mutator(index++, parse(value.replace(/^\^|\$$/g, ''), mutator)));
});
break;
case 'literal':
default:
var isString, flags;
object = (isString = /^"(.*)"$/.test(string))? R.$1: string.length && +string <= +Infinity? +string: /\b(true|false)\b/.test(string)? !(string.length - 4): null;
if(isString && /\$\=([imguy]+)$/.test(object)) {
flags = R.$1;
object = R(object.replace(R['$&'], ''), flags);
}
break;
}
return object;
};
// Help
SynQ.help = function(item) {
if(item == undefined || item == null)
item = "help";
item = item.replace(/[^a-z\d]|synq([\.\-]|_token)/gi, "");
var m, i = item.toLowerCase();
switch(i) {
/* HTML */
case 'attr':
m = "Synchronizes an element's attributes. The list should be space separated./~Usage: <% $-@=attributes-to-sync>...<\\%>/~Interpreted Type: Array//$-@: defaults to all attributes if no value is given."
break;
case 'data':
m = "Synchronizes an element's value, or innerText (priority to value)./~Usage: <% $-@>...<\\%>/~Interpreted Type: Boolean"
break;
case 'host':
m = "Creates a main object (for multiple frames), and is given the highest priority. Also sets the element's [NAME] to its [ID], or [SYNQ-UUID]./~Usage: <% $-@=host-name>...<\\%>/~Interpreted Type: String"
break;
case 'html':
m = "Synchronizes an element's outerHTML./~Usage: <% $-@>...<\\%>/~Interpreted Type: Boolean"
break;
case 'skip':
m = "Delays synchronizing an element. Especially useful for multiple IFRAMEs with access to the parent document./~Usage: <% $-@=number-of-delays>...<\\%>/~Interpreted Type: Number//$-@: defaults to 0 if no value is given."
break;
case 'text':
m = "Synchronizes an element's innerHTML./~Usage: <% $-@>...<\\%>/~Interpreted Type: Boolean"
break;
case 'uuid':
m = "The UUID that $ generates for each synchronized element./~Usage: <% $-@=static-uuid>...<\\%>/~Interpreted Type: String//$-@: if you set [$-@], all copies of the element (including across frames) will need to have the same value. Otherwise, SynQ will handle this assignment automatically."
break;
/* JS */
case 'addeventlistener':
m = "Adds an event listener to $'s {events} method./~Usage: $.@(event-name, callback)/~Arguments: String, Function/~Returns: Number//event-name: {events}./return: the number of events attached."
break;
case 'append':
m = "Pushes data to a global array./~Usage: $.@(array-name, data[, key[, delimiter]])/~Arguments: String, String[, String[, String]]/~Returns: <array-name>//key: {key}."
break;
case 'clear':
m = "Removes all owned items from storage (related to $)./~Usage: $.@([remove-all])/~Arguments: [Boolean]/~Returns: Undefined//remove-all: if set to true, will remove all private items too."
break;
case 'decodeurl':
m = "Decodes a URL string (e.g. '%20' -> ' ')./~Usage: $.@(URL)/~Arguments: String/~Returns: String"
break;
case 'deflate':
m = "Polyfill of JSON.stringify. Defaltes\\serializes objects (turns them into strings)./~Usage: $.@(object[, filter[, spaces]])/~Arguments: *[, Function[, String | Number]]/~Returns: String => '<object>'//object: any JavaScript object./filter: searches for true\\non-empty entries. Called on as:/~for(var key in <object>)/~~<filter>(key, <object>[key]);//spaces: if set, then the ouput will be stylised using indentation."
break;
case 'download':
m = "Retruns data from the global storage item (see also, '$.get')./~Usage: $.@(name[, key])/~Arguments: String[, String]/~Returns: String//key: {key}."
break;
case 'encodeurl':
m = "Encodes a URL string (e.g. ' ' -> '%20')./~Usage: $.@(URL)/~Arguments: String/~Returns: String"
break;
case 'esc':
m = "The list delimeter for $ to use when storing element values./~Usage: $.@ = delimeter/~Types: String/~Current: '{esc}'/~Default: '%%'"
break;
case 'eventlistener':
m = "The Event Listener for $ (fires automatically from $)./~Usage: $.@(event)/~Arguments: Object/~Returns: Undefined"
break;
case 'find':
m = "Searches $ for the given item(s). Can also traverse foreign (unowned) entries./~Usage: $.@(query[, search-private[, previous-finds]])/~Arguments: Array | RegExp | String[, Boolean[, Array]]//query: if you have a $ URL to another site (verbatim), you can search its entries via:/~synq:{uuid}\\?auth=<BtoA {username}:{password}>&all=<search-private>&query=<query>&query=...//previous-finds: used to prevent $ from duplicating entries/URL => auth: btoa(username + ':' + password), only used for VPN searches, where 'password' is the original key (before being signed)"
break;
case 'get':
m = "Returns data from the local storage item./~Usage: $.@(name[, key])/~Arguments: String[, String]/~Returns: String//key: {key}."
break;
case 'help':
m = "Displays help messages./~Usage: $.@(item-name)/~Arguments: String/~Returns: String"
break;
case 'inflate':
m = "Polyfill of JSON.parse. Inflates\\deserializes strings (turns them into objects)./~Usage: $.@(json-string[, mutator])/~Arguments: String[, Function]/~Returns: Array | Boolean | Object | Number | String | null | undefined => <object>//string: a JSON compliant string./mutator: mutates each entry. Called on as:/~for(var key in <object>)/~~<mutator>(key, <object>[key]);"
break;
case 'isnan':
m = "Tests to see if an object is a number or not./~Usage: @(object)/~Arguments: */~Returns: Boolean"
break;
case 'last':
m = "An array that's used to hold each item's name in the order they're created./~Usage: $.@ = ['name-1', 'name-2'...]/~Types: Array"
break;
case 'lastupload':
m = "An array that's used to hold each item's name in the order they're created./~Usage: $.@ = ['name-1', 'name-2'...]/~Types: Array"
break;
case 'list':
m = "Returns the currently owned storage items./~Usage: $.@([show-all])/~Arguments: Boolean/~Returns: Object//show-all: when set to true, will return private items as well."
break;
case 'lock':
case 'unlock':
m = "Locks and unlocks a string./~Usage: $.@(data, key)/~Arguments: String, String/~Returns: String"
break;
case 'pack':
m = "Packs (encodes) a UTF-8 string into UTF-16 characters./~Usage: $.@(string)/~Arguments: String/~Return: String => UTF16"
break;
case 'pack16':
m = "Packs (encodes) a UTF-16 character, by using two UTF-8 characters./~Usage: $.@(character[, character])/~Arguments: Character[, Character]/~Returns: String//return: if the second character is missing, then the first character will be returned."
break;
case 'parsefunction':
m = "Returns an array of function data./~Usage: $.@(function-string)/~Arguments: String/~Returns: Array => {0: [function parameters], 1: [function statements], 2: [function name], length: 3}"
break;
case 'parsesize':
m = "Returns a number from an SI formatted* string./~Usage: $.@(number[, base[, symbol]])/~Arguments: String[, Number[, String]]/~Returns: Number//* Does not recognize 'd' (deci), 'h' (hecto) or 'c' (centi)"
break;
case 'parseurl':
m = "Returns a URL object./~Usage: $.@(URL)/~Arguments: String/~Returns: Object => {href, origin, protocol, scheme, username, password, host, port, path, search, searchParameters, hash}"
break;
case 'ping':
m = "Pings an address. If no address is given, then pings a random address. Powered by Lorem Picsum (https:picsum.photos)./~Usage: $.@(address[, options])/~Arguments: String[, Object => {callback, pass, fail, timeout}]/~Returns: Undefined//address: must be an address to an image./return: calls on <callback> using: <callback>.call(null, ping.trip);//ping.trip => {/~address: <address>/~resolve: [image address]/~size: [image height * image.width]/~speed: [image size \\ time]/~status: 'pass' | 'fail'/~time: [ping time]/~uplink: [speed \\ 2]/}"
break;
case 'pop':
m = "Removes, and returns the item from the local storage./~Usage: $.@([name[, key]])/~Argumments: [String[, String]]/~Returns: String//name: the name of the item to fetch. If left empty, will use the name of the last item created./key: {key}."
break;
case 'prevent':
m = "Used to prevent a value from being used./~Usage: $.@(value-to-test, illegal-values, error-message[, helper-link])/~Arguments: Array | String, Array | RegExp, String[, String]/~Returns: Undefined/~Throws: Error(<message[ + helper-link]>)//helper-link: the word, or phrase used by $.help to display the help message,/~~e.g. $.prevent($.doesNotExist, [null, undefined], 'This is an example error', 'example')/~~throws Error => 'This is an eample error, see $.help('example')'."
break;
case 'pull':
m = "Returns data from a local storage array./~Usage: $.@(array-name[, key[, delimiter]])/~Arguments: String[, String[, String]]/~Returns: Array//key: {key}"
break;
case 'push':
m = "Pushes data to a local storage array (delimited by $.esc)./~Usage: $.@(array-name, data[, key])/~Arguments: String, String[, String]/~Returns: String<array-name>//key: {key}"
break;
case 'recall':
m = "Gets data from a global array (see also, '$.pull')./~Usage: $.@(array-name[, key[, delimiter]])/~Arguments: String[, String[, String]]/~Returns: Array//key: {key}"
break;
case 'removeeventlistener':
m = "Removes a(n) event listener(s)./~Usage: $.@(function-name[, event-name])/~Arguments: String[, String]/~Returns: Array | String//event-name: {events}./~If left empty, will remove <function-name> from every event."
break;
case 'salt':
m = "Salts (encrypts) a string, usually a password./~Usage: $.@(string)/~Arguments: String/~Returns: String"
break;
case 'set':
m = "Adds the item to storage./~Usage: $.@(name, data[, key])/~Arguments: String, String[, String]/~Returns: <name>//key: {key}."
break;
case 'sign':
m = "Hashes a string (think of SHA, or MD5)./~Usage: $.@(string[, fidelity-level])/~Arguments: String[, Float]/~Returns: String//fidelity-level: determines the size of the returned string. The closer to 1 the level is, the shorter the string."
break;
case 'signature':
m = "The UUID of the current page (if use_global# is undefined), or current domain.//Current Signature: {signature}/Global Token Flag: {global#}"
break;
case 'size':
m = "1. Returns the maximum amount of space for the storage (in bytes; 1B = 8b)/~Usage: $.@()/~Arguments: NONE/~Returns: Integer//2. Returns the SI formatted version of the given number./~Usage: $.@(number[, base[, symbol]])/~Arguments: Number[, Number[, String]]/~Returns: String//base: the base to use, e.g. 1000; default is 1024./symbol: the symbol to append to the returned string, default is 'iB'."
break;
case 'snip':
m = "Removes, and returns the item from the global storage (see also, '$.pop')./~Usage: $.@(name[, key])/~Arguments: String[, String]/~Returns: String//key: {key}."
break;
case 'syn':
m = "The attribute name(s) for elements to update./~Usage: $.@ = ['name-1', 'name-2'...]/~Types: Array | String/~Default: {syn}"
break;
case 'synq':
m = "The main function and container. Updates the storage, while also updating all 'attached' elements./~Usage: $([attribute-names])/~Arguments: String | Array/~Returns: Undefined//attribute-name: if you want to use multiple names, you can also set $.syn"
break;
case 'triggerevent':
m = "Triggers all event listeners for an event./~Usage: $.@(event-name[, data])/~Arguments: String[, Array]/~Returns: <data>//data: the arguments to pass onto each listener."
break;
case 'unpack':
m = "Unacks (decodes) a UTF-16 string into UTF-8 characters./~Usage: $.@(string)/~Arguments: String/~Return: String => {UTF8}"
break;
case 'unpack16':
m = "Unpacks (decodes) a UTF-16 character into two UTF-8 characters./~Usage: $.@(character)/~Arguments: Character/~Returns: String//return: automatically handles UTF-8 characters, and returns the character iteslf."
break;
case 'upload':
m = "Adds data to global storage (see also, '$.set')./~Usage: $.@(name[, data[, key]])/~Arguments: String[, String[, String]]/~Returns: String//return: a UUID for the data./data: if no data is given, still returns the UUID./key: {key}."
break;
case 'used':
m = "Returns the number of bytes (1B = 8b) in use./~Usage: $.@([$-only])/~Arguments: [Boolean]/~Returns: Integer//$-only: when set to true, will ony return the amount of owned space $ is using."
break;
case 'usecookie':
m = "Forces the page to use cookies instead of the localStorage./This may increase storage capcity on some browsers*, but not all. See the table below.//Browser (version)/~UTF-16 \\ UTF-8//Chrome (65.0.3325.181)/~22 \\ 44 MiB/Firefox (59.0.2)/~22 \\ 44 MiB/Opera (52.0.2871.40)/~22 \\ 44 MiB/Safari (7534.57.2)**/~22 \\ 44 B/Edge (41.16299.248.0)/~22 \\ 44 MiB/Internet Explorer (11.309.16299.0)**/~32 \\ 64 B//* Obtained values by hand (Windows 10 PC, x64)./** Note that this browser wasn't intended for my machine. Also, the cookie size can be changed by the user."
break;
case 'useuuid':
m = "TODO!Forces $ to use the assigned value as the 'UUID.'/This can be useful for dynamic pages that change their name often (like CodePen's debug feature), but still need a static ID.//Basically, a manual, selective version of 'global#'"
break;
case 'useglobal':
m = "Used to determine if $ should use a local* or global** name.//* $ will save all data for the current URL only, i.e. http:github.com\\page-1 will not have access to http:github.com\\page-2./** $ will save all data for the current host, i.e. http:github.com will be used by $, instead of http:github.com\\page-n."
break;
case 'useutf16':
m = "Used to instruct $ to save data as UTF-16 streams, e.g. 'acdf' will be combined into 'be' (as an example)./It does this by combining UTF-8 characters (\u0000 - \u00ff), and generating a UTF-16 character (\u0100 - \uffff)."
break;
case 'usevpn':
m = "Forces $ to use a VPN-like setup with a 'private' session. This uses sessionStorage; a one-time, non-iterable signature; and a forced local state./~Usage: vpn# = <value>/~Interpreted Type: String"
break;
case '':
case '*':
m = "Help can be used for the following items://<!-- HTML -->//$-attr/$-data/$-host/$-html/$-skip/$-text/$-uuid//\\* JavaScript *\\//ping/$/~addEventListener/~append#push/~clear/~decodeURL/~deflate/~download#get/~enocdeURL/~esc/~eventlistener/~find/~get/~help/~host/~inflate/~last/~list/~lock/~pack/~pack16/~parseFunction/~parseSize/~parseURL/~pop/~prevent/~pull/~push/~recall#pull/~removeEventListener/~salt/~set/~sign/~signature/~size/~snip#pop/~syn/~triggerEvent/~unlock/~unpack/~unpack16/~upload#set/~used/cookie#/global#/utf16#/uuid#/vpn#"
break;
default:
m = "Sorry, couldn't find '@'; try $.help('*') to list all items that have help messages."
break;
}
m = ("/" + m + "/")
.replace(/\bTODO!/, "This feature (@) is not yet ready, but is planned for future releases./Please note that the documentation for '@' is not final, and may change or be removed.//")
.replace(/\//g, "\n")
.replace(/~/g, "\t")
.replace(/\\/g, "/")
.replace(/\$-/g, "synq-")
.replace(/\$/g, "SynQ")
.replace(/\b(?:use_?)?(\w+)#(?!\b)/g, "use_$1_synq_token")
.replace(/%/g, "element")
.replace(/#(\w+)/g, " - Global $1")
.replace(/@/g, item)
.replace(/\b(https?|synq)\:/g, "$1://")
.replace(/(\w+\s+\|(?:[\w \|]+?))( ?[^\w |])/g, '($1)$2')
.replace(/\B'([^']+?)'\B/g, '"$1"')
.replace(/\{key\}/gi, "the password to lock/unlock the data with")
.replace(/\{events\}/gi, SynQ.events.split(' ').join(', ').replace(/(.+),\s*(.+?)$/, "$1, or $2"))
.replace(/\{syn\}/gi, '["' + SynQ.syn.join('","') + '"]');
for(var r = /\{(\w+?)\}/, e = [], i = 0; r.test(m) && !~e.indexOf(RegExp["$&"]);)
m = m.replace(r, function($0, $1, $$, $_) {
return ($1 in SynQ)? SynQ[$1]: ($1 in window)? window[$1]: e[i++] = $0;
});
return m;
};
/* Polyfills */
// localStorage - Mozilla
if(!("localStorage" in window) || (use_cookie_synq_token != undefined && !SA))
Object.defineProperty(window, "localStorage", new(function() {
var keys = [],
StorageObject = {},
onstorage = new CustomEvent("storage", {bubbles: false, cancelable: false, composed: true});
Object.defineProperty(StorageObject, "getItem", {
value: function(key) {
return (key)?
this[key]:
null;
},
writable: false,
configurable: false,
enumerable: false
});
Object.defineProperty(StorageObject, "key", {
value: function(keyID) {
return keys[keyID];
},
writable: false,
configurable: false,
enumerable: false
});
Object.defineProperty(StorageObject, "setItem", {
value: function(key, value) {
if(key == undefined || key == null)
return;
document.cookie = escape(key) + "=" + escape(value) + ";expires=Thu, Dec 31 2099 23:59:59 GMT;path=/";
window.dispatchEvent(onstorage);
},
writable: false,
configurable: false,
enumerable: false
});
Object.defineProperty(StorageObject, "length", {
get: function() {
return keys.length;
},
configurable: false,
enumerable: false
});
Object.defineProperty(StorageObject, "removeItem", {
value: function(key) {
if(key == undefined || key == null)
return;
document.cookie = escape(key) + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/";
window.dispatchEvent(onstorage);
},
writable: false,
configurable: false,
enumerable: false
});
Object.defineProperty(StorageObject, "clear", {
value: function() {
if(keys.length == undefined || keys.length == null)
return;
for(var key in keys)
document.cookie = escape(key) + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/";
window.dispatchEvent(onstorage);
},
writable: false,
configurable: false,
enumerable: false
});
Object.defineProperty(StorageObject, "type", {
value: 'cookie',
writable: false,
configurable: false,
enumerable: false
});
this.get = function() {
var index;
for(var key in StorageObject) {
index = keys.indexOf(key);
if(index == -1)
StorageObject.setItem(key, StorageObject[key]);
else
keys.splice(index, 1);
delete StorageObject[key];
}
for(keys; keys.length > 0; keys.splice(0, 1))
StorageObject.removeItem(keys[0]);
for(var cookie, key, index = 0, cookies = document.cookie.split(/\s*;\s*/); index < cookies.length; index++) {
cookie = cookies[index].split(/\s*=\s*/);
if(cookie.length > 1)
key = unescape(cookie[0]),
StorageObject[key] = unescape(cookie[1]),
keys.push(key);
}
return StorageObject;
};
this.configurable = false;
this.enumerable = true;
})());
storage = (use_vpn_synq_token == undefined)?
window.localStorage:
window.sessionStorage || window.localStorage;
// navigator.connection - Mozilla, Ephellon
if(!("connection" in navigator))
Object.defineProperty(window, "NetworkInformation", new(function() {
var keys = [],
NetworkObject = {},
onnetwork = new CustomEvent("online", {bubbles: false, cancelable: false, composed: true});
ping("", {
callback: function(TripInformation) {
var k = 1000, M = k * k,
t = TripInformation, s = t.speed, m = t.time,
r = function(x, y) { return (x / y) - ((x / y) % y) };
s = r(s, 25);
m = r(m, 25);
Object.defineProperty(NetworkObject, "downlink", {
value: s,
writable: false,
configurable: false,
enumerable: true
});
Object.defineProperty(NetworkObject, "downlinkMax", {
value: s,
writable: false,
configurable: false,
enumerable: true
});
Object.defineProperty(NetworkObject, "effectiveType", {
value: (function(speed) {
if(speed < 500)
return '4g';
if(speed < 500 * k)
return '3g';
if(speed < 500 * M)
return '2g';
return 'slow-2g';
})(s),
writable: false,
configurable: false,
enumerable: true
});
Object.defineProperty(NetworkObject, "onchange", {
value: null,
writable: true,
configurable: false,
enumerable: true
});
Object.defineProperty(NetworkObject, "rtt", {
value: m,
writable: false,
configurable: false,
enumerable: true
});
Object.defineProperty(NetworkObject, "saveData", {
value: false,
writable: true,
configurable: false,
enumerable: true
});
Object.defineProperty(NetworkObject, "type", {
value: 'unknown',
writable: false,
configurable: false,
enumerable: true
});
}
});
this.get = function() {
return NetworkObject;
};
this.configurable = false;
this.enumerable = true;
})());
connection = navigator.connection;
/* Setup and auto-management */
// Auto-update & run
if(use_uuid_synq_token != undefined)
Object.defineProperty(SynQ, "signature", {
value: ("synq://" + use_uuid_synq_token + "/"),
writable: false,
configurable: false,
enumerable: true
});
else if(use_vpn_synq_token != undefined)
Object.defineProperty(SynQ, "signature", {
value: "synq://" + SynQ.sign(+(new Date)) + ":" + (use_vpn_synq_token = SynQ.sign(use_vpn_synq_token, 0.75)) + "@" + SynQ.sign(location, 1) + ":443/",
writable: false,
configurable: false,
enumerable: false
});
else if(use_global_synq_token == undefined)
Object.defineProperty(SynQ, "signature", {
value: "synq://" + SynQ.sign(location) + "/",
writable: false,
configurable: false,
enumerable: true
});
else
Object.defineProperty(SynQ, "signature", {
value: "synq://" + SynQ.sign(location.origin) + "/",
writable: false,
configurable: false,
enumerable: true
});
SynQ.eventName = '.events';
SynQ.events = "set get pop push pull upload download snip append recall clear";
SynQ.last = [];
SynQ.last_upload = [];
SynQ.host = null;
SynQ.shadow = null;
SynQ[internal] |= 0;
SynQ.eventlistener();
window.addEventListener("storage", window.onstorage = SynQ.eventlistener, false);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment