[FL-3918] Full-fledged JS SDK + npm packages (#3963)

* feat: js sdk
* refactor: move js back where it belongs
* docs: generate docs using typedoc
* feat: sdk versioning scheme
* ci: silence pvs warning
* docs: bring back old incomplete js docs
* style: readAnalog naming
* fix: rename script compatibility screens

Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
Anna Antonenko 2024-10-31 05:42:57 +03:00 committed by GitHub
parent e4c8270824
commit 85e5642b2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
62 changed files with 3150 additions and 419 deletions

3
.gitignore vendored
View File

@ -64,3 +64,6 @@ PVS-Studio.log
.gdbinit .gdbinit
/fbt_options_local.py /fbt_options_local.py
# JS packages
node_modules/

View File

@ -1,4 +1,15 @@
let tests = require("tests"); let tests = require("tests");
let flipper = require("flipper");
tests.assert_eq(1337, 1337); tests.assert_eq(1337, 1337);
tests.assert_eq("hello", "hello"); tests.assert_eq("hello", "hello");
tests.assert_eq("compatible", sdkCompatibilityStatus(0, 1));
tests.assert_eq("firmwareTooOld", sdkCompatibilityStatus(100500, 0));
tests.assert_eq("firmwareTooNew", sdkCompatibilityStatus(-100500, 0));
tests.assert_eq(true, doesSdkSupport(["baseline"]));
tests.assert_eq(false, doesSdkSupport(["abobus", "other-nonexistent-feature"]));
tests.assert_eq("flipperdevices", flipper.firmwareVendor);
tests.assert_eq(0, flipper.jsSdkVersion[0]);
tests.assert_eq(1, flipper.jsSdkVersion[1]);

View File

@ -5,7 +5,6 @@ App(
provides=[ provides=[
"updater_app", "updater_app",
"js_app", "js_app",
"js_app_start",
# "archive", # "archive",
], ],
) )

View File

@ -6,6 +6,16 @@ App(
stack_size=2 * 1024, stack_size=2 * 1024,
resources="examples", resources="examples",
order=0, order=0,
provides=["js_app_start"],
sources=[
"js_app.c",
"js_modules.c",
"js_thread.c",
"plugin_api/app_api_table.cpp",
"views/console_view.c",
"modules/js_flipper.c",
"modules/js_tests.c",
],
) )
App( App(
@ -13,6 +23,7 @@ App(
apptype=FlipperAppType.STARTUP, apptype=FlipperAppType.STARTUP,
entry_point="js_app_on_system_start", entry_point="js_app_on_system_start",
order=160, order=160,
sources=["js_app.c"],
) )
App( App(

View File

@ -19,7 +19,7 @@ eventLoop.subscribe(eventLoop.timer("periodic", 1000), function (_, _item, led,
// read potentiometer when button is pressed // read potentiometer when button is pressed
print("Press the button (PC1)"); print("Press the button (PC1)");
eventLoop.subscribe(button.interrupt(), function (_, _item, pot) { eventLoop.subscribe(button.interrupt(), function (_, _item, pot) {
print("PC0 is at", pot.read_analog(), "mV"); print("PC0 is at", pot.readAnalog(), "mV");
}, pot); }, pot);
// the program will just exit unless this is here // the program will just exit unless this is here

View File

@ -1,6 +1,8 @@
#include <core/common_defines.h> #include <core/common_defines.h>
#include "js_modules.h" #include "js_modules.h"
#include <m-array.h> #include <m-array.h>
#include <dialogs/dialogs.h>
#include <assets_icons.h>
#include "modules/js_flipper.h" #include "modules/js_flipper.h"
#ifdef FW_CFG_unit_tests #ifdef FW_CFG_unit_tests
@ -76,6 +78,12 @@ JsModuleData* js_find_loaded_module(JsModules* instance, const char* name) {
} }
mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len) { mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len) {
// Ignore the initial part of the module name
const char* optional_module_prefix = "@" JS_SDK_VENDOR "/fz-sdk/";
if(strncmp(name, optional_module_prefix, strlen(optional_module_prefix)) == 0) {
name += strlen(optional_module_prefix);
}
// Check if module is already installed // Check if module is already installed
JsModuleData* module_inst = js_find_loaded_module(modules, name); JsModuleData* module_inst = js_find_loaded_module(modules, name);
if(module_inst) { //-V547 if(module_inst) { //-V547
@ -175,3 +183,133 @@ void* js_module_get(JsModules* modules, const char* name) {
furi_string_free(module_name); furi_string_free(module_name);
return module_inst ? module_inst->context : NULL; return module_inst ? module_inst->context : NULL;
} }
typedef enum {
JsSdkCompatStatusCompatible,
JsSdkCompatStatusFirmwareTooOld,
JsSdkCompatStatusFirmwareTooNew,
} JsSdkCompatStatus;
/**
* @brief Checks compatibility between the firmware and the JS SDK version
* expected by the script
*/
static JsSdkCompatStatus
js_internal_sdk_compatibility_status(int32_t exp_major, int32_t exp_minor) {
if(exp_major < JS_SDK_MAJOR) return JsSdkCompatStatusFirmwareTooNew;
if(exp_major > JS_SDK_MAJOR || exp_minor > JS_SDK_MINOR)
return JsSdkCompatStatusFirmwareTooOld;
return JsSdkCompatStatusCompatible;
}
#define JS_SDK_COMPAT_ARGS \
int32_t major, minor; \
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_INT32(&major), JS_ARG_INT32(&minor));
void js_sdk_compatibility_status(struct mjs* mjs) {
JS_SDK_COMPAT_ARGS;
JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor);
switch(status) {
case JsSdkCompatStatusCompatible:
mjs_return(mjs, mjs_mk_string(mjs, "compatible", ~0, 0));
return;
case JsSdkCompatStatusFirmwareTooOld:
mjs_return(mjs, mjs_mk_string(mjs, "firmwareTooOld", ~0, 0));
return;
case JsSdkCompatStatusFirmwareTooNew:
mjs_return(mjs, mjs_mk_string(mjs, "firmwareTooNew", ~0, 0));
return;
}
}
void js_is_sdk_compatible(struct mjs* mjs) {
JS_SDK_COMPAT_ARGS;
JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor);
mjs_return(mjs, mjs_mk_boolean(mjs, status == JsSdkCompatStatusCompatible));
}
/**
* @brief Asks the user whether to continue executing an incompatible script
*/
static bool js_internal_compat_ask_user(const char* message) {
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
DialogMessage* dialog = dialog_message_alloc();
dialog_message_set_header(dialog, message, 64, 0, AlignCenter, AlignTop);
dialog_message_set_text(
dialog, "This script may not\nwork as expected", 79, 32, AlignCenter, AlignCenter);
dialog_message_set_icon(dialog, &I_Warning_30x23, 0, 18);
dialog_message_set_buttons(dialog, "Go back", NULL, "Run anyway");
DialogMessageButton choice = dialog_message_show(dialogs, dialog);
dialog_message_free(dialog);
furi_record_close(RECORD_DIALOGS);
return choice == DialogMessageButtonRight;
}
void js_check_sdk_compatibility(struct mjs* mjs) {
JS_SDK_COMPAT_ARGS;
JsSdkCompatStatus status = js_internal_sdk_compatibility_status(major, minor);
if(status != JsSdkCompatStatusCompatible) {
FURI_LOG_E(
TAG,
"Script requests JS SDK %ld.%ld, firmware provides JS SDK %d.%d",
major,
minor,
JS_SDK_MAJOR,
JS_SDK_MINOR);
const char* message = (status == JsSdkCompatStatusFirmwareTooOld) ? "Outdated Firmware" :
"Outdated Script";
if(!js_internal_compat_ask_user(message)) {
JS_ERROR_AND_RETURN(mjs, MJS_NOT_IMPLEMENTED_ERROR, "Incompatible script");
}
}
}
static const char* extra_features[] = {
"baseline", // dummy "feature"
};
/**
* @brief Determines whether a feature is supported
*/
static bool js_internal_supports(const char* feature) {
for(size_t i = 0; i < COUNT_OF(extra_features); i++) { // -V1008
if(strcmp(feature, extra_features[i]) == 0) return true;
}
return false;
}
/**
* @brief Determines whether all of the requested features are supported
*/
static bool js_internal_supports_all_of(struct mjs* mjs, mjs_val_t feature_arr) {
furi_assert(mjs_is_array(feature_arr));
for(size_t i = 0; i < mjs_array_length(mjs, feature_arr); i++) {
mjs_val_t feature = mjs_array_get(mjs, feature_arr, i);
const char* feature_str = mjs_get_string(mjs, &feature, NULL);
if(!feature_str) return false;
if(!js_internal_supports(feature_str)) return false;
}
return true;
}
void js_does_sdk_support(struct mjs* mjs) {
mjs_val_t features;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&features));
mjs_return(mjs, mjs_mk_boolean(mjs, js_internal_supports_all_of(mjs, features)));
}
void js_check_sdk_features(struct mjs* mjs) {
mjs_val_t features;
JS_FETCH_ARGS_OR_RETURN(mjs, JS_EXACTLY, JS_ARG_ARR(&features));
if(!js_internal_supports_all_of(mjs, features)) {
FURI_LOG_E(TAG, "Script requests unsupported features");
if(!js_internal_compat_ask_user("Unsupported Feature")) {
JS_ERROR_AND_RETURN(mjs, MJS_NOT_IMPLEMENTED_ERROR, "Incompatible script");
}
}
}

View File

@ -9,6 +9,10 @@
#define PLUGIN_APP_ID "js" #define PLUGIN_APP_ID "js"
#define PLUGIN_API_VERSION 1 #define PLUGIN_API_VERSION 1
#define JS_SDK_VENDOR "flipperdevices"
#define JS_SDK_MAJOR 0
#define JS_SDK_MINOR 1
/** /**
* @brief Returns the foreign pointer in `obj["_"]` * @brief Returns the foreign pointer in `obj["_"]`
*/ */
@ -275,3 +279,28 @@ mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_le
* @returns Pointer to module context, NULL if the module is not instantiated * @returns Pointer to module context, NULL if the module is not instantiated
*/ */
void* js_module_get(JsModules* modules, const char* name); void* js_module_get(JsModules* modules, const char* name);
/**
* @brief `sdkCompatibilityStatus` function
*/
void js_sdk_compatibility_status(struct mjs* mjs);
/**
* @brief `isSdkCompatible` function
*/
void js_is_sdk_compatible(struct mjs* mjs);
/**
* @brief `checkSdkCompatibility` function
*/
void js_check_sdk_compatibility(struct mjs* mjs);
/**
* @brief `doesSdkSupport` function
*/
void js_does_sdk_support(struct mjs* mjs);
/**
* @brief `checkSdkFeatures` function
*/
void js_check_sdk_features(struct mjs* mjs);

View File

@ -231,18 +231,29 @@ static int32_t js_thread(void* arg) {
struct mjs* mjs = mjs_create(worker); struct mjs* mjs = mjs_create(worker);
worker->modules = js_modules_create(mjs, worker->resolver); worker->modules = js_modules_create(mjs, worker->resolver);
mjs_val_t global = mjs_get_global(mjs); mjs_val_t global = mjs_get_global(mjs);
mjs_set(mjs, global, "print", ~0, MJS_MK_FN(js_print));
mjs_set(mjs, global, "delay", ~0, MJS_MK_FN(js_delay));
mjs_set(mjs, global, "toString", ~0, MJS_MK_FN(js_global_to_string));
mjs_set(mjs, global, "ffi_address", ~0, MJS_MK_FN(js_ffi_address));
mjs_set(mjs, global, "require", ~0, MJS_MK_FN(js_require));
mjs_val_t console_obj = mjs_mk_object(mjs); mjs_val_t console_obj = mjs_mk_object(mjs);
mjs_set(mjs, console_obj, "log", ~0, MJS_MK_FN(js_console_log));
mjs_set(mjs, console_obj, "warn", ~0, MJS_MK_FN(js_console_warn)); JS_ASSIGN_MULTI(mjs, global) {
mjs_set(mjs, console_obj, "error", ~0, MJS_MK_FN(js_console_error)); JS_FIELD("print", MJS_MK_FN(js_print));
mjs_set(mjs, console_obj, "debug", ~0, MJS_MK_FN(js_console_debug)); JS_FIELD("delay", MJS_MK_FN(js_delay));
mjs_set(mjs, global, "console", ~0, console_obj); JS_FIELD("toString", MJS_MK_FN(js_global_to_string));
JS_FIELD("ffi_address", MJS_MK_FN(js_ffi_address));
JS_FIELD("require", MJS_MK_FN(js_require));
JS_FIELD("console", console_obj);
JS_FIELD("sdkCompatibilityStatus", MJS_MK_FN(js_sdk_compatibility_status));
JS_FIELD("isSdkCompatible", MJS_MK_FN(js_is_sdk_compatible));
JS_FIELD("checkSdkCompatibility", MJS_MK_FN(js_check_sdk_compatibility));
JS_FIELD("doesSdkSupport", MJS_MK_FN(js_does_sdk_support));
JS_FIELD("checkSdkFeatures", MJS_MK_FN(js_check_sdk_features));
}
JS_ASSIGN_MULTI(mjs, console_obj) {
JS_FIELD("log", MJS_MK_FN(js_console_log));
JS_FIELD("warn", MJS_MK_FN(js_console_warn));
JS_FIELD("error", MJS_MK_FN(js_console_error));
JS_FIELD("debug", MJS_MK_FN(js_console_debug));
}
mjs_set_ffi_resolver(mjs, js_dlsym, worker->resolver); mjs_set_ffi_resolver(mjs, js_dlsym, worker->resolver);

View File

@ -27,11 +27,19 @@ static void js_flipper_get_battery(struct mjs* mjs) {
void* js_flipper_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) { void* js_flipper_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
UNUSED(modules); UNUSED(modules);
mjs_val_t sdk_vsn = mjs_mk_array(mjs);
mjs_array_push(mjs, sdk_vsn, mjs_mk_number(mjs, JS_SDK_MAJOR));
mjs_array_push(mjs, sdk_vsn, mjs_mk_number(mjs, JS_SDK_MINOR));
mjs_val_t flipper_obj = mjs_mk_object(mjs); mjs_val_t flipper_obj = mjs_mk_object(mjs);
mjs_set(mjs, flipper_obj, "getModel", ~0, MJS_MK_FN(js_flipper_get_model));
mjs_set(mjs, flipper_obj, "getName", ~0, MJS_MK_FN(js_flipper_get_name));
mjs_set(mjs, flipper_obj, "getBatteryCharge", ~0, MJS_MK_FN(js_flipper_get_battery));
*object = flipper_obj; *object = flipper_obj;
JS_ASSIGN_MULTI(mjs, flipper_obj) {
JS_FIELD("getModel", MJS_MK_FN(js_flipper_get_model));
JS_FIELD("getName", MJS_MK_FN(js_flipper_get_name));
JS_FIELD("getBatteryCharge", MJS_MK_FN(js_flipper_get_battery));
JS_FIELD("firmwareVendor", mjs_mk_string(mjs, JS_SDK_VENDOR, ~0, false));
JS_FIELD("jsSdkVersion", sdk_vsn);
}
return (void*)1; return (void*)1;
} }

View File

@ -220,7 +220,7 @@ static void js_gpio_interrupt(struct mjs* mjs) {
* let gpio = require("gpio"); * let gpio = require("gpio");
* let pot = gpio.get("pc0"); * let pot = gpio.get("pc0");
* pot.init({ direction: "in", inMode: "analog" }); * pot.init({ direction: "in", inMode: "analog" });
* print("voltage:" pot.read_analog(), "mV"); * print("voltage:" pot.readAnalog(), "mV");
* ``` * ```
*/ */
static void js_gpio_read_analog(struct mjs* mjs) { static void js_gpio_read_analog(struct mjs* mjs) {
@ -274,7 +274,7 @@ static void js_gpio_get(struct mjs* mjs) {
mjs_set(mjs, manager, "init", ~0, MJS_MK_FN(js_gpio_init)); mjs_set(mjs, manager, "init", ~0, MJS_MK_FN(js_gpio_init));
mjs_set(mjs, manager, "write", ~0, MJS_MK_FN(js_gpio_write)); mjs_set(mjs, manager, "write", ~0, MJS_MK_FN(js_gpio_write));
mjs_set(mjs, manager, "read", ~0, MJS_MK_FN(js_gpio_read)); mjs_set(mjs, manager, "read", ~0, MJS_MK_FN(js_gpio_read));
mjs_set(mjs, manager, "read_analog", ~0, MJS_MK_FN(js_gpio_read_analog)); mjs_set(mjs, manager, "readAnalog", ~0, MJS_MK_FN(js_gpio_read_analog));
mjs_set(mjs, manager, "interrupt", ~0, MJS_MK_FN(js_gpio_interrupt)); mjs_set(mjs, manager, "interrupt", ~0, MJS_MK_FN(js_gpio_interrupt));
mjs_return(mjs, manager); mjs_return(mjs, manager);

View File

@ -0,0 +1,20 @@
# Flipper Zero JavaScript SDK Wizard
This package contains an interactive wizard that lets you scaffold a JavaScript
application for Flipper Zero.
## Getting started
Create your application using the interactive wizard:
```shell
npx @flipperdevices/create-fz-app@latest
```
Then, enter the directory with your application and launch it:
```shell
cd my-flip-app
npm start
```
You are free to use `pnpm` or `yarn` instead of `npm`.
## Documentation
Check out the [JavaScript section in the Developer Documentation](https://developer.flipper.net/flipperzero/doxygen/js.html)

View File

@ -0,0 +1,68 @@
#!/usr/bin/env node
import prompts from "prompts";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "url";
import { spawnSync } from "node:child_process";
import { replaceInFileSync } from "replace-in-file";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
(async () => {
const { name, pkgManager, confirm } = await prompts([
{
type: "text",
name: "name",
message: "What is the name of your project?",
initial: "my-flip-app"
},
{
type: "select",
name: "pkgManager",
message: "What package manager should your project use?",
choices: [
{ title: "npm", value: "npm" },
{ title: "pnpm", value: "pnpm" },
{ title: "yarn", value: "yarn" },
],
},
{
type: "confirm",
name: "confirm",
message: "Create project?",
initial: true,
},
]);
if (!confirm)
return;
if (fs.existsSync(name)) {
const { replace } = await prompts([
{
type: "confirm",
name: "replace",
message: `File or directory \`${name}\` already exists. Continue anyway?`,
initial: false,
},
]);
if (!replace)
return;
}
fs.rmSync(name, { recursive: true, force: true });
console.log("Copying files...");
fs.cpSync(path.resolve(__dirname, "template"), name, { recursive: true });
replaceInFileSync({ files: `${name}/**/*`, from: /<app_name>/g, to: name });
console.log("Installing packages...");
spawnSync("bash", ["-c", `cd ${name} && ${pkgManager} install`], {
cwd: process.cwd(),
detached: true,
stdio: "inherit",
});
console.log(`Done! Created ${name}. Run \`cd ${name} && ${pkgManager} start\` to run it on your Flipper.`);
})();

View File

@ -0,0 +1,22 @@
{
"name": "@flipperdevices/create-fz-app",
"version": "0.1.0",
"description": "Template package for JS apps Flipper Zero",
"bin": "index.js",
"type": "module",
"keywords": [
"flipper",
"flipper zero"
],
"author": "Flipper Devices",
"license": "GPL-3.0-only",
"repository": {
"type": "git",
"url": "git+https://github.com/flipperdevices/flipperzero-firmware.git",
"directory": "applications/system/js_app/packages/create-fz-app"
},
"dependencies": {
"prompts": "^2.4.2",
"replace-in-file": "^8.2.0"
}
}

View File

@ -0,0 +1,373 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
prompts:
specifier: ^2.4.2
version: 2.4.2
replace-in-file:
specifier: ^8.2.0
version: 8.2.0
packages:
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
ansi-regex@6.1.0:
resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
engines: {node: '>=12'}
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
ansi-styles@6.2.1:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
engines: {node: '>=12'}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
brace-expansion@2.0.1:
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
chalk@5.3.0:
resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
foreground-child@3.3.0:
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
engines: {node: '>=14'}
get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
glob@10.4.5:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
hasBin: true
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
kleur@3.0.3:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
engines: {node: '>=6'}
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
minipass@7.1.2:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'}
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
path-scurry@1.11.1:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
prompts@2.4.2:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'}
replace-in-file@8.2.0:
resolution: {integrity: sha512-hMsQtdYHwWviQT5ZbNsgfu0WuCiNlcUSnnD+aHAL081kbU9dPkPocDaHlDvAHKydTWWpx1apfcEcmvIyQk3CpQ==}
engines: {node: '>=18'}
hasBin: true
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
shebang-regex@3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
sisteransi@1.0.5:
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
string-width@5.1.2:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
strip-ansi@7.1.0:
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
engines: {node: '>=12'}
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
hasBin: true
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
wrap-ansi@8.1.0:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
snapshots:
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
string-width-cjs: string-width@4.2.3
strip-ansi: 7.1.0
strip-ansi-cjs: strip-ansi@6.0.1
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
'@pkgjs/parseargs@0.11.0':
optional: true
ansi-regex@5.0.1: {}
ansi-regex@6.1.0: {}
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
ansi-styles@6.2.1: {}
balanced-match@1.0.2: {}
brace-expansion@2.0.1:
dependencies:
balanced-match: 1.0.2
chalk@5.3.0: {}
cliui@8.0.1:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 7.0.0
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
color-name@1.1.4: {}
cross-spawn@7.0.3:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
eastasianwidth@0.2.0: {}
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
escalade@3.2.0: {}
foreground-child@3.3.0:
dependencies:
cross-spawn: 7.0.3
signal-exit: 4.1.0
get-caller-file@2.0.5: {}
glob@10.4.5:
dependencies:
foreground-child: 3.3.0
jackspeak: 3.4.3
minimatch: 9.0.5
minipass: 7.1.2
package-json-from-dist: 1.0.1
path-scurry: 1.11.1
is-fullwidth-code-point@3.0.0: {}
isexe@2.0.0: {}
jackspeak@3.4.3:
dependencies:
'@isaacs/cliui': 8.0.2
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
kleur@3.0.3: {}
lru-cache@10.4.3: {}
minimatch@9.0.5:
dependencies:
brace-expansion: 2.0.1
minipass@7.1.2: {}
package-json-from-dist@1.0.1: {}
path-key@3.1.1: {}
path-scurry@1.11.1:
dependencies:
lru-cache: 10.4.3
minipass: 7.1.2
prompts@2.4.2:
dependencies:
kleur: 3.0.3
sisteransi: 1.0.5
replace-in-file@8.2.0:
dependencies:
chalk: 5.3.0
glob: 10.4.5
yargs: 17.7.2
require-directory@2.1.1: {}
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
shebang-regex@3.0.0: {}
signal-exit@4.1.0: {}
sisteransi@1.0.5: {}
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
string-width@5.1.2:
dependencies:
eastasianwidth: 0.2.0
emoji-regex: 9.2.2
strip-ansi: 7.1.0
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
strip-ansi@7.1.0:
dependencies:
ansi-regex: 6.1.0
which@2.0.2:
dependencies:
isexe: 2.0.0
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi@8.1.0:
dependencies:
ansi-styles: 6.2.1
string-width: 5.1.2
strip-ansi: 7.1.0
y18n@5.0.8: {}
yargs-parser@21.1.1: {}
yargs@17.7.2:
dependencies:
cliui: 8.0.1
escalade: 3.2.0
get-caller-file: 2.0.5
require-directory: 2.1.1
string-width: 4.2.3
y18n: 5.0.8
yargs-parser: 21.1.1

View File

@ -0,0 +1,2 @@
/dist
node_modules/

View File

@ -0,0 +1,23 @@
{
build: {
// Where to put the compiled file
output: "dist/<app_name>.js",
// Whether to reduce the final file size at the cost of readability and
// clarity of error messages
minify: false,
// Set this to `false` if you've thoroughly read the documentation and
// are sure that you can use manual version checks to your advantage
enforceSdkVersion: true,
},
upload: {
// Where to grab the file from. If you're not doing any extra processing
// after the SDK, this should match `build.output`
input: "dist/<app_name>.js",
// Where to put the file on the device
output: "/ext/apps/Scripts/<app_name>.js",
},
}

View File

@ -0,0 +1,30 @@
// import modules
// caution: `eventLoop` HAS to be imported before `gui`, and `gui` HAS to be
// imported before any `gui` submodules.
import * as eventLoop from "@flipperdevices/fz-sdk/event_loop";
import * as gui from "@flipperdevices/fz-sdk/gui";
import * as dialog from "@flipperdevices/fz-sdk/gui/dialog";
// a common pattern is to declare all the views that your app uses on one object
const views = {
dialog: dialog.makeWith({
header: "Hello from <app_name>",
text: "Check out index.ts and\nchange something :)",
center: "Gonna do that!",
}),
};
// stop app on center button press
eventLoop.subscribe(views.dialog.input, (_sub, button, eventLoop) => {
if (button === "center")
eventLoop.stop();
}, eventLoop);
// stop app on back button press
eventLoop.subscribe(gui.viewDispatcher.navigation, (_sub, _item, eventLoop) => {
eventLoop.stop();
}, eventLoop);
// run app
gui.viewDispatcher.switchTo(views.dialog);
eventLoop.run();

View File

@ -0,0 +1,12 @@
{
"name": "<app_name>",
"version": "1.0.0",
"scripts": {
"build": "tsc && node node_modules/@flipperdevices/fz-sdk/sdk.js build",
"start": "npm run build && node node_modules/@flipperdevices/fz-sdk/sdk.js upload"
},
"devDependencies": {
"@flipperdevices/fz-sdk": "^0.1",
"typescript": "^5.6.3"
}
}

View File

@ -0,0 +1,20 @@
{
"compilerOptions": {
"outDir": "dist",
"checkJs": true,
"module": "CommonJS",
"noLib": true,
"target": "ES2015",
},
"files": [
"./node_modules/@flipperdevices/fz-sdk/global.d.ts",
],
"include": [
"./**/*.ts",
"./**/*.js"
],
"exclude": [
"./node_modules/**/*",
"dist/**/*",
],
}

View File

@ -0,0 +1 @@
docs/

View File

@ -0,0 +1,31 @@
# Flipper Zero JavaScript SDK
This package contains official tooling and typings for developing Flipper Zero
applications in JavaScript.
## Getting started
Create your application using the interactive wizard:
```shell
npx @flipperdevices/create-fz-app@latest
```
Then, enter the directory with your application and launch it:
```shell
cd my-flip-app
npm start
```
You are free to use `pnpm` or `yarn` instead of `npm`.
## Versioning
For each version of this package, the major and minor components match those of
the Flipper Zero JS SDK version that that package version targets. This version
follows semver. For example, apps compiled with SDK version `0.1.0` will be
compatible with SDK versions `0.1`...`1.0` (not including `1.0`).
Every API has a version history reflected in its JSDoc comment. It is heavily
recommended to check SDK compatibility using a combination of
`sdkCompatibilityStatus`, `isSdkCompatible`, `assertSdkCompatibility` depending
on your use case.
## Documentation
Check out the [JavaScript section in the Developer Documentation](https://developer.flipper.net/flipperzero/doxygen/js.html)

View File

@ -1,8 +1,10 @@
/** /**
* @brief Special key codes that this module recognizes * @brief Special key codes that this module recognizes
* @version Added in JS SDK 0.1
*/ */
export type ModifierKey = "CTRL" | "SHIFT" | "ALT" | "GUI"; export type ModifierKey = "CTRL" | "SHIFT" | "ALT" | "GUI";
/** @version Added in JS SDK 0.1 */
export type MainKey = export type MainKey =
"DOWN" | "LEFT" | "RIGHT" | "UP" | "DOWN" | "LEFT" | "RIGHT" | "UP" |
@ -28,16 +30,19 @@ export type MainKey =
"m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" |
"y" | "z"; "y" | "z";
/** @version Added in JS SDK 0.1 */
export type KeyCode = MainKey | ModifierKey | number; export type KeyCode = MainKey | ModifierKey | number;
/** /**
* @brief Initializes the module * @brief Initializes the module
* @param settings USB device settings. Omit to select default parameters * @param settings USB device settings. Omit to select default parameters
* @version Added in JS SDK 0.1
*/ */
export declare function setup(settings?: { vid: number, pid: number, mfrName?: string, prodName?: string }): void; export declare function setup(settings?: { vid: number, pid: number, mfrName?: string, prodName?: string }): void;
/** /**
* @brief Tells whether the virtual USB HID device has successfully connected * @brief Tells whether the virtual USB HID device has successfully connected
* @version Added in JS SDK 0.1
*/ */
export declare function isConnected(): boolean; export declare function isConnected(): boolean;
@ -46,6 +51,7 @@ export declare function isConnected(): boolean;
* @param keys The arguments represent a set of keys to. Out of that set, only * @param keys The arguments represent a set of keys to. Out of that set, only
* one of the keys may represent a "main key" (see `MainKey`), with * one of the keys may represent a "main key" (see `MainKey`), with
* the rest being modifier keys (see `ModifierKey`). * the rest being modifier keys (see `ModifierKey`).
* @version Added in JS SDK 0.1
*/ */
export declare function press(...keys: KeyCode[]): void; export declare function press(...keys: KeyCode[]): void;
@ -54,6 +60,7 @@ export declare function press(...keys: KeyCode[]): void;
* @param keys The arguments represent a set of keys to. Out of that set, only * @param keys The arguments represent a set of keys to. Out of that set, only
* one of the keys may represent a "main key" (see `MainKey`), with * one of the keys may represent a "main key" (see `MainKey`), with
* the rest being modifier keys (see `ModifierKey`). * the rest being modifier keys (see `ModifierKey`).
* @version Added in JS SDK 0.1
*/ */
export declare function hold(...keys: KeyCode[]): void; export declare function hold(...keys: KeyCode[]): void;
@ -62,6 +69,7 @@ export declare function hold(...keys: KeyCode[]): void;
* @param keys The arguments represent a set of keys to. Out of that set, only * @param keys The arguments represent a set of keys to. Out of that set, only
* one of the keys may represent a "main key" (see `MainKey`), with * one of the keys may represent a "main key" (see `MainKey`), with
* the rest being modifier keys (see `ModifierKey`). * the rest being modifier keys (see `ModifierKey`).
* @version Added in JS SDK 0.1
*/ */
export declare function release(...keys: KeyCode[]): void; export declare function release(...keys: KeyCode[]): void;
@ -69,6 +77,7 @@ export declare function release(...keys: KeyCode[]): void;
* @brief Prints a string by repeatedly pressing and releasing keys * @brief Prints a string by repeatedly pressing and releasing keys
* @param string The string to print * @param string The string to print
* @param delay How many milliseconds to wait between key presses * @param delay How many milliseconds to wait between key presses
* @version Added in JS SDK 0.1
*/ */
export declare function print(string: string, delay?: number): void; export declare function print(string: string, delay?: number): void;
@ -77,5 +86,6 @@ export declare function print(string: string, delay?: number): void;
* "Enter" after printing the string * "Enter" after printing the string
* @param string The string to print * @param string The string to print
* @param delay How many milliseconds to wait between key presses * @param delay How many milliseconds to wait between key presses
* @version Added in JS SDK 0.1
*/ */
export declare function println(string: string, delay?: number): void; export declare function println(string: string, delay?: number): void;

View File

@ -0,0 +1 @@
# Welcome

View File

@ -0,0 +1,182 @@
/**
* Module for dealing with events
*
* ```js
* let eventLoop = require("event_loop");
* ```
*
* The event loop is central to event-based programming in many frameworks, and
* our JS subsystem is no exception. It is a good idea to familiarize yourself
* with the event loop first before using any of the advanced modules (e.g. GPIO
* and GUI).
*
* # Conceptualizing the event loop
* If you ever wrote JavaScript before, you have definitely seen callbacks. It's
* when a function accepts another function (usually an anonymous one) as one of
* the arguments, which it will call later on, e.g. when an event happens or
* when data becomes ready:
* ```js
* setTimeout(function() { console.log("Hello, World!") }, 1000);
* ```
*
* Many JavaScript engines employ a queue that the runtime fetches events from
* as they occur, subsequently calling the corresponding callbacks. This is done
* in a long-running loop, hence the name "event loop". Here's the pseudocode
* for a typical event loop:
* ```js
* while(loop_is_running()) {
* if(event_available_in_queue()) {
* let event = fetch_event_from_queue();
* let callback = get_callback_associated_with(event);
* if(callback)
* callback(get_extra_data_for(event));
* } else {
* // avoid wasting CPU time
* sleep_until_any_event_becomes_available();
* }
* }
* ```
*
* Most JS runtimes enclose the event loop within themselves, so that most JS
* programmers does not even need to be aware of its existence. This is not the
* case with our JS subsystem.
*
* # Example
* This is how one would write something similar to the `setTimeout` example
* above:
* ```js
* // import module
* let eventLoop = require("event_loop");
*
* // create an event source that will fire once 1 second after it has been created
* let timer = eventLoop.timer("oneshot", 1000);
*
* // subscribe a callback to the event source
* eventLoop.subscribe(timer, function(_subscription, _item, eventLoop) {
* print("Hello, World!");
* eventLoop.stop();
* }, eventLoop); // notice this extra argument. we'll come back to this later
*
* // run the loop until it is stopped
* eventLoop.run();
*
* // the previous line will only finish executing once `.stop()` is called, hence
* // the following line will execute only after "Hello, World!" is printed
* print("Stopped");
* ```
*
* I promised you that we'll come back to the extra argument after the callback
* function. Our JavaScript engine does not support closures (anonymous
* functions that access values outside of their arguments), so we ask
* `subscribe` to pass an outside value (namely, `eventLoop`) as an argument to
* the callback so that we can access it. We can modify this extra state:
* ```js
* // this timer will fire every second
* let timer = eventLoop.timer("periodic", 1000);
* eventLoop.subscribe(timer, function(_subscription, _item, counter, eventLoop) {
* print("Counter is at:", counter);
* if(counter === 10)
* eventLoop.stop();
* // modify the extra arguments that will be passed to us the next time
* return [counter + 1, eventLoop];
* }, 0, eventLoop);
* ```
*
* Because we have two extra arguments, if we return anything other than an
* array of length 2, the arguments will be kept as-is for the next call.
*
* The first two arguments that get passed to our callback are:
* - The subscription manager that lets us `.cancel()` our subscription
* - The event item, used for events that have extra data. Timer events do
* not, they just produce `undefined`.
*
* @version Added in JS SDK 0.1
* @module
*/
/**
* @ignore
*/
type Lit = undefined | null | {};
/**
* Subscription control interface
* @version Added in JS SDK 0.1
*/
export interface Subscription {
/**
* Cancels the subscription, preventing any future events managed by the
* subscription from firing
* @version Added in JS SDK 0.1
*/
cancel(): void;
}
/**
* Opaque event source identifier
* @version Added in JS SDK 0.1
*/
export type Contract<Item = undefined> = symbol & { "__tag__": "contract" };
// introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist.
/**
* A callback can be assigned to an event loop to listen to an event. It may
* return an array with values that will be passed to it as arguments the next
* time that it is called. The first argument is always the subscription
* manager, and the second argument is always the item that trigged the event.
* The type of the item is defined by the event source.
* @version Added in JS SDK 0.1
*/
export type Callback<Item, Args extends Lit[]> = (subscription: Subscription, item: Item, ...args: Args) => Args | undefined | void;
/**
* Subscribes a callback to an event
* @param contract Event identifier
* @param callback Function to call when the event is triggered
* @param args Initial arguments passed to the callback
* @version Added in JS SDK 0.1
*/
export function subscribe<Item, Args extends Lit[]>(contract: Contract<Item>, callback: Callback<Item, Args>, ...args: Args): Subscription;
/**
* Runs the event loop until it is stopped (potentially never)
* @version Added in JS SDK 0.1
*/
export function run(): void | never;
/**
* Stops the event loop
* @version Added in JS SDK 0.1
*/
export function stop(): void;
/**
* Creates a timer event that can be subscribed to just like any other event
* @param mode Either `"oneshot"` or `"periodic"`
* @param interval Timer interval in milliseconds
* @version Added in JS SDK 0.1
*/
export function timer(mode: "oneshot" | "periodic", interval: number): Contract;
/**
* Message queue
* @version Added in JS SDK 0.1
*/
export declare class Queue<T> {
/**
* Message event
* @version Added in JS SDK 0.1
*/
input: Contract<T>;
/**
* Sends a message to the queue
* @param message message to send
* @version Added in JS SDK 0.1
*/
send(message: T): void;
}
/**
* Creates a message queue
* @param length maximum queue capacity
* @version Added in JS SDK 0.1
*/
export function queue<T>(length: number): Queue<T>;

View File

@ -0,0 +1,41 @@
/**
* Module for querying device properties
* @version Added in JS SDK 0.1
* @module
*/
/**
* @brief Returns the device model
* @version Added in JS SDK 0.1
*/
export declare function getModel(): string;
/**
* @brief Returns the name of the virtual dolphin
* @version Added in JS SDK 0.1
*/
export declare function getName(): string;
/**
* @brief Returns the battery charge percentage
* @version Added in JS SDK 0.1
*/
export declare function getBatteryCharge(): number;
/**
* @warning Do **NOT** use this to check the presence or absence of features. If
* you do, I'm gonna be sad :( Instead, refer to `checkSdkFeatures` and
* other similar mechanisms.
* @note Original firmware reports `"flipperdevices"`.
* @version Added in JS SDK 0.1
*/
export declare const firmwareVendor: string;
/**
* @warning Do **NOT** use this to check the presence or absence of features. If
* you do, I'm gonna be sad :( Instead, refer to
* `checkSdkCompatibility` and other similar mechanisms.
* @note You're looking at JS SDK 0.1
* @version Added in JS SDK 0.1
*/
export declare const jsSdkVersion: [number, number];

View File

@ -0,0 +1,344 @@
/**
* Things from this module are automatically available to you without having to
* explicitly import anything.
*
* # SDK versioning and features
*
* ## Motivation
* It is very important that you check that features are implemented before you
* use them. By adding the necessary checks, you ensure that your users get a
* clear warning instead of a cryptic error message when running the script.
*
* This system has been designed in collaboration with our community in order to
* make things better for everybody involved. You can find out more in this
* discussion: https://github.com/flipperdevices/flipperzero-firmware/pull/3961
*
* ## Community agreement
* Each interpreter implementation (aka "JS SDK", aka "JS API"), including
* those found in third-party firmware distributions, defines two markers for
* signaling what it supports: the **SDK version** and the
* **extra feature set**.
*
* The **SDK version** consists of two semver-like integer components: the major
* version and the minor version. Like semver, the major version is bumped when
* a breaking change is introduced (i.e. one that would require correction of
* apps by their developers), and the minor version is bumped when a new
* non-breaking feature is introduced. Because we have adopted TypeScript,
* the https://www.semver-ts.org/ standard is used to determine whether a change
* is breaking or not. The basis of `semver-ts` is the "no new red squiggles"
* rule.
*
* Every major version is associated with a set of **extra features** that are
* present in some firmware distributions but not others. Distributions may
* cross-port features between each other, until at some point they get ported
* into the upstream firmware distribution. With the next major release of the
* JS SDK, all extra features present in the upstream distribution are now
* declared **baseline features**, and thus no longer recognized as "extra
* features".
*
* Before using a feature, you must check that the interpreter that you're
* running on actually supports it. If you don't, the portability of your
* application will suffer.
*
* ## Implementation
* Use the following functions to check version compatibility:
* - `checkSdkCompatibility` when your script absolutely cannot function on an
* incompatible interpreter
* - `isSdkCompatible` when your script can leverage multiple interpreter
* editions to its advantage
* - `sdkCompatibilityStatus` when you need a detailed status on compatibility
*
* Use the following functions to check feature compatibility:
* - `checkSdkFeatures` when your script absolutely cannot function on an
* incompatible interpreter
* - `doesSdkSupport` when your script can leverage multiple interpreter
* editions to its advantage
*
* ## Automatic version enforcement
* The SDK will automatically insert a call to `checkSdkCompatibility` in the
* beginning of the resulting script. If you would like to disable this check
* and instead use other manual compatibility checking facilities, edit your
* `fz-sdk.config.json5`.
*
* # Standard library
* Standard library features are mostly unimplemented. This module defines,
* among other things, the features that _are_ implemented.
*
* @version Added in JS SDK 0.1
* @module
*/
/**
* @brief Checks compatibility between the script and the JS SDK that the
* firmware provides
*
* @note You're looking at JS SDK v0.1
*
* @param expectedMajor JS SDK major version expected by the script
* @param expectedMinor JS SDK minor version expected by the script
* @returns Compatibility status:
* - `"compatible"` if the script and the JS SDK are compatible
* - `"firmwareTooOld"` if the expected major version is larger than the
* version of the firmware, or if the expected minor version is larger than
* the version of the firmware
* - `"firmwareTooNew"` if the expected major version is lower than the
* version of the firmware
* @version Added in JS SDK 0.1
*/
declare function sdkCompatibilityStatus(expectedMajor: number, expectedMinor: number):
"compatible" | "firmwareTooOld" | "firmwareTooNew";
/**
* @brief Checks compatibility between the script and the JS SDK that the
* firmware provides in a boolean fashion
*
* @note You're looking at JS SDK v0.1
*
* @param expectedMajor JS SDK major version expected by the script
* @param expectedMinor JS SDK minor version expected by the script
* @returns `true` if the two are compatible, `false` otherwise
* @version Added in JS SDK 0.1
*/
declare function isSdkCompatible(expectedMajor: number, expectedMinor: number): boolean;
/**
* @brief Asks the user whether to continue executing the script if the versions
* are not compatible. Does nothing if they are.
*
* @note You're looking at JS SDK v0.1
*
* @param expectedMajor JS SDK major version expected by the script
* @param expectedMinor JS SDK minor version expected by the script
* @version Added in JS SDK 0.1
*/
declare function checkSdkCompatibility(expectedMajor: number, expectedMinor: number): void | never;
/**
* @brief Checks whether all of the specified extra features are supported by
* the interpreter.
* @warning This function will return `false` if a queried feature is now
* recognized as a baseline feature. For more info, consult the module
* documentation.
* @param features Array of named features to query
*/
declare function doesSdkSupport(features: string[]): boolean;
/**
* @brief Checks whether all of the specified extra features are supported by
* the interpreter, asking the user if they want to continue running the
* script if they're not.
* @warning This function will act as if the feature is not implemented for
* features that are now recognized as baseline features. For more
* info, consult the module documentation.
* @param features Array of named features to query
*/
declare function checkSdkFeatures(features: string[]): void | never;
/**
* @brief Pauses JavaScript execution for a while
* @param ms How many milliseconds to pause the execution for
* @version Added in JS SDK 0.1
*/
declare function delay(ms: number): void;
/**
* @brief Prints to the GUI console view
* @param args The arguments are converted to strings, concatenated without any
* spaces in between and printed to the console view
* @version Added in JS SDK 0.1
*/
declare function print(...args: any[]): void;
/**
* @brief Converts a number to a string
* @param value The number to convert to a string
* @param base Integer base (`2`...`16`), default: 10
* @version Added in JS SDK 0.1
*/
declare function toString(value: number, base?: number): string;
/**
* @brief Reads a JS value from a file
*
* Reads a file at the specified path, interprets it as a JS value and returns
* said value.
*
* @param path The path to the file
* @version Added in JS SDK 0.1
*/
declare function load(path: string): any;
/**
* @brief Loads a natively implemented module
* @param module The name of the module to load
* @version Added in JS SDK 0.1
*/
declare function require(module: string): any;
/**
* @brief mJS Foreign Pointer type
*
* JavaScript code cannot do anything with values of `RawPointer` type except
* acquire them from native code and pass them right back to other parts of
* native code. These values cannot be turned into something meaningful, nor can
* be they modified.
*
* @version Added in JS SDK 0.1
*/
declare type RawPointer = symbol & { "__tag__": "raw_ptr" };
// introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist.
/**
* @brief Holds raw bytes
* @version Added in JS SDK 0.1
*/
declare class ArrayBuffer {
/**
* @brief The pointer to the byte buffer
* @note Like other `RawPointer` values, this value is essentially useless
* to JS code.
* @version Added in JS SDK 0.1
*/
getPtr: RawPointer;
/**
* @brief The length of the buffer in bytes
* @version Added in JS SDK 0.1
*/
byteLength: number;
/**
* @brief Creates an `ArrayBuffer` that contains a sub-part of the buffer
* @param start The index of the byte in the source buffer to be used as the
* start for the new buffer
* @param end The index of the byte in the source buffer that follows the
* byte to be used as the last byte for the new buffer
* @version Added in JS SDK 0.1
*/
slice(start: number, end?: number): ArrayBuffer;
}
declare function ArrayBuffer(): ArrayBuffer;
declare type ElementType = "u8" | "i8" | "u16" | "i16" | "u32" | "i32";
declare class TypedArray<E extends ElementType> {
/**
* @brief The length of the buffer in bytes
* @version Added in JS SDK 0.1
*/
byteLength: number;
/**
* @brief The length of the buffer in typed elements
* @version Added in JS SDK 0.1
*/
length: number;
/**
* @brief The underlying `ArrayBuffer`
* @version Added in JS SDK 0.1
*/
buffer: ArrayBuffer;
}
declare class Uint8Array extends TypedArray<"u8"> { }
declare class Int8Array extends TypedArray<"i8"> { }
declare class Uint16Array extends TypedArray<"u16"> { }
declare class Int16Array extends TypedArray<"i16"> { }
declare class Uint32Array extends TypedArray<"u32"> { }
declare class Int32Array extends TypedArray<"i32"> { }
declare function Uint8Array(data: ArrayBuffer | number | number[]): Uint8Array;
declare function Int8Array(data: ArrayBuffer | number | number[]): Int8Array;
declare function Uint16Array(data: ArrayBuffer | number | number[]): Uint16Array;
declare function Int16Array(data: ArrayBuffer | number | number[]): Int16Array;
declare function Uint32Array(data: ArrayBuffer | number | number[]): Uint32Array;
declare function Int32Array(data: ArrayBuffer | number | number[]): Int32Array;
declare const console: {
/**
* @brief Prints to the UART logs at the `[I]` level
* @param args The arguments are converted to strings, concatenated without any
* spaces in between and printed to the logs
* @version Added in JS SDK 0.1
*/
log(...args: any[]): void;
/**
* @brief Prints to the UART logs at the `[D]` level
* @param args The arguments are converted to strings, concatenated without any
* spaces in between and printed to the logs
* @version Added in JS SDK 0.1
*/
debug(...args: any[]): void;
/**
* @brief Prints to the UART logs at the `[W]` level
* @param args The arguments are converted to strings, concatenated without any
* spaces in between and printed to the logs
* @version Added in JS SDK 0.1
*/
warn(...args: any[]): void;
/**
* @brief Prints to the UART logs at the `[E]` level
* @param args The arguments are converted to strings, concatenated without any
* spaces in between and printed to the logs
* @version Added in JS SDK 0.1
*/
error(...args: any[]): void;
};
declare class Array<T> {
/**
* @brief Takes items out of the array
*
* Removes elements from the array and returns them in a new array
*
* @param start The index to start taking elements from
* @param deleteCount How many elements to take
* @returns The elements that were taken out of the original array as a new
* array
* @version Added in JS SDK 0.1
*/
splice(start: number, deleteCount: number): T[];
/**
* @brief Adds a value to the end of the array
* @param value The value to add
* @returns New length of the array
* @version Added in JS SDK 0.1
*/
push(value: T): number;
/**
* @brief How many elements there are in the array
* @version Added in JS SDK 0.1
*/
length: number;
}
declare class String {
/**
* @brief How many characters there are in the string
* @version Added in JS SDK 0.1
*/
length: number;
/**
* @brief Returns the character code at an index in the string
* @param index The index to consult
* @version Added in JS SDK 0.1
*/
charCodeAt(index: number): number;
/**
* See `charCodeAt`
* @version Added in JS SDK 0.1
*/
at(index: number): number;
}
declare class Boolean { }
declare class Function { }
declare class Number { }
declare class Object { }
declare class RegExp { }
declare interface IArguments { }
declare type Partial<O extends object> = { [K in keyof O]?: O[K] };

View File

@ -1,5 +1,37 @@
/**
* Module for accessing the GPIO (General Purpose Input/Output) ports
*
* ```js
* let eventLoop = require("event_loop");
* let gpio = require("gpio");
* ```
*
* This module depends on the `event_loop` module, so it _must_ only be imported
* after `event_loop` is imported.
*
* # Example
* ```js
* let eventLoop = require("event_loop");
* let gpio = require("gpio");
*
* let led = gpio.get("pc3");
* led.init({ direction: "out", outMode: "push_pull" });
*
* led.write(true);
* delay(1000);
* led.write(false);
* delay(1000);
* ```
*
* @version Added in JS SDK 0.1
* @module
*/
import type { Contract } from "../event_loop"; import type { Contract } from "../event_loop";
/**
* @version Added in JS SDK 0.1
*/
export interface Mode { export interface Mode {
direction: "in" | "out"; direction: "in" | "out";
outMode?: "push_pull" | "open_drain"; outMode?: "push_pull" | "open_drain";
@ -8,31 +40,39 @@ export interface Mode {
pull?: "up" | "down"; pull?: "up" | "down";
} }
/**
* @version Added in JS SDK 0.1
*/
export interface Pin { export interface Pin {
/** /**
* Configures a pin. This may be done several times. * Configures a pin. This may be done several times.
* @param mode Pin configuration object * @param mode Pin configuration object
* @version Added in JS SDK 0.1
*/ */
init(mode: Mode): void; init(mode: Mode): void;
/** /**
* Sets the output value of a pin if it's been configured with * Sets the output value of a pin if it's been configured with
* `direction: "out"`. * `direction: "out"`.
* @param value Logic value to output * @param value Logic value to output
* @version Added in JS SDK 0.1
*/ */
write(value: boolean): void; write(value: boolean): void;
/** /**
* Gets the input value of a pin if it's been configured with * Gets the input value of a pin if it's been configured with
* `direction: "in"`, but not `inMode: "analog"`. * `direction: "in"`, but not `inMode: "analog"`.
* @version Added in JS SDK 0.1
*/ */
read(): boolean; read(): boolean;
/** /**
* Gets the input voltage of a pin in millivolts if it's been configured * Gets the input voltage of a pin in millivolts if it's been configured
* with `direction: "in"` and `inMode: "analog"` * with `direction: "in"` and `inMode: "analog"`
* @version Added in JS SDK 0.1
*/ */
read_analog(): number; readAnalog(): number;
/** /**
* Returns an `event_loop` event that can be used to listen to interrupts, * Returns an `event_loop` event that can be used to listen to interrupts,
* as configured by `init` * as configured by `init`
* @version Added in JS SDK 0.1
*/ */
interrupt(): Contract; interrupt(): Contract;
} }
@ -41,5 +81,6 @@ export interface Pin {
* Returns an object that can be used to manage a GPIO pin. For the list of * Returns an object that can be used to manage a GPIO pin. For the list of
* available pins, see https://docs.flipper.net/gpio-and-modules#miFsS * available pins, see https://docs.flipper.net/gpio-and-modules#miFsS
* @param pin Pin name (e.g. `"PC3"`) or number (e.g. `7`) * @param pin Pin name (e.g. `"PC3"`) or number (e.g. `7`)
* @version Added in JS SDK 0.1
*/ */
export function get(pin: string | number): Pin; export function get(pin: string | number): Pin;

View File

@ -0,0 +1,45 @@
/**
* Displays a dialog with up to three options.
*
* <img src="../images/dialog.png" width="200" alt="Sample screenshot of the view" />
*
* ```js
* let eventLoop = require("event_loop");
* let gui = require("gui");
* let dialogView = require("gui/dialog");
* ```
*
* This module depends on the `gui` module, which in turn depends on the
* `event_loop` module, so they _must_ be imported in this order. It is also
* recommended to conceptualize these modules first before using this one.
*
* # Example
* For an example refer to the `gui.js` example script.
*
* # View props
* - `header`: Text displayed in bold at the top of the screen
* - `text`: Text displayed in the middle of the string
* - `left`: Text for the left button
* - `center`: Text for the center button
* - `right`: Text for the right button
*
* @version Added in JS SDK 0.1
* @module
*/
import type { View, ViewFactory } from ".";
import type { Contract } from "../event_loop";
type Props = {
header: string,
text: string,
left: string,
center: string,
right: string,
}
declare class Dialog extends View<Props> {
input: Contract<"left" | "center" | "right">;
}
declare class DialogFactory extends ViewFactory<Props, Dialog> { }
declare const factory: DialogFactory;
export = factory;

View File

@ -0,0 +1,32 @@
/**
* Displays nothing.
*
* <img src="../images/empty.png" width="200" alt="Sample screenshot of the view" />
*
* ```js
* let eventLoop = require("event_loop");
* let gui = require("gui");
* let emptyView = require("gui/empty_screen");
* ```
*
* This module depends on the `gui` module, which in turn depends on the
* `event_loop` module, so they _must_ be imported in this order. It is also
* recommended to conceptualize these modules first before using this one.
*
* # Example
* For an example refer to the GUI example.
*
* # View props
* This view does not have any props.
*
* @version Added in JS SDK 0.1
* @module
*/
import type { View, ViewFactory } from ".";
type Props = {};
declare class EmptyScreen extends View<Props> { }
declare class EmptyScreenFactory extends ViewFactory<Props, EmptyScreen> { }
declare const factory: EmptyScreenFactory;
export = factory;

View File

@ -0,0 +1,171 @@
/**
* ```js
* let eventLoop = require("event_loop");
* let gui = require("gui");
* ```
*
* This module depends on the `event_loop` module, so it _must_ only be imported
* after `event_loop` is imported.
*
* ## Conceptualizing GUI
* ### Event loop
* It is highly recommended to familiarize yourself with the event loop first
* before doing GUI-related things.
*
* ### Canvas
* The canvas is just a drawing area with no abstractions over it. Drawing on
* the canvas directly (i.e. not through a viewport) is useful in case you want
* to implement a custom design element, but this is rather uncommon.
*
* ### Viewport
* A viewport is a window into a rectangular portion of the canvas. Applications
* always access the canvas through a viewport.
*
* ### View
* In Flipper's terminology, a "View" is a fullscreen design element that
* assumes control over the entire viewport and all input events. Different
* types of views are available (not all of which are unfortunately currently
* implemented in JS):
* | View | Has JS adapter? |
* |----------------------|------------------|
* | `button_menu` | |
* | `button_panel` | |
* | `byte_input` | |
* | `dialog_ex` | (as `dialog`) |
* | `empty_screen` | |
* | `file_browser` | |
* | `loading` | |
* | `menu` | |
* | `number_input` | |
* | `popup` | |
* | `submenu` | |
* | `text_box` | |
* | `text_input` | |
* | `variable_item_list` | |
* | `widget` | |
*
* In JS, each view has its own set of properties (or just "props"). The
* programmer can manipulate these properties in two ways:
* - Instantiate a `View` using the `makeWith(props)` method, passing an
* object with the initial properties
* - Call `set(name, value)` to modify a property of an existing `View`
*
* ### View Dispatcher
* The view dispatcher holds references to all the views that an application
* needs and switches between them as the application makes requests to do so.
*
* ### Scene Manager
* The scene manager is an optional add-on to the view dispatcher that makes
* managing applications with complex navigation flows easier. It is currently
* inaccessible from JS.
*
* ### Approaches
* In total, there are three different approaches that you may take when writing
* a GUI application:
* | Approach | Use cases | Available from JS |
* |----------------|------------------------------------------------------------------------------|-------------------|
* | ViewPort only | Accessing the graphics API directly, without any of the nice UI abstractions | |
* | ViewDispatcher | Common UI elements that fit with the overall look of the system | |
* | SceneManager | Additional navigation flow management for complex applications | |
*
* # Example
* An example with three different views using the ViewDispatcher approach:
* ```js
* let eventLoop = require("event_loop");
* let gui = require("gui");
* let loadingView = require("gui/loading");
* let submenuView = require("gui/submenu");
* let emptyView = require("gui/empty_screen");
*
* // Common pattern: declare all the views in an object. This is absolutely not
* // required, but adds clarity to the script.
* let views = {
* // the view dispatcher auto-✨magically✨ remembers views as they are created
* loading: loadingView.make(),
* empty: emptyView.make(),
* demos: submenuView.makeWith({
* items: [
* "Hourglass screen",
* "Empty screen",
* "Exit app",
* ],
* }),
* };
*
* // go to different screens depending on what was selected
* eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, views) {
* if (index === 0) {
* gui.viewDispatcher.switchTo(views.loading);
* } else if (index === 1) {
* gui.viewDispatcher.switchTo(views.empty);
* } else if (index === 2) {
* eventLoop.stop();
* }
* }, gui, eventLoop, views);
*
* // go to the demo chooser screen when the back key is pressed
* eventLoop.subscribe(gui.viewDispatcher.navigation, function (_sub, _, gui, views) {
* gui.viewDispatcher.switchTo(views.demos);
* }, gui, views);
*
* // run UI
* gui.viewDispatcher.switchTo(views.demos);
* eventLoop.run();
* ```
*
* @version Added in JS SDK 0.1
* @module
*/
import type { Contract } from "../event_loop";
type Properties = { [K: string]: any };
export declare class View<Props extends Properties> {
set<P extends keyof Props>(property: P, value: Props[P]): void;
}
export declare class ViewFactory<Props extends Properties, V extends View<Props>> {
make(): V;
makeWith(initial: Partial<Props>): V;
}
/**
* @version Added in JS SDK 0.1
*/
declare class ViewDispatcher {
/**
* Event source for `sendCustom` events
* @version Added in JS SDK 0.1
*/
custom: Contract<number>;
/**
* Event source for navigation events (back key presses)
* @version Added in JS SDK 0.1
*/
navigation: Contract;
/**
* Sends a number to the custom event handler
* @param event number to send
* @version Added in JS SDK 0.1
*/
sendCustom(event: number): void;
/**
* Switches to a view
* @param assoc View-ViewDispatcher association as returned by `add`
* @version Added in JS SDK 0.1
*/
switchTo(assoc: View<any>): void;
/**
* Sends this ViewDispatcher to the front or back, above or below all other
* GUI viewports
* @param direction Either `"front"` or `"back"`
* @version Added in JS SDK 0.1
*/
sendTo(direction: "front" | "back"): void;
}
/**
* @version Added in JS SDK 0.1
*/
export const viewDispatcher: ViewDispatcher;

View File

@ -0,0 +1,33 @@
/**
* Displays an animated hourglass icon. Suppresses all `navigation` events,
* making it impossible for the user to exit the view by pressing the back key.
*
* <img src="../images/loading.png" width="200" alt="Sample screenshot of the view" />
*
* ```js
* let eventLoop = require("event_loop");
* let gui = require("gui");
* let loadingView = require("gui/loading");
* ```
*
* This module depends on the `gui` module, which in turn depends on the
* `event_loop` module, so they _must_ be imported in this order. It is also
* recommended to conceptualize these modules first before using this one.
*
* # Example
* For an example refer to the GUI example.
*
* # View props
* This view does not have any props.
*
* @version Added in JS SDK 0.1
* @module
*/
import type { View, ViewFactory } from ".";
type Props = {};
declare class Loading extends View<Props> { }
declare class LoadingFactory extends ViewFactory<Props, Loading> { }
declare const factory: LoadingFactory;
export = factory;

View File

@ -0,0 +1,39 @@
/**
* Displays a scrollable list of clickable textual entries.
*
* <img src="../images/submenu.png" width="200" alt="Sample screenshot of the view" />
*
* ```js
* let eventLoop = require("event_loop");
* let gui = require("gui");
* let submenuView = require("gui/submenu");
* ```
*
* This module depends on the `gui` module, which in turn depends on the
* `event_loop` module, so they _must_ be imported in this order. It is also
* recommended to conceptualize these modules first before using this one.
*
* # Example
* For an example refer to the GUI example.
*
* # View props
* - `header`: Text displayed at the top of the screen in bold
* - `items`: Array of selectable textual items
*
* @version Added in JS SDK 0.1
* @module
*/
import type { View, ViewFactory } from ".";
import type { Contract } from "../event_loop";
type Props = {
header: string,
items: string[],
};
declare class Submenu extends View<Props> {
chosen: Contract<number>;
}
declare class SubmenuFactory extends ViewFactory<Props, Submenu> { }
declare const factory: SubmenuFactory;
export = factory;

View File

@ -0,0 +1,41 @@
/**
* Displays a scrollable read-only text field.
*
* <img src="text_box.png" width="200" alt="Sample screenshot of the view" />
*
* ```js
* let eventLoop = require("event_loop");
* let gui = require("gui");
* let textBoxView = require("gui/text_box");
* ```
*
* This module depends on the `gui` module, which in turn depends on the
* `event_loop` module, so they _must_ be imported in this order. It is also
* recommended to conceptualize these modules first before using this one.
*
* # Example
* For an example refer to the `gui.js` example script.
*
* # View props
* - `text`: Text in the text box
* - `font`: The font to display the text in (`"text"` or `"hex"`)
* - `focus`: The initial focus of the text box (`"start"` or `"end"`)
*
* @version Added in JS SDK 0.1
* @module
*/
import type { View, ViewFactory } from ".";
import type { Contract } from "../event_loop";
type Props = {
text: string,
font: "text" | "hex",
focus: "start" | "end",
}
declare class TextBox extends View<Props> {
chosen: Contract<number>;
}
declare class TextBoxFactory extends ViewFactory<Props, TextBox> { }
declare const factory: TextBoxFactory;
export = factory;

View File

@ -0,0 +1,41 @@
/**
* Displays a keyboard.
*
* <img src="../images/text_input.png" width="200" alt="Sample screenshot of the view" />
*
* ```js
* let eventLoop = require("event_loop");
* let gui = require("gui");
* let textInputView = require("gui/text_input");
* ```
*
* This module depends on the `gui` module, which in turn depends on the
* `event_loop` module, so they _must_ be imported in this order. It is also
* recommended to conceptualize these modules first before using this one.
*
* # Example
* For an example refer to the `gui.js` example script.
*
* # View props
* - `header`: Text displayed at the top of the screen
* - `minLength`: Minimum allowed text length
* - `maxLength`: Maximum allowed text length
*
* @version Added in JS SDK 0.1
* @module
*/
import type { View, ViewFactory } from ".";
import type { Contract } from "../event_loop";
type Props = {
header: string,
minLength: number,
maxLength: number,
}
declare class TextInput extends View<Props> {
input: Contract<string>;
}
declare class TextInputFactory extends ViewFactory<Props, TextInput> { }
declare const factory: TextInputFactory;
export = factory;

View File

@ -1,24 +1,54 @@
/**
* Math operations
* @version Added in JS SDK 0.1
* @module
*/
/** @version Added in JS SDK 0.1 */
export function abs(n: number): number; export function abs(n: number): number;
/** @version Added in JS SDK 0.1 */
export function acos(n: number): number; export function acos(n: number): number;
/** @version Added in JS SDK 0.1 */
export function acosh(n: number): number; export function acosh(n: number): number;
/** @version Added in JS SDK 0.1 */
export function asin(n: number): number; export function asin(n: number): number;
/** @version Added in JS SDK 0.1 */
export function asinh(n: number): number; export function asinh(n: number): number;
/** @version Added in JS SDK 0.1 */
export function atan(n: number): number; export function atan(n: number): number;
/** @version Added in JS SDK 0.1 */
export function atan2(a: number, b: number): number; export function atan2(a: number, b: number): number;
/** @version Added in JS SDK 0.1 */
export function atanh(n: number): number; export function atanh(n: number): number;
/** @version Added in JS SDK 0.1 */
export function cbrt(n: number): number; export function cbrt(n: number): number;
/** @version Added in JS SDK 0.1 */
export function ceil(n: number): number; export function ceil(n: number): number;
/** @version Added in JS SDK 0.1 */
export function clz32(n: number): number; export function clz32(n: number): number;
/** @version Added in JS SDK 0.1 */
export function cos(n: number): number; export function cos(n: number): number;
/** @version Added in JS SDK 0.1 */
export function exp(n: number): number; export function exp(n: number): number;
/** @version Added in JS SDK 0.1 */
export function floor(n: number): number; export function floor(n: number): number;
/** @version Added in JS SDK 0.1 */
export function max(n: number, m: number): number; export function max(n: number, m: number): number;
/** @version Added in JS SDK 0.1 */
export function min(n: number, m: number): number; export function min(n: number, m: number): number;
/** @version Added in JS SDK 0.1 */
export function pow(n: number, m: number): number; export function pow(n: number, m: number): number;
/** @version Added in JS SDK 0.1 */
export function random(): number; export function random(): number;
/** @version Added in JS SDK 0.1 */
export function sign(n: number): number; export function sign(n: number): number;
/** @version Added in JS SDK 0.1 */
export function sin(n: number): number; export function sin(n: number): number;
/** @version Added in JS SDK 0.1 */
export function sqrt(n: number): number; export function sqrt(n: number): number;
/** @version Added in JS SDK 0.1 */
export function trunc(n: number): number; export function trunc(n: number): number;
/** @version Added in JS SDK 0.1 */
declare const PI: number; declare const PI: number;
/** @version Added in JS SDK 0.1 */
declare const EPSILON: number; declare const EPSILON: number;

View File

@ -1,6 +1,13 @@
/**
* Module for using the color LED and vibration motor
* @version Added in JS SDK 0.1
* @module
*/
/** /**
* @brief Signals success to the user via the color LED, speaker and vibration * @brief Signals success to the user via the color LED, speaker and vibration
* motor * motor
* @version Added in JS SDK 0.1
*/ */
export declare function success(): void; export declare function success(): void;
@ -10,11 +17,15 @@ export declare function success(): void;
*/ */
export declare function error(): void; export declare function error(): void;
/**
* @version Added in JS SDK 0.1
*/
export type Color = "red" | "green" | "blue" | "yellow" | "cyan" | "magenta"; export type Color = "red" | "green" | "blue" | "yellow" | "cyan" | "magenta";
/** /**
* @brief Displays a basic color on the color LED * @brief Displays a basic color on the color LED
* @param color The color to display, see `Color` * @param color The color to display, see `Color`
* @param duration The duration, either `"short"` (10ms) or `"long"` (100ms) * @param duration The duration, either `"short"` (10ms) or `"long"` (100ms)
* @version Added in JS SDK 0.1
*/ */
export declare function blink(color: Color, duration: "short" | "long"): void; export declare function blink(color: Color, duration: "short" | "long"): void;

View File

@ -0,0 +1,29 @@
{
"name": "@flipperdevices/fz-sdk",
"version": "0.1.0",
"description": "Type declarations and documentation for native JS modules available on Flipper Zero",
"keywords": [
"flipper",
"flipper zero",
"framework"
],
"author": "Flipper Devices",
"license": "GPL-3.0-only",
"repository": {
"type": "git",
"url": "git+https://github.com/flipperdevices/flipperzero-firmware.git",
"directory": "applications/system/js_app/packages/fz-sdk"
},
"type": "module",
"devDependencies": {
"esbuild": "^0.24.0",
"esbuild-plugin-tsc": "^0.4.0",
"json5": "^2.2.3",
"typedoc": "^0.26.10",
"typedoc-material-theme": "^1.1.0"
},
"dependencies": {
"prompts": "^2.4.2",
"serialport": "^12.0.0"
}
}

View File

@ -0,0 +1,896 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
prompts:
specifier: ^2.4.2
version: 2.4.2
serialport:
specifier: ^12.0.0
version: 12.0.0
devDependencies:
esbuild:
specifier: ^0.24.0
version: 0.24.0
esbuild-plugin-tsc:
specifier: ^0.4.0
version: 0.4.0(typescript@5.6.3)
json5:
specifier: ^2.2.3
version: 2.2.3
typedoc:
specifier: ^0.26.10
version: 0.26.10(typescript@5.6.3)
typedoc-material-theme:
specifier: ^1.1.0
version: 1.1.0(typedoc@0.26.10(typescript@5.6.3))
packages:
'@esbuild/aix-ppc64@0.24.0':
resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.24.0':
resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.24.0':
resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.24.0':
resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.24.0':
resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.24.0':
resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.24.0':
resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.24.0':
resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.24.0':
resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.24.0':
resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.24.0':
resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.24.0':
resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.24.0':
resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.24.0':
resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.24.0':
resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.24.0':
resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.24.0':
resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-x64@0.24.0':
resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-arm64@0.24.0':
resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.24.0':
resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/sunos-x64@0.24.0':
resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.24.0':
resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.24.0':
resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.24.0':
resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
'@material/material-color-utilities@0.2.7':
resolution: {integrity: sha512-0FCeqG6WvK4/Cc06F/xXMd/pv4FeisI0c1tUpBbfhA2n9Y8eZEv4Karjbmf2ZqQCPUWMrGp8A571tCjizxoTiQ==}
'@serialport/binding-mock@10.2.2':
resolution: {integrity: sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==}
engines: {node: '>=12.0.0'}
'@serialport/bindings-cpp@12.0.1':
resolution: {integrity: sha512-r2XOwY2dDvbW7dKqSPIk2gzsr6M6Qpe9+/Ngs94fNaNlcTRCV02PfaoDmRgcubpNVVcLATlxSxPTIDw12dbKOg==}
engines: {node: '>=16.0.0'}
'@serialport/bindings-interface@1.2.2':
resolution: {integrity: sha512-CJaUd5bLvtM9c5dmO9rPBHPXTa9R2UwpkJ0wdh9JCYcbrPWsKz+ErvR0hBLeo7NPeiFdjFO4sonRljiw4d2XiA==}
engines: {node: ^12.22 || ^14.13 || >=16}
'@serialport/parser-byte-length@12.0.0':
resolution: {integrity: sha512-0ei0txFAj+s6FTiCJFBJ1T2hpKkX8Md0Pu6dqMrYoirjPskDLJRgZGLqoy3/lnU1bkvHpnJO+9oJ3PB9v8rNlg==}
engines: {node: '>=12.0.0'}
'@serialport/parser-cctalk@12.0.0':
resolution: {integrity: sha512-0PfLzO9t2X5ufKuBO34DQKLXrCCqS9xz2D0pfuaLNeTkyGUBv426zxoMf3rsMRodDOZNbFblu3Ae84MOQXjnZw==}
engines: {node: '>=12.0.0'}
'@serialport/parser-delimiter@11.0.0':
resolution: {integrity: sha512-aZLJhlRTjSmEwllLG7S4J8s8ctRAS0cbvCpO87smLvl3e4BgzbVgF6Z6zaJd3Aji2uSiYgfedCdNc4L6W+1E2g==}
engines: {node: '>=12.0.0'}
'@serialport/parser-delimiter@12.0.0':
resolution: {integrity: sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==}
engines: {node: '>=12.0.0'}
'@serialport/parser-inter-byte-timeout@12.0.0':
resolution: {integrity: sha512-GnCh8K0NAESfhCuXAt+FfBRz1Cf9CzIgXfp7SdMgXwrtuUnCC/yuRTUFWRvuzhYKoAo1TL0hhUo77SFHUH1T/w==}
engines: {node: '>=12.0.0'}
'@serialport/parser-packet-length@12.0.0':
resolution: {integrity: sha512-p1hiCRqvGHHLCN/8ZiPUY/G0zrxd7gtZs251n+cfNTn+87rwcdUeu9Dps3Aadx30/sOGGFL6brIRGK4l/t7MuQ==}
engines: {node: '>=8.6.0'}
'@serialport/parser-readline@11.0.0':
resolution: {integrity: sha512-rRAivhRkT3YO28WjmmG4FQX6L+KMb5/ikhyylRfzWPw0nSXy97+u07peS9CbHqaNvJkMhH1locp2H36aGMOEIA==}
engines: {node: '>=12.0.0'}
'@serialport/parser-readline@12.0.0':
resolution: {integrity: sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==}
engines: {node: '>=12.0.0'}
'@serialport/parser-ready@12.0.0':
resolution: {integrity: sha512-ygDwj3O4SDpZlbrRUraoXIoIqb8sM7aMKryGjYTIF0JRnKeB1ys8+wIp0RFMdFbO62YriUDextHB5Um5cKFSWg==}
engines: {node: '>=12.0.0'}
'@serialport/parser-regex@12.0.0':
resolution: {integrity: sha512-dCAVh4P/pZrLcPv9NJ2mvPRBg64L5jXuiRxIlyxxdZGH4WubwXVXY/kBTihQmiAMPxbT3yshSX8f2+feqWsxqA==}
engines: {node: '>=12.0.0'}
'@serialport/parser-slip-encoder@12.0.0':
resolution: {integrity: sha512-0APxDGR9YvJXTRfY+uRGhzOhTpU5akSH183RUcwzN7QXh8/1jwFsFLCu0grmAUfi+fItCkR+Xr1TcNJLR13VNA==}
engines: {node: '>=12.0.0'}
'@serialport/parser-spacepacket@12.0.0':
resolution: {integrity: sha512-dozONxhPC/78pntuxpz/NOtVps8qIc/UZzdc/LuPvVsqCoJXiRxOg6ZtCP/W58iibJDKPZPAWPGYeZt9DJxI+Q==}
engines: {node: '>=12.0.0'}
'@serialport/stream@12.0.0':
resolution: {integrity: sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==}
engines: {node: '>=12.0.0'}
'@shikijs/core@1.22.0':
resolution: {integrity: sha512-S8sMe4q71TJAW+qG93s5VaiihujRK6rqDFqBnxqvga/3LvqHEnxqBIOPkt//IdXVtHkQWKu4nOQNk0uBGicU7Q==}
'@shikijs/engine-javascript@1.22.0':
resolution: {integrity: sha512-AeEtF4Gcck2dwBqCFUKYfsCq0s+eEbCEbkUuFou53NZ0sTGnJnJ/05KHQFZxpii5HMXbocV9URYVowOP2wH5kw==}
'@shikijs/engine-oniguruma@1.22.0':
resolution: {integrity: sha512-5iBVjhu/DYs1HB0BKsRRFipRrD7rqjxlWTj4F2Pf+nQSPqc3kcyqFFeZXnBMzDf0HdqaFVvhDRAGiYNvyLP+Mw==}
'@shikijs/types@1.22.0':
resolution: {integrity: sha512-Fw/Nr7FGFhlQqHfxzZY8Cwtwk5E9nKDUgeLjZgt3UuhcM3yJR9xj3ZGNravZZok8XmEZMiYkSMTPlPkULB8nww==}
'@shikijs/vscode-textmate@9.3.0':
resolution: {integrity: sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==}
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
'@types/mdast@4.0.4':
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
'@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
'@ungap/structured-clone@1.2.0':
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
brace-expansion@2.0.1:
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
character-entities-html4@2.1.0:
resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
character-entities-legacy@3.0.0:
resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
comma-separated-tokens@2.0.3:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
debug@4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
esbuild-plugin-tsc@0.4.0:
resolution: {integrity: sha512-q9gWIovt1nkwchMLc2zhyksaiHOv3kDK4b0AUol8lkMCRhJ1zavgfb2fad6BKp7FT9rh/OHmEBXVjczLoi/0yw==}
peerDependencies:
typescript: ^4.0.0 || ^5.0.0
esbuild@0.24.0:
resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==}
engines: {node: '>=18'}
hasBin: true
hast-util-to-html@9.0.3:
resolution: {integrity: sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==}
hast-util-whitespace@3.0.0:
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
html-void-elements@3.0.0:
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
json5@2.2.3:
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
hasBin: true
kleur@3.0.3:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
engines: {node: '>=6'}
linkify-it@5.0.0:
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
lunr@2.3.9:
resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==}
markdown-it@14.1.0:
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
hasBin: true
mdast-util-to-hast@13.2.0:
resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==}
mdurl@2.0.0:
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
micromark-util-character@2.1.0:
resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==}
micromark-util-encode@2.0.0:
resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==}
micromark-util-sanitize-uri@2.0.0:
resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==}
micromark-util-symbol@2.0.0:
resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==}
micromark-util-types@2.0.0:
resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==}
minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
node-addon-api@7.0.0:
resolution: {integrity: sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==}
node-gyp-build@4.6.0:
resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==}
hasBin: true
oniguruma-to-js@0.4.3:
resolution: {integrity: sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==}
prompts@2.4.2:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'}
property-information@6.5.0:
resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
punycode.js@2.3.1:
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
engines: {node: '>=6'}
regex@4.3.3:
resolution: {integrity: sha512-r/AadFO7owAq1QJVeZ/nq9jNS1vyZt+6t1p/E59B56Rn2GCya+gr1KSyOzNL/er+r+B7phv5jG2xU2Nz1YkmJg==}
serialport@12.0.0:
resolution: {integrity: sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==}
engines: {node: '>=16.0.0'}
shiki@1.22.0:
resolution: {integrity: sha512-/t5LlhNs+UOKQCYBtl5ZsH/Vclz73GIqT2yQsCBygr8L/ppTdmpL4w3kPLoZJbMKVWtoG77Ue1feOjZfDxvMkw==}
sisteransi@1.0.5:
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
space-separated-tokens@2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
stringify-entities@4.0.4:
resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
strip-comments@2.0.1:
resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==}
engines: {node: '>=10'}
trim-lines@3.0.1:
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
typedoc-material-theme@1.1.0:
resolution: {integrity: sha512-LLWGVb8w+i+QGnsu/a0JKjcuzndFQt/UeGVOQz0HFFGGocROEHv5QYudIACrj+phL2LDwH05tJx0Ob3pYYH2UA==}
engines: {node: '>=18.0.0', npm: '>=8.6.0'}
peerDependencies:
typedoc: ^0.25.13 || ^0.26.3
typedoc@0.26.10:
resolution: {integrity: sha512-xLmVKJ8S21t+JeuQLNueebEuTVphx6IrP06CdV7+0WVflUSW3SPmR+h1fnWVdAR/FQePEgsSWCUHXqKKjzuUAw==}
engines: {node: '>= 18'}
hasBin: true
peerDependencies:
typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x
typescript@5.6.3:
resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
engines: {node: '>=14.17'}
hasBin: true
uc.micro@2.1.0:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
unist-util-is@6.0.0:
resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
unist-util-position@5.0.0:
resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
unist-util-stringify-position@4.0.0:
resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
unist-util-visit-parents@6.0.1:
resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==}
unist-util-visit@5.0.0:
resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
vfile-message@4.0.2:
resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==}
vfile@6.0.3:
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
yaml@2.6.0:
resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==}
engines: {node: '>= 14'}
hasBin: true
zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
snapshots:
'@esbuild/aix-ppc64@0.24.0':
optional: true
'@esbuild/android-arm64@0.24.0':
optional: true
'@esbuild/android-arm@0.24.0':
optional: true
'@esbuild/android-x64@0.24.0':
optional: true
'@esbuild/darwin-arm64@0.24.0':
optional: true
'@esbuild/darwin-x64@0.24.0':
optional: true
'@esbuild/freebsd-arm64@0.24.0':
optional: true
'@esbuild/freebsd-x64@0.24.0':
optional: true
'@esbuild/linux-arm64@0.24.0':
optional: true
'@esbuild/linux-arm@0.24.0':
optional: true
'@esbuild/linux-ia32@0.24.0':
optional: true
'@esbuild/linux-loong64@0.24.0':
optional: true
'@esbuild/linux-mips64el@0.24.0':
optional: true
'@esbuild/linux-ppc64@0.24.0':
optional: true
'@esbuild/linux-riscv64@0.24.0':
optional: true
'@esbuild/linux-s390x@0.24.0':
optional: true
'@esbuild/linux-x64@0.24.0':
optional: true
'@esbuild/netbsd-x64@0.24.0':
optional: true
'@esbuild/openbsd-arm64@0.24.0':
optional: true
'@esbuild/openbsd-x64@0.24.0':
optional: true
'@esbuild/sunos-x64@0.24.0':
optional: true
'@esbuild/win32-arm64@0.24.0':
optional: true
'@esbuild/win32-ia32@0.24.0':
optional: true
'@esbuild/win32-x64@0.24.0':
optional: true
'@material/material-color-utilities@0.2.7': {}
'@serialport/binding-mock@10.2.2':
dependencies:
'@serialport/bindings-interface': 1.2.2
debug: 4.3.4
transitivePeerDependencies:
- supports-color
'@serialport/bindings-cpp@12.0.1':
dependencies:
'@serialport/bindings-interface': 1.2.2
'@serialport/parser-readline': 11.0.0
debug: 4.3.4
node-addon-api: 7.0.0
node-gyp-build: 4.6.0
transitivePeerDependencies:
- supports-color
'@serialport/bindings-interface@1.2.2': {}
'@serialport/parser-byte-length@12.0.0': {}
'@serialport/parser-cctalk@12.0.0': {}
'@serialport/parser-delimiter@11.0.0': {}
'@serialport/parser-delimiter@12.0.0': {}
'@serialport/parser-inter-byte-timeout@12.0.0': {}
'@serialport/parser-packet-length@12.0.0': {}
'@serialport/parser-readline@11.0.0':
dependencies:
'@serialport/parser-delimiter': 11.0.0
'@serialport/parser-readline@12.0.0':
dependencies:
'@serialport/parser-delimiter': 12.0.0
'@serialport/parser-ready@12.0.0': {}
'@serialport/parser-regex@12.0.0': {}
'@serialport/parser-slip-encoder@12.0.0': {}
'@serialport/parser-spacepacket@12.0.0': {}
'@serialport/stream@12.0.0':
dependencies:
'@serialport/bindings-interface': 1.2.2
debug: 4.3.4
transitivePeerDependencies:
- supports-color
'@shikijs/core@1.22.0':
dependencies:
'@shikijs/engine-javascript': 1.22.0
'@shikijs/engine-oniguruma': 1.22.0
'@shikijs/types': 1.22.0
'@shikijs/vscode-textmate': 9.3.0
'@types/hast': 3.0.4
hast-util-to-html: 9.0.3
'@shikijs/engine-javascript@1.22.0':
dependencies:
'@shikijs/types': 1.22.0
'@shikijs/vscode-textmate': 9.3.0
oniguruma-to-js: 0.4.3
'@shikijs/engine-oniguruma@1.22.0':
dependencies:
'@shikijs/types': 1.22.0
'@shikijs/vscode-textmate': 9.3.0
'@shikijs/types@1.22.0':
dependencies:
'@shikijs/vscode-textmate': 9.3.0
'@types/hast': 3.0.4
'@shikijs/vscode-textmate@9.3.0': {}
'@types/hast@3.0.4':
dependencies:
'@types/unist': 3.0.3
'@types/mdast@4.0.4':
dependencies:
'@types/unist': 3.0.3
'@types/unist@3.0.3': {}
'@ungap/structured-clone@1.2.0': {}
argparse@2.0.1: {}
balanced-match@1.0.2: {}
brace-expansion@2.0.1:
dependencies:
balanced-match: 1.0.2
ccount@2.0.1: {}
character-entities-html4@2.1.0: {}
character-entities-legacy@3.0.0: {}
comma-separated-tokens@2.0.3: {}
debug@4.3.4:
dependencies:
ms: 2.1.2
dequal@2.0.3: {}
devlop@1.1.0:
dependencies:
dequal: 2.0.3
entities@4.5.0: {}
esbuild-plugin-tsc@0.4.0(typescript@5.6.3):
dependencies:
strip-comments: 2.0.1
typescript: 5.6.3
esbuild@0.24.0:
optionalDependencies:
'@esbuild/aix-ppc64': 0.24.0
'@esbuild/android-arm': 0.24.0
'@esbuild/android-arm64': 0.24.0
'@esbuild/android-x64': 0.24.0
'@esbuild/darwin-arm64': 0.24.0
'@esbuild/darwin-x64': 0.24.0
'@esbuild/freebsd-arm64': 0.24.0
'@esbuild/freebsd-x64': 0.24.0
'@esbuild/linux-arm': 0.24.0
'@esbuild/linux-arm64': 0.24.0
'@esbuild/linux-ia32': 0.24.0
'@esbuild/linux-loong64': 0.24.0
'@esbuild/linux-mips64el': 0.24.0
'@esbuild/linux-ppc64': 0.24.0
'@esbuild/linux-riscv64': 0.24.0
'@esbuild/linux-s390x': 0.24.0
'@esbuild/linux-x64': 0.24.0
'@esbuild/netbsd-x64': 0.24.0
'@esbuild/openbsd-arm64': 0.24.0
'@esbuild/openbsd-x64': 0.24.0
'@esbuild/sunos-x64': 0.24.0
'@esbuild/win32-arm64': 0.24.0
'@esbuild/win32-ia32': 0.24.0
'@esbuild/win32-x64': 0.24.0
hast-util-to-html@9.0.3:
dependencies:
'@types/hast': 3.0.4
'@types/unist': 3.0.3
ccount: 2.0.1
comma-separated-tokens: 2.0.3
hast-util-whitespace: 3.0.0
html-void-elements: 3.0.0
mdast-util-to-hast: 13.2.0
property-information: 6.5.0
space-separated-tokens: 2.0.2
stringify-entities: 4.0.4
zwitch: 2.0.4
hast-util-whitespace@3.0.0:
dependencies:
'@types/hast': 3.0.4
html-void-elements@3.0.0: {}
json5@2.2.3: {}
kleur@3.0.3: {}
linkify-it@5.0.0:
dependencies:
uc.micro: 2.1.0
lunr@2.3.9: {}
markdown-it@14.1.0:
dependencies:
argparse: 2.0.1
entities: 4.5.0
linkify-it: 5.0.0
mdurl: 2.0.0
punycode.js: 2.3.1
uc.micro: 2.1.0
mdast-util-to-hast@13.2.0:
dependencies:
'@types/hast': 3.0.4
'@types/mdast': 4.0.4
'@ungap/structured-clone': 1.2.0
devlop: 1.1.0
micromark-util-sanitize-uri: 2.0.0
trim-lines: 3.0.1
unist-util-position: 5.0.0
unist-util-visit: 5.0.0
vfile: 6.0.3
mdurl@2.0.0: {}
micromark-util-character@2.1.0:
dependencies:
micromark-util-symbol: 2.0.0
micromark-util-types: 2.0.0
micromark-util-encode@2.0.0: {}
micromark-util-sanitize-uri@2.0.0:
dependencies:
micromark-util-character: 2.1.0
micromark-util-encode: 2.0.0
micromark-util-symbol: 2.0.0
micromark-util-symbol@2.0.0: {}
micromark-util-types@2.0.0: {}
minimatch@9.0.5:
dependencies:
brace-expansion: 2.0.1
ms@2.1.2: {}
node-addon-api@7.0.0: {}
node-gyp-build@4.6.0: {}
oniguruma-to-js@0.4.3:
dependencies:
regex: 4.3.3
prompts@2.4.2:
dependencies:
kleur: 3.0.3
sisteransi: 1.0.5
property-information@6.5.0: {}
punycode.js@2.3.1: {}
regex@4.3.3: {}
serialport@12.0.0:
dependencies:
'@serialport/binding-mock': 10.2.2
'@serialport/bindings-cpp': 12.0.1
'@serialport/parser-byte-length': 12.0.0
'@serialport/parser-cctalk': 12.0.0
'@serialport/parser-delimiter': 12.0.0
'@serialport/parser-inter-byte-timeout': 12.0.0
'@serialport/parser-packet-length': 12.0.0
'@serialport/parser-readline': 12.0.0
'@serialport/parser-ready': 12.0.0
'@serialport/parser-regex': 12.0.0
'@serialport/parser-slip-encoder': 12.0.0
'@serialport/parser-spacepacket': 12.0.0
'@serialport/stream': 12.0.0
debug: 4.3.4
transitivePeerDependencies:
- supports-color
shiki@1.22.0:
dependencies:
'@shikijs/core': 1.22.0
'@shikijs/engine-javascript': 1.22.0
'@shikijs/engine-oniguruma': 1.22.0
'@shikijs/types': 1.22.0
'@shikijs/vscode-textmate': 9.3.0
'@types/hast': 3.0.4
sisteransi@1.0.5: {}
space-separated-tokens@2.0.2: {}
stringify-entities@4.0.4:
dependencies:
character-entities-html4: 2.1.0
character-entities-legacy: 3.0.0
strip-comments@2.0.1: {}
trim-lines@3.0.1: {}
typedoc-material-theme@1.1.0(typedoc@0.26.10(typescript@5.6.3)):
dependencies:
'@material/material-color-utilities': 0.2.7
typedoc: 0.26.10(typescript@5.6.3)
typedoc@0.26.10(typescript@5.6.3):
dependencies:
lunr: 2.3.9
markdown-it: 14.1.0
minimatch: 9.0.5
shiki: 1.22.0
typescript: 5.6.3
yaml: 2.6.0
typescript@5.6.3: {}
uc.micro@2.1.0: {}
unist-util-is@6.0.0:
dependencies:
'@types/unist': 3.0.3
unist-util-position@5.0.0:
dependencies:
'@types/unist': 3.0.3
unist-util-stringify-position@4.0.0:
dependencies:
'@types/unist': 3.0.3
unist-util-visit-parents@6.0.1:
dependencies:
'@types/unist': 3.0.3
unist-util-is: 6.0.0
unist-util-visit@5.0.0:
dependencies:
'@types/unist': 3.0.3
unist-util-is: 6.0.0
unist-util-visit-parents: 6.0.1
vfile-message@4.0.2:
dependencies:
'@types/unist': 3.0.3
unist-util-stringify-position: 4.0.0
vfile@6.0.3:
dependencies:
'@types/unist': 3.0.3
vfile-message: 4.0.2
yaml@2.6.0: {}
zwitch@2.0.4: {}

View File

@ -0,0 +1,176 @@
#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { SerialPort } from "serialport";
import prompts from "prompts";
import esbuild from "esbuild";
import json5 from "json5";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
async function build(config) {
await esbuild.build({
entryPoints: ["./dist/index.js"],
outfile: config.output,
tsconfig: "./tsconfig.json",
format: "cjs",
bundle: true,
minify: config.minify,
external: [
"@flipperdevices/fz-sdk/*"
],
supported: {
"array-spread": false,
"arrow": false,
"async-await": false,
"async-generator": false,
"bigint": false,
"class": false,
"const-and-let": true,
"decorators": false,
"default-argument": false,
"destructuring": false,
"dynamic-import": false,
"exponent-operator": false,
"export-star-as": false,
"for-await": false,
"for-of": false,
"function-name-configurable": false,
"function-or-class-property-access": false,
"generator": false,
"hashbang": false,
"import-assertions": false,
"import-meta": false,
"inline-script": false,
"logical-assignment": false,
"nested-rest-binding": false,
"new-target": false,
"node-colon-prefix-import": false,
"node-colon-prefix-require": false,
"nullish-coalescing": false,
"object-accessors": false,
"object-extensions": false,
"object-rest-spread": false,
"optional-catch-binding": false,
"optional-chain": false,
"regexp-dot-all-flag": false,
"regexp-lookbehind-assertions": false,
"regexp-match-indices": false,
"regexp-named-capture-groups": false,
"regexp-set-notation": false,
"regexp-sticky-and-unicode-flags": false,
"regexp-unicode-property-escapes": false,
"rest-argument": false,
"template-literal": false,
"top-level-await": false,
"typeof-exotic-object-is-object": false,
"unicode-escapes": false,
"using": false,
},
});
let outContents = fs.readFileSync(config.output, "utf8");
outContents = "let exports = {};\n" + outContents;
if (config.enforceSdkVersion) {
const version = json5.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8")).version;
let [major, minor, _] = version.split(".");
outContents = `checkSdkCompatibility(${major}, ${minor});\n${outContents}`;
}
fs.writeFileSync(config.output, outContents);
}
async function upload(config) {
const appFile = fs.readFileSync(config.input, "utf8");
const flippers = (await SerialPort.list()).filter(x => x.serialNumber?.startsWith("flip_"));
if (!flippers) {
console.error("No Flippers found");
process.exit(1);
}
let portPath = flippers[0].path;
if (flippers.length > 1) {
port = (await prompts([{
type: "select",
name: "port",
message: "Select Flipper to run the app on",
choices: flippers.map(x => ({ title: x.serialNumber.replace("flip_", ""), value: x.path })),
}])).port;
}
console.log(`Connecting to Flipper at ${portPath}`);
let port = new SerialPort({ path: portPath, baudRate: 230400 });
let received = "";
let lastMatch = 0;
async function waitFor(string, timeoutMs) {
return new Promise((resolve, _reject) => {
let timeout = undefined;
if (timeoutMs) {
timeout = setTimeout(() => {
console.error("Error: timeout");
process.exit(1);
}, timeoutMs);
}
setInterval(() => {
let idx = received.indexOf(string, lastMatch);
if (idx !== -1) {
lastMatch = idx;
if (timeoutMs)
clearTimeout(timeout);
resolve();
}
}, 50);
});
}
port.on("data", (data) => {
received += data.toString();
});
await waitFor(">: ", 1000);
console.log("Uploading application file");
port.write(`storage remove ${config.output}\x0d`);
port.drain();
await waitFor(">: ", 1000);
port.write(`storage write_chunk ${config.output} ${appFile.length}\x0d`);
await waitFor("Ready", 1000);
port.write(appFile);
port.drain();
await waitFor(">: ", 1000);
console.log("Launching application");
port.write(`js ${config.output}\x0d`);
port.drain();
await waitFor("Running", 1000);
process.stdout.write(received.slice(lastMatch));
port.on("data", (data) => {
process.stdout.write(data.toString());
});
process.on("exit", () => {
port.write("\x03");
});
await waitFor("Script done!", 0);
process.exit(0);
}
(async () => {
const commands = {
"build": build,
"upload": upload,
};
const config = json5.parse(fs.readFileSync("./fz-sdk.config.json5", "utf8"));
const command = process.argv[2];
if (!Object.keys(commands).includes(command)) {
console.error(`Unknown command ${command}. Supported: ${Object.keys(commands).join(", ")}`);
process.exit(1);
}
await commands[command](config[command]);
})();

View File

@ -1,7 +1,14 @@
/**
* Module for accessing the serial port
* @version Added in JS SDK 0.1
* @module
*/
/** /**
* @brief Initializes the serial port * @brief Initializes the serial port
* @param port The port to initialize (`"lpuart"` or `"start"`) * @param port The port to initialize (`"lpuart"` or `"start"`)
* @param baudRate * @param baudRate
* @version Added in JS SDK 0.1
*/ */
export declare function setup(port: "lpuart" | "usart", baudRate: number): void; export declare function setup(port: "lpuart" | "usart", baudRate: number): void;
@ -13,6 +20,7 @@ export declare function setup(port: "lpuart" | "usart", baudRate: number): void;
* - Arrays of numbers will get sent as a sequence of bytes. * - Arrays of numbers will get sent as a sequence of bytes.
* - `ArrayBuffer`s and `TypedArray`s will be sent as a sequence * - `ArrayBuffer`s and `TypedArray`s will be sent as a sequence
* of bytes. * of bytes.
* @version Added in JS SDK 0.1
*/ */
export declare function write<E extends ElementType>(value: string | number | number[] | ArrayBuffer | TypedArray<E>): void; export declare function write<E extends ElementType>(value: string | number | number[] | ArrayBuffer | TypedArray<E>): void;
@ -24,6 +32,7 @@ export declare function write<E extends ElementType>(value: string | number | nu
* unset, the function will wait forever. * unset, the function will wait forever.
* @returns The received data interpreted as ASCII, or `undefined` if 0 bytes * @returns The received data interpreted as ASCII, or `undefined` if 0 bytes
* were read. * were read.
* @version Added in JS SDK 0.1
*/ */
export declare function read(length: number, timeout?: number): string | undefined; export declare function read(length: number, timeout?: number): string | undefined;
@ -39,6 +48,7 @@ export declare function read(length: number, timeout?: number): string | undefin
* applies to characters, not entire strings. * applies to characters, not entire strings.
* @returns The received data interpreted as ASCII, or `undefined` if 0 bytes * @returns The received data interpreted as ASCII, or `undefined` if 0 bytes
* were read. * were read.
* @version Added in JS SDK 0.1
*/ */
export declare function readln(timeout?: number): string; export declare function readln(timeout?: number): string;
@ -50,6 +60,7 @@ export declare function readln(timeout?: number): string;
* unset, the function will wait forever. * unset, the function will wait forever.
* @returns The received data as an ArrayBuffer, or `undefined` if 0 bytes were * @returns The received data as an ArrayBuffer, or `undefined` if 0 bytes were
* read. * read.
* @version Added in JS SDK 0.1
*/ */
export declare function readBytes(length: number, timeout?: number): ArrayBuffer; export declare function readBytes(length: number, timeout?: number): ArrayBuffer;
@ -73,5 +84,6 @@ export declare function readBytes(length: number, timeout?: number): ArrayBuffer
* @returns The index of the matched pattern if multiple were provided, or 0 if * @returns The index of the matched pattern if multiple were provided, or 0 if
* only one was provided and it matched, or `undefined` if none of the * only one was provided and it matched, or `undefined` if none of the
* patterns matched. * patterns matched.
* @version Added in JS SDK 0.1
*/ */
export declare function expect(patterns: string | number[] | string[] | number[][], timeout?: number): number | undefined; export declare function expect(patterns: string | number[] | string[] | number[][], timeout?: number): number | undefined;

View File

@ -1,8 +1,15 @@
/**
* Module for accessing the filesystem
* @version Added in JS SDK 0.1
* @module
*/
/** /**
* File readability mode: * File readability mode:
* - `"r"`: read-only * - `"r"`: read-only
* - `"w"`: write-only * - `"w"`: write-only
* - `"rw"`: read-write * - `"rw"`: read-write
* @version Added in JS SDK 0.1
*/ */
export type AccessMode = "r" | "w" | "rw"; export type AccessMode = "r" | "w" | "rw";
@ -13,53 +20,78 @@ export type AccessMode = "r" | "w" | "rw";
* - `"open_append"`: open file and set r/w pointer to EOF, or create a new one if it doesn't exist * - `"open_append"`: open file and set r/w pointer to EOF, or create a new one if it doesn't exist
* - `"create_new"`: create new file or fail if it exists * - `"create_new"`: create new file or fail if it exists
* - `"create_always"`: truncate and open file, or create a new empty one if it doesn't exist * - `"create_always"`: truncate and open file, or create a new empty one if it doesn't exist
* @version Added in JS SDK 0.1
*/ */
export type OpenMode = "open_existing" | "open_always" | "open_append" | "create_new" | "create_always"; export type OpenMode = "open_existing" | "open_always" | "open_append" | "create_new" | "create_always";
/** Standard UNIX timestamp */ /**
* Standard UNIX timestamp
* @version Added in JS SDK 0.1
*/
export type Timestamp = number; export type Timestamp = number;
/** File information structure */ /**
* File information structure
* @version Added in JS SDK 0.1
*/
export declare class FileInfo { export declare class FileInfo {
/** /**
* Full path (e.g. "/ext/test", returned by `stat`) or file name * Full path (e.g. "/ext/test", returned by `stat`) or file name
* (e.g. "test", returned by `readDirectory`) * (e.g. "test", returned by `readDirectory`)
* @version Added in JS SDK 0.1
*/ */
path: string; path: string;
/** /**
* Is the file a directory? * Is the file a directory?
* @version Added in JS SDK 0.1
*/ */
isDirectory: boolean; isDirectory: boolean;
/** /**
* File size in bytes, or 0 in the case of directories * File size in bytes, or 0 in the case of directories
* @version Added in JS SDK 0.1
*/ */
size: number; size: number;
/** /**
* Time of last access as a UNIX timestamp * Time of last access as a UNIX timestamp
* @version Added in JS SDK 0.1
*/ */
accessTime: Timestamp; accessTime: Timestamp;
} }
/** Filesystem information structure */ /**
* Filesystem information structure
* @version Added in JS SDK 0.1
*/
export declare class FsInfo { export declare class FsInfo {
/** Total size of the filesystem, in bytes */ /**
* Total size of the filesystem, in bytes
* @version Added in JS SDK 0.1
*/
totalSpace: number; totalSpace: number;
/** Free space in the filesystem, in bytes */ /**
* Free space in the filesystem, in bytes
* @version Added in JS SDK 0.1
*/
freeSpace: number; freeSpace: number;
} }
// file operations // file operations
/** File class */ /**
* File class
* @version Added in JS SDK 0.1
*/
export declare class File { export declare class File {
/** /**
* Closes the file. After this method is called, all other operations * Closes the file. After this method is called, all other operations
* related to this file become unavailable. * related to this file become unavailable.
* @returns `true` on success, `false` on failure * @returns `true` on success, `false` on failure
* @version Added in JS SDK 0.1
*/ */
close(): boolean; close(): boolean;
/** /**
* Is the file currently open? * Is the file currently open?
* @version Added in JS SDK 0.1
*/ */
isOpen(): boolean; isOpen(): boolean;
/** /**
@ -70,6 +102,7 @@ export declare class File {
* @returns an `ArrayBuf` if the mode is `"binary"`, a `string` if the mode * @returns an `ArrayBuf` if the mode is `"binary"`, a `string` if the mode
* is `ascii`. The number of bytes that was actually read may be * is `ascii`. The number of bytes that was actually read may be
* fewer than requested. * fewer than requested.
* @version Added in JS SDK 0.1
*/ */
read<T extends ArrayBuffer | string>(mode: T extends ArrayBuffer ? "binary" : "ascii", bytes: number): T; read<T extends ArrayBuffer | string>(mode: T extends ArrayBuffer ? "binary" : "ascii", bytes: number): T;
/** /**
@ -77,36 +110,43 @@ export declare class File {
* @param data The data to write: a string that will be ASCII-encoded, or an * @param data The data to write: a string that will be ASCII-encoded, or an
* ArrayBuf * ArrayBuf
* @returns the amount of bytes that was actually written * @returns the amount of bytes that was actually written
* @version Added in JS SDK 0.1
*/ */
write(data: ArrayBuffer | string): number; write(data: ArrayBuffer | string): number;
/** /**
* Moves the R/W pointer forward * Moves the R/W pointer forward
* @param bytes How many bytes to move the pointer forward by * @param bytes How many bytes to move the pointer forward by
* @returns `true` on success, `false` on failure * @returns `true` on success, `false` on failure
* @version Added in JS SDK 0.1
*/ */
seekRelative(bytes: number): boolean; seekRelative(bytes: number): boolean;
/** /**
* Moves the R/W pointer to an absolute position inside the file * Moves the R/W pointer to an absolute position inside the file
* @param bytes The position inside the file * @param bytes The position inside the file
* @returns `true` on success, `false` on failure * @returns `true` on success, `false` on failure
* @version Added in JS SDK 0.1
*/ */
seekAbsolute(bytes: number): boolean; seekAbsolute(bytes: number): boolean;
/** /**
* Gets the absolute position of the R/W pointer in bytes * Gets the absolute position of the R/W pointer in bytes
* @version Added in JS SDK 0.1
*/ */
tell(): number; tell(): number;
/** /**
* Discards the data after the current position of the R/W pointer in a file * Discards the data after the current position of the R/W pointer in a file
* opened in either write-only or read-write mode. * opened in either write-only or read-write mode.
* @returns `true` on success, `false` on failure * @returns `true` on success, `false` on failure
* @version Added in JS SDK 0.1
*/ */
truncate(): boolean; truncate(): boolean;
/** /**
* Reads the total size of the file in bytes * Reads the total size of the file in bytes
* @version Added in JS SDK 0.1
*/ */
size(): number; size(): number;
/** /**
* Detects whether the R/W pointer has reached the end of the file * Detects whether the R/W pointer has reached the end of the file
* @version Added in JS SDK 0.1
*/ */
eof(): boolean; eof(): boolean;
/** /**
@ -115,6 +155,7 @@ export declare class File {
* @param dest The file to copy the bytes into * @param dest The file to copy the bytes into
* @param bytes The number of bytes to copy * @param bytes The number of bytes to copy
* @returns `true` on success, `false` on failure * @returns `true` on success, `false` on failure
* @version Added in JS SDK 0.1
*/ */
copyTo(dest: File, bytes: number): boolean; copyTo(dest: File, bytes: number): boolean;
} }
@ -126,12 +167,14 @@ export declare class File {
* @param openMode `"open_existing"`, `"open_always"`, `"open_append"`, * @param openMode `"open_existing"`, `"open_always"`, `"open_append"`,
* `"create_new"` or `"create_always"`; see `OpenMode` * `"create_new"` or `"create_always"`; see `OpenMode`
* @returns a `File` on success, or `undefined` on failure * @returns a `File` on success, or `undefined` on failure
* @version Added in JS SDK 0.1
*/ */
export declare function openFile(path: string, accessMode: AccessMode, openMode: OpenMode): File | undefined; export declare function openFile(path: string, accessMode: AccessMode, openMode: OpenMode): File | undefined;
/** /**
* Detects whether a file exists * Detects whether a file exists
* @param path The path to the file * @param path The path to the file
* @returns `true` on success, `false` on failure * @returns `true` on success, `false` on failure
* @version Added in JS SDK 0.1
*/ */
export declare function fileExists(path: string): boolean; export declare function fileExists(path: string): boolean;
@ -142,17 +185,20 @@ export declare function fileExists(path: string): boolean;
* @param path The path to the directory * @param path The path to the directory
* @returns Array of `FileInfo` structures with directory entries, * @returns Array of `FileInfo` structures with directory entries,
* or `undefined` on failure * or `undefined` on failure
* @version Added in JS SDK 0.1
*/ */
export declare function readDirectory(path: string): FileInfo[] | undefined; export declare function readDirectory(path: string): FileInfo[] | undefined;
/** /**
* Detects whether a directory exists * Detects whether a directory exists
* @param path The path to the directory * @param path The path to the directory
* @version Added in JS SDK 0.1
*/ */
export declare function directoryExists(path: string): boolean; export declare function directoryExists(path: string): boolean;
/** /**
* Creates an empty directory * Creates an empty directory
* @param path The path to the new directory * @param path The path to the new directory
* @returns `true` on success, `false` on failure * @returns `true` on success, `false` on failure
* @version Added in JS SDK 0.1
*/ */
export declare function makeDirectory(path: string): boolean; export declare function makeDirectory(path: string): boolean;
@ -161,24 +207,28 @@ export declare function makeDirectory(path: string): boolean;
/** /**
* Detects whether a file or a directory exists * Detects whether a file or a directory exists
* @param path The path to the file or directory * @param path The path to the file or directory
* @version Added in JS SDK 0.1
*/ */
export declare function fileOrDirExists(path: string): boolean; export declare function fileOrDirExists(path: string): boolean;
/** /**
* Acquires metadata about a file or directory * Acquires metadata about a file or directory
* @param path The path to the file or directory * @param path The path to the file or directory
* @returns A `FileInfo` structure or `undefined` on failure * @returns A `FileInfo` structure or `undefined` on failure
* @version Added in JS SDK 0.1
*/ */
export declare function stat(path: string): FileInfo | undefined; export declare function stat(path: string): FileInfo | undefined;
/** /**
* Removes a file or an empty directory * Removes a file or an empty directory
* @param path The path to the file or directory * @param path The path to the file or directory
* @returns `true` on success, `false` on failure * @returns `true` on success, `false` on failure
* @version Added in JS SDK 0.1
*/ */
export declare function remove(path: string): boolean; export declare function remove(path: string): boolean;
/** /**
* Removes a file or recursively removes a possibly non-empty directory * Removes a file or recursively removes a possibly non-empty directory
* @param path The path to the file or directory * @param path The path to the file or directory
* @returns `true` on success, `false` on failure * @returns `true` on success, `false` on failure
* @version Added in JS SDK 0.1
*/ */
export declare function rmrf(path: string): boolean; export declare function rmrf(path: string): boolean;
/** /**
@ -187,6 +237,7 @@ export declare function rmrf(path: string): boolean;
* @param newPath The new path that the file or directory will become accessible * @param newPath The new path that the file or directory will become accessible
* under * under
* @returns `true` on success, `false` on failure * @returns `true` on success, `false` on failure
* @version Added in JS SDK 0.1
*/ */
export declare function rename(oldPath: string, newPath: string): boolean; export declare function rename(oldPath: string, newPath: string): boolean;
/** /**
@ -194,11 +245,13 @@ export declare function rename(oldPath: string, newPath: string): boolean;
* @param oldPath The original path to the file or directory * @param oldPath The original path to the file or directory
* @param newPath The new path that the copy of the file or directory will be * @param newPath The new path that the copy of the file or directory will be
* accessible under * accessible under
* @version Added in JS SDK 0.1
*/ */
export declare function copy(oldPath: string, newPath: string): boolean; export declare function copy(oldPath: string, newPath: string): boolean;
/** /**
* Fetches generic information about a filesystem * Fetches generic information about a filesystem
* @param filesystem The path to the filesystem (e.g. `"/ext"` or `"/int"`) * @param filesystem The path to the filesystem (e.g. `"/ext"` or `"/int"`)
* @version Added in JS SDK 0.1
*/ */
export declare function fsInfo(filesystem: string): FsInfo | undefined; export declare function fsInfo(filesystem: string): FsInfo | undefined;
/** /**
@ -218,6 +271,7 @@ export declare function fsInfo(filesystem: string): FsInfo | undefined;
* @param maxLen The maximum length of the filename with the numeric suffix * @param maxLen The maximum length of the filename with the numeric suffix
* @returns The base of the filename with the next available numeric suffix, * @returns The base of the filename with the next available numeric suffix,
* without the extension or the base directory. * without the extension or the base directory.
* @version Added in JS SDK 0.1
*/ */
export declare function nextAvailableFilename(dirPath: string, fileName: string, fileExt: string, maxLen: number): string; export declare function nextAvailableFilename(dirPath: string, fileName: string, fileExt: string, maxLen: number): string;
@ -226,6 +280,7 @@ export declare function nextAvailableFilename(dirPath: string, fileName: string,
/** /**
* Determines whether the two paths are equivalent. Respects filesystem-defined * Determines whether the two paths are equivalent. Respects filesystem-defined
* path equivalence rules. * path equivalence rules.
* @version Added in JS SDK 0.1
*/ */
export declare function arePathsEqual(path1: string, path2: string): boolean; export declare function arePathsEqual(path1: string, path2: string): boolean;
/** /**
@ -233,5 +288,6 @@ export declare function arePathsEqual(path1: string, path2: string): boolean;
* filesystem-defined path equivalence rules. * filesystem-defined path equivalence rules.
* @param parentPath The parent path * @param parentPath The parent path
* @param childPath The child path * @param childPath The child path
* @version Added in JS SDK 0.1
*/ */
export declare function isSubpathOf(parentPath: string, childPath: string): boolean; export declare function isSubpathOf(parentPath: string, childPath: string): boolean;

View File

@ -1,6 +1,8 @@
/** /**
* Unit test module. Only available if the firmware has been configured with * Unit test module. Only available if the firmware has been configured with
* `FIRMWARE_APP_SET=unit_tests`. * `FIRMWARE_APP_SET=unit_tests`.
* @version Added in JS SDK 0.1
* @module
*/ */
export function fail(message: string): never; export function fail(message: string): never;

View File

@ -0,0 +1,13 @@
{
"compilerOptions": {
"checkJs": true,
"module": "CommonJS",
"noLib": true,
},
"include": [
"./**/*.d.ts"
],
"exclude": [
"node_modules",
]
}

View File

@ -0,0 +1,19 @@
{
"$schema": "https://typedoc.org/schema.json",
"name": "Flipper Zero JS API",
"excludePrivate": true,
"entryPointStrategy": "expand",
"entryPoints": [
".",
],
"exclude": [
"node_modules"
],
"cleanOutputDir": true,
"out": "./docs",
"plugin": [
"typedoc-material-theme",
],
"readme": "./docs_readme.md",
"themeColor": "#ff8200",
}

View File

@ -1,70 +0,0 @@
type Lit = undefined | null | {};
/**
* Subscription control interface
*/
export interface Subscription {
/**
* Cancels the subscription, preventing any future events managed by the
* subscription from firing
*/
cancel(): void;
}
/**
* Opaque event source identifier
*/
export type Contract<Item = undefined> = symbol;
/**
* A callback can be assigned to an event loop to listen to an event. It may
* return an array with values that will be passed to it as arguments the next
* time that it is called. The first argument is always the subscription
* manager, and the second argument is always the item that trigged the event.
* The type of the item is defined by the event source.
*/
export type Callback<Item, Args extends Lit[]> = (subscription: Subscription, item: Item, ...args: Args) => Args | undefined | void;
/**
* Subscribes a callback to an event
* @param contract Event identifier
* @param callback Function to call when the event is triggered
* @param args Initial arguments passed to the callback
*/
export function subscribe<Item, Args extends Lit[]>(contract: Contract<Item>, callback: Callback<Item, Args>, ...args: Args): Subscription;
/**
* Runs the event loop until it is stopped (potentially never)
*/
export function run(): void | never;
/**
* Stops the event loop
*/
export function stop(): void;
/**
* Creates a timer event that can be subscribed to just like any other event
* @param mode Either `"oneshot"` or `"periodic"`
* @param interval Timer interval in milliseconds
*/
export function timer(mode: "oneshot" | "periodic", interval: number): Contract;
/**
* Message queue
*/
export interface Queue<T> {
/**
* Message event
*/
input: Contract<T>;
/**
* Sends a message to the queue
* @param message message to send
*/
send(message: T): void;
}
/**
* Creates a message queue
* @param length maximum queue capacity
*/
export function queue<T>(length: number): Queue<T>;

View File

@ -1,14 +0,0 @@
/**
* @brief Returns the device model
*/
export declare function getModel(): string;
/**
* @brief Returns the name of the virtual dolphin
*/
export declare function getName(): string;
/**
* @brief Returns the battery charge percentage
*/
export declare function getBatteryCharge(): number;

View File

@ -1,178 +0,0 @@
/**
* @brief Pauses JavaScript execution for a while
* @param ms How many milliseconds to pause the execution for
*/
declare function delay(ms: number): void;
/**
* @brief Prints to the GUI console view
* @param args The arguments are converted to strings, concatenated without any
* spaces in between and printed to the console view
*/
declare function print(...args: any[]): void;
/**
* @brief Converts a number to a string
* @param value The number to convert to a string
* @param base Integer base (`2`...`16`), default: 16
*/
declare function toString(value: number, base?: number): string;
/**
* @brief Reads a JS value from a file
*
* Reads a file at the specified path, interprets it as a JS value and returns
* said value.
*
* @param path The path to the file
*/
declare function load(path: string): any;
/**
* @brief mJS Foreign Pointer type
*
* JavaScript code cannot do anything with values of `RawPointer` type except
* acquire them from native code and pass them right back to other parts of
* native code. These values cannot be turned into something meaningful, nor can
* be they modified.
*/
declare type RawPointer = symbol & { "__tag__": "raw_ptr" };
// introducing a nominal type in a hacky way; the `__tag__` property doesn't really exist.
/**
* @brief Holds raw bytes
*/
declare class ArrayBuffer {
/**
* @brief The pointer to the byte buffer
* @note Like other `RawPointer` values, this value is essentially useless
* to JS code.
*/
getPtr: RawPointer;
/**
* @brief The length of the buffer in bytes
*/
byteLength: number;
/**
* @brief Creates an `ArrayBuffer` that contains a sub-part of the buffer
* @param start The index of the byte in the source buffer to be used as the
* start for the new buffer
* @param end The index of the byte in the source buffer that follows the
* byte to be used as the last byte for the new buffer
*/
slice(start: number, end?: number): ArrayBuffer;
}
declare function ArrayBuffer(): ArrayBuffer;
declare type ElementType = "u8" | "i8" | "u16" | "i16" | "u32" | "i32";
declare class TypedArray<E extends ElementType> {
/**
* @brief The length of the buffer in bytes
*/
byteLength: number;
/**
* @brief The length of the buffer in typed elements
*/
length: number;
/**
* @brief The underlying `ArrayBuffer`
*/
buffer: ArrayBuffer;
}
declare class Uint8Array extends TypedArray<"u8"> { }
declare class Int8Array extends TypedArray<"i8"> { }
declare class Uint16Array extends TypedArray<"u16"> { }
declare class Int16Array extends TypedArray<"i16"> { }
declare class Uint32Array extends TypedArray<"u32"> { }
declare class Int32Array extends TypedArray<"i32"> { }
declare function Uint8Array(data: ArrayBuffer | number | number[]): Uint8Array;
declare function Int8Array(data: ArrayBuffer | number | number[]): Int8Array;
declare function Uint16Array(data: ArrayBuffer | number | number[]): Uint16Array;
declare function Int16Array(data: ArrayBuffer | number | number[]): Int16Array;
declare function Uint32Array(data: ArrayBuffer | number | number[]): Uint32Array;
declare function Int32Array(data: ArrayBuffer | number | number[]): Int32Array;
declare const console: {
/**
* @brief Prints to the UART logs at the `[I]` level
* @param args The arguments are converted to strings, concatenated without any
* spaces in between and printed to the logs
*/
log(...args: any[]): void;
/**
* @brief Prints to the UART logs at the `[D]` level
* @param args The arguments are converted to strings, concatenated without any
* spaces in between and printed to the logs
*/
debug(...args: any[]): void;
/**
* @brief Prints to the UART logs at the `[W]` level
* @param args The arguments are converted to strings, concatenated without any
* spaces in between and printed to the logs
*/
warn(...args: any[]): void;
/**
* @brief Prints to the UART logs at the `[E]` level
* @param args The arguments are converted to strings, concatenated without any
* spaces in between and printed to the logs
*/
error(...args: any[]): void;
};
declare class Array<T> {
/**
* @brief Takes items out of the array
*
* Removes elements from the array and returns them in a new array
*
* @param start The index to start taking elements from
* @param deleteCount How many elements to take
* @returns The elements that were taken out of the original array as a new
* array
*/
splice(start: number, deleteCount: number): T[];
/**
* @brief Adds a value to the end of the array
* @param value The value to add
* @returns New length of the array
*/
push(value: T): number;
/**
* @brief How many elements there are in the array
*/
length: number;
}
declare class String {
/**
* @brief How many characters there are in the string
*/
length: number;
/**
* @brief Returns the character code at an index in the string
* @param index The index to consult
*/
charCodeAt(index: number): number;
/**
* See `charCodeAt`
*/
at(index: number): number;
}
declare class Boolean { }
declare class Function { }
declare class Number { }
declare class Object { }
declare class RegExp { }
declare interface IArguments { }
declare type Partial<O extends object> = { [K in keyof O]?: O[K] };

View File

@ -1,16 +0,0 @@
import type { View, ViewFactory } from ".";
import type { Contract } from "../event_loop";
type Props = {
header: string,
text: string,
left: string,
center: string,
right: string,
}
declare class Dialog extends View<Props> {
input: Contract<"left" | "center" | "right">;
}
declare class DialogFactory extends ViewFactory<Props, Dialog> { }
declare const factory: DialogFactory;
export = factory;

View File

@ -1,7 +0,0 @@
import type { View, ViewFactory } from ".";
type Props = {};
declare class EmptyScreen extends View<Props> { }
declare class EmptyScreenFactory extends ViewFactory<Props, EmptyScreen> { }
declare const factory: EmptyScreenFactory;
export = factory;

View File

@ -1,41 +0,0 @@
import type { Contract } from "../event_loop";
type Properties = { [K: string]: any };
export declare class View<Props extends Properties> {
set<P extends keyof Props>(property: P, value: Props[P]): void;
}
export declare class ViewFactory<Props extends Properties, V extends View<Props>> {
make(): V;
makeWith(initial: Partial<Props>): V;
}
declare class ViewDispatcher {
/**
* Event source for `sendCustom` events
*/
custom: Contract<number>;
/**
* Event source for navigation events (back key presses)
*/
navigation: Contract;
/**
* Sends a number to the custom event handler
* @param event number to send
*/
sendCustom(event: number): void;
/**
* Switches to a view
* @param assoc View-ViewDispatcher association as returned by `add`
*/
switchTo(assoc: View<any>): void;
/**
* Sends this ViewDispatcher to the front or back, above or below all other
* GUI viewports
* @param direction Either `"front"` or `"back"`
*/
sendTo(direction: "front" | "back"): void;
}
export const viewDispatcher: ViewDispatcher;

View File

@ -1,7 +0,0 @@
import type { View, ViewFactory } from ".";
type Props = {};
declare class Loading extends View<Props> { }
declare class LoadingFactory extends ViewFactory<Props, Loading> { }
declare const factory: LoadingFactory;
export = factory;

View File

@ -1,13 +0,0 @@
import type { View, ViewFactory } from ".";
import type { Contract } from "../event_loop";
type Props = {
header: string,
items: string[],
};
declare class Submenu extends View<Props> {
chosen: Contract<number>;
}
declare class SubmenuFactory extends ViewFactory<Props, Submenu> { }
declare const factory: SubmenuFactory;
export = factory;

View File

@ -1,14 +0,0 @@
import type { View, ViewFactory } from ".";
import type { Contract } from "../event_loop";
type Props = {
text: string,
font: "text" | "hex",
focus: "start" | "end",
}
declare class TextBox extends View<Props> {
chosen: Contract<number>;
}
declare class TextBoxFactory extends ViewFactory<Props, TextBox> { }
declare const factory: TextBoxFactory;
export = factory;

View File

@ -1,14 +0,0 @@
import type { View, ViewFactory } from ".";
import type { Contract } from "../event_loop";
type Props = {
header: string,
minLength: number,
maxLength: number,
}
declare class TextInput extends View<Props> {
input: Contract<string>;
}
declare class TextInputFactory extends ViewFactory<Props, TextInput> { }
declare const factory: TextInputFactory;
export = factory;

View File

@ -61,7 +61,7 @@ Reads a digital value from a pin configured with `direction: "in"` and any
#### Returns #### Returns
Boolean logic level Boolean logic level
### `Pin.read_analog()` ### `Pin.readAnalog()`
Reads an analog voltage level in millivolts from a pin configured with Reads an analog voltage level in millivolts from a pin configured with
`direction: "in"` and `inMode: "analog"` `direction: "in"` and `inMode: "analog"`

View File

@ -146,10 +146,17 @@ void mjs_init_builtin(struct mjs* mjs, mjs_val_t obj) {
// mjs_set(mjs, obj, "JSON", ~0, v); // mjs_set(mjs, obj, "JSON", ~0, v);
/* /*
* Populate Object.create() * Populate Object
*/ */
v = mjs_mk_object(mjs); v = mjs_mk_object(mjs);
mjs_set(mjs, v, "create", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_create_object)); mjs_set(mjs, v, "create", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_create_object));
mjs_set(
mjs,
v,
"defineProperty",
~0,
mjs_mk_foreign_func(
mjs, (mjs_func_ptr_t)mjs_op_object_define_property)); // stub, do not use
mjs_set(mjs, obj, "Object", ~0, v); mjs_set(mjs, obj, "Object", ~0, v);
/* /*

View File

@ -294,6 +294,11 @@ clean:
mjs_return(mjs, ret); mjs_return(mjs, ret);
} }
MJS_PRIVATE void mjs_op_object_define_property(struct mjs* mjs) {
// stub, do not use
mjs_return(mjs, MJS_UNDEFINED);
}
mjs_val_t mjs_val_t
mjs_struct_to_obj(struct mjs* mjs, const void* base, const struct mjs_c_struct_member* defs) { mjs_struct_to_obj(struct mjs* mjs, const void* base, const struct mjs_c_struct_member* defs) {
mjs_val_t obj; mjs_val_t obj;

View File

@ -50,6 +50,11 @@ MJS_PRIVATE mjs_err_t mjs_set_internal(
*/ */
MJS_PRIVATE void mjs_op_create_object(struct mjs* mjs); MJS_PRIVATE void mjs_op_create_object(struct mjs* mjs);
/*
* Stub of `Object.defineProperty()`
*/
MJS_PRIVATE void mjs_op_object_define_property(struct mjs* mjs);
/* /*
* Cell destructor for object arena * Cell destructor for object arena
*/ */

View File

@ -76,7 +76,8 @@ static int s_assign_ops[] = {
static int findtok(int* toks, int tok) { static int findtok(int* toks, int tok) {
int i = 0; int i = 0;
while(tok != toks[i] && toks[i] != TOK_EOF) i++; while(tok != toks[i] && toks[i] != TOK_EOF)
i++;
return toks[i]; return toks[i];
} }
@ -87,7 +88,7 @@ static void emit_op(struct pstate* pstate, int tok) {
} }
#define BINOP_STACK_FRAME_SIZE 16 #define BINOP_STACK_FRAME_SIZE 16
#define STACK_LIMIT 8192 #define STACK_LIMIT 8192
// Intentionally left as macro rather than a function, to let the // Intentionally left as macro rather than a function, to let the
// compiler to inline calls and mimimize runtime stack usage. // compiler to inline calls and mimimize runtime stack usage.
@ -166,7 +167,8 @@ static mjs_err_t parse_statement_list(struct pstate* p, int et) {
if(drop) emit_byte(p, OP_DROP); if(drop) emit_byte(p, OP_DROP);
res = parse_statement(p); res = parse_statement(p);
drop = 1; drop = 1;
while(p->tok.tok == TOK_SEMICOLON) pnext1(p); while(p->tok.tok == TOK_SEMICOLON)
pnext1(p);
} }
/* /*
@ -523,7 +525,11 @@ static mjs_err_t parse_expr(struct pstate* p) {
static mjs_err_t parse_let(struct pstate* p) { static mjs_err_t parse_let(struct pstate* p) {
mjs_err_t res = MJS_OK; mjs_err_t res = MJS_OK;
LOG(LL_VERBOSE_DEBUG, ("[%.*s]", 10, p->tok.ptr)); LOG(LL_VERBOSE_DEBUG, ("[%.*s]", 10, p->tok.ptr));
EXPECT(p, TOK_KEYWORD_LET); if((p)->tok.tok != TOK_KEYWORD_VAR && (p)->tok.tok != TOK_KEYWORD_LET &&
(p)->tok.tok != TOK_KEYWORD_CONST)
SYNTAX_ERROR(p);
else
pnext1(p);
for(;;) { for(;;) {
struct tok tmp = p->tok; struct tok tmp = p->tok;
EXPECT(p, TOK_IDENT); EXPECT(p, TOK_IDENT);
@ -910,6 +916,8 @@ static mjs_err_t parse_statement(struct pstate* p) {
pnext1(p); pnext1(p);
return MJS_OK; return MJS_OK;
case TOK_KEYWORD_LET: case TOK_KEYWORD_LET:
case TOK_KEYWORD_VAR:
case TOK_KEYWORD_CONST:
return parse_let(p); return parse_let(p);
case TOK_OPEN_CURLY: case TOK_OPEN_CURLY:
return parse_block(p, 1); return parse_block(p, 1);
@ -939,7 +947,6 @@ static mjs_err_t parse_statement(struct pstate* p) {
case TOK_KEYWORD_SWITCH: case TOK_KEYWORD_SWITCH:
case TOK_KEYWORD_THROW: case TOK_KEYWORD_THROW:
case TOK_KEYWORD_TRY: case TOK_KEYWORD_TRY:
case TOK_KEYWORD_VAR:
case TOK_KEYWORD_VOID: case TOK_KEYWORD_VOID:
case TOK_KEYWORD_WITH: case TOK_KEYWORD_WITH:
mjs_set_errorf( mjs_set_errorf(

View File

@ -80,12 +80,13 @@ static int getnum(struct pstate* p) {
} }
static int is_reserved_word_token(const char* s, int len) { static int is_reserved_word_token(const char* s, int len) {
const char* reserved[] = {"break", "case", "catch", "continue", "debugger", "default", const char* reserved[] = {"break", "case", "catch", "continue", "debugger",
"delete", "do", "else", "false", "finally", "for", "default", "delete", "do", "else", "false",
"function", "if", "in", "instanceof", "new", "null", "finally", "for", "function", "if", "in",
"return", "switch", "this", "throw", "true", "try", "instanceof", "new", "null", "return", "switch",
"typeof", "var", "void", "while", "with", "let", "this", "throw", "true", "try", "typeof",
"undefined", NULL}; "var", "void", "while", "with", "let",
"const", "undefined", NULL};
int i; int i;
if(!mjs_is_alpha(s[0])) return 0; if(!mjs_is_alpha(s[0])) return 0;
for(i = 0; reserved[i] != NULL; i++) { for(i = 0; reserved[i] != NULL; i++) {
@ -95,7 +96,8 @@ static int is_reserved_word_token(const char* s, int len) {
} }
static int getident(struct pstate* p) { static int getident(struct pstate* p) {
while(mjs_is_ident(p->pos[0]) || mjs_is_digit(p->pos[0])) p->pos++; while(mjs_is_ident(p->pos[0]) || mjs_is_digit(p->pos[0]))
p->pos++;
p->tok.len = p->pos - p->tok.ptr; p->tok.len = p->pos - p->tok.ptr;
p->pos--; p->pos--;
return TOK_IDENT; return TOK_IDENT;
@ -125,7 +127,8 @@ static void skip_spaces_and_comments(struct pstate* p) {
p->pos++; p->pos++;
} }
if(p->pos[0] == '/' && p->pos[1] == '/') { if(p->pos[0] == '/' && p->pos[1] == '/') {
while(p->pos[0] != '\0' && p->pos[0] != '\n') p->pos++; while(p->pos[0] != '\0' && p->pos[0] != '\n')
p->pos++;
} }
if(p->pos[0] == '/' && p->pos[1] == '*') { if(p->pos[0] == '/' && p->pos[1] == '*') {
p->pos += 2; p->pos += 2;
@ -142,8 +145,8 @@ static void skip_spaces_and_comments(struct pstate* p) {
} }
static int ptranslate(int tok) { static int ptranslate(int tok) {
#define DT(a, b) ((a) << 8 | (b)) #define DT(a, b) ((a) << 8 | (b))
#define TT(a, b, c) ((a) << 16 | (b) << 8 | (c)) #define TT(a, b, c) ((a) << 16 | (b) << 8 | (c))
#define QT(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d)) #define QT(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d))
/* Map token ID produced by mjs_tok.c to token ID produced by lemon */ /* Map token ID produced by mjs_tok.c to token ID produced by lemon */
/* clang-format off */ /* clang-format off */

View File

@ -125,6 +125,7 @@ enum {
TOK_KEYWORD_WHILE, TOK_KEYWORD_WHILE,
TOK_KEYWORD_WITH, TOK_KEYWORD_WITH,
TOK_KEYWORD_LET, TOK_KEYWORD_LET,
TOK_KEYWORD_CONST,
TOK_KEYWORD_UNDEFINED, TOK_KEYWORD_UNDEFINED,
TOK_MAX TOK_MAX
}; };

View File

@ -3,13 +3,13 @@
"checkJs": true, "checkJs": true,
"module": "CommonJS", "module": "CommonJS",
"typeRoots": [ "typeRoots": [
"./applications/system/js_app/types" "./applications/system/js_app/packages/fz-sdk/"
], ],
"noLib": true, "noLib": true,
}, },
"include": [ "include": [
"./applications/system/js_app/examples/apps/Scripts", "./applications/system/js_app/examples/apps/Scripts",
"./applications/debug/unit_tests/resources/unit_tests/js", "./applications/debug/unit_tests/resources/unit_tests/js",
"./applications/system/js_app/types/global.d.ts", "./applications/system/js_app/packages/fz-sdk/global.d.ts",
] ]
} }