Merge pull request #2821 from bobrippling/popcon-fixes

Catch additional errors from readJSON (via typescript)
pull/2826/head
Gordon Williams 2023-06-14 08:15:33 +01:00 committed by GitHub
commit 540eae1ddd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 132 additions and 52 deletions

View File

@ -686,7 +686,7 @@ const setIntervals = (
const redrawInterval = setInterval(redraw, /*replaced*/1000);
Bangle.on("lock", locked => setIntervals(locked));
let bleInterval: undefined | number;
let bleInterval: undefined | IntervalId;
NRF.on("connect", () => setIntervals(undefined, true));
NRF.on("disconnect", () => setIntervals(undefined, false));

View File

@ -1,6 +1,6 @@
(() => {
let durationOnPause = "---";
let redrawInterval: number | undefined;
let redrawInterval: IntervalId | undefined;
let startTime: number | undefined;
let showMillis = true;
const milliTime = 60;
@ -16,7 +16,7 @@
if (startTime) {
if (showMillis && Date.now() - startTime > milliTime * 1000) {
showMillis = false;
changeInterval(redrawInterval, 1000);
changeInterval(redrawInterval!, 1000);
}
} else {
unqueueRedraw();

View File

@ -10,8 +10,10 @@ type StopWatchSettings = {
const SETTINGS_FILE = "clkinfostopw.setting.json";
const storage = require("Storage");
const settings: StopWatchSettings = storage.readJSON(SETTINGS_FILE, true) || {};
settings.format ??= StopWatchFormat.HMS;
const settings: StopWatchSettings = Object.assign(
{ format: StopWatchFormat.HMS },
storage.readJSON(SETTINGS_FILE, true),
);
const save = () => {
storage.writeJSON(SETTINGS_FILE, settings)

View File

@ -1,7 +1,7 @@
const app = "drained";
// from boot.js
declare var drainedInterval: number | undefined;
declare var drainedInterval: IntervalId | undefined;
if(typeof drainedInterval !== "undefined")
drainedInterval = clearInterval(drainedInterval) as undefined;
@ -41,7 +41,7 @@ Bangle.setOptions({
});
// clock
let nextDraw: number | undefined;
let nextDraw: TimeoutId | undefined;
const draw = () => {
const x = g.getWidth() / 2;
const y = g.getHeight() / 2 - 48;

View File

@ -1 +1,2 @@
0.01: New app!
0.02: Handle files potentially not existing

View File

@ -115,7 +115,7 @@
// Get the icon and text, skip if the space is empty. Always draw text for folders even if disabled
switch (entry.type) {
case 'app':
let app: AppInfo = storage.readJSON(entry.id + '.info', false);
let app = storage.readJSON(entry.id + '.info', false) as AppInfo;
icon = storage.read(app.icon!)!;
text = app.name;
empty = false;
@ -186,7 +186,7 @@
switch (entry.type) {
case "app":
Bangle.buzz();
let infoFile = storage.readJSON(entry.id + '.info', false);
let infoFile = storage.readJSON(entry.id + '.info', false) as AppInfo;
load(infoFile.src);
break;
case "folder":

View File

@ -28,7 +28,7 @@ function cleanAndSave(config) {
var installedAppIds = [];
for (var _i = 0, infoFiles_1 = infoFiles; _i < infoFiles_1.length; _i++) {
var infoFile = infoFiles_1[_i];
installedAppIds.push(storage.readJSON(infoFile, true).id);
installedAppIds.push((storage.readJSON(infoFile, true) || {}).id);
}
var toRemove = [];
for (var appId in config.apps)
@ -67,7 +67,7 @@ module.exports = {
infoFiles.sort(infoFileSorter);
for (var _i = 0, infoFiles_2 = infoFiles; _i < infoFiles_2.length; _i++) {
var infoFile = infoFiles_2[_i];
var app_1 = storage.readJSON(infoFile, false);
var app_1 = storage.readJSON(infoFile, false) || {};
if ((!config.showClocks && app_1.type == 'clock') ||
(!config.showLaunchers && app_1.type == 'launch') ||
(app_1.type == 'widget') ||

View File

@ -53,7 +53,7 @@ function cleanAndSave(config: Config): Config {
let infoFiles: Array<string> = storage.list(/\.info$/);
let installedAppIds: Array<string> = [];
for (let infoFile of infoFiles)
installedAppIds.push(storage.readJSON(infoFile, true).id);
installedAppIds.push(((storage.readJSON(infoFile, true) || {}) as AppInfo).id);
// Remove nonexistent apps from appInfo
let toRemove: Array<string> = [];
@ -77,8 +77,8 @@ function cleanAndSave(config: Config): Config {
* @return negative if a should go first, positive if b should go first, zero if equivalent.
*/
let infoFileSorter = (a: string, b: string): number => {
let aJson: AppInfo = storage.readJSON(a, false);
let bJson: AppInfo = storage.readJSON(b, false);
let aJson = storage.readJSON(a, false) as AppInfo;
let bJson = storage.readJSON(b, false) as AppInfo;
var n = (0 | aJson.sortorder!) - (0 | bJson.sortorder!);
if (n) return n; // do sortorder first
if (aJson.name < bJson.name) return -1;
@ -97,7 +97,7 @@ export = {
* @return the loaded configuration
*/
getConfig: (): Config => {
let config = storage.readJSON(SETTINGS_FILE, true) || DEFAULT_CONFIG;
let config = (storage.readJSON(SETTINGS_FILE, true) as Config | undefined) || DEFAULT_CONFIG;
// We only need to load data from the filesystem if there is a change
if (config.hash == storage.hash(/\.info$/)) {
@ -110,7 +110,7 @@ export = {
infoFiles.sort(infoFileSorter);
for (let infoFile of infoFiles) {
let app: AppInfo = storage.readJSON(infoFile, false);
let app = storage.readJSON(infoFile, false) as AppInfo | undefined || ({} as AppInfo);
// If the app is to be hidden by policy, exclude it completely
if (
@ -138,12 +138,12 @@ export = {
// Note: Relies on curFolder secretly being a reference rather than a copy
let curFolder: Folder = config.rootFolder;
let depth = 0;
for (let folderName of config.apps[app.id].folder) {
for (let folderName of config.apps[app.id]!.folder) {
if (curFolder.folders.hasOwnProperty(folderName)) {
curFolder = curFolder.folders[folderName]!;
depth++;
} else {
config.apps[app.id].folder = config.apps[app.id].folder.slice(0, depth);
config.apps[app.id]!.folder = config.apps[app.id]!.folder.slice(0, depth);
break;
}
}

View File

@ -1,7 +1,7 @@
{
"id": "folderlaunch",
"name": "Folder launcher",
"version": "0.01",
"version": "0.02",
"description": "Launcher that allows you to put your apps into folders",
"icon": "icon.png",
"type": "launch",

View File

@ -24,7 +24,7 @@
onchange // Do nothing, but stop typescript from yelling at me for this function being unused. It gets used by eval. I know eval is evil, but the menus are a bit limited.
for (let app in config.apps) {
let appInfo: AppInfo = storage.readJSON(app + '.info', false);
let appInfo = storage.readJSON(app + '.info', false) as AppInfo;
menu[appInfo.name] = {
value: config.hidden.includes(app),
format: (value: boolean) => (value ? 'Yes' : 'No'),
@ -36,7 +36,7 @@
};
let getAppInfo = (id: string): AppInfo => {
return storage.readJSON(id + '.info', false);
return storage.readJSON(id + '.info', false) as AppInfo;
}
let showFolderMenu = (path: Array<string>) => {
@ -142,7 +142,7 @@
}
}
for (let appId of folder.apps) {
menu[storage.readJSON(appId + '.info', false).name] = () => { };
menu[(storage.readJSON(appId + '.info', false) as AppInfo).name] = () => { };
}
E.showMenu(menu);
}

View File

@ -21,7 +21,6 @@ type Config = {
apps: { // Saved info for each app
[key: string]: {
folder: FolderList, // Folder path
fast: boolean, // Whether the app should be fast launched
nagged: boolean // Whether the app's fast launch setting was configured
}
},

View File

@ -2,3 +2,4 @@
0.02: Trim old entries from the popcon app cache
0.03: Avoid polluting global scope
0.04: Add settings app for resetting popcon
0.05: Handle info files potentially not existing

View File

@ -26,7 +26,7 @@
trimCache(cache);
require("Storage").writeJSON("popcon.cache.json", cache);
if (orderChanged) {
var info = oldRead("popconlaunch.info", true);
var info = oldRead("popconlaunch.info", true) || { cacheBuster: true };
info.cacheBuster = !info.cacheBuster;
require("Storage").writeJSON("popconlaunch.info", info);
}
@ -65,7 +65,7 @@
require("Storage").readJSON = (function (fname, skipExceptions) {
var _a;
var j = oldRead(fname, skipExceptions);
if (/\.info$/.test(fname)) {
if (j && /\.info$/.test(fname)) {
var cache_1 = ensureCache();
var so = void 0;
if (j.src && (so = (_a = cache_1[j.src]) === null || _a === void 0 ? void 0 : _a.sortorder) != null)

View File

@ -15,7 +15,7 @@ let cache: undefined | Cache;
const ensureCache = (): NonNull<typeof cache> => {
if(!cache){
cache = oldRead("popcon.cache.json", true);
cache = oldRead("popcon.cache.json", true) as Cache | undefined;
if(!cache)
cache = {};
}
@ -38,7 +38,7 @@ const saveCache = (cache: Cache, orderChanged: boolean) => {
require("Storage").writeJSON("popcon.cache.json", cache);
if(orderChanged){
// ensure launchers reload their caches:
const info: AppInfo & { cacheBuster?: boolean } = oldRead("popconlaunch.info", true);
const info = (oldRead("popconlaunch.info", true) as undefined | AppInfo & { cacheBuster?: boolean }) || {cacheBuster:true};
info.cacheBuster = !info.cacheBuster;
require("Storage").writeJSON("popconlaunch.info", info);
}
@ -80,10 +80,10 @@ const sortCache = () => {
};
require("Storage").readJSON = ((fname, skipExceptions) => {
const j: AppInfo = oldRead(fname, skipExceptions);
// ^ technically only AppInfo if we're "*.info"
const j = oldRead(fname, skipExceptions) as AppInfo | undefined;
// technically only AppInfo if we're "*.info" ^
if(/\.info$/.test(fname)){
if(j && /\.info$/.test(fname)){
const cache = ensureCache();
let so;

View File

@ -2,7 +2,7 @@
"id": "popconlaunch",
"name": "Popcon Launcher",
"shortName": "Popcon",
"version": "0.04",
"version": "0.05",
"description": "Launcher modification - your launchers will display your favourite (popular) apps first. Overrides `readJSON`, may slow down your watch",
"readme": "README.md",
"icon": "app.png",

View File

@ -6,7 +6,7 @@
const S = require("Storage");
S.erase("popcon.cache.json");
const info: AppInfo & { cacheBuster?: boolean } = S.readJSON("popconlaunch.info", true);
const info = S.readJSON("popconlaunch.info", true) as AppInfo & { cacheBuster?: boolean };
info.cacheBuster = !info.cacheBuster;
S.writeJSON("popconlaunch.info", info);

View File

@ -1,5 +1,5 @@
(() => {
const settings: Settings = require("Storage").readJSON("setting.json", true) || { HID: false } as Settings;
const settings = require("Storage").readJSON("setting.json", true) as Settings || ({ HID: false } as Settings);
if (settings.HID !== "kbmedia") {
console.log("widhid: can't enable, HID setting isn't \"kbmedia\"");
return;
@ -10,7 +10,7 @@
let anchor = {x:0,y:0};
let start = {x:0,y:0};
let dragging = false;
let activeTimeout: number | undefined;
let activeTimeout: TimeoutId | undefined;
let waitForRelease = true;
const onSwipe = ((_lr, ud) => {

View File

@ -260,6 +260,9 @@ type Theme = {
dark: boolean;
};
type IntervalId = number & { _brand: "interval" };
type TimeoutId = number & { _brand: "timeout" };
type PinMode =
| "analog"
| "input"
@ -3863,7 +3866,7 @@ declare class Bangle {
* of the BPM reading.
* * `hrmSportMode` - on the newest Bangle.js 2 builds with with the proprietary
* heart rate algorithm, this is the sport mode passed to the algorithm. See `libs/misc/vc31_binary/algo.h`
* for more info. 0 = normal (default), 1 = running, 2 = ...
* for more info. -1 = auto, 0 = normal (default), 1 = running, 2 = ...
* * `seaLevelPressure` (Bangle.js 2) Normally 1013.25 millibars - this is used for
* calculating altitude with the pressure sensor
* Where accelerations are used they are in internal units, where `8192 = 1g`
@ -4092,7 +4095,8 @@ declare class Bangle {
static getHealthStatus(range?: "current" | "last" | "day"): HealthStatus;
/**
* Reads debug info. Exposes the current values of `accHistoryIdx`, `accGestureCount`, `accIdleCount` and `pollInterval`.
* Reads debug info. Exposes the current values of `accHistoryIdx`, `accGestureCount`, `accIdleCount`, `pollInterval` and others.
* Please see the declaration of this function for more information (click the `==>` link above [this description](http://www.espruino.com/Reference#l_Bangle_dbg))
* @returns {any}
* @url http://www.espruino.com/Reference#l_Bangle_dbg
*/
@ -4297,6 +4301,13 @@ declare class Bangle {
*/
static showClock(): void;
/**
* Show a 'recovery' menu that allows you to perform certain tasks on your Bangle.
* You can also enter this menu by restarting while holding down the `BTN1`
* @url http://www.espruino.com/Reference#l_Bangle_showRecoveryMenu
*/
static showRecoveryMenu(): void;
/**
* This behaves the same as the global `load()` function, but if fast
* loading is possible (`Bangle.setUI` was called with a `remove` handler)
@ -4971,11 +4982,19 @@ declare class Graphics<IsBuffer extends boolean = boolean> {
* XXXXXXXXX
* `);
* g.drawImage(img, x,y);
* var img = Graphics.createImage(`
* .....
* .XXX.
* .X.X.
* .XXX.
* .....
* `);
* g.drawImage(img, x,y);
* ```
* If the characters at the beginning and end of the string are newlines, they will
* be ignored. Spaces are treated as `0`, and any other character is a `1`
*
* @param {any} str - A String containing a newline-separated image - space is 0, anything else is 1
* @param {any} str - A String containing a newline-separated image - space/. is 0, anything else is 1
* @returns {any} An Image object that can be used with `Graphics.drawImage`
* @url http://www.espruino.com/Reference#l_Graphics_createImage
*/
@ -5352,6 +5371,14 @@ declare class Graphics<IsBuffer extends boolean = boolean> {
*/
setFontCustom(bitmap: ArrayBuffer, firstChar: number, width: number | string, height: number): Graphics;
/**
*
* @param {any} file - The font as a PBF file
* @returns {any} The instance of Graphics this was called on, to allow call chaining
* @url http://www.espruino.com/Reference#l_Graphics_setFontPBF
*/
setFontPBF(file: any): Graphics;
/**
* Set the alignment for subsequent calls to `drawString`
*
@ -8097,21 +8124,65 @@ declare class E {
static toArrayBuffer(str: string): ArrayBuffer;
/**
* Returns a 'flat' string representing the data in the arguments, or return
* `undefined` if a flat string cannot be created.
* This creates a string from the given arguments. If an argument is a String or an
* Array, each element is traversed and added as an 8 bit character. If it is
* anything else, it is converted to a character directly.
* Returns a `String` representing the data in the arguments.
* This creates a string from the given arguments in the same way as `E.toUint8Array`. If each argument is:
* * A String or an Array, each element is traversed and added as an 8 bit character
* * `{data : ..., count : N}` causes `data` to be repeated `count` times
* * `{callback : fn}` calls the function and adds the result
* * Anything else is converted to a character directly.
* In the case where there's one argument which is an 8 bit typed array backed by a
* flat string of the same length, the backing string will be returned without
* doing a copy or other allocation. The same applies if there's a single argument
* which is itself a flat string.
* ```JS
* E.toString(0,1,2,"Hi",3)
* "\0\1\2Hi\3"
* E.toString(1,2,{data:[3,4], count:4},5,6)
* "\1\2\3\4\3\4\3\4\3\4\5\6"
* >E.toString(1,2,{callback : () => "Hello World"},5,6)
* ="\1\2Hello World\5\6"
* ```
* **Note:** Prior to Espruino 2v18 `E.toString` would always return a flat string,
* or would return `undefined` if one couldn't be allocated. Now, it will return
* a normal (fragmented) String if a contiguous chunk of memory cannot be allocated.
* You can still check if the returned value is a Flat string using `E.getAddressOf(str, true)!=0`,
* or can use `E.toFlatString` instead.
*
* @param {any} args - The arguments to convert to a String
* @returns {any} A String (or `undefined` if a Flat String cannot be created)
* @returns {any} A String
* @url http://www.espruino.com/Reference#l_E_toString
*/
static toString(...args: any[]): string | undefined;
static toString(...args: any[]): string;
/**
* Returns a Flat `String` representing the data in the arguments, or `undefined` if one can't be allocated.
* This provides the same behaviour that `E.toString` had in Espruino before 2v18 - see `E.toString` for
* more information.
*
* @param {any} args - The arguments to convert to a Flat String
* @returns {any} A Flat String (or undefined)
* @url http://www.espruino.com/Reference#l_E_toFlatString
*/
static toFlatString(...args: any[]): string | undefined;
/**
* By default, strings in Espruino are standard 8 bit binary strings.
* However calling E.asUTF8 will convert one of those strings to
* UTF8.
* ```
* var s = String.fromCharCode(0xF0,0x9F,0x8D,0x94);
* var u = E.asUTF8(s);
* s.length // 4
* s[0] // "\xF0"
* u.length // 1
* u[0] // hamburger emoji
* ```
*
* @param {any} str - The string to turn into a UTF8 Unicode String
* @returns {any} A String
* @url http://www.espruino.com/Reference#l_E_asUTF8
*/
static asUTF8(str: any): string;
/**
* This creates a Uint8Array from the given arguments. These are handled as
@ -11226,7 +11297,7 @@ declare function getSerial(): any;
* @returns {any} An ID that can be passed to clearInterval
* @url http://www.espruino.com/Reference#l__global_setInterval
*/
declare function setInterval(func: any, timeout: number, ...args: any[]): any;
declare function setInterval(func: string | Function, timeout: number, ...args: any[]): IntervalId;
/**
* Call the function (or evaluate the string) specified ONCE after the timeout in
@ -11261,7 +11332,7 @@ declare function setInterval(func: any, timeout: number, ...args: any[]): any;
* @returns {any} An ID that can be passed to clearTimeout
* @url http://www.espruino.com/Reference#l__global_setTimeout
*/
declare function setTimeout(func: any, timeout: number, ...args: any[]): any;
declare function setTimeout(func: string | Function, timeout: number, ...args: any[]): TimeoutId;
/**
* Clear the Interval that was created with `setInterval`, for example:
@ -11273,7 +11344,7 @@ declare function setTimeout(func: any, timeout: number, ...args: any[]): any;
* @param {any} id - The id returned by a previous call to setInterval. **Only one argument is allowed.**
* @url http://www.espruino.com/Reference#l__global_clearInterval
*/
declare function clearInterval(...id: any[]): void;
declare function clearInterval(id: IntervalId): void;
/**
* Clear the Timeout that was created with `setTimeout`, for example:
@ -11285,7 +11356,7 @@ declare function clearInterval(...id: any[]): void;
* @param {any} id - The id returned by a previous call to setTimeout. **Only one argument is allowed.**
* @url http://www.espruino.com/Reference#l__global_clearTimeout
*/
declare function clearTimeout(...id: any[]): void;
declare function clearTimeout(id: TimeoutId): void;
/**
* Change the Interval on a callback created with `setInterval`, for example:
@ -11299,7 +11370,7 @@ declare function clearTimeout(...id: any[]): void;
* @param {number} time - The new time period in ms
* @url http://www.espruino.com/Reference#l__global_changeInterval
*/
declare function changeInterval(id: any, time: number): void;
declare function changeInterval(id: IntervalId, time: number): void;
/**
* Read 8 bits of memory at the given location - DANGEROUS!
@ -13155,10 +13226,13 @@ declare module "tensorflow" {
* Functions here take and return buffers of data. There is no support for
* streaming, so both the compressed and decompressed data must be able to fit in
* memory at the same time.
* If you'd like a way to perform compression/decompression on desktop, check out https://github.com/espruino/EspruinoWebTools#heatshrinkjs
* @url http://www.espruino.com/Reference#heatshrink
*/
declare module "heatshrink" {
/**
* Compress the heatshrink-encoded data supplied as input.
* If you'd like a way to perform compression/decompression on desktop, check out https://github.com/espruino/EspruinoWebTools#heatshrinkjs
*
* @param {any} data - The data to compress
* @returns {any} Returns the result as an ArrayBuffer
@ -13167,6 +13241,8 @@ declare module "heatshrink" {
function compress(data: any): ArrayBuffer;
/**
* Decompress the heatshrink-encoded data supplied as input.
* If you'd like a way to perform compression/decompression on desktop, check out https://github.com/espruino/EspruinoWebTools#heatshrinkjs
*
* @param {any} data - The data to decompress
* @returns {any} Returns the result as an ArrayBuffer
@ -13316,7 +13392,8 @@ declare module "Storage" {
* @returns {any} An object containing parsed JSON from the file, or undefined
* @url http://www.espruino.com/Reference#l_Storage_readJSON
*/
function readJSON(name: string, noExceptions: ShortBoolean): any;
function readJSON(name: string, noExceptions?: false | 0): unknown;
function readJSON(name: string, noExceptions?: ShortBoolean): unknown | undefined;
/**
* Read a file from the flash storage area that has been written with