Files
MosisService/APP_SPECS.md

15 KiB

Mosis App Specifications

This document defines the complete specification for Mosis apps, including package format, manifest schema, permissions, and available APIs.


Package Format

Mosis apps are distributed as .mosis files, which are signed ZIP archives.

File Structure

com.developer.appname-1.0.0.mosis
├── manifest.json           # App metadata (required)
├── META-INF/
│   ├── MANIFEST.MF         # SHA-256 hashes of all files
│   ├── CERT.PEM            # Developer's public key
│   └── CERT.SIG            # Ed25519 signature of MANIFEST.MF
├── assets/
│   ├── main.rml            # Entry point (required)
│   ├── screens/            # Additional RML screens
│   ├── styles/             # RCSS stylesheets
│   └── scripts/            # Lua scripts
├── icons/
│   ├── icon-32.png         # 32x32 icon
│   ├── icon-64.png         # 64x64 icon
│   └── icon-128.png        # 128x128 icon
└── locales/                # Optional i18n files
    ├── en.json
    └── es.json

Size Limits

Limit Value
Max package size 50 MB
Max individual file 10 MB
Max files count 1000
Max path length 256 chars
Max manifest size 64 KB

Allowed File Types

Category Extensions
UI .rml
Styles .rcss
Scripts .lua
Images .png, .jpg, .jpeg, .tga, .webp
Fonts .ttf, .otf
Data .json
Audio .ogg, .wav, .mp3

Forbidden: .exe, .dll, .so, .dylib, .sh, .bat, .ps1, .py, .js, nested archives


Manifest Schema

Required Fields

{
  "$schema": "https://mosis.dev/schemas/manifest-v1.json",
  "id": "com.developer.appname",
  "name": "My App",
  "version": "1.0.0",
  "version_code": 1,
  "entry": "assets/main.rml",
  "min_mosis_version": "1.0.0"
}

Full Schema

{
  "$schema": "https://mosis.dev/schemas/manifest-v1.json",

  "id": "com.developer.appname",
  "name": "My App",
  "version": "1.0.0",
  "version_code": 1,

  "description": "A short description of the app",
  "entry": "assets/main.rml",

  "author": {
    "name": "Developer Name",
    "email": "dev@example.com",
    "url": "https://developer.com"
  },

  "permissions": [
    "storage",
    "network.internet",
    "camera"
  ],

  "icons": {
    "32": "icons/icon-32.png",
    "64": "icons/icon-64.png",
    "128": "icons/icon-128.png"
  },

  "min_mosis_version": "1.0.0",
  "target_mosis_version": "1.2.0",

  "category": "utilities",
  "tags": ["productivity", "tools"],

  "orientation": "portrait",
  "background_color": "#FFFFFF",

  "locales": ["en", "es", "fr"],
  "default_locale": "en",

  "network": {
    "allowed_domains": [
      "api.example.com",
      "*.cdn.example.com"
    ],
    "allow_http": false,
    "max_connections": 10
  }
}

Field Reference

Field Type Required Description
id string Yes Unique package ID (reverse domain: ^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$)
name string Yes Display name (max 30 chars)
version string Yes Semantic version (X.Y.Z)
version_code integer Yes Incremental build number (must increase with each release)
entry string Yes Path to entry RML file
min_mosis_version string Yes Minimum Mosis version required
description string No Short description (max 80 chars)
author object No Author information
permissions array No Required permissions
icons object No Icon paths by size
category string No App store category
tags array No Searchable tags
orientation string No portrait, landscape, any
background_color string No Hex color for loading screen
locales array No Supported locale codes

Signing

Algorithm

  • Key type: Ed25519
  • Signature size: 64 bytes
  • Hash: SHA-256 for file manifests

MANIFEST.MF Format

Manifest-Version: 1.0
Created-By: mosis-cli 1.0.0

Name: manifest.json
SHA-256-Digest: base64encodedHash==

Name: assets/main.rml
SHA-256-Digest: base64encodedHash==

Name: assets/scripts/main.lua
SHA-256-Digest: base64encodedHash==

Signing Flow

  1. Generate MANIFEST.MF with SHA-256 hash of each file
  2. Sign MANIFEST.MF with developer's Ed25519 private key
  3. Store signature in META-INF/CERT.SIG
  4. Include developer's public key in META-INF/CERT.PEM

Verification Flow

  1. Extract META-INF/MANIFEST.MF
  2. Verify signature using CERT.PEM
  3. Verify CERT.PEM is registered with developer account
  4. Verify each file hash matches MANIFEST.MF

Permissions

Apps must declare required permissions in the manifest. Some permissions are auto-granted, others require user approval.

Permission Categories

Permission Description Risk Level
storage App-private file access Normal (auto-granted)
network.internet HTTP requests Dangerous
network.websocket WebSocket connections Dangerous
camera Camera access Dangerous
microphone Audio recording Dangerous
location.coarse Approximate location Dangerous
location.fine Precise GPS location Dangerous
contacts.read Read contacts Dangerous
contacts.write Modify contacts Dangerous
bluetooth Bluetooth access Dangerous
sensors.body Body sensors (heart rate) Dangerous
clipboard.read Read clipboard Dangerous
clipboard.write Write to clipboard Dangerous
system.notifications Show notifications Normal

Requesting Permissions

-- Request a dangerous permission (shows system dialog)
local granted = permissions.request("camera")

if granted then
    -- Permission granted, can now use camera API
    camera.start()
else
    -- Permission denied, show fallback UI
    showPermissionDeniedMessage()
end

User Gesture Requirements

Dangerous hardware permissions (camera, microphone, location) require a recent user gesture (within 5 seconds) before the permission request is shown.


Runtime Limits

Apps run in a sandboxed Lua environment with resource limits.

Memory Limits

Resource Limit
Total memory 16 MB
Max string size 1 MB
Max table entries 100,000

CPU Limits

Context Instruction Limit Approx Time
Event handler 1,000,000 ~10ms
Init/load 10,000,000 ~100ms
Timer callback 500,000 ~5ms

Storage Limits

Resource Limit
App storage 100 MB
Cache storage 50 MB
Single file 50 MB
Files per directory 10,000
SQLite database 50 MB

Lua APIs

Safe Built-in Globals

-- Retained from standard Lua
print           -- Redirected to system logger
type, tonumber, tostring
pairs, ipairs, next
pcall, xpcall
assert, error
select, unpack
string.*        -- Except string.dump
table.*
math.*
utf8.*

Removed Globals (Security)

-- These do NOT exist in the sandbox
dofile, loadfile, load, loadstring
rawget, rawset, rawequal, rawlen
getmetatable, setmetatable
collectgarbage
os, io, debug, package, require
ffi, jit, newproxy

Storage API

-- App-private storage (auto-granted)
storage.write("data/config.json", json.encode(config))
local data = storage.read("data/config.json")
local exists = storage.exists("data/config.json")
storage.delete("data/config.json")
storage.mkdir("data/subfolder")
local files = storage.list("data/")

-- Cache storage (can be cleared by system)
storage.write("cache/temp.dat", data)

-- Temp storage (cleared on app stop)
storage.write("temp/session.dat", data)

Database API (SQLite)

-- Open database (creates if not exists)
local db = database.open("mydata.db")

-- Execute SQL
db:exec([[
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY,
        name TEXT NOT NULL,
        email TEXT UNIQUE
    )
]])

-- Prepared statements with parameters
local stmt = db:prepare("INSERT INTO users (name, email) VALUES (?, ?)")
stmt:bind("John Doe", "john@example.com")
stmt:exec()

-- Query with results
local rows = db:query("SELECT * FROM users WHERE name LIKE ?", "%John%")
for _, row in ipairs(rows) do
    print(row.id, row.name, row.email)
end

-- Close when done
db:close()

Network API

HTTP Requests

-- Requires "network.internet" permission
local response = network.request({
    url = "https://api.example.com/data",
    method = "POST",  -- GET, POST, PUT, DELETE, PATCH
    headers = {
        ["Content-Type"] = "application/json",
        ["Authorization"] = "Bearer " .. token
    },
    body = json.encode({key = "value"}),
    timeout = 30000  -- milliseconds
})

-- Response structure
if response.error then
    print("Error:", response.error)
else
    print("Status:", response.status)      -- 200, 404, etc.
    print("Body:", response.body)          -- Response body string
    -- response.headers: table of headers
end

WebSocket

-- Requires "network.websocket" permission
local ws = network.websocket("wss://api.example.com/ws")

ws:on("open", function()
    ws:send(json.encode({type = "hello"}))
end)

ws:on("message", function(data)
    local msg = json.decode(data)
    print("Received:", msg.type)
end)

ws:on("close", function(code, reason)
    print("Closed:", code, reason)
end)

ws:on("error", function(err)
    print("Error:", err)
end)

-- Send data
ws:send("Hello, server!")

-- Close connection
ws:close()

Camera API

-- Requires "camera" permission + user gesture
local granted = permissions.request("camera")
if not granted then return end

-- Start camera preview
camera.start({
    facing = "back",      -- "back" or "front"
    resolution = "720p",  -- "480p", "720p", "1080p"
    target = "preview"    -- Element ID to render preview
})

-- Capture photo
camera.capture(function(image)
    -- image.data: base64 encoded JPEG
    -- image.width, image.height: dimensions
    storage.write("photos/capture.jpg", base64.decode(image.data))
end)

-- Stop camera
camera.stop()

Microphone API

-- Requires "microphone" permission + user gesture
local granted = permissions.request("microphone")
if not granted then return end

-- Start recording
microphone.start({
    format = "wav",       -- "wav" or "ogg"
    sample_rate = 44100,
    channels = 1
})

-- Stop and get recording
microphone.stop(function(audio)
    -- audio.data: base64 encoded audio
    -- audio.duration: length in seconds
    storage.write("recordings/voice.wav", base64.decode(audio.data))
end)

Location API

-- Requires "location.fine" or "location.coarse" permission
local granted = permissions.request("location.fine")
if not granted then return end

-- Get current location
location.get(function(pos)
    if pos.error then
        print("Error:", pos.error)
    else
        print("Lat:", pos.latitude)
        print("Lon:", pos.longitude)
        print("Accuracy:", pos.accuracy)  -- meters
    end
end)

-- Watch location changes
local watch_id = location.watch(function(pos)
    print("Location updated:", pos.latitude, pos.longitude)
end, {
    interval = 5000,  -- minimum time between updates (ms)
    distance = 10     -- minimum distance change (meters)
})

-- Stop watching
location.unwatch(watch_id)

Sensors API

-- Motion sensors (no permission required)
sensors.accelerometer.start(function(data)
    print("Accel:", data.x, data.y, data.z)
end, { interval = 100 })  -- ms

sensors.gyroscope.start(function(data)
    print("Gyro:", data.x, data.y, data.z)
end)

sensors.magnetometer.start(function(data)
    print("Mag:", data.x, data.y, data.z)
end)

-- Stop sensor
sensors.accelerometer.stop()

-- Body sensors (requires "sensors.body" permission)
local granted = permissions.request("sensors.body")
if granted then
    sensors.heartrate.start(function(data)
        print("Heart rate:", data.bpm)
    end)
end

Timer API

-- One-shot timer
local timer_id = timer.after(1000, function()
    print("1 second elapsed")
end)

-- Repeating timer
local interval_id = timer.every(500, function()
    print("Every 500ms")
end)

-- Cancel timers
timer.cancel(timer_id)
timer.cancel(interval_id)

JSON API

-- Encode Lua table to JSON string
local json_str = json.encode({
    name = "John",
    age = 30,
    active = true,
    tags = {"lua", "mosis"}
})

-- Decode JSON string to Lua table
local data = json.decode(json_str)
print(data.name)  -- "John"

Crypto API

-- Hashing
local hash = crypto.sha256("hello world")
local hash = crypto.sha512("hello world")
local hash = crypto.md5("hello world")  -- Not for security

-- HMAC
local mac = crypto.hmac("sha256", "secret key", "message")

-- Random bytes
local random = crypto.random(32)  -- 32 random bytes (base64)

-- UUID generation
local uuid = crypto.uuid()  -- "550e8400-e29b-41d4-a716-446655440000"

Navigation

-- Navigate to another screen
navigateTo("screens/settings.rml")

-- Go back
goBack()

-- Go to home screen
goHome()

-- Get current screen
local screen = getCurrentScreen()

App Categories

Valid categories for the category manifest field:

Category Description
utilities Tools and utilities
productivity Work and productivity
communication Messaging and social
entertainment Games and media
lifestyle Health, fitness, lifestyle
finance Banking and finance
education Learning and reference
news News and magazines
travel Travel and navigation
shopping Shopping and commerce

Error Handling

-- Safe API calls with pcall
local ok, result = pcall(function()
    return network.request({url = "https://api.example.com"})
end)

if not ok then
    print("Error:", result)
end

-- API errors return error field
local response = network.request({url = "https://invalid"})
if response.error then
    print("Network error:", response.error)
end

Best Practices

Do

  • Request only the permissions you need
  • Handle permission denials gracefully
  • Cache network responses when appropriate
  • Clean up timers and resources when done
  • Use try/catch patterns for error handling
  • Store sensitive data encrypted

Don't

  • Request permissions before they're needed
  • Store sensitive data in plain text
  • Make unnecessary network requests
  • Hold camera/microphone open unnecessarily
  • Create infinite loops or excessive recursion
  • Assume network is always available

References