JSos Tutorial

This is a first draft. This tutorial will cover some key areas.

Applications you can launch now

Two main demo applications exist at the moment. One is a chat service and the other is a notebook. New users are currently configured to be set up to run the chat service by default but we will go over how to launch it from scratch.

There are three files you can edit which control what you end up seeing when you press the run button. These are global, init, and build.

To edit them just click their corrisponding button at the top.

Place in the build file

chat

Then place in the init file

startChat();

Click run and you should be in a room where you can chat with other people. Everything you see, including the server orchistrating the chat was written inside the editor you were just interacting with.

The other application you can run right now is a notebook manager.
build

notebook

init

startNotebook();

This application showcases the ability for software runing on the platform to save data, and the chat application illustrates the peer facilitation resources. In the case of the chat application you are establishing a connection with another user called chat-server which then can orchistrate more customized interactions between peers.

Writing your own basic appliation

This will likely be the most boring part of this tutorial. This is the helloworld of the platform and we are going to make it pretty boring.

In globals perpare this function declaration

function start() {
 if(Math.random()>0.5) {
  alert('Hello World');
 }
 else {
  alert('Ahoy, hoy!');
 }
}

In build simply list your own username on one line.

In init write

start();

Well that was pretty boring. In fact it was so boring you might be turned off of this project. Why did we write code in multiple places and mess with build in order to do that? Couldn’t I just do that in codepen.io? How does this relate to being able to connect to other users on the platform and the ability for users to have control over the code they run? Well, we will talk about that in our next section.

Philosophy of the platform

I ranted here. Come back when I write it better.

Importing other users’ code

This part is so simple I was tempted to include it in the hello world.

An #import declaration is provided to the globals file. It works exactly like you would expect it to.

#import getscript
function myStart () {
 getscript('https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js',()=>{
    $('#button').click(()=>{
       alert('mmm, absolutely delicious.'); 
    });
 });
}

The getscript user also provides a getscripts function that takes an array of urls.

Existing resources to import from

chat, notebook, monaco, getscript, chat-server.

Soon to be added, if not within an hour of me posting this will be,

parallel, peers, keys.

Pulling in code for elsewhere on the net

We did cover that in the importing section, so you might want to review that.

Some external libraries I recommend are peerjs, jquery, jss, mustache, and pug.

Overriding functions in an already existing app

This can be done simply by redeclaring a function. There are two ways you can include the original code. You can place it in the build file. Items with higher preference should be placed lower. Or you can use the #import declaration.

#import chat

function remove_user_from_ui(user) {
 $('#joined #user_'+user).remove();
 add_msg_to_ui('server',`${user} hast left the chat`);  //This is new
}

If you explicitly invoke chat in the build it’s functions will recieve more weight when resolving clobbers.

Other options for overriding chat could include changing the css, providing a direct messaging capability, prompting the user with an option to select another server beside chat-server, picking a different chat-server user for your friends to use entirely, providing a media embed, adding a stored block list using user storage.

Services provided by the back end for apps

There are two main ones currently. They are peer registration and a key value store.

POST /peer/register/{id}
POST /peer/getid/{user}

This can be used for registering how you can be contacted via peerjs's webRTC broker.
There is no reason you have to use webRTC unless you want to run some sort of peered application. If you do you can use the above methods to tell others where you are in an authenticatable way.

Other data could be stored there as well. Anything you want other users to know about you.

Next is the persistence system.

Obviously there are other forms of persistence and good applications could be made out of any of them. localStorage, sessionStorage, webSQL, cookies. But if you want something that sticks to the user when they hop on another browser:

POST /skey/set/{key}
...POST DATA
/skey/get/{key}

It is best to predicate your key with your application name to not clobber other data.

You can also store data in a way where others can see it with pkey, and ppkey. pkey means that other logged in users can lookup the key:value pair. ppkey means that the whole world can look up the key:value pair.

POST /pkey/set/{key}
...POST DATA
/pkey/get/{user}/{key}

ppkey works the same way except it is visable to people who are not logged it, which gets helpful with fset.

fset allows the storage of files rather that just strings. It has a seperate key space. To upload a file you pass a multi-part post with a file in the name "file".

You can either make the set url the action attribute of a form or you can use a bit of javascript.


<form method="POST" enctype="multipart/form-data" action="/ppkey/fset/{key}">
<input type="file" name="file"></input>
<input type="submit"></input>
</form>
/ppkey/fget/{user}/{key}

var fd = new FormData($('form')[0]);
$.post({
 url:'/skey/fset/place.jpg',
 data:fd
},()=>{
 alert('Your file is here: /skey/fget/place.jpg');
});

When uploading your own bytes make sure to set the mime-type.


 var fd = new FormData();
 fd.append('file',new Blob(['some text']),{type:'application/json'},'unusedname');

For more examples look at the Upload user.

We can also upload by passing a url

POST /ppkey/fseturl/{key}
...url
/ppkey/fget/{user}/{key}

The addition of the user is needed because it is public. When looking up an skey the user is assumed. Specifying the user in the lookup url is needed for pkey and ppkey.

Making a peer oriented application

I’m going to handle this one later. One could look at /outputscript.js when chat is in the build and figure it out. I still want to make an easy example. One doesn’t necissarily need a cental server to run all the time.

If you do want to do it with a central server and you don’t want to leave a tab open for it I should hand you a bit of code at least to run it in the background on a vps.

var puppeteer = require('puppeteer');

async function runuser(user,pass) {
 const browser = await puppeteer.launch({args:['--no-sandbox','--disable-setuid-sandbox']});
 const loginpage = await browser.newPage();
 /*loginpage.on('console', msg => {
  for (let i = 0; i < msg.args().length; ++i) {
   console.log(`loginpage ${i}: ${msg.args()[i]}`);
  }
 });*/
 await loginpage.goto('https://js.lifelist.pw',{waitUntil:'networkidle2'});
 await loginpage.type('#username',user);
 await loginpage.type('#password',pass);
 await loginpage.$eval('form', form => form.submit(),{waitUntil:'newtworkidle2'});
 const runpage = await browser.newPage();
 runpage.on('console', msg => {
  for (let i = 0; i < msg.args().length; ++i) {
   console.log(`runpage ${i}: ${msg.args()[i]}`);
  }
 });
 await runpage.goto('https://js.lifelist.pw/run',{waitUntil:'networkidle2'});
 loginpage.close();
 //console.log(await runpage.content());
 //await loginpage.screenshot({path: '../../src/jsos/static/me.png'});
 //await browser.close();
}

Sharing your appliction with /app

So obviously you wouldn’t want every user to have to configure themselves every time they want to run a different app. With /app we can share configurations and launch any app (or configuration thereof) from any user.

Try it now:

https://js.lifelist.pw/app/wchat <-- A websocket version of chat.
https://js.lifelist.pw/app/notebook

When a user visit /app/{yourusername} they will run the same code you would if you hit run. If they sign up in that process they will automatically be configured to run it again if they hit the run button making it easier for them to visit the domain again and run your app.

When users visit the site for the first time without /app they are simply configured to run chat.

The key thing to remember when publishing an application is to edit the init and build so it works from the run button. We spend most of our time editing globals so it is easy to forget. There are defaults in your build for running another app which users will then run if you don’t edit them.

WebSocket messaging

Besides helping peers find eachother with WebRTC the server can act as a WebSockets intermediary.

We open a websockets connection to the url /peerws. We send it JSON of the form

{"to":"targetuser","msg":"Message to send"}

If sending to mutlple people an array is prefered. Also it is ok to send an object as the message.

{"to":["user1","user2"],"msg":{"type":"video","url":"https://cdn.vid.site/funny.mp4"}}

The server will respond with any users it did not send to. If you provide a nonce you will get it back.

{"to":"goneuser","msg":"Hi","nonce":"uuid_adijadkalmmej0923jokqm0fa"}
{"failedtosend":["goneuser"],"nonce":"uuid_adijadkalmmej0923jokqm0fa"}

Users with a websocket available will recieve a message with:

{"from":"yourusername","nonce":"ifpresent","msg":"Some message"}

Some sample code:

var ws = new WebSocket(location.protocol.replace('http','ws')+'//'+location.host+'/peerws');
ws.onerror= error=>{
 console.log("ws error",error);
});
ws.onopen= ()=>{
 ws.send(JSON.stringify({to:['myfrind'],msg:"What's going on Friday?"}));
}
ws.onmessage= wsmessage=>{
 var message_container = JSON.parse(wsmessage.data);
 var msg = message_container.msg;
 console.log(message_container.from+':',msg);
}

Announcing a “location”

Sometimes it helps to know who is in a similar “area” so you can interact with them.

In the most typical sense this would be announcing what application you are running, or which space in that application you are in, but more creative uses can be had.

This is similar to setting a peerid for use with peerjs but allows people to look users up in reverse, and limits the number of people in an area. Similar to peerid you can only have one location.

If you would like to know about users using an application above the limit it is smart to set up a peer exchange system. As long as their is a chain of peers eveyone can find each other. This is just meant as an initial peer seeding system,

var ws = new WebSocket('wss://js.lifelist.pw/peerws');
var app='wchat';
var room='moviegoats';
$.post(`/announce/${app}/${room}`);
ws.onopen= ()=>{
 $.post(`/findpeers/${app}/${room}`,peers=>{
  ws.send(JSON.stringify({to:peers,msg:'hi'}));
 });
});

Obviously that code could improve. ws could use a more event handling. We would want to say more than hi. But you get the idea.

This would work equally well with webrtc.

To see an example of peer exchanging visit /u/wchat

Run muliple users inside Firefox

It is practical for testing and development purposes to be able to run more than one user. The addon you want for that is Firefox Multi-Account-Containers.

It is smart to erase all the default categories and then name them after users as you need them. Creating them takes zero ime so just do it on demand.

This is especially useful when you add a function to your program and realize more people would want that as a resource. Without logging out you can easily create a new user and code the function, then #import that user in your code. This helps improve code reuse across your projects.