In a nutshell, tinSSB miniApps are “Javascript+HTML bundles” that can be dynamically loaded and executed inside the tinySSB Android app. This implies that miniApps are isolated from low level implementation details, making them especially well suited for first explorations of the tinySSB environment and for rapid prototyping before porting a new miniApp’s logic to embedded devices which lack JS and HTML support. The isolation property also permits to run (i.e., test) a miniApp inside an emulation environment written for the Chrome Web browser.
Tremola/tinySSB supports two environments for running miniApps:
- Android app (Tremola) – for running on an Android device;
https://github.com/tinySSB/android-app - tremola4chrome – for testing in a desktop browser
https://github.com/tinySSB/tremola4chrome
Throughout this document we show the concepts of a miniApp with an example of the “Wuff” miniApp. This miniApp aims to provide users with a single button that, when clicked, sends the word “Wuff” to the screens of all users who have the miniApp launched.
The main task of a miniApp, in general, is to: (a) receive new log entries, (b) display them on the screen, (c) register user input and (d) if necessary, create new log entries. Applied to our Wuff miniApp, this means that we must intercept clicks (c), write a click event into the append-only log (d) and show incoming click events on the screen (a,b).
The first step is to define the manifest.json file with the essential miniApp information. Such information includes the name, description, path to icon, function to be called when a user initializes the miniApp, id, and paths to CSS, html and JavaScript files.
For our Wuff miniApp we have chosen the following file system layout (all files, including the manifest.json, will be inside the wuff directory):
wuff
├── manifest.json
├── assets
│ └── wuff.svg
├── resources
│ ├── wuff.css
│ └── wuff.html
└── src
└── wuff.js
A scenario should be defined for every screen that a miniApp would have; in our Wuff miniApp, there is only one such screen. For each screen we define how the menu should look like as well as the elements that we expect to be displayed. This information will also appear in the manifest file.
The next step is to create an html file (and if necessary CSS file) in the same path as listed in the manifest.json file. The html file needs to include all visual elements that belong to the application (buttons, divisions..etc).
Afterwards, we create the JavaScript file that needs to:
- Define the miniApp as its own window, using window.miniApps[miniAppID]
- Define a handleRequest function to be able to react to input received from the backend. Such input comes with a command, such as “onBackPressed” or “incoming_notifcation”, the miniApp should have predefined steps to be executed for each command it expects to receive. All miniApps should expect to receive incoming_notication as that is the command that is specific to the app’s own log entries.
- Define the function that will be called when the user launches the miniApp (as defined in the manifest.json file)
In order to write a log entry, we use of the writeLogEntry(json_string)function
where json_string is the stringified version of a javascript object that the miniApp wants to write into the append-only log. Note that the ID of the current MiniApp is automatically added to that log entry by the miniApp environment.
On top of that, for our Wuff miniApp, we define two functions; the first function registerWuff is called when the button is pressed by a user - it will write a log entry to indicate that event. The second function is called handleWuff and will be invoked when the tinySSB client receives a Wuff log entry, which will be forwarded to the Wuff miniApp. The result of executing the handleWuff function is the display of the Wuff prompt on the screen.
Having created all parts of our miniApp, the next step is to install it in one of the two environments. There are two options for that: the first option involves the android App which is where the miniApps are intended to be used for “production”. The other option is in the chrome environment where it is intended to test the miniApp before releasing it.
To test our miniApp in the Chrome-based environment (tremola4chrome):
- Locate the miniApps folder in the tremola4chrome project.
- Upload the new miniApp folder into that directory.
Once placed, the miniApp will be available for use within the tremola4chrome interface.
miniApps on Android reside in the TinySSB app’s internal data directory. To add a miniApp, follow these steps:
- Access the device’s data folder using Android Studio:
- Connect your device via USB or WiFi.
- Open the Device Explorer tab.
- Navigate to:
/data/data/nz.scuttlebutt.tremolavossbol/miniApps
- Upload the miniApp folder (a directory named after the miniApp) to the above miniApps directory.
- The Android app will automatically detect and include the new miniApp in the user interface.
In this appendix we list all required files associated with the Wuff miniApp.
manifest.json:
{
"name": "Wuff",
"description": "Wuff is a simple application that allows users to express their emotions through a button click.",
"icon": "assets/wuff.svg",
"init": "initWuff()",
"id": "wuff",
"cssFile": "resources/wuff.css",
"scripts": [
"src/wuff.js"
],
"htmlFile": "resources/wuff.html",
"scenario": {
"wuff-screen": {
"menu": {
"Settings": "menu\_settings",
"About": "menu\_about"
},
"display": [
"div:back",
"div:wuff-screen"
]
}
}
}
wuff.html:
<div id="div:wuff-screen" style="display: none;">
<div id="wuff-title">
Wuff MiniApp
</div>
<div id="div:wuff-prompt">
Wuff!
</div>
<button style="height: 30px;" onclick="registerWuff()">Wuff</button>
</div>
wuff.js:
window.miniApps["wuff"] = {
handleRequest: function(command, args) {
console.log("Wuff handling request:", command);
switch (command) {
case "onBackPressed":
quitApp();
break;
case "incoming_notification":
console.log("Wuff incoming_notification:", JSON.stringify(args, null, 2));
handleWuff(args.args);
break;
}
return "Response from Wuff";
}
};
function initWuff() {
console.log("Initializing Wuff app...");
}
function handleWuff(raw) {
const args = typeof raw === "string" ? JSON.parse(raw) : raw;
console.log("Handling Wuff args:", args);
console.log("Wuff Type: " + args[0].type);
if (args[0].type === "Wuff") {
showWuffPrompt();
}
}
function showWuffPrompt() {
const wuffPrompt = document.getElementById('div:wuff-prompt');
wuffPrompt.style.opacity = 1;
setTimeout(() => {
wuffPrompt.style.opacity = 0;
}, 1000);
}
function registerWuff() {
let json = { type: 'Wuff'};
writeLogEntry(JSON.stringify(json));
}
wuff.css
#div\:wuff-screen {
position: absolute;
top: 50%;
left: 50%;
background: rgba(255, 255, 255, 0.9);
border-radius: 12px;
padding: 24px;
text-align: center;
width: 30vw;
}
#wuff-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 12px;
}
#div\:wuff-prompt {
font-size: 16px;
opacity: 0;
transition: opacity 0.5s ease;
margin-bottom: 16px;
}
#div\:wuff-screen button {
color: black;
}
wuff.svg
<svg
class="svg-icon"
style="width: 1em; height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M927.3 453c-21.1-55.3-51.7-109.1-88.4-155.5-41.1-51.9-88.3-93.2-140.1-122.6-58.6-33.2-121.3-50.1-186.4-50.1-65.9 0-129.2 16.7-188.1 49.7-52.2 29.2-99.5 70.3-140.7 122-36.7 46.1-67.3 99.7-88.3 154.9-20.3 53.1-31 105.4-31 151.1 0 41.7 9.2 76.8 26.5 101.5 19.6 28 49.4 42.8 86.1 42.8 27.4 0 51-6.6 69.7-18.6 24.1 51.8 75.7 85 132.9 85 17.8 0 34.9-3.2 50.7-9.1v14.1c0 44.1 35.9 80 80 80s80-35.9 80-80v-14.8c16.3 6.3 33.9 9.8 52.3 9.8 57.5 0 109.1-33.6 133-86 18.9 12.6 43 19.5 71.1 19.5 36.4 0 66-14.6 85.4-42.2 17.2-24.4 26.2-59.1 26.2-100.3 0.2-45.5-10.6-97.9-30.9-151.2zM560.2 818.4c0 27.6-22.4 50-50 50s-50-22.4-50-50V789c13.8-9.2 26-20.7 36-33.9v45.8c0 8.3 6.7 15 15 15s15-6.7 15-15v-45.7c9.5 12.6 21 23.6 34 32.5v30.7z m-49.1-167.6h-0.2l-73-40.8 20.8-29.8h104.4l20.8 29.8-72.8 40.8z m396.5 36.6c-13.8 19.6-34.2 29.5-60.9 29.5-26.1 0-47.8-7.2-62.9-20.9-15.7-14.2-23.6-34.7-23.6-60.8 0-57.3-0.4-130.2-0.7-183.5-0.2-34.3-0.4-61.4-0.4-75 0-8.3-6.7-15-15-15s-15 6.7-15 15c0 13.7 0.2 40.8 0.4 75.1 0.3 53.2 0.7 126.2 0.7 183.3 0 28.5 8.1 52.8 22.7 71.4-1.2 1.3-2.2 2.8-2.9 4.5-18.1 43.9-60.3 72.3-107.4 72.3-60.6 0-110.5-47.1-115.9-107l74.1-41.5c6.8-3.8 11.7-10.4 13.5-18 1.7-7.6 0.2-15.6-4.3-22L587 562c-5.2-7.4-13.7-11.8-22.7-11.8H457.6c-9.1 0-17.6 4.4-22.7 11.8l-23 32.9c-4.5 6.4-6 14.4-4.3 22 1.7 7.6 6.6 14.2 13.4 18l74.7 41.9c-5.5 59.7-55.5 106.6-116.2 106.6-47 0-89.2-28.2-107.5-71.8-0.6-1.4-1.4-2.7-2.3-3.8 15.3-18.7 23.7-43.4 23.7-72.6 0-57.2 0.4-130.1 0.7-183.3 0.2-34.3 0.4-61.5 0.4-75.1 0-8.3-6.7-15-15-15s-15 6.7-15 15c0 13.6-0.2 40.7-0.4 75-0.3 53.3-0.7 126.2-0.7 183.5 0 26.1-8 46.6-23.6 60.8-15.1 13.7-36.8 20.9-62.9 20.9-26.9 0-47.6-10.1-61.5-30-13.8-19.6-21-48.8-21-84.3 0-42.2 10-90.7 29-140.5 20-52.4 48.9-103.1 83.8-146.9 38.7-48.7 83.1-87.2 131.9-114.5 54.4-30.5 112.7-45.9 173.4-45.9 59.9 0 117.6 15.5 171.6 46.2 48.5 27.5 92.7 66.2 131.4 115.1 68.7 86.8 113 199.9 113 288.2-0.1 35-7.3 63.7-20.9 83z"
/>
<path
d="M383.2 480.9m-25 0a25 25 0 1 0 50 0 25 25 0 1 0-50 0Z"
/>
<path
d="M639.2 480.9m-25 0a25 25 0 1 0 50 0 25 25 0 1 0-50 0Z"
/>
</svg>