sandboxos => tildefriends

git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3157 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
2016-03-12 18:50:43 +00:00
commit 7c6a377c0b
94 changed files with 27121 additions and 0 deletions

View File

@ -0,0 +1,57 @@
"use strict";
var kMessages = [
[
" _ _ _ ",
" / \\ | |__ ___ _ _| |_ ",
" / _ \\ | '_ \\ / _ \\| | | | __|",
" / ___ \\| |_) | (_) | |_| | |_ ",
"/_/ \\_\\_.__/ \\___/ \\__,_|\\__|",
"",
"Tilde Friends: Webapps that anyone can download, modify, run, and share.",
"",
"You are looking at a web site running on a JavaScript and C++ web server that uses Google V8 to let visitors author webapps.",
"",
["Full source is here <",
{href: "https://www.unprompted.com/projects/browser/sandboxos/trunk/"},
">, but it is probably more fun and useful to poke around the ",
{href: "/~cory/index", value: "existing webapps"},
". A ",
{href: "https://www.unprompted.com/projects/wiki/Projects/SandboxOS", value: "prebuilt Windows .zip"},
" is available as well. ",
],
"",
[
"Use the links at the top of the page to explore existing apps. When you are ready, click edit and start making your own. See the ",
{href: "/~cory/documentation", value: "documentation"},
" for more information.",
],
],
];
var gIndex = 0;
function printNextMessage() {
if (gIndex < kMessages.length) {
var block = kMessages[gIndex];
for (var i = 0; i < block.length; i++) {
terminal.print(block[i]);
}
terminal.print("");
}
if (gIndex < kMessages.length) {
gIndex++;
if (gIndex < kMessages.length) {
terminal.print("(press enter to continue, \"exit\" to exit)");
}
}
}
core.register("onInput", function(input) {
if (input == "exit") {
exit();
} else {
printNextMessage();
}
});
printNextMessage();

View File

@ -0,0 +1,137 @@
"use strict";
//! {"permissions": ["administration"]}
terminal.print("Administration");
if (core.user.credentials.permissions &&
core.user.credentials.permissions.administration) {
core.register("onInput", onInput);
terminal.print("Welcome, administrator.");
terminal.print("Usage:");
let kCommands = [
[
"set",
"List all global settings.",
],
[
["set ", {class: "cyan", value: "key value"}],
["Set global setting key to value."],
],
[
"permission list",
"List all permissions."
],
[
["permission add ", {class: "cyan", value: "user action1 action2 ..."}],
["Grant permission for ", {class: "cyan", value: "action1"}, ", ", {class: "cyan", value: "action2"}, ", ", {class: "cyan", value: "..."}, " to ", {class: "cyan", value: "user"}, "."],
],
[
["permission remove ", {class: "cyan", value: "user action1 action2 ..."}],
["Revoke permission for ", {class: "cyan", value: "action1"}, ", ", {class: "cyan", value: "action2"}, ", ", {class: "cyan", value: "..."}, " from ", {class: "cyan", value: "user"}, "."],
],
[
"statistics", "List statistics."
],
];
for (var i = 0; i < kCommands.length; i++) {
terminal.print({class: "yellow", value: kCommands[i][0]});
terminal.print({style: "display: block; margin-left: 2em", value: kCommands[i][1]});
}
} else {
terminal.print("You are not an administrator.");
}
var kSimpleSettings = [
'httpPort',
'httpsPort',
'index',
];
function printSettings(settings) {
terminal.print("Current settings:");
for (let i = 0; i < kSimpleSettings.length; i++) {
terminal.print(" ", {class: "magenta", value: kSimpleSettings[i]}, " = ", {class: "yellow", value: settings[kSimpleSettings[i]]});
}
}
function printPermissions(settings) {
terminal.print("Current permissions:");
let permissions = settings.permissions || {};
for (let entry in permissions) {
terminal.print(" ", {class: "magenta", value: entry}, ": ", {class: "yellow", value: permissions[entry].join(" ")});
}
}
function onInput(input) {
try {
let match;
if (input == "set") {
administration.getGlobalSettings().then(printSettings);
} else if (input == "statistics") {
administration.getStatistics().then(function(s) {
for (var i in s) {
terminal.print(" ".repeat(16 - s[i].toString().length), s[i].toString(), " ", i);
}
});
} else if (match = /^\s*set\s+(\w+)\s+(.*)/.exec(input)) {
var key = match[1];
var value = match[2];
administration.getGlobalSettings().then(function(settings) {
if (kSimpleSettings.indexOf(key) != -1) {
settings[key] = value;
administration.setGlobalSettings(settings).then(function() {
administration.getGlobalSettings().then(printSettings);
}).catch(function(error) {
terminal.print("Error updating settings: " + JSON.stringify(error));
});
} else {
terminal.print("Unknown setting: " + key);
}
});
} else if (match = /^\s*permission\s+(\w+)(?:\s+(.*))?/.exec(input)) {
var command = match[1];
var remaining = (match[2] || "").split(/\s+/);
if (command == "list") {
administration.getGlobalSettings().then(printPermissions);
} else if (command == "add") {
var user = remaining[0];
administration.getGlobalSettings().then(function(settings) {
settings.permissions = settings.permissions || {};
settings.permissions[user] = settings.permissions[user] || [];
for (var i = 1; i < remaining.length; i++) {
if (settings.permissions[user].indexOf(remaining[i]) == -1) {
settings.permissions[user].push(remaining[i]);
}
}
settings.permissions[user].sort();
administration.setGlobalSettings(settings).then(function() {
administration.getGlobalSettings().then(printPermissions);
}).catch(function(error) {
terminal.print("Error updating permissions: " + JSON.stringify(error));
});
});
} else if (command == "remove") {
var user = remaining[0];
administration.getGlobalSettings().then(function(settings) {
if (settings.permissions && settings.permissions[user]) {
for (var i = 1; i < remaining.length; i++) {
settings.permissions[user] = settings.permissions[user].filter(x => x != remaining[i]);
}
if (settings.permissions[user].length == 0) {
delete settings.permissions[user];
}
}
administration.setGlobalSettings(settings).then(function() {
administration.getGlobalSettings().then(printPermissions);
}).catch(function(error) {
terminal.print("Error updating permissions: " + JSON.stringify(error));
});
});
}
} else if (typeof input == "string") {
terminal.print("I didn't understand that.");
}
} catch (error) {
terminal.print("error: " + error);
}
}

315
packages/cory/bbs/bbs.js Normal file
View File

@ -0,0 +1,315 @@
"use strict";
var gOnInput = null;
var kMaxHistory = 20;
var kShowHistory = 20;
var lastTimestamp = null;
if (imports.terminal) {
core.register("onMessage", function(sender, message) {
if (message.message && message.when) {
printMessage(message, true);
}
});
core.register("onSessionBegin", function(user) {
if (user.packageName === core.user.packageName &&
user.index !== core.user.index) {
listUsers(user.name + " has joined the BBS. ");
}
});
core.register("onSessionEnd", function(user) {
if (user.packageName === core.user.packageName &&
user.index !== core.user.index) {
listUsers(user.name + " has left the BBS. ");
}
});
} else {
// Chat service process.
core.register("onMessage", function(sender, message) {
if (message.message && message.when) {
message.sender = sender;
return database.get("board").catch(function() {
return null;
}).then(function(data) {
try {
data = JSON.parse(data);
} catch(error) {
data = [];
}
data.push(message);
while (data.length > kMaxHistory) {
data.shift();
}
return saveBoard(data);
}).then(function() {
return core.broadcast(message);
});
}
});
}
function listUsers() {
return core.getUsers(core.user.packageOwner, core.user.packageName).then(function(users) {
terminal.select("users");
terminal.clear();
terminal.print("Users:");
var counts = {};
for (var i = 0; i < users.length; i++) {
counts[users[i].name] = (counts[users[i].name] || 0) + 1;
}
var names = Object.keys(counts).sort();
for (var i = 0; i < names.length; i++) {
var name = names[i];
var message = [];
if (message.length > 1) {
message.push(", ");
}
message.push({class: "orange", value: name});
if (counts[name] > 1) {
message.push({class: "base01", value: "(x" + counts[name] + ")"});
}
terminal.print(message);
}
terminal.select("terminal");
});
}
function saveBoard(data) {
return database.set("board", JSON.stringify(data)).catch(function(error) {
if (error.message.indexOf("MDB_MAP_FULL") != -1) {
data.shift();
return saveBoard(data);
} else {
throw error;
}
});
}
core.register("onInput", function(input) {
if (gOnInput && typeof input == "string") {
gOnInput(input);
}
});
function logo() {
terminal.clear();
terminal.print("");
terminal.print("");
terminal.print('Welcome to');
terminal.print(' ______ _ ____ ____ _____');
terminal.print(' / ____/___ _______ _( )_____ / __ )/ __ ) ___/');
terminal.print(' / / / __ \\/ ___/ / / /// ___/ / __ / __ \\__ \\ ');
terminal.print('/ /___/ /_/ / / / /_/ / (__ ) / /_/ / /_/ /__/ / ');
terminal.print('\\____/\\____/_/ \\__, / /____/ /_____/_____/____/ ');
terminal.print(' /____/ ');
terminal.print(' yesterday\'s technology...today!');
terminal.print("");
}
function welcome() {
logo();
chat();
}
function main() {
terminal.clear();
logo();
terminal.print("");
terminal.print("Main menu commands:");
terminal.print(" ", {command: "chat"}, " chat message board");
terminal.print(" ", {command: "guess"}, " guess the number game");
terminal.print(" ", {command: "exit"}, " back to that sweet logo");
gOnInput = function(input) {
input = input.toLowerCase();
if (input == "chat") {
chat();
} else if (input == "guess") {
guess();
} else if (input == "exit") {
terminal.print("Goodbye.");
exit(0);
} else {
terminal.print("I didn't understand that: " + input);
main();
}
};
}
function formatMessage(message) {
var result;
if (typeof message == "string") {
result = [];
var regex = /(\w+:\/*\S+?)(?=(?:[\.!?])?(?:$|\s))/gi;
var match;
var lastIndex = 0;
while ((match = regex.exec(message)) !== null) {
result.push({class: "base1", value: message.substring(lastIndex, match.index)});
result.push({href: match[0]});
lastIndex = regex.lastIndex;
}
result.push({class: "base1", value: message.substring(lastIndex)});
} else {
result = message;
}
return result;
}
function niceTime(lastTime, thisTime) {
if (!lastTime) {
return thisTime;
}
let result = [];
let lastParts = lastTime.split(" ");
let thisParts = thisTime.split(" ");
for (let i = 0; i < thisParts.length; i++) {
if (thisParts[i] !== lastParts[i]) {
result.push(thisParts[i]);
}
}
return result.join(" ");
}
function printMessage(message, notify) {
terminal.print(
{class: "base0", value: niceTime(lastTimestamp, message.when)},
" ",
{class: "base00", value: "<"},
{class: "base3", value: (message.sender ? message.sender.name : "unknown")},
{class: "base00", value: ">"},
" ",
formatMessage(message.message));
lastTimestamp = message.when;
if (notify) {
return core.getUser().then(function(user) {
if (message.message.indexOf("!") != -1) {
return terminal.notify("SOMEONE IS SHOUTING!", {body: "<" + (message.sender ? message.sender.name : "unknown") + "> " + message.message});
} else if (message.message.indexOf(user.name + ":") != -1) {
return terminal.notify("Someone is talking at you.", {body: "<" + (message.sender ? message.sender.name : "unknown") + "> " + message.message});
}
});
}
}
function chat() {
terminal.setEcho(false);
terminal.print("");
terminal.print("You are now in a chat. Anything you type will be broadcast to everyone else connected. To leave, say ", {command: "exit"}, ".");
listUsers();
database.get("board").catch(function() {
return null;
}).then(function(board) {
try {
board = JSON.parse(board);
} catch (error) {
board = [];
}
for (let i = Math.max(0, board.length - kShowHistory); i < board.length; i++) {
printMessage(board[i], false);
}
});
gOnInput = function(input) {
if (input == "exit") {
terminal.setEcho(true);
main();
} else {
core.getService("chat").then(function(chatService) {
return chatService.postMessage({when: new Date().toString(), message: input});
}).catch(function(error) {
terminal.print("ERROR: " + JSON.stringify(error));
});
}
};
}
function guess() {
terminal.clear();
var number = Math.round(Math.random() * 100);
var guesses = 0;
terminal.print("OK, I have a number in mind. What do you think it is? Use ", {command: "exit"}, " to stop.");
gOnInput = function(input) {
if (input == "exit") {
main();
} else {
var guess = parseInt(input);
guesses++;
if (input != guess.toString()) {
terminal.print("I'm not sure that's an integer. Please guess only integers.");
} else {
if (guess < number) {
terminal.print("Too low.");
} else if (guess > number) {
terminal.print("Too high.");
} else if (guess == number) {
terminal.print("Wow, you got it in " + guesses + " guesses! It was " + number + ".");
guessEnd(guesses);
}
}
}
};
}
function guessEnd(guesses) {
terminal.print("What's your name, for the high score table?");
gOnInput = function(name) {
var entry = {'guesses': guesses, 'name': name, 'when': new Date().toString()};
database.get("guessHighScores").then(function(data) {
data = JSON.parse(data);
var index = data.length;
for (var i in data) {
if (guesses < data[i].guesses) {
index = i;
break;
}
}
data.splice(index, 0, entry);
printHighScores(data);
database.set("guessHighScores", JSON.stringify(data));
gOnInput = function() {
main();
};
}).catch(function() {
var data = [entry];
printHighScores(data);
database.set("guessHighScores", JSON.stringify(data));
main();
});
};
}
function printTable(data) {
var widths = [];
for (var i in data) {
var row = data[i];
for (var c in row) {
widths[c] = Math.max(widths[c] || 0, row[c].length);
}
}
for (var i in data) {
var row = data[i];
var line = "";
for (var c in row) {
line += row[c];
line += " ".repeat(widths[c] - row[c].length + 2);
}
terminal.print(line);
}
}
function printHighScores(data) {
printTable([["Name", "Guesses", "Date"]].concat(data.map(function(entry) {
return [entry.name, entry.guesses.toString(), entry.when];
})));
}
if (imports.terminal) {
terminal.split([
{type: "horizontal", children: [
{name: "terminal", grow: 1},
{name: "users", grow: 0},
]},
]);
welcome();
}

View File

@ -0,0 +1,93 @@
"use strict";
let kDocumentation = {
"core.broadcast": ["message", "Broadcast a message to every other instance of the same app. Messages will be received through the \"onMessage\" event."],
"core.getService": ["name", "Get a reference to a long-running service process identified by name. A process will be started if it is not already running. Useful for coordinating between client processes."],
"core.getPackages": ["", "Get a list of all available applications."],
"core.getUser": ["", "Gets information about the current user."],
"core.getUsers": ["packageOwner, packageName", "Get a list of all online users, restricted to a package if specified."],
"core.register": ["eventName, handlerFunction", "Register a callback function for the given event."],
"database.get": ["key", "Retrieve the database value associated with the given key."],
"database.set": ["key, value", "Sets the database value for the given key, overwriting any existing value."],
"database.getAll": ["", "Retrieve a list of all key names."],
"database.remove": ["key", "Remove the database entry for the given key."],
"terminal.print": ["arguments...", `Print to the terminal. Multiple arguments and lists are all expanded. The following special values are supported:
{href: "http://www..."} => Create a link to the href value. Text will be the href value or 'value' if specified.
{iframe: "<html>...</html>", width: 640, height: 480} => Create an iframe with the given srcdoc.
{style: "color: #f00", value: "Hello, world!"} => Create styled text.
{command: "exit", value: "get out of here"} => Create a link that when clicked will act as if the user typed the given command.`],
"terminal.clear": ["", "Remove all terminal output."],
"terminal.readLine": ["", "Produces the next line of text from user input."],
"terminal.setEcho": ["echo", "Controls whether the terminal will automatically echo user input (default=true)."],
"terminal.setTitle": ["title", "Sets the browser window/tab title."],
"terminal.setPrompt": ["prompt", "Sets the terminal prompt (default \">\")."],
"terminal.setPassword": ["enabled", "Controls whether the terminal input box is set as a password input and obscures the entered text."],
"terminal.setHash": ["hash", "Sets the URL #hash, typically so that the user can copy / bookmark it and return to a similar state."],
"terminal.postMessageToIframe": ["name, message", "Sends the message to the iframe that was created with the given name using window.postMessage."],
"terminal.notify": ["body, {title, icon}", ["Produces an ", {href: "https://developer.mozilla.org/en-US/docs/Web/API/notification", value: "HTML5 Notification"}, ". Arguments are the same as the Notification constructor."]],
};
terminal.print("V8 Version ", version);
terminal.print("");
heading("API Documentation");
dumpDocumentation("imports", imports);
heading("Notes");
terminal.print(`All API functions are invoked asynchronously. They
immediately return a `,
{href: "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise", value: "Promise"},
` object. If you want to do
something with the result, you most likely want to call them
like this:
database.get(key).then(function(value) {
doSomethingWithTheResult(value);
});`);
terminal.print("");
heading("Colors (CSS class names)");
dumpColors();
function heading(text) {
terminal.print({class: "green", value: "+" + "-".repeat(text.length + 2) + "+"});
terminal.print({class: "green", value: "| " + text + " |"});
terminal.print({class: "green", value: "+" + "-".repeat(text.length + 2) + "+"});
}
function dumpDocumentation(prefix, object, depth) {
if (typeof object == "function") {
let documentation = kDocumentation[prefix.substring("imports.".length)] || ["", ""];
terminal.print(
{class: "yellow", value: prefix.substring("imports.".length)},
"(",
{class: "base0", value: documentation[0]},
")");
terminal.print({style: "display: block; margin-left: 2em", value: documentation[1]});
terminal.print("");
} else if (object && typeof object != "string") {
for (let i in object) {
dumpDocumentation(prefix + "." + i, object[i], (depth || 0) + 1);
}
}
}
function dumpColors() {
var kColors = [
"base03",
"base02",
"base01",
"base00",
"base0",
"base1",
"base2",
"base3",
"yellow",
"red",
"magenta",
"violet",
"blue",
"cyan",
"green",
];
terminal.print({style: "background-color: #000", value: kColors.map(function(color) { return [" ", {class: color, value: color}, " "]; })});
}

View File

@ -0,0 +1,53 @@
"use strict";
core.register("onSessionBegin", index);
core.register("onSessionEnd", index);
function index() {
Promise.all([core.getPackages(), core.getUsers()]).then(function(values) {
let packages = values[0];
let users = values[1];
let usersByApp = {};
for (let i in users) {
let user = users[i];
if (!usersByApp["/~" + user.packageOwner + "/" + user.packageName]) {
usersByApp["/~" + user.packageOwner + "/" + user.packageName] = [];
}
usersByApp["/~" + user.packageOwner + "/" + user.packageName].push(user.name);
}
terminal.clear();
terminal.print("Available applications [active users]:");
packages.sort(function(x, y) {
return Math.sign(x.owner.localeCompare(y.owner)) * 10 + Math.sign(x.name.localeCompare(y.name)) * 1;
}).forEach(function(app) {
let users = usersByApp["/~" + app.owner + "/" + app.name];
let message = [];
if (users) {
message.push(" [");
let counts = {};
for (let i = 0; i < users.length; i++) {
counts[users[i]] = (counts[users[i]] || 0) + 1;
}
let names = Object.keys(counts).sort();
for (let i = 0; i < names.length; i++) {
var name = names[i];
if (message.length > 1) {
message.push(", ");
}
message.push({class: "orange", value: name});
if (counts[name] > 1) {
message.push({class: "base01", value: "(x" + counts[name] + ")"});
}
}
message.push("]");
}
terminal.print(
"* ",
{href: "/~" + app.owner + "/" + app.name},
message);
});
});
}
index();

View File

@ -0,0 +1,124 @@
"use strict";
// This script runs server-side, once for each client session.
if (imports.terminal) {
terminal.setEcho(false);
terminal.split([
{name: "graphics", basis: "520px", shrink: "0", grow: "0"},
{name: "text"},
]);
// Request a callback every time the user hits enter at the terminal prompt.
core.register("onInput", function(input) {
// Ask a persistent service session to broadcast our message. We'll also get a copy back.
return core.getService("turtle").then(function(service) {
return service.postMessage(input);
});
});
// Request a callback for every message that is broadcast.
core.register("onMessage", function(sender, message) {
if (message.history) {
for (var i = 0; i < message.history.length; i++) {
// Pass the message on to the iframe in the client.
terminal.postMessageToIframe("turtle", message.history[i]);
}
} else {
// Pass the message on to the iframe in the client.
terminal.postMessageToIframe("turtle", message);
}
});
core.register("onWindowMessage", function(data) {
terminal.print(data.message);
});
terminal.select("graphics");
terminal.print("MMO Turtle Graphics using ", {href: "http://codeheartjs.com/turtle/"}, ".");
// Add an iframe to the terminal. This is how we sandbox code running on the client.
var contents = `
<script src="http://codeheartjs.com/turtle/turtle.min.js">-*- javascript -*-</script>
<script>
setScale(2);
setWidth(3);
// Receive messages in the iframe and use them to draw.
function onMessage(event) {
var parts = event.data.split(" ");
var command = parts.shift();
if (command == "reset") {
setPosition(0, 0);
setHeading(0);
clear(WHITE);
_ch_startTimer(30);
} else if (command == "home") {
var wasDown = _turtle.penDown;
pu();
setPosition(0, 0);
setHeading(0);
if (wasDown) {
pd();
}
_ch_startTimer(30);
} else if (command == "clear") {
clear(WHITE);
_ch_startTimer(30);
} else if (["fd", "bk", "rt", "lt", "pu", "pd"].indexOf(command) != -1) {
window[command].apply(window, parts.map(parseInt));
event.source.postMessage(event.data, event.origin);
_ch_startTimer(30);
} else {
event.source.postMessage("Unrecognized command: " + command, event.origin);
}
}
// Register for messages in the iframe
window.addEventListener('message', onMessage, false);
</script>
`
terminal.print({iframe: contents, width: 640, height: 480, name: "turtle"});
terminal.select("text");
terminal.print("Supported commands: ", ["fd <distance>", "bk <distance>", "rt <angle>", "lt <angle>", "pu", "pd", "home", "reset", "clear"].join(", "));
// Get the party started by asking for the history of commands (the turtle party).
setTimeout(function() {
core.getService("turtle").then(function(service) {
return service.postMessage("sync");
});
}, 1000);
} else {
var gHistory = null;
function ensureHistoryLoaded() {
if (!gHistory) {
return database.get("history").then(function(data) {
gHistory = JSON.parse(data);
return gHistory;
}).catch(function(error) {
gHistory = [];
return gHistory;
});
} else {
return new Promise(function(resolve, reject) { resolve(gHistory); });
}
}
core.register("onMessage", function(sender, message) {
return ensureHistoryLoaded().then(function(history) {
if (message == "reset") {
history.length = 0;
database.set("history", JSON.stringify(history));
return core.broadcast(message);
} else if (message == "sync") {
sender.postMessage({history: history});
} else {
history.push(message);
database.set("history", JSON.stringify(history));
return core.broadcast(message);
}
});
});
}

View File

@ -0,0 +1,74 @@
"use strict";
//! {"permissions": ["network"]}
terminal.print("Hello, world!");
let kFrom = core.user.name + "@unprompted.com";
let kTo = "test@unprompted.com";
let kSubject = "Hello, world!";
let kBody = "This is the body of the email."
let inBuffer = "";
let sentFrom = false;
let sentTo = false;
let sentData = false;
function lineReceived(socket, line) {
terminal.print("> ", line);
let parts = line.split(" ", 1);
terminal.print(JSON.stringify(parts));
if (parts[0] == "220") {
socket.write("HELO rowlf.unprompted.com\r\n");
} else if (parts[0] == "250") {
if (!sentFrom) {
terminal.print("FROM");
socket.write("MAIL FROM: " + kFrom + "\r\n");
sentFrom = true;
} else if (!sentTo) {
terminal.print("TO");
socket.write("RCPT TO: " + kTo + "\r\n");
sentTo = true;
} else if (!sentData) {
terminal.print("DATA");
socket.write("DATA\r\n");
sentData = true;
} else {
terminal.print("QUIT");
socket.write("QUIT\r\n");
}
} else if (parts[0] == "354") {
terminal.print("MESSAGE");
socket.write("Subject: " + kSubject + "\r\n\r\n" + kBody + "\r\n.\r\n");
}
}
function dataReceived(socket, data) {
if (data === null) {
return;
}
terminal.print(data);
inBuffer += data;
let again = true;
while (again) {
again = false;
let end = inBuffer.indexOf("\n");
if (end != -1) {
again = true;
let line = inBuffer.substring(0, end);
inBuffer = inBuffer.substring(end + 1);
lineReceived(socket, line);
}
}
}
network.newConnection().then(function(socket) {
socket.read(function(data) {
try {
dataReceived(socket, data);
} catch (error) {
terminal.print("ERROR: ", error.message);
}
});
socket.connect("localhost", 25);
});

204
packages/cory/todo/todo.js Normal file
View File

@ -0,0 +1,204 @@
"use strict";
var kUnchecked = "☐";
var kChecked = "☑";
let activeList = null;
let confirmRemove;
terminal.setPrompt("Add Item>");
core.register("onInput", function(command) {
if (typeof command == "string" && command.substring(0, "action:".length) == "action:") {
command = JSON.parse(command.substring("action:".length));
if (confirmRemove && command.action != "reallyRemoveList" && command.action != "reallyRemove") {
confirmRemove = false;
}
if (command.action == "set") {
setItem(command.key, command.item, command.value).then(notifyChanged).then(redisplay);
} else if (command.action == "remove") {
confirmRemove = command;
redisplay();
} else if (command.action == "reallyRemove") {
confirmRemove = false;
removeItem(command.key, command.item).then(notifyChanged).then(redisplay);
} else if (command.action == "editList") {
activeList = command.key;
terminal.setHash(activeList);
redisplay();
} else if (command.action == "lists") {
activeList = null;
redisplay();
} else if (command.action == "removeList") {
confirmRemove = true;
redisplay();
} else if (command.action == "reallyRemoveList") {
confirmRemove = false;
activeList = null;
database.remove(command.key).then(notifyChanged).then(redisplay).catch(function(error) {
terminal.print(JSON.stringify(error));
terminal.print(command.key);
});
}
} else if (typeof command == "string") {
if (activeList) {
addItem(activeList, command).then(notifyChanged).then(redisplay);
} else {
activeList = makePrivateKey(command);
writeList(activeList, {name: command, items: []}).then(notifyChanged).then(redisplay);
}
} else if (command.hash) {
activeList = command.hash;
if (activeList.charAt(0) == "#") {
activeList = activeList.substring(1);
}
redisplay();
}
});
core.register("onMessage", function(message) {
return redisplay();
});
function notifyChanged() {
return core.broadcast({changed: true});
}
function readList(key) {
return database.get(key).catch(function(error) {
return null;
}).then(function(todo) {
try {
todo = JSON.parse(todo);
} catch (error) {
todo = {name: "TODO", items: []};
}
return todo;
});
}
function writeList(key, todo) {
return database.set(key, JSON.stringify(todo));
}
function addItem(key, name) {
return readList(key).then(function(todo) {
todo.items.push({name: name, value: false});
return writeList(key, todo);
});
}
function setItem(key, name, value) {
return readList(key).then(function(todo) {
for (var i = 0; i < todo.items.length; i++) {
if (todo.items[i].name == name) {
todo.items[i].value = value;
}
}
return writeList(key, todo);
});
}
function removeItem(key, name) {
return readList(key).then(function(todo) {
todo.items = todo.items.filter(function(item) {
return item.name != name;
});
return writeList(key, todo);
});
}
function printList(name, key, items) {
terminal.print(name,
" - ",
{command: "action:" + JSON.stringify({action: "lists"}), value: "back"},
" - ",
{command: "action:" + JSON.stringify({action: (confirmRemove === true ? "reallyRemoveList" : "removeList"), key: key}), value: (confirmRemove === true ? "confirm remove" : "remove")});
terminal.print("=".repeat(name.length));
for (var i = 0; i < items.length; i++) {
var isChecked = items[i].value;
var style = ["", "text-decoration: line-through"];
terminal.print(
{command: "action:" + JSON.stringify({action: "set", key: key, item: items[i].name, value: !isChecked}), value: isChecked ? kChecked : kUnchecked},
" ",
{style: style[isChecked ? 1 : 0], value: items[i].name},
" (",
{command: "action:" + JSON.stringify({
action: (confirmRemove && confirmRemove.item == items[i].name ? "reallyRemove" : "remove"),
key: key,
item: items[i].name}), value: (confirmRemove && confirmRemove.item == items[i].name ? "confirm remove" : "remove")},
")");
}
}
function redisplay() {
terminal.clear();
terminal.setEcho(false);
if (activeList) {
readList(activeList).then(function(data) {
printList(getName(activeList), activeList, data.items);
}).catch(function(error) {
terminal.print("error: " + error);
});
} else {
printListOfLists();
}
}
function makeId() {
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var result = "";
for (var i = 0; i < 32; i++) {
result += alphabet.charAt(Math.floor(Math.random() * alphabet.length));
}
return result;
}
function makePublicKey(name) {
return JSON.stringify({public: true, id: makeId(), name: name});
}
function makePrivateKey(name) {
return JSON.stringify({public: false, id: makeId(), name: name, user: core.user.name});
}
function hasPermission(key) {
let result = false;
try {
let data = JSON.parse(key);
result = data.public || data.user == core.user.name;
} catch (error) {
result = true;
}
return result;
}
function getName(key) {
let name = "TODO";
try {
name = JSON.parse(key).name || name;
} catch (error) {
}
return name;
}
function getVisibleLists() {
return database.getAll().then(function(data) {
return data.filter(hasPermission);
});
}
function printListOfLists() {
terminal.print("TODO Lists:");
getVisibleLists().then(function(keys) {
for (var i = 0; i < keys.length; i++) {
let key = keys[i];
terminal.print({
command: "action:" + JSON.stringify({action: "editList", key: key}),
value: getName(key),
});
}
});
}
redisplay();

View File

@ -0,0 +1,41 @@
"use strict";
// Start at bottom left facing up.
// Height = 20. Width = 10.
// 10 between.
var letters = {
A: 'fd(20); rt(90); fd(10); rt(90); fd(10); rt(90); fd(10); pu(); bk(10); lt(90); pd(); fd(10); pu(); lt(90); fd(10); lt(90); pd();',
D: 'fd(20); rt(90); fd(10); rt(70); fd(11); rt(40); fd(11); rt(70); fd(10); pu(); bk(20); rt(90); pd();',
E: 'pu(); fd(20); rt(90); fd(10); lt(180); pd(); fd(10); lt(90); fd(10); lt(90); fd(8); pu(); rt(180); fd(8); lt(90); pd(); fd(10); lt(90); fd(10); pu(); fd(10); lt(90); pd()',
H: 'fd(20); pu(); bk(10); pd(); rt(90); fd(10); lt(90); pu(); fd(10); rt(180); pd(); fd(20); pu(); lt(90); fd(10); lt(90); pd();',
L: 'pu(); fd(20); rt(180); pd(); fd(20); lt(90); fd(10); pu(); fd(10); lt(90); pd();',
O: 'fd(20); rt(90); fd(10); rt(90); fd(20); rt(90); fd(10); pu(); bk(20); rt(90); pd();',
R: 'fd(20); rt(90); fd(10); rt(90); fd(10); rt(90); fd(10); pu(); bk(8); lt(90); pd(); fd(10); pu(); lt(90); fd(12); lt(90); pd();',
W: 'pu(); fd(20); rt(180); pd(); fd(20); lt(90); fd(5); lt(90); fd(12); rt(180); pu(); fd(12); pd(); lt(90); fd(5); lt(90); fd(20); pu(); bk(20); rt(90); fd(10); lt(90); pd();',
' ': 'pu(); rt(90); fd(20); lt(90); pd();',
};
function render(text) {
terminal.clear();
terminal.print(text, " using ", {href: "http://codeheartjs.com/turtle/"}, ".");
var contents = '<script src="http://codeheartjs.com/turtle/turtle.min.js">-*- javascript -*-</script><script>\n';
contents += 'setScale(2); setWidth(5);\n';
for (var i = 0; i < text.length; i++) {
var c = text.charAt(i).toUpperCase();
if (letters[c]) {
contents += letters[c] + '\n';
} else {
contents += letters[' '] + '\n';
}
}
contents += "ht();\n";
contents += "window.addEventListener('message', function(event) { console.debug(event.data); }, false);\n";
contents += "</script>\n";
terminal.print({iframe: contents, width: 640, height: 480});
terminal.print("Type text and the letters ", {style: "color: #ff0", value: Object.keys(letters).join("")}, " in it will be drawn.");
}
render("Hello, world!");
core.register("onInput", render);

952
packages/cory/xmpp/xmpp.js Normal file
View File

@ -0,0 +1,952 @@
"use strict";
//! {"permissions": ["network"]}
// md5.js
/*
* JavaScript MD5 1.0.1
* https://github.com/blueimp/JavaScript-MD5
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*
* Based on
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
/*jslint bitwise: true */
/*global unescape, define */
'use strict';
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y) {
var lsw = (x & 0xFFFF) + (y & 0xFFFF),
msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function bit_rol(num, cnt) {
return (num << cnt) | (num >>> (32 - cnt));
}
/*
* These functions implement the four basic operations the algorithm uses.
*/
function md5_cmn(q, a, b, x, s, t) {
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b);
}
function md5_ff(a, b, c, d, x, s, t) {
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t) {
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t) {
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t) {
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}
/*
* Calculate the MD5 of an array of little-endian words, and a bit length.
*/
function binl_md5(x, len) {
/* append padding */
x[len >> 5] |= 0x80 << (len % 32);
x[(((len + 64) >>> 9) << 4) + 14] = len;
var i, olda, oldb, oldc, oldd,
a = 1732584193,
b = -271733879,
c = -1732584194,
d = 271733878;
for (i = 0; i < x.length; i += 16) {
olda = a;
oldb = b;
oldc = c;
oldd = d;
a = md5_ff(a, b, c, d, x[i], 7, -680876936);
d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586);
c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819);
b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330);
a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897);
d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426);
c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341);
b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983);
a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416);
d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417);
c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);
b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);
a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682);
d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);
c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);
b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329);
a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510);
d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632);
c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713);
b = md5_gg(b, c, d, a, x[i], 20, -373897302);
a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691);
d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083);
c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);
b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848);
a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438);
d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690);
c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961);
b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501);
a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467);
d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784);
c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473);
b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);
a = md5_hh(a, b, c, d, x[i + 5], 4, -378558);
d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463);
c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562);
b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);
a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060);
d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353);
c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632);
b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);
a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174);
d = md5_hh(d, a, b, c, x[i], 11, -358537222);
c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979);
b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189);
a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487);
d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);
c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520);
b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651);
a = md5_ii(a, b, c, d, x[i], 6, -198630844);
d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415);
c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);
b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055);
a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571);
d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606);
c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);
b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799);
a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359);
d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);
c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380);
b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649);
a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070);
d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);
c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259);
b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551);
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
}
return [a, b, c, d];
}
/*
* Convert an array of little-endian words to a string
*/
function binl2rstr(input) {
var i,
output = '';
for (i = 0; i < input.length * 32; i += 8) {
output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF);
}
return output;
}
/*
* Convert a raw string to an array of little-endian words
* Characters >255 have their high-byte silently ignored.
*/
function rstr2binl(input) {
var i,
output = [];
output[(input.length >> 2) - 1] = undefined;
for (i = 0; i < output.length; i += 1) {
output[i] = 0;
}
for (i = 0; i < input.length * 8; i += 8) {
output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32);
}
return output;
}
/*
* Calculate the MD5 of a raw string
*/
function rstr_md5(s) {
return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
}
/*
* Calculate the HMAC-MD5, of a key and some data (raw strings)
*/
function rstr_hmac_md5(key, data) {
var i,
bkey = rstr2binl(key),
ipad = [],
opad = [],
hash;
ipad[15] = opad[15] = undefined;
if (bkey.length > 16) {
bkey = binl_md5(bkey, key.length * 8);
}
for (i = 0; i < 16; i += 1) {
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
}
/*
* Convert a raw string to a hex string
*/
function rstr2hex(input) {
var hex_tab = '0123456789abcdef',
output = '',
x,
i;
for (i = 0; i < input.length; i += 1) {
x = input.charCodeAt(i);
output += hex_tab.charAt((x >>> 4) & 0x0F) +
hex_tab.charAt(x & 0x0F);
}
return output;
}
/*
* Encode a string as utf-8
*/
function str2rstr_utf8(input) {
return unescape(input);
}
/*
* Take string arguments and return either raw or hex encoded strings
*/
function raw_md5(s) {
return rstr_md5(str2rstr_utf8(s));
}
function hex_md5(s) {
return rstr2hex(raw_md5(s));
}
function raw_hmac_md5(k, d) {
return rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d));
}
function hex_hmac_md5(k, d) {
return rstr2hex(raw_hmac_md5(k, d));
}
function md5(string, key, raw) {
if (!key) {
if (!raw) {
return hex_md5(string);
}
return raw_md5(string);
}
if (!raw) {
return hex_hmac_md5(key, string);
}
return raw_hmac_md5(key, string);
}
// end md5.js
// base64.js
/**
*
* Base64 encode / decode
* http://www.webtoolkit.info/
*
**/
// private property
var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var Base64 = {
// public method for encoding
encode : function (input) {
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = Base64._utf8_encode(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output +
_keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
_keyStr.charAt(enc3) + _keyStr.charAt(enc4);
}
return output;
},
// public method for decoding
decode : function (input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
while (i < input.length) {
enc1 = _keyStr.indexOf(input.charAt(i++));
enc2 = _keyStr.indexOf(input.charAt(i++));
enc3 = _keyStr.indexOf(input.charAt(i++));
enc4 = _keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
}
output = Base64._utf8_decode(output);
return output;
},
// private method for UTF-8 encoding
_utf8_encode : function (string) {
string = string.replace(/\r\n/g,"\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
}
else if((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
},
// private method for UTF-8 decoding
_utf8_decode : function (utftext) {
var string = "";
var i = 0;
var c = 0;
var c1 = 0;
var c2 = 0;
while ( i < utftext.length ) {
c = utftext.charCodeAt(i);
if (c < 128) {
string += String.fromCharCode(c);
i++;
}
else if((c > 191) && (c < 224)) {
c2 = utftext.charCodeAt(i+1);
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2;
}
else {
c2 = utftext.charCodeAt(i+1);
c3 = utftext.charCodeAt(i+2);
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}
}
return string;
}
}
// end base64.js
function xmlEncode(text) {
return text.replace(/([\&"'<>])/g, function(x, item) {
return {'&': '&amp;', '"': '&quot;', '<': '&lt;', '>': '&gt;', "'": '&apos;'}[item];
});
}
function xmlDecode(xml) {
return xml.replace(/(&quot;|&lt;|&gt;|&amp;|&apos;)/g, function(x, item) {
return {'&amp;': '&', '&quot;': '"', '&lt;': '<', '&gt;': '>', '&apos;': "'"}[item];
});
}
// xmpp.js
function XmlStreamParser() {
this.buffer = "";
this._parsed = [];
this.reset();
return this;
}
XmlStreamParser.kText = "text";
XmlStreamParser.kElement = "element";
XmlStreamParser.kEndElement = "endElement";
XmlStreamParser.kAttributeName = "attributeName";
XmlStreamParser.kAttributeValue = "attributeValue";
XmlStreamParser.prototype.reset = function() {
this._state = XmlStreamParser.kText;
this._attributes = {};
this._attributeName = "";
this._attributeValue = "";
this._attributeEquals = false;
this._attributeQuote = "";
this._slash = false;
this._value = "";
this._decl = false;
}
XmlStreamParser.prototype.parse = function(data) {
this._parsed = [];
for (var i = 0; i < data.length; i++) {
var c = data.charAt(i);
this.parseCharacter(c);
}
return this._parsed;
}
XmlStreamParser.prototype.flush = function() {
var node = {type: this._state};
if (this._value) {
node.value = xmlDecode(this._value);
}
if (this._attributes.length || this._state == XmlStreamParser.kElement) {
node.attributes = this._attributes;
}
if (this._state != XmlStreamParser.kText || this._value) {
this.emit(node);
}
this.reset();
}
XmlStreamParser.prototype.parseCharacter = function(c) {
switch (this._state) {
case XmlStreamParser.kText:
if (c == '<') {
this.flush();
this._state = XmlStreamParser.kElement;
} else {
this._value += c;
}
break;
case XmlStreamParser.kElement:
case XmlStreamParser.kEndElement:
switch (c) {
case '>':
this.finishElement();
break;
case '/':
if (!this._value) {
this._state = XmlStreamParser.kEndElement;
} else if (!this._slash) {
this._slash = true;
} else {
this._value += c;
}
break;
case '?':
if (!this._value) {
this._decl = true;
} else {
this._value += '?';
}
break;
case ' ':
case '\t':
case '\r':
case '\n':
this._state = XmlStreamParser.kAttributeName;
break;
default:
if (this._slash) {
this._slash = false;
this._value += '/';
}
this._value += c;
break;
}
break;
case XmlStreamParser.kAttributeName:
switch (c) {
case ' ':
case '\t':
case '\r':
case '\n':
if (this._attributeName) {
this._state = XmlStreamParser.kAttributeValue;
}
break;
case '/':
if (!this._slash) {
this._slash = true;
} else {
this._value += '/';
}
break;
case '=':
this._state = XmlStreamParser.kAttributeValue;
break;
case '>':
if (this._attributeName) {
this._attributes[this._attributeName] = null;
}
this._state = XmlStreamParser.kElement;
this.finishElement();
break;
default:
this._attributeName += c;
break;
}
break;
case XmlStreamParser.kAttributeValue:
switch (c) {
case ' ':
case '\t':
case '\r':
case '\n':
if (this._attributeValue) {
this._state = XmlStreamParser.kAttributeName;
}
break;
case '"':
case "'":
if (!this._attributeValue && !this._attributeQuote) {
this._attributeQuote = c;
} else if (this._attributeQuote == c) {
this._attributes[this._attributeName] = this._attributeValue;
this._attributeName = "";
this._attributeValue = "";
this._attributeQuote = "";
this._state = XmlStreamParser.kAttributeName;
} else {
this._attributeValue += c;
}
break;
case '>':
this.finishElement();
break;
default:
this._attributeValue += c;
break;
}
break;
}
}
XmlStreamParser.prototype.finishElement = function() {
if (this._decl) {
this.reset();
} else {
var value = this._value;
var slash = this._slash;
this.flush();
if (slash) {
this._state = XmlStreamParser.kEndElement;
this._value = value;
this.flush();
}
}
this._state = XmlStreamParser.kText;
}
XmlStreamParser.prototype.emit = function(node) {
this._parsed.push(node);
}
function XmlStanzaParser(depth) {
this._depth = depth || 0;
this._parsed = [];
this._stack = [];
this._stream = new XmlStreamParser();
return this;
}
XmlStanzaParser.prototype.reset = function() {
this._parsed = [];
this._stack = [];
this._stream.reset();
}
XmlStanzaParser.prototype.emit = function(stanza) {
this._parsed.push(stanza);
}
XmlStanzaParser.prototype.parse = function(data) {
this._parsed = [];
var nodes = this._stream.parse(data);
for (var i = 0; i < nodes.length; i++) {
this.parseNode(nodes[i]);
}
return this._parsed;
}
XmlStanzaParser.prototype.parseNode = function(node) {
switch (node.type) {
case XmlStreamParser.kElement:
this._stack.push({name: node.value, attributes: node.attributes, children: [], text: ""});
break;
case XmlStreamParser.kEndElement:
if (this._stack.length == 1 + this._depth) {
this.emit(this._stack.pop());
} else {
var last = this._stack.pop();
this._stack[this._stack.length - 1].children.push(last);
}
break;
case XmlStreamParser.kText:
if (this._stack) {
this._stack[this._stack.length - 1].text += node.value;
}
break;
}
}
// end xmpp.js
var gFocus = false;
var gUnread = 0;
function updateTitle() {
if (gUnread) {
terminal.setTitle("(" + gUnread.toString() + ") ~Friends XMPP");
} else {
terminal.setTitle("~Friends XMPP");
}
}
terminal.print("~Friends XMPP");
updateTitle();
terminal.setEcho(false);
terminal.setPrompt("Username:");
terminal.readLine().then(function(userName) {
terminal.setPassword(true);
terminal.setPrompt("Password:");
terminal.readLine().then(function(password) {
terminal.setPrompt(">");
terminal.setPassword(false);
network.newConnection().then(function(socket) {
connect(socket, userName, password);
}).catch(function(error) {
terminal.print(error);
});
});
});
function niceTime(lastTime, thisTime) {
if (!lastTime) {
return thisTime;
}
let result = [];
let lastParts = lastTime.split(" ");
let thisParts = thisTime.split(" ");
for (let i = 0; i < thisParts.length; i++) {
if (thisParts[i] !== lastParts[i]) {
result.push(thisParts[i]);
}
}
return result.join(" ");
}
function formatMessage(message) {
var result;
if (typeof message == "string") {
result = [];
var regex = /(\w+:\/*\S+?)(?=(?:[\.!?])?(?:$|\s))/gi;
var match;
var lastIndex = 0;
while ((match = regex.exec(message)) !== null) {
result.push({class: "base1", value: message.substring(lastIndex, match.index)});
result.push({href: match[0]});
lastIndex = regex.lastIndex;
}
result.push({class: "base1", value: message.substring(lastIndex)});
} else {
result = message;
}
return result;
}
var lastTimestamp = null;
function printMessage(stanza) {
var body;
var delayed = false;
var now = new Date().toString();
for (var i in stanza.children) {
if (stanza.children[i].name == "body") {
body = stanza.children[i].text;
}
if (stanza.children[i].name == "delay") {
delayed = true;
now = new Date(stanza.children[i].attributes.stamp).toString();
}
}
var from = stanza.attributes.from || "unknown";
if (from && from.indexOf('/') != -1) {
from = from.split("/")[1];
}
terminal.print(
{class: "base0", value: niceTime(lastTimestamp, now)},
" ",
{class: "base00", value: "<"},
{class: "base3", value: from},
{class: "base00", value: ">"},
" ",
formatMessage(body));
lastTimestamp = now;
}
var gRecent = [];
core.register("focus", function() {
gFocus = true;
gUnread = 0;
updateTitle();
});
core.register("blur", function() {
gFocus = false;
});
var gPingCount = 0;
function schedulePing(socket) {
setTimeout(function() {
socket.write("<iq type='get' id='ping" + (++gPingCount) + "'><ping xmlns='urn:xmpp:ping'/></iq>");
schedulePing(socket);
}, 60000);
}
terminal.split([
{type: "horizontal", children: [
{name: "terminal", grow: 1},
{name: "users", grow: 0},
]},
]);
terminal.select("terminal");
var gPresence = {};
function refreshUsers() {
terminal.select("users");
terminal.clear();
for (var i in gPresence) {
terminal.print(i);
}
terminal.select("terminal");
}
function connect(socket, userName, password) {
var kTrustedCertificate = "-----BEGIN CERTIFICATE-----\n" +
"MIICqjCCAhOgAwIBAgIJAPEhMguftPdoMA0GCSqGSIb3DQEBCwUAMG4xCzAJBgNV\n" +
"BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEPMA0GA1UEBwwGQXVzdGluMRswGQYDVQQK\n" +
"DBJUcm91YmxlIEltcGFjdCBMTEMxITAfBgNVBAMMGGphYmJlci50cm91YmxlaW1w\n" +
"YWN0LmNvbTAeFw0xNDEyMjYwMzU5NDRaFw0yNDEyMjMwMzU5NDRaMG4xCzAJBgNV\n" +
"BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEPMA0GA1UEBwwGQXVzdGluMRswGQYDVQQK\n" +
"DBJUcm91YmxlIEltcGFjdCBMTEMxITAfBgNVBAMMGGphYmJlci50cm91YmxlaW1w\n" +
"YWN0LmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAueniASgCpF7mQFGt\n" +
"TycOhMt9VMetFwwDkwVglvO+VKq8JWxWkJaCWm8YYacG6+zn4RlV3zVQhrAcReTU\n" +
"pPQAe+28wJdqVt/HPyfcwJtLKUEL7Nk5N8mY2s6yyBVvMn9e7Yt/fnv7pOCpcmBi\n" +
"kuLlwSGEfMnDskt8kH4coidP4w0CAwEAAaNQME4wHQYDVR0OBBYEFOztZhuuqXrN\n" +
"yUnPo/9aoNNb/o2CMB8GA1UdIwQYMBaAFOztZhuuqXrNyUnPo/9aoNNb/o2CMAwG\n" +
"A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAgK/7yoGEHeG95i6E1A8ZBkeL\n" +
"monKMys3RxnJciuFdBrUcvymsgOTrAGvatPXatNbHQ/eY8LnkKHtf0pCCs0B/xST\n" +
"DTO3KdlNCXApMUieFPjVggRzikbmbPCvtTt2BzqQKzVqubf9eM+kbsD7Pkgycm5+\n" +
"q46TZws0oz5lAvklIgo=\n" +
"-----END CERTIFICATE-----";
var resource = "tildefriend" + core.user.index;
socket.connect("jabber.troubleimpact.com", 5222).then(function() {
var parse = new XmlStanzaParser(1);
socket.write("<?xml version='1.0'?>");
socket.write("<stream:stream to='jabber.troubleimpact.com' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>");
var started = false;
var authenticated = false;
socket.onError(function(error) {
terminal.print("SOCKET ERROR");
terminal.print(JSON.stringify(error));
terminal.print(error);
terminal.print(error.message);
});
socket.read(function(data) {
try {
gRecent.push(data);
while (gRecent.length > 10) {
gRecent.shift();
}
if (data === undefined) {
terminal.print(JSON.stringify(data));
terminal.print("Disconnected.");
terminal.print("Recent data:");
for (let i = 0; i < gRecent.length; i++) {
terminal.print(JSON.stringify(gRecent[i]));
}
return;
}
parse.parse(data).forEach(function(stanza) {
if (stanza.name == "stream:features") {
if (!started) {
socket.write("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
} else if (!authenticated) {
socket.write("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>");
} else {
socket.write("<iq type='set' id='bind0'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>" + resource + "</resource></bind></iq>");
}
} else if (stanza.name == "proceed") {
if (!started) {
started = true;
socket.addTrustedCertificate(kTrustedCertificate);
socket.startTls().then(function() {
parse.reset();
socket.write("<stream:stream to='jabber.troubleimpact.com' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>");
}).catch(function(e) {
terminal.print("TLS FAILED: " + e);
});
}
} else if (stanza.name == "success") {
authenticated = true;
socket.write("<?xml version='1.0'?>");
socket.write("<stream:stream to='jabber.troubleimpact.com' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>");
parse.reset();
} else if (stanza.name == "iq") {
if (stanza.attributes.id == "bind0") {
socket.write("<iq type='set' id='session0'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>");
} else if (stanza.attributes.id == "session0") {
socket.write("<presence to='chadhappyfuntime@conference.jabber.troubleimpact.com/" + userName + "'><priority>1</priority><x xmlns='http://jabber.org/protocol/muc'/></presence>");
core.register("onInput", function(input) {
socket.write("<message type='groupchat' to='chadhappyfuntime@conference.jabber.troubleimpact.com'><body>" + xmlEncode(input) + "</body></message>");
});
schedulePing(socket);
} else if (stanza.attributes.id == "ping" + gPingCount) {
// Ping response.
} else {
terminal.print(JSON.stringify(stanza));
}
} else if (stanza.name == "message") {
printMessage(stanza);
if (!gFocus) {
++gUnread;
updateTitle();
}
} else if (stanza.name == "challenge") {
var challenge = Base64.decode(stanza.text);
var parts = challenge.split(',');
challenge = {};
for (var i = 0; i < parts.length; i++) {
var equals = parts[i].indexOf("=");
if (equals != -1) {
var key = parts[i].substring(0, equals);
var value = parts[i].substring(equals + 1);
if (value.length > 2 && value.charAt(0) == '"' && value.charAt(value.length - 1) == '"') {
value = value.substring(1, value.length - 1);
}
challenge[key] = value;
}
}
if (challenge.rspauth) {
socket.write("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>");
} else {
var realm = "jabber.troubleimpact.com";
var cnonce = Base64.encode(new Date().toString());
var x = userName + ":" + realm + ":" + password;
var y = raw_md5(x);
var a1 = y + ":" + challenge.nonce + ":" + cnonce;
var digestUri = "xmpp/" + realm;
var a2 = "AUTHENTICATE:" + digestUri;
var ha1 = md5(a1);
var ha2 = md5(a2);
var nc = "00000001";
var kd = ha1 + ":" + challenge.nonce + ":" + nc + ":" + cnonce + ":" + challenge.qop + ":" + ha2;
var response = md5(kd);
var value = Base64.encode('username="' + userName + '",realm="' + realm + '",nonce="' + challenge.nonce + '",cnonce="' + cnonce + '",nc=' + nc + ',qop=' + challenge.qop + ',digest-uri="' + digestUri + '",response=' + response + ',charset=utf-8');
socket.write("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" + value + "</response>");
}
} else if (stanza.name == "presence") {
var name = stanza.attributes.from.split('/', 2)[1];
if (stanza.attributes.type == "unavailable") {
terminal.print(name + " has left the room.");
delete gPresence[name];
} else {
if (!gPresence[name]) {
terminal.print(name + " has joined the room.");
}
gPresence[name] = stanza;
}
refreshUsers();
} else {
terminal.print(data);
}
});
} catch (error) {
terminal.print("ERROR: " + error);
terminal.print("ERROR: " + JSON.stringify(error));
terminal.print("ERROR: " + error.message);
}
});
}).catch(function(e) {
terminal.print("connect failed: ", e);
});
}