├── .gitignore ├── src ├── errors │ ├── index.ts │ └── PermissionError.ts ├── models │ ├── Manifest.ts │ ├── Location.ts │ ├── CallbackFn.ts │ ├── MozWindow.ts │ ├── NetworkStatus.ts │ ├── AlarmData.ts │ ├── Request.ts │ ├── BatteryStatus.ts │ ├── FileSearchResult.ts │ ├── Connection.ts │ ├── MozBattery.ts │ ├── DomApplication.ts │ ├── MozDeviceStorage.ts │ ├── index.ts │ └── MozNavigator.ts ├── enums │ ├── index.ts │ ├── StorageName.ts │ └── ConnectionType.ts ├── modules │ ├── navigator.ts │ ├── localStorage.ts │ ├── app.ts │ ├── volume.ts │ ├── activity.ts │ ├── battery.ts │ ├── geolocation.ts │ ├── network.ts │ ├── alarm.ts │ ├── fileStorage.ts │ └── qrCode.ts └── index.ts ├── release.config.js ├── tsconfig.json ├── .circleci └── config.yml ├── LICENSE ├── package.json ├── CHANGELOG.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist 3 | lib 4 | node_modules -------------------------------------------------------------------------------- /src/errors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './PermissionError'; 2 | -------------------------------------------------------------------------------- /src/models/Manifest.ts: -------------------------------------------------------------------------------- 1 | export type Manifest = any; 2 | -------------------------------------------------------------------------------- /src/models/Location.ts: -------------------------------------------------------------------------------- 1 | export type Location = GeolocationPosition; 2 | -------------------------------------------------------------------------------- /src/enums/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ConnectionType'; 2 | export * from './StorageName'; 3 | -------------------------------------------------------------------------------- /src/models/CallbackFn.ts: -------------------------------------------------------------------------------- 1 | export type CallbackFn = ((data?: T) => void) | null; 2 | -------------------------------------------------------------------------------- /src/models/MozWindow.ts: -------------------------------------------------------------------------------- 1 | export type MozWindow = Window & { 2 | MozActivity: any; 3 | } & any; 4 | -------------------------------------------------------------------------------- /src/enums/StorageName.ts: -------------------------------------------------------------------------------- 1 | export type StorageName = 'music' | 'pictures' | 'sdcard' | 'videos' | 'apps'; 2 | -------------------------------------------------------------------------------- /src/models/NetworkStatus.ts: -------------------------------------------------------------------------------- 1 | export type NetworkStatus = { 2 | online: boolean; 3 | type: 'wifi' | 'cellular' | 'none'; 4 | }; 5 | -------------------------------------------------------------------------------- /src/enums/ConnectionType.ts: -------------------------------------------------------------------------------- 1 | export enum ConnectionType { 2 | None = 'none', 3 | Cellular = 'cellular', 4 | Wifi = 'wifi', 5 | } 6 | -------------------------------------------------------------------------------- /src/models/AlarmData.ts: -------------------------------------------------------------------------------- 1 | export type AlarmData = { 2 | id: number; 3 | date: Date; 4 | respectTimezone: boolean; 5 | data: any; 6 | }; 7 | -------------------------------------------------------------------------------- /src/models/Request.ts: -------------------------------------------------------------------------------- 1 | export type Request = { 2 | error?: Error; 3 | result: T; 4 | onsuccess: () => void; 5 | onerror: () => void; 6 | }; 7 | -------------------------------------------------------------------------------- /src/modules/navigator.ts: -------------------------------------------------------------------------------- 1 | import { MozNavigator } from '../models'; 2 | 3 | export class Navigator { 4 | static navigator: MozNavigator = window.navigator as MozNavigator; 5 | } 6 | -------------------------------------------------------------------------------- /src/models/BatteryStatus.ts: -------------------------------------------------------------------------------- 1 | export type BatteryStatus = { 2 | charging: boolean; 3 | level: number; 4 | temperature: number; 5 | health: string; 6 | present: boolean; 7 | }; 8 | -------------------------------------------------------------------------------- /src/models/FileSearchResult.ts: -------------------------------------------------------------------------------- 1 | export type FileSearchResult = { 2 | name: string; 3 | size: number; 4 | type: string; 5 | lastModified: string; 6 | lastModifiedDate: Date; 7 | }; 8 | -------------------------------------------------------------------------------- /src/errors/PermissionError.ts: -------------------------------------------------------------------------------- 1 | export class PermissionError extends Error { 2 | name: string = 'PermissionError'; 3 | 4 | constructor(message?: string) { 5 | super(message); 6 | 7 | if (message) { 8 | this.message = message; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/models/Connection.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionType } from '../enums'; 2 | 3 | export type Connection = { 4 | type: ConnectionType; 5 | ontypechange: () => void; 6 | addEventListener: (event: string, cb: () => void) => void; 7 | removeEventListener: (event: string, cb: () => void) => void; 8 | }; 9 | -------------------------------------------------------------------------------- /src/modules/localStorage.ts: -------------------------------------------------------------------------------- 1 | export class LocalStorage { 2 | setItem(key: string, data: any): void { 3 | window.localStorage.setItem(key, JSON.stringify(data)); 4 | } 5 | getItem(key: string): T | null { 6 | return JSON.parse(window.localStorage.getItem(key) as string); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/models/MozBattery.ts: -------------------------------------------------------------------------------- 1 | export type MozBattery = { 2 | charging: boolean; 3 | level: number; 4 | temperature: number; 5 | health: string; 6 | present: boolean; 7 | onlevelchange: (() => void) | null; 8 | onchargingchange: (() => void) | null; 9 | addEventListener: (event: string, cb: () => void) => void; 10 | removeEventListener: (event: string, cb: () => void) => void; 11 | }; 12 | -------------------------------------------------------------------------------- /src/models/DomApplication.ts: -------------------------------------------------------------------------------- 1 | import { Manifest } from './Manifest'; 2 | 3 | export type DomApplication = { 4 | manifest: Manifest; 5 | updateManifest: null; 6 | manifestURL: string; 7 | origin: string; 8 | installOrigin: string; 9 | oldVersion: string; 10 | installTime: number; 11 | removable: boolean; 12 | enabled: boolean; 13 | role: string; 14 | 15 | launch: () => void; 16 | }; 17 | -------------------------------------------------------------------------------- /src/models/MozDeviceStorage.ts: -------------------------------------------------------------------------------- 1 | import { Request } from './Request'; 2 | 3 | export type MozDeviceStorage = { 4 | storageName: string; 5 | get: (filePath: string) => Request; 6 | addNamed: (file: File | Blob, filePath: string) => Request; 7 | appendNamed: (file: File | Blob, filePath: string) => Request; 8 | delete: (filePath: string) => Request; 9 | enumerate: any; 10 | }; 11 | -------------------------------------------------------------------------------- /src/modules/app.ts: -------------------------------------------------------------------------------- 1 | import { Manifest } from '../models'; 2 | import { Navigator } from './navigator'; 3 | 4 | export class App { 5 | getManifest(): Promise { 6 | return new Promise((resolve, reject) => { 7 | const request = Navigator.navigator.mozApps.getSelf(); 8 | request.onsuccess = () => resolve(request.result.manifest); 9 | request.onerror = () => reject(request.error); 10 | }); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | branches: ['main'], 3 | plugins: [ 4 | '@semantic-release/commit-analyzer', 5 | '@semantic-release/release-notes-generator', 6 | '@semantic-release/changelog', 7 | '@semantic-release/npm', 8 | [ 9 | '@semantic-release/github', 10 | { 11 | successComment: false, 12 | failComment: false, 13 | }, 14 | ], 15 | '@semantic-release/git', 16 | ], 17 | }; 18 | -------------------------------------------------------------------------------- /src/modules/volume.ts: -------------------------------------------------------------------------------- 1 | import { Navigator } from './navigator'; 2 | 3 | export class Volume { 4 | up(): Promise { 5 | Navigator.navigator.volumeManager.requestUp(); 6 | return Promise.resolve(); 7 | } 8 | 9 | down(): Promise { 10 | Navigator.navigator.volumeManager.requestDown(); 11 | return Promise.resolve(); 12 | } 13 | 14 | show(): Promise { 15 | Navigator.navigator.volumeManager.requestShow(); 16 | return Promise.resolve(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AlarmData'; 2 | export * from './BatteryStatus'; 3 | export * from './CallbackFn'; 4 | export * from './Connection'; 5 | export * from './DomApplication'; 6 | export * from './FileSearchResult'; 7 | export * from './Location'; 8 | export * from './Manifest'; 9 | export * from './MozBattery'; 10 | export * from './MozDeviceStorage'; 11 | export * from './MozNavigator'; 12 | export * from './MozWindow'; 13 | export * from './NetworkStatus'; 14 | export * from './Request'; 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "ES2015", 5 | "rootDir": "./src", 6 | "outDir": "./lib", 7 | "moduleResolution": "node", 8 | "resolveJsonModule": true, 9 | "declaration": true, 10 | "sourceMap": false, 11 | "allowSyntheticDefaultImports": true, 12 | "esModuleInterop": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "strict": true, 15 | "noImplicitAny": true, 16 | "skipLibCheck": true 17 | }, 18 | "include": ["src"], 19 | "exclude": ["node_modules", "lib"] 20 | } 21 | -------------------------------------------------------------------------------- /src/modules/activity.ts: -------------------------------------------------------------------------------- 1 | import { MozWindow } from '../models'; 2 | 3 | type Data = { 4 | name: string; 5 | data: { [key: string]: any }; 6 | }; 7 | 8 | export class Activity { 9 | data: Data; 10 | 11 | constructor(data: Data) { 12 | this.data = data; 13 | } 14 | 15 | start(): Promise { 16 | return new Promise((resolve, reject) => { 17 | const activity = new (window as MozWindow).MozActivity(this.data); 18 | 19 | activity.onsuccess = function () { 20 | resolve(this.result); 21 | }; 22 | 23 | activity.onerror = function () { 24 | reject(this.error); 25 | }; 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/models/MozNavigator.ts: -------------------------------------------------------------------------------- 1 | import { StorageName } from '../enums/StorageName'; 2 | import { Connection } from './Connection'; 3 | import { DomApplication } from './DomApplication'; 4 | import { MozBattery } from './MozBattery'; 5 | import { MozDeviceStorage } from './MozDeviceStorage'; 6 | import { Request } from './Request'; 7 | 8 | export type MozNavigator = Navigator & { 9 | mozApps: { 10 | getSelf: () => Request; 11 | }; 12 | battery: MozBattery; 13 | connection: Connection; 14 | getDeviceStorage: (name: StorageName) => MozDeviceStorage; 15 | volumeManager: { 16 | requestUp: () => void; 17 | requestDown: () => void; 18 | requestShow: () => void; 19 | }; 20 | geolocation: any; 21 | mozAlarms: any; 22 | mozSetMessageHandler: any; 23 | } & any; 24 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | executors: 3 | node-lts: 4 | docker: 5 | - image: cimg/node:lts 6 | 7 | jobs: 8 | install: 9 | executor: node-lts 10 | steps: 11 | - checkout 12 | - run: npm ci 13 | - persist_to_workspace: 14 | root: ./ 15 | paths: 16 | - ./* 17 | build: 18 | executor: node-lts 19 | steps: 20 | - checkout 21 | - attach_workspace: 22 | at: . 23 | - run: npm run build 24 | version-and-publish: 25 | executor: node-lts 26 | steps: 27 | - attach_workspace: 28 | at: . 29 | - run: npx semantic-release 30 | 31 | workflows: 32 | build-test-deploy: 33 | jobs: 34 | - install 35 | - build: 36 | requires: 37 | - install 38 | - version-and-publish: 39 | context: 40 | - github-versioning 41 | - npm-publishing 42 | requires: 43 | - build 44 | filters: 45 | branches: 46 | only: 47 | - main 48 | -------------------------------------------------------------------------------- /src/modules/battery.ts: -------------------------------------------------------------------------------- 1 | import { BatteryStatus } from '../models'; 2 | import { Navigator } from './navigator'; 3 | 4 | export class Battery { 5 | current(): Promise { 6 | return new Promise((resolve) => { 7 | const batt = Navigator.navigator.battery; 8 | resolve(this.getStatus()); 9 | }); 10 | } 11 | 12 | subscribe(success: (data: BatteryStatus) => void): void { 13 | Navigator.navigator.battery.onlevelchange = () => success(this.getStatus()); 14 | Navigator.navigator.battery.onchargingchange = () => success(this.getStatus()); 15 | } 16 | 17 | unsubscribe(): void { 18 | Navigator.navigator.battery.onlevelchange = null; 19 | Navigator.navigator.battery.onchargingchange = null; 20 | } 21 | 22 | private getStatus(): BatteryStatus { 23 | return { 24 | charging: Navigator.navigator.battery.charging, 25 | level: Navigator.navigator.battery.level, 26 | temperature: Navigator.navigator.battery.temperature, 27 | health: Navigator.navigator.battery.health, 28 | present: Navigator.navigator.battery.present, 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/modules/geolocation.ts: -------------------------------------------------------------------------------- 1 | import { Navigator } from './navigator'; 2 | 3 | type Options = { 4 | maximumAge?: number; 5 | timeout?: number; 6 | enableHighAccuracy?: boolean; 7 | }; 8 | 9 | export class Geolocation { 10 | subscriptionId: number | null; 11 | 12 | constructor() { 13 | this.subscriptionId = null; 14 | } 15 | 16 | current(options?: Options): Promise { 17 | return new Promise((resolve, reject) => { 18 | Navigator.navigator.geolocation.getCurrentPosition( 19 | (position: Location) => resolve(position), 20 | (err: GeolocationPositionError) => reject(err), 21 | options 22 | ); 23 | }); 24 | } 25 | 26 | subscribe( 27 | success: (data: Location) => void, 28 | error?: (err: Error) => void, 29 | options?: Options 30 | ): void { 31 | if (this.subscriptionId !== null) { 32 | this.unsubscribe(); 33 | } 34 | this.subscriptionId = Navigator.navigator.geolocation.watchPosition(success, error, options); 35 | } 36 | 37 | unsubscribe(): void { 38 | Navigator.navigator.geolocation.clearWatch(this.subscriptionId); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Garrett Downs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Activity } from './modules/activity'; 2 | import { Alarm } from './modules/alarm'; 3 | import { App } from './modules/app'; 4 | import { Battery } from './modules/battery'; 5 | import { FileStorage } from './modules/fileStorage'; 6 | import { Geolocation } from './modules/geolocation'; 7 | import { LocalStorage } from './modules/localStorage'; 8 | import { Network } from './modules/network'; 9 | import { QRCode } from './modules/qrCode'; 10 | import { Volume } from './modules/volume'; 11 | 12 | export * from './modules/activity'; 13 | export * from './modules/alarm'; 14 | export * from './modules/app'; 15 | export * from './modules/battery'; 16 | export * from './modules/fileStorage'; 17 | export * from './modules/geolocation'; 18 | export * from './modules/localStorage'; 19 | export * from './modules/network'; 20 | export * from './modules/qrCode'; 21 | export * from './modules/volume'; 22 | 23 | export default class KaiOS { 24 | static Activity = Activity; 25 | static Alarm = Alarm; 26 | static App = App; 27 | static Battery = Battery; 28 | static FileStorage = FileStorage; 29 | static Geolocation = Geolocation; 30 | static LocalStorage = LocalStorage; 31 | static Network = Network; 32 | static QRCode = QRCode; 33 | static Volume = Volume; 34 | } 35 | -------------------------------------------------------------------------------- /src/modules/network.ts: -------------------------------------------------------------------------------- 1 | import { NetworkStatus } from '../models'; 2 | import { Navigator } from './navigator'; 3 | 4 | export class Network { 5 | private subCallback: ((status: NetworkStatus) => void) | null; 6 | 7 | constructor() { 8 | this.subCallback = null; 9 | } 10 | 11 | current(): Promise { 12 | return Promise.resolve({ 13 | online: Navigator.navigator.onLine, 14 | type: Navigator.navigator.connection.type, 15 | }); 16 | } 17 | 18 | subscribe(callback: (status: NetworkStatus) => void): void { 19 | if (this.subCallback) { 20 | this.unsubscribe(); 21 | } 22 | 23 | this.subCallback = callback; 24 | 25 | window.addEventListener('online', this.handleUpdate); 26 | window.addEventListener('offline', this.handleUpdate); 27 | Navigator.navigator.connection.addEventListener('change', this.handleUpdate); 28 | } 29 | 30 | unsubscribe(): void { 31 | window.removeEventListener('online', this.handleUpdate); 32 | window.removeEventListener('offline', this.handleUpdate); 33 | Navigator.navigator.connection.removeEventListener('change', this.handleUpdate); 34 | 35 | this.subCallback = null; 36 | } 37 | 38 | private handleUpdate() { 39 | this.subCallback?.({ 40 | online: Navigator.navigator.onLine, 41 | type: Navigator.navigator.connection.type, 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kaios-lib", 3 | "version": "1.6.0", 4 | "description": "A standard library to interact with KaiOS 2.x and 3.x APIs.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/garredow/kaios-lib.git" 8 | }, 9 | "author": "Garrett Downs", 10 | "license": "MIT", 11 | "bugs": { 12 | "url": "https://github.com/garredow/kaios-lib/issues" 13 | }, 14 | "homepage": "https://github.com/garredow/kaios-lib#readme", 15 | "main": "lib/index.js", 16 | "types": "lib/index.d.ts", 17 | "files": [ 18 | "lib", 19 | "README.md" 20 | ], 21 | "scripts": { 22 | "dev": "npm run clean && concurrently --kill-others \"npm run build-lib -- --watch --sourcemap\" \"npm run build-types -- --watch\"", 23 | "build": "npm run clean && npm run build-lib -- --minify && npm run build-types", 24 | "build-lib": "esbuild --outdir=lib --target=es6 src/*.ts src/**/*.ts", 25 | "build-types": "tsc --emitDeclarationOnly", 26 | "prepare": "npm run build", 27 | "commit": "cz", 28 | "clean": "rimraf ./lib" 29 | }, 30 | "keywords": [], 31 | "devDependencies": { 32 | "@babel/core": "^7.17.8", 33 | "@babel/preset-env": "^7.16.11", 34 | "@babel/preset-typescript": "^7.16.7", 35 | "@semantic-release/changelog": "^6.0.1", 36 | "@semantic-release/exec": "^6.0.3", 37 | "@semantic-release/git": "^10.0.1", 38 | "commitizen": "^4.2.4", 39 | "concurrently": "^7.0.0", 40 | "cz-conventional-changelog": "^3.3.0", 41 | "esbuild": "^0.14.27", 42 | "rimraf": "^3.0.2", 43 | "semantic-release": "^19.0.2", 44 | "typescript": "^4.6.2" 45 | }, 46 | "config": { 47 | "commitizen": { 48 | "path": "./node_modules/cz-conventional-changelog" 49 | } 50 | }, 51 | "dependencies": { 52 | "jsqr": "^1.4.0" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/modules/alarm.ts: -------------------------------------------------------------------------------- 1 | import { AlarmData } from '../models'; 2 | import { Navigator } from './navigator'; 3 | 4 | export class Alarm { 5 | add(newAlarm: Omit): Promise { 6 | return new Promise((resolve, reject) => { 7 | const request = Navigator.navigator.mozAlarms.add( 8 | newAlarm.date, 9 | newAlarm.respectTimezone ? 'honorTimezone' : 'ignoreTimezone', 10 | newAlarm.data 11 | ); 12 | 13 | request.onsuccess = function () { 14 | resolve({ 15 | id: this.result, 16 | date: newAlarm.date, 17 | respectTimezone: newAlarm.respectTimezone, 18 | data: newAlarm.data, 19 | }); 20 | }; 21 | request.onerror = function () { 22 | reject(this.error); 23 | }; 24 | }); 25 | } 26 | 27 | remove(id: number): Promise { 28 | return new Promise((resolve) => { 29 | Navigator.navigator.mozAlarms.remove(id); 30 | resolve(); 31 | }); 32 | } 33 | 34 | async removeAll(): Promise { 35 | const alarms = await this.getAll(); 36 | for (const alarm of alarms) { 37 | await this.remove(alarm.id); 38 | } 39 | } 40 | 41 | getAll(): Promise { 42 | return new Promise((resolve, reject) => { 43 | const request = Navigator.navigator.mozAlarms.getAll(); 44 | 45 | request.onsuccess = function () { 46 | resolve(this.result); 47 | }; 48 | request.onerror = function () { 49 | reject(this.error); 50 | }; 51 | }); 52 | } 53 | 54 | subscribe(success: (data: AlarmData) => void): void { 55 | Navigator.navigator.mozSetMessageHandler('alarm', (alarm: any) => success(alarm)); 56 | } 57 | 58 | unsubscribe(): void { 59 | Navigator.navigator.mozSetMessageHandler('alarm', null); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [1.6.0](https://github.com/garredow/kaios-lib/compare/v1.5.0...v1.6.0) (2022-04-02) 2 | 3 | 4 | ### Features 5 | 6 | * **qrcode:** add qr code module ([4df2c42](https://github.com/garredow/kaios-lib/commit/4df2c42c8b7ccdfebfb26fa8e6510359ea3ce066)) 7 | 8 | # [1.5.0](https://github.com/garredow/kaios-lib/compare/v1.4.0...v1.5.0) (2022-03-26) 9 | 10 | 11 | ### Features 12 | 13 | * **activity:** add activity module ([9d99ec5](https://github.com/garredow/kaios-lib/commit/9d99ec5c6b6e00ab67da8338cf805dc9d81d050d)) 14 | 15 | # [1.4.0](https://github.com/garredow/kaios-lib/compare/v1.3.0...v1.4.0) (2022-03-26) 16 | 17 | 18 | ### Features 19 | 20 | * **network:** add network module ([0b968ee](https://github.com/garredow/kaios-lib/commit/0b968ee01fbfc1a9a3c24d8f8748c1601ac3e8b8)) 21 | 22 | # [1.3.0](https://github.com/garredow/kaios-lib/compare/v1.2.0...v1.3.0) (2022-03-26) 23 | 24 | 25 | ### Features 26 | 27 | * **modules:** use new unsubscribe method ([14dd6a1](https://github.com/garredow/kaios-lib/commit/14dd6a1769f0d1c26cd8d22bf0f7734097735f65)) 28 | 29 | # [1.2.0](https://github.com/garredow/kaios-lib/compare/v1.1.0...v1.2.0) (2022-03-26) 30 | 31 | 32 | ### Features 33 | 34 | * **alarm:** add alarm module ([951a14b](https://github.com/garredow/kaios-lib/commit/951a14bc6f868d29ffbedbc95ac181c3a2887c86)) 35 | * **battery:** refactor to use standard interface ([f30e6f5](https://github.com/garredow/kaios-lib/commit/f30e6f584598c73a9fe2a6c4f73f77141e5e06f3)) 36 | * **geolocation:** add subscribe method ([823b7c4](https://github.com/garredow/kaios-lib/commit/823b7c4365b9ff3072819d5d8fdddc350c5f19f4)) 37 | 38 | # [1.1.0](https://github.com/garredow/kaios-lib/compare/v1.0.0...v1.1.0) (2022-03-26) 39 | 40 | 41 | ### Features 42 | 43 | * **app:** add app module ([1cf31a1](https://github.com/garredow/kaios-lib/commit/1cf31a138626d97f24c8a1eced87cdc898dc3f06)) 44 | * **geolocation:** add geolocation module ([69b954d](https://github.com/garredow/kaios-lib/commit/69b954d32a5ce3c57146b9618400451eb7824f2b)) 45 | 46 | # 1.0.0 (2022-03-25) 47 | 48 | 49 | ### Bug Fixes 50 | 51 | * fix typescript errors ([4aaa3b9](https://github.com/garredow/kaios-lib/commit/4aaa3b968f08d20dd96a48841f19ec0c3b71d685)) 52 | 53 | 54 | ### Features 55 | 56 | * add battery module ([13a0791](https://github.com/garredow/kaios-lib/commit/13a079187c9e789309893a9a5d00b571f7dd5982)) 57 | * add cicd ([3000d72](https://github.com/garredow/kaios-lib/commit/3000d724bf7ac35f4b1c15011193596f24c5f08f)) 58 | -------------------------------------------------------------------------------- /src/modules/fileStorage.ts: -------------------------------------------------------------------------------- 1 | import { StorageName } from '../enums'; 2 | import { FileSearchResult, MozDeviceStorage } from '../models'; 3 | import { Navigator } from './navigator'; 4 | 5 | export class FileStorage { 6 | name: string; 7 | storage: MozDeviceStorage; 8 | 9 | constructor(storageName: StorageName) { 10 | this.name = this.getActualStorageName(storageName); 11 | this.storage = Navigator.navigator.getDeviceStorage(storageName); 12 | } 13 | 14 | get(filePath: string): Promise { 15 | return new Promise((resolve, reject) => { 16 | const request = this.storage.get(filePath); 17 | request.onsuccess = () => resolve(request.result); 18 | request.onerror = () => reject(request.error); 19 | }); 20 | } 21 | 22 | getAsFileUrl(filePathAndName: string): Promise { 23 | return new Promise((resolve, reject) => { 24 | const request = this.storage.get(filePathAndName); 25 | request.onsuccess = () => resolve(URL.createObjectURL(request.result)); 26 | request.onerror = () => reject(request.error); 27 | }); 28 | } 29 | 30 | addNamed(filePathAndName: string, file: Blob | File): Promise { 31 | return new Promise((resolve, reject) => { 32 | const request = this.storage.addNamed(file, filePathAndName); 33 | request.onsuccess = () => resolve(request.result); 34 | request.onerror = () => reject(request.error); 35 | }); 36 | } 37 | 38 | appendNamed(filePathAndName: string, file: Blob | File): Promise { 39 | return new Promise((resolve, reject) => { 40 | const request = this.storage.appendNamed(file, filePathAndName); 41 | request.onsuccess = () => resolve(request.result); 42 | request.onerror = () => reject(request.error); 43 | }); 44 | } 45 | 46 | delete(filePathAndName: string): Promise { 47 | return new Promise((resolve, reject) => { 48 | const request = this.storage.delete(filePathAndName); 49 | request.onsuccess = () => resolve(); 50 | request.onerror = () => reject(request.error); 51 | }); 52 | } 53 | 54 | search(regex: RegExp): Promise { 55 | return new Promise((resolve, reject) => { 56 | const files: FileSearchResult[] = []; 57 | const cursor = this.storage.enumerate(); 58 | 59 | cursor.onsuccess = function (): void { 60 | if (!this.result) { 61 | resolve(files); 62 | return; 63 | } 64 | 65 | const match = this.result.name.match(regex); 66 | if (match) { 67 | files.push({ 68 | name: this.result.name, 69 | size: this.result.size, 70 | type: this.result.type, 71 | lastModified: this.result.lastModified, 72 | lastModifiedDate: this.result.lastModifiedDate, 73 | }); 74 | } 75 | 76 | this.continue(); 77 | }; 78 | 79 | cursor.onerror = function (): void { 80 | reject(this.error); 81 | }; 82 | }); 83 | } 84 | 85 | getActualStorageName(storageName: StorageName): string { 86 | return Navigator.navigator.getDeviceStorage(storageName)?.storageName; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kaios-lib 2 | 3 | [![CircleCI](https://circleci.com/gh/garredow/kaios-lib/tree/main.svg?style=svg)](https://circleci.com/gh/garredow/kaios-lib/tree/main) 4 | [![npm](https://img.shields.io/npm/v/kaios-lib.svg)](https://www.npmjs.com/package/kaios-lib) 5 | 6 | A standard library to interact with KaiOS 2.x and 3.x\* APIs. 7 | 8 | \* 3.x support coming when there is a good dev device available for testing purposes 9 | 10 | ## Installation 11 | 12 | ``` 13 | npm install kaios-lib 14 | ``` 15 | 16 | ## Examples 17 | 18 | ### Activity 19 | 20 | ```js 21 | // Create the service 22 | const activity = new KaiOS.Activity({ 23 | name: 'toolbox/qr-to-text', 24 | data: {}, 25 | }); 26 | 27 | // Send the request and await the result 28 | const result = await activity.start(); 29 | ``` 30 | 31 | ### Alarm 32 | 33 | ```js 34 | // Create the service 35 | const alarms = new KaiOS.Alarm(); 36 | 37 | // Add an alarm 38 | await alarms.add({ 39 | date: new Date(Date.now() + 1000 * 10), 40 | respectTimezone: true, 41 | data: { name: 'Garrett' }, 42 | }); 43 | 44 | // List all alarms 45 | const all = await alarms.getAll(); 46 | console.log('alarms', all); 47 | 48 | // Subscribe to when an alarm is fired 49 | alarms.subscribe((data) => console.log('alarm fired', data)); 50 | ``` 51 | 52 | ### App 53 | 54 | ```js 55 | // Example coming soon 56 | ``` 57 | 58 | ### Battery 59 | 60 | ```js 61 | // Create the service 62 | const battery = new KaiOS.Battery(); 63 | 64 | // Get the current battery status 65 | const current = await battery.current(); 66 | 67 | // Subscribe to changes (fires on level and charging change) 68 | battery.subscribe((status) => console.log('battery status', status)); 69 | 70 | // Unsubscribe when you don't need it anymore 71 | battery.unsubscribe(); 72 | ``` 73 | 74 | ### FileStorage 75 | 76 | ```js 77 | // Example coming soon 78 | ``` 79 | 80 | ### Geolocation 81 | 82 | ```js 83 | // Create the service 84 | const geolocation = new KaiOS.Geolocation(); 85 | 86 | // Get current position 87 | const current = await geolocation.current(); 88 | 89 | // Subscribe to changes 90 | geolocation.subscribe((position) => { 91 | console.log('got position', position); 92 | }); 93 | 94 | // Unsubscribe when you don't need it anymore 95 | geolocation.unsubscribe(); 96 | ``` 97 | 98 | ### LocalStorage 99 | 100 | ```js 101 | // Create the service 102 | const localStorage = new KaiOS.LocalStorage(); 103 | 104 | // Write to storage. Will use `JSON.stringify` on object passed in 105 | localStorage.setItem('myKey', { name: 'Garrett' }); 106 | 107 | // Retrieve data 108 | const data = localStorage.getItem('myKey'); 109 | ``` 110 | 111 | ### Network 112 | 113 | ```js 114 | // Create the service 115 | const network = new KaiOS.Network(); 116 | 117 | // Get current status 118 | const current = await network.current(); 119 | 120 | // Subscribe to changes 121 | network.subscribe((status) => console.log('got status', status)); 122 | 123 | // Unsubscribe when you don't need it anymore 124 | network.unsubscribe(); 125 | ``` 126 | 127 | ### QRCode 128 | 129 | ```js 130 | // Create the service 131 | const qrCode = new KaiOS.QRCode(); 132 | 133 | // Read QR code as text 134 | const text = await qrCode.readAsText(); 135 | ``` 136 | 137 | ### Volume 138 | 139 | ```js 140 | // Create the service 141 | const volume = new KaiOS.Volume(); 142 | 143 | await volume.show(); 144 | await volume.up(); 145 | await volume.down(); 146 | ``` 147 | -------------------------------------------------------------------------------- /src/modules/qrCode.ts: -------------------------------------------------------------------------------- 1 | import jsQR from 'jsqr'; 2 | import { Navigator } from './navigator'; 3 | 4 | export class QRCode { 5 | private element: HTMLElement; 6 | private interval: NodeJS.Timer | null = null; 7 | private status: 'idle' | 'scanning' | 'cancelled' = 'idle'; 8 | private handler: any; 9 | 10 | constructor() { 11 | this.element = this.createRootElement(); 12 | } 13 | 14 | async readAsText(): Promise { 15 | if (this.status !== 'idle') { 16 | throw new Error('Already scanning for qr code'); 17 | } 18 | 19 | this.status = 'scanning'; 20 | 21 | this.showViewer(); 22 | await this.startVideo(); 23 | const result = await this.checkForQRCode(); 24 | this.hideViewer(); 25 | 26 | this.status = 'idle'; 27 | 28 | return result; 29 | } 30 | 31 | private handleKeyPress(ev: KeyboardEvent) { 32 | if (ev.key !== 'SoftRight' && ev.key !== 'Backspace') { 33 | return; 34 | } 35 | 36 | ev.stopImmediatePropagation(); 37 | ev.stopPropagation(); 38 | ev.preventDefault(); 39 | 40 | this.status = 'cancelled'; 41 | } 42 | 43 | private createRootElement(): HTMLElement { 44 | const root = document.createElement('div'); 45 | root.id = 'kosl__scanner'; 46 | root.style.cssText = ` 47 | position: absolute; 48 | top: 0; 49 | left: 0; 50 | bottom: 0; 51 | right: 0; 52 | z-index: 9999999; 53 | background: #000; 54 | color: rgba(255, 255, 255, .88); 55 | padding: 0px; 56 | margin: 0px; 57 | display: flex; 58 | flex-direction: column; 59 | overflow: hidden; 60 | `; 61 | 62 | const header = document.createElement('header'); 63 | header.style.cssText = ` 64 | font-weight: bold; 65 | text-align: center; 66 | padding-bottom: 5px; 67 | `; 68 | header.textContent = 'Scan a QR Code'; 69 | 70 | const video = document.createElement('video'); 71 | video.style.cssText = ` 72 | flex: 1; 73 | min-height: 0; 74 | `; 75 | 76 | const footer = document.createElement('footer'); 77 | footer.style.cssText = ` 78 | font-weight: bold; 79 | text-align: right; 80 | padding: 5px 5px 0 5px; 81 | `; 82 | footer.textContent = 'Cancel'; 83 | 84 | root.appendChild(header); 85 | root.appendChild(video); 86 | root.appendChild(footer); 87 | 88 | return root; 89 | } 90 | 91 | private showViewer() { 92 | document.body.appendChild(this.element); 93 | this.handler = this.handleKeyPress.bind(this); 94 | document.addEventListener('keydown', this.handler, { capture: true }); 95 | } 96 | 97 | private hideViewer() { 98 | document.removeEventListener('keydown', this.handler, { capture: true }); 99 | clearInterval(this.interval!); 100 | this.element?.remove(); 101 | } 102 | 103 | private startVideo(): Promise { 104 | return new Promise((resolve, reject) => { 105 | Navigator.navigator.mozGetUserMedia( 106 | { 107 | audio: false, 108 | video: { 109 | width: 240, 110 | height: 240, 111 | }, 112 | }, 113 | (stream: any) => { 114 | const video = document.querySelector('#kosl__scanner > video') as HTMLVideoElement | null; 115 | if (!video) return reject('Unable to find video element'); 116 | 117 | video.srcObject = stream; 118 | 119 | video.onloadedmetadata = () => { 120 | video.play(); 121 | resolve(); 122 | }; 123 | }, 124 | reject 125 | ); 126 | }); 127 | } 128 | 129 | private checkForQRCode(): Promise { 130 | return new Promise((resolve, reject) => { 131 | const video = document.querySelector('#kosl__scanner > video') as HTMLVideoElement | null; 132 | if (!video) return reject('Unable to find video element'); 133 | 134 | const canvas = document.createElement('canvas'); 135 | canvas.width = video.videoWidth; 136 | canvas.height = video.videoHeight; 137 | 138 | this.interval = setInterval(() => { 139 | if (this.status === 'cancelled') { 140 | return resolve(null); 141 | } 142 | const context = canvas.getContext('2d') as CanvasRenderingContext2D; 143 | context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight); 144 | const imageData = context.getImageData(0, 0, video.videoWidth, video.videoHeight); 145 | const code = jsQR(imageData.data, video.videoWidth, video.videoHeight); 146 | if (code) { 147 | resolve(code.data); 148 | } 149 | }, 1000); 150 | }); 151 | } 152 | } 153 | --------------------------------------------------------------------------------