diff --git a/packages/kbot/cat.png b/packages/kbot/cat.png new file mode 100644 index 00000000..87113636 Binary files /dev/null and b/packages/kbot/cat.png differ diff --git a/packages/kbot/dist-in/commands/images.js b/packages/kbot/dist-in/commands/images.js index 8e73c99b..d048b096 100644 --- a/packages/kbot/dist-in/commands/images.js +++ b/packages/kbot/dist-in/commands/images.js @@ -30,14 +30,17 @@ export const ImageOptionsSchema = () => { }; async function launchGuiAndGetPrompt(argv) { return new Promise((resolve, reject) => { - const guiAppPath = path.join(process.cwd(), 'gui', 'tauri-app', 'src-tauri', 'target', 'release', 'tauri-app.exe'); + const guiAppPath = path.join(process.cwd(), 'dist', 'win-64', 'tauri-app.exe'); + console.log('guiAppPath', guiAppPath); if (!exists(guiAppPath)) { return reject(new Error(`GUI application not found at: ${guiAppPath}. Please build it first by running 'npm run tauri build' in 'gui/tauri-app'.`)); } const args = []; if (argv.include) { const includes = Array.isArray(argv.include) ? argv.include : [argv.include]; - args.push(...includes); + // Resolve all paths to absolute paths before passing them to the GUI + const absoluteIncludes = includes.map(p => path.resolve(p)); + args.push(...absoluteIncludes); } const tauriProcess = spawn(guiAppPath, args, { stdio: ['pipe', 'pipe', 'pipe'] }); let output = ''; @@ -133,4 +136,4 @@ export const imageCommand = async (argv) => { logger.error('Failed to parse options or generate image:', error.message, error.issues, error.stack); } }; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1hZ2VzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbW1hbmRzL2ltYWdlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsQ0FBQyxFQUFFLE1BQU0sS0FBSyxDQUFDO0FBQ3hCLE9BQU8sS0FBSyxJQUFJLE1BQU0sV0FBVyxDQUFDO0FBQ2xDLE9BQU8sRUFBRSxJQUFJLElBQUksS0FBSyxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDbkQsT0FBTyxFQUFFLElBQUksSUFBSSxNQUFNLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUVyRCxPQUFPLEVBQUUsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBRTlELE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUNqRCxPQUFPLEVBQUUsV0FBVyxFQUFFLFNBQVMsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ2pFLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDeEMsT0FBTyxFQUFFLE1BQU0sSUFBSSxhQUFhLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDdkQsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQzVDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUM1QyxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFM0MsTUFBTSxDQUFDLE1BQU0sa0JBQWtCLEdBQUcsR0FBRyxFQUFFO0lBQ25DLE1BQU0sVUFBVSxHQUFHLGFBQWEsRUFBRSxDQUFDLElBQUksQ0FBQztRQUNwQyxNQUFNLEVBQUUsSUFBSTtRQUNaLE9BQU8sRUFBRSxJQUFJO1FBQ2IsR0FBRyxFQUFFLElBQUk7UUFDVCxLQUFLLEVBQUUsSUFBSTtRQUNYLFFBQVEsRUFBRSxJQUFJO1FBQ2QsTUFBTSxFQUFFLElBQUk7UUFDWixPQUFPLEVBQUUsSUFBSTtRQUNiLEdBQUcsRUFBRSxJQUFJO0tBQ1osQ0FBQyxDQUFDO0lBRUgsT0FBTyxVQUFVLENBQUMsTUFBTSxDQUFDO1FBQ3JCLEdBQUcsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUMsUUFBUSxDQUFDLDBCQUEwQixDQUFDO1FBQ2hFLEtBQUssRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsT0FBTyxDQUFDLGdDQUFnQyxDQUFDLENBQUMsUUFBUSxDQUFDLCtDQUErQyxDQUFDO1FBQ3JILEdBQUcsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxDQUFDLGtEQUFrRCxDQUFDO1FBQzVFLE1BQU0sRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUMsUUFBUSxDQUFDLDJDQUEyQyxDQUFDO0tBQ3RGLENBQUMsQ0FBQztBQUNQLENBQUMsQ0FBQTtBQUVELEtBQUssVUFBVSxxQkFBcUIsQ0FBQyxJQUFTO0lBQzFDLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDbkMsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLEVBQUUsS0FBSyxFQUFFLFdBQVcsRUFBRSxXQUFXLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxlQUFlLENBQUMsQ0FBQztRQUVuSCxJQUFJLENBQUMsTUFBTSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7WUFDdEIsT0FBTyxNQUFNLENBQUMsSUFBSSxLQUFLLENBQUMsaUNBQWlDLFVBQVUsOEVBQThFLENBQUMsQ0FBQyxDQUFDO1FBQ3hKLENBQUM7UUFFRCxNQUFNLElBQUksR0FBYSxFQUFFLENBQUM7UUFDMUIsSUFBSSxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDZixNQUFNLFFBQVEsR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDN0UsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLFFBQVEsQ0FBQyxDQUFDO1FBQzNCLENBQUM7UUFFRCxNQUFNLFlBQVksR0FBRyxLQUFLLENBQUMsVUFBVSxFQUFFLElBQUksRUFBRSxFQUFFLEtBQUssRUFBRSxDQUFDLE1BQU0sRUFBRSxNQUFNLEVBQUUsTUFBTSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBRWxGLElBQUksTUFBTSxHQUFHLEVBQUUsQ0FBQztRQUNoQixZQUFZLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxJQUFJLEVBQUUsRUFBRTtZQUNwQyxNQUFNLElBQUksSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQzlCLENBQUMsQ0FBQyxDQUFDO1FBRUgsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsTUFBTSxFQUFFLENBQUMsSUFBSSxFQUFFLEVBQUU7WUFDcEMsT0FBTyxDQUFDLEtBQUssQ0FBQyxvQkFBb0IsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUM5QyxDQUFDLENBQUMsQ0FBQztRQUVILFlBQVksQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsSUFBSSxFQUFFLEVBQUU7WUFDOUIsSUFBSSxJQUFJLEtBQUssQ0FBQyxFQUFFLENBQUM7Z0JBQ2IsT0FBTyxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsSUFBSSxJQUFJLENBQUMsQ0FBQztZQUNuQyxDQUFDO2lCQUFNLENBQUM7Z0JBQ0osTUFBTSxDQUFDLElBQUksS0FBSyxDQUFDLDhCQUE4QixJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUM7WUFDNUQsQ0FBQztRQUNMLENBQUMsQ0FBQyxDQUFDO1FBRUgsWUFBWSxDQUFDLEVBQUUsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxHQUFHLEVBQUUsRUFBRTtZQUM3QixNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDaEIsQ0FBQyxDQUFDLENBQUM7SUFDUCxDQUFDLENBQUMsQ0FBQztBQUNQLENBQUM7QUFHRCxNQUFNLENBQUMsTUFBTSxZQUFZLEdBQUcsS0FBSyxFQUFFLElBQVMsRUFBRSxFQUFFO0lBQzVDLE1BQU0sTUFBTSxHQUFHLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUUvQixJQUFJLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUNYLElBQUksQ0FBQztZQUNELE1BQU0sU0FBUyxHQUFHLE1BQU0scUJBQXFCLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDcEQsSUFBSSxTQUFTLEVBQUUsQ0FBQztnQkFDWixNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLFNBQVMsQ0FBQyxDQUFDO2dCQUN0QyxJQUFJLENBQUMsTUFBTSxHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUM7Z0JBQzdCLElBQUksT0FBTyxDQUFDLEtBQUssSUFBSSxPQUFPLENBQUMsS0FBSyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztvQkFDNUMsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDO2dCQUNqQyxDQUFDO1lBQ0wsQ0FBQztpQkFBTSxDQUFDO2dCQUNKLE1BQU0sQ0FBQyxJQUFJLENBQUMsd0NBQXdDLENBQUMsQ0FBQztnQkFDdEQsT0FBTztZQUNYLENBQUM7UUFDTCxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNiLE1BQU0sQ0FBQyxLQUFLLENBQUMsb0JBQW9CLEVBQUUsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1lBQ2xELE9BQU87UUFDWCxDQUFDO0lBQ0wsQ0FBQztJQUVELElBQUksSUFBSSxDQUFDLE9BQU8sSUFBSSxRQUFRLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7UUFDekMsSUFBSSxDQUFDLE9BQU8sR0FBRyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUNsQyxDQUFDO0lBRUQsSUFBSSxDQUFDO1FBQ0QsTUFBTSxhQUFhLEdBQUcsa0JBQWtCLEVBQUUsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDdkQsTUFBTSxFQUFFLE9BQU8sRUFBRSxHQUFHLEVBQUUsR0FBRyxJQUFJLEVBQUUsR0FBRyxhQUFhLENBQUM7UUFFaEQsTUFBTSxhQUFhLEdBQUcsTUFBTSxhQUFhLENBQUMsYUFBYSxDQUFDLENBQUM7UUFDekQsTUFBTSxNQUFNLEdBQUcsYUFBYSxFQUFFLE9BQWlCLElBQUksRUFBRSxDQUFDO1FBRXRELElBQUksQ0FBQyxNQUFNLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUN0QixNQUFNLENBQUMsS0FBSyxDQUFDLHlGQUF5RixDQUFDLENBQUM7WUFDeEcsT0FBTztRQUNYLENBQUM7UUFFRCxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDUCxNQUFNLENBQUMsS0FBSyxDQUFDLG9EQUFvRCxDQUFDLENBQUM7WUFDbkUsT0FBTztRQUNYLENBQUM7UUFFRCxJQUFJLFdBQVcsR0FBa0IsSUFBSSxDQUFDO1FBRXRDLElBQUksT0FBTyxJQUFJLE9BQU8sQ0FBQyxPQUFPLENBQUMsSUFBSSxPQUFPLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1lBQ3BELGdCQUFnQjtZQUNoQixLQUFLLE1BQU0sU0FBUyxJQUFJLE9BQU8sRUFBRSxDQUFDO2dCQUM5QixJQUFJLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUM7b0JBQ3JCLE1BQU0sQ0FBQyxLQUFLLENBQUMsNkJBQTZCLFNBQVMsRUFBRSxDQUFDLENBQUM7b0JBQ3ZELE9BQU87Z0JBQ1gsQ0FBQztZQUNMLENBQUM7WUFDRCxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7Z0JBQ1YsTUFBTSxDQUFDLEtBQUssQ0FBQyx5Q0FBeUMsQ0FBQyxDQUFDO2dCQUN4RCxPQUFPO1lBQ1gsQ0FBQztZQUNELE1BQU0sQ0FBQyxJQUFJLENBQUMscUJBQXFCLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLG1CQUFtQixNQUFNLEdBQUcsQ0FBQyxDQUFDO1lBQ2pGLFdBQVcsR0FBRyxNQUFNLFNBQVMsQ0FBQyxNQUFNLEVBQUUsT0FBTyxFQUFFLGFBQWEsQ0FBQyxDQUFDO1FBQ2xFLENBQUM7YUFBTSxJQUFJLE1BQU0sRUFBRSxDQUFDO1lBQ2hCLGlCQUFpQjtZQUNqQixNQUFNLENBQUMsSUFBSSxDQUFDLGdDQUFnQyxNQUFNLEdBQUcsQ0FBQyxDQUFDO1lBQ3ZELFdBQVcsR0FBRyxNQUFNLFdBQVcsQ0FBQyxNQUFNLEVBQUUsYUFBYSxDQUFDLENBQUM7UUFDM0QsQ0FBQztRQUVELElBQUksV0FBVyxFQUFFLENBQUM7WUFDZCxNQUFNLElBQUksR0FBRyxTQUFTLENBQUMsYUFBYSxDQUFDLENBQUM7WUFDdEMsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLGFBQWEsQ0FBQyxHQUFHLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQztZQUNwRSxLQUFLLENBQUMsT0FBTyxFQUFFLFdBQVcsQ0FBQyxDQUFDO1lBQzVCLE1BQU0sQ0FBQyxJQUFJLENBQUMsbUJBQW1CLE9BQU8sRUFBRSxDQUFDLENBQUM7UUFDOUMsQ0FBQzthQUFNLENBQUM7WUFDSixNQUFNLENBQUMsS0FBSyxDQUFDLDJCQUEyQixDQUFDLENBQUM7UUFDOUMsQ0FBQztJQUVMLENBQUM7SUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1FBQ2IsTUFBTSxDQUFDLEtBQUssQ0FBQyw0Q0FBNEMsRUFBRSxLQUFLLENBQUMsT0FBTyxFQUFFLEtBQUssQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3pHLENBQUM7QUFDTCxDQUFDLENBQUMifQ== \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1hZ2VzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbW1hbmRzL2ltYWdlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsQ0FBQyxFQUFFLE1BQU0sS0FBSyxDQUFDO0FBQ3hCLE9BQU8sS0FBSyxJQUFJLE1BQU0sV0FBVyxDQUFDO0FBQ2xDLE9BQU8sRUFBRSxJQUFJLElBQUksS0FBSyxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDbkQsT0FBTyxFQUFFLElBQUksSUFBSSxNQUFNLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUVyRCxPQUFPLEVBQUUsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBRTlELE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUNqRCxPQUFPLEVBQUUsV0FBVyxFQUFFLFNBQVMsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ2pFLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDeEMsT0FBTyxFQUFFLE1BQU0sSUFBSSxhQUFhLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDdkQsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQzVDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUM1QyxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFM0MsTUFBTSxDQUFDLE1BQU0sa0JBQWtCLEdBQUcsR0FBRyxFQUFFO0lBQ25DLE1BQU0sVUFBVSxHQUFHLGFBQWEsRUFBRSxDQUFDLElBQUksQ0FBQztRQUNwQyxNQUFNLEVBQUUsSUFBSTtRQUNaLE9BQU8sRUFBRSxJQUFJO1FBQ2IsR0FBRyxFQUFFLElBQUk7UUFDVCxLQUFLLEVBQUUsSUFBSTtRQUNYLFFBQVEsRUFBRSxJQUFJO1FBQ2QsTUFBTSxFQUFFLElBQUk7UUFDWixPQUFPLEVBQUUsSUFBSTtRQUNiLEdBQUcsRUFBRSxJQUFJO0tBQ1osQ0FBQyxDQUFDO0lBRUgsT0FBTyxVQUFVLENBQUMsTUFBTSxDQUFDO1FBQ3JCLEdBQUcsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUMsUUFBUSxDQUFDLDBCQUEwQixDQUFDO1FBQ2hFLEtBQUssRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsT0FBTyxDQUFDLGdDQUFnQyxDQUFDLENBQUMsUUFBUSxDQUFDLCtDQUErQyxDQUFDO1FBQ3JILEdBQUcsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxDQUFDLGtEQUFrRCxDQUFDO1FBQzVFLE1BQU0sRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUMsUUFBUSxDQUFDLDJDQUEyQyxDQUFDO0tBQ3RGLENBQUMsQ0FBQztBQUNQLENBQUMsQ0FBQTtBQUVELEtBQUssVUFBVSxxQkFBcUIsQ0FBQyxJQUFTO0lBQzFDLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDbkMsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxlQUFlLENBQUMsQ0FBQztRQUMvRSxPQUFPLENBQUMsR0FBRyxDQUFDLFlBQVksRUFBRSxVQUFVLENBQUMsQ0FBQztRQUN0QyxJQUFJLENBQUMsTUFBTSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7WUFDdEIsT0FBTyxNQUFNLENBQUMsSUFBSSxLQUFLLENBQUMsaUNBQWlDLFVBQVUsOEVBQThFLENBQUMsQ0FBQyxDQUFDO1FBQ3hKLENBQUM7UUFDRCxNQUFNLElBQUksR0FBYSxFQUFFLENBQUM7UUFDMUIsSUFBSSxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDZixNQUFNLFFBQVEsR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDN0UscUVBQXFFO1lBQ3JFLE1BQU0sZ0JBQWdCLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUM1RCxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsZ0JBQWdCLENBQUMsQ0FBQztRQUNuQyxDQUFDO1FBRUQsTUFBTSxZQUFZLEdBQUcsS0FBSyxDQUFDLFVBQVUsRUFBRSxJQUFJLEVBQUUsRUFBRSxLQUFLLEVBQUUsQ0FBQyxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0sQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUVsRixJQUFJLE1BQU0sR0FBRyxFQUFFLENBQUM7UUFDaEIsWUFBWSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsTUFBTSxFQUFFLENBQUMsSUFBSSxFQUFFLEVBQUU7WUFDcEMsTUFBTSxJQUFJLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUM5QixDQUFDLENBQUMsQ0FBQztRQUVILFlBQVksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxDQUFDLElBQUksRUFBRSxFQUFFO1lBQ3BDLE9BQU8sQ0FBQyxLQUFLLENBQUMsb0JBQW9CLElBQUksRUFBRSxDQUFDLENBQUM7UUFDOUMsQ0FBQyxDQUFDLENBQUM7UUFFSCxZQUFZLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxDQUFDLElBQUksRUFBRSxFQUFFO1lBQzlCLElBQUksSUFBSSxLQUFLLENBQUMsRUFBRSxDQUFDO2dCQUNiLE9BQU8sQ0FBQyxNQUFNLENBQUMsSUFBSSxFQUFFLElBQUksSUFBSSxDQUFDLENBQUM7WUFDbkMsQ0FBQztpQkFBTSxDQUFDO2dCQUNKLE1BQU0sQ0FBQyxJQUFJLEtBQUssQ0FBQyw4QkFBOEIsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDO1lBQzVELENBQUM7UUFDTCxDQUFDLENBQUMsQ0FBQztRQUVILFlBQVksQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsR0FBRyxFQUFFLEVBQUU7WUFDN0IsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ2hCLENBQUMsQ0FBQyxDQUFDO0lBQ1AsQ0FBQyxDQUFDLENBQUM7QUFDUCxDQUFDO0FBR0QsTUFBTSxDQUFDLE1BQU0sWUFBWSxHQUFHLEtBQUssRUFBRSxJQUFTLEVBQUUsRUFBRTtJQUM1QyxNQUFNLE1BQU0sR0FBRyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUM7SUFFL0IsSUFBSSxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7UUFDWCxJQUFJLENBQUM7WUFDRCxNQUFNLFNBQVMsR0FBRyxNQUFNLHFCQUFxQixDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ3BELElBQUksU0FBUyxFQUFFLENBQUM7Z0JBQ1osTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxTQUFTLENBQUMsQ0FBQztnQkFDdEMsSUFBSSxDQUFDLE1BQU0sR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDO2dCQUM3QixJQUFJLE9BQU8sQ0FBQyxLQUFLLElBQUksT0FBTyxDQUFDLEtBQUssQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7b0JBQzVDLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQztnQkFDakMsQ0FBQztZQUNMLENBQUM7aUJBQU0sQ0FBQztnQkFDSixNQUFNLENBQUMsSUFBSSxDQUFDLHdDQUF3QyxDQUFDLENBQUM7Z0JBQ3RELE9BQU87WUFDWCxDQUFDO1FBQ0wsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDYixNQUFNLENBQUMsS0FBSyxDQUFDLG9CQUFvQixFQUFFLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUNsRCxPQUFPO1FBQ1gsQ0FBQztJQUNMLENBQUM7SUFFRCxJQUFJLElBQUksQ0FBQyxPQUFPLElBQUksUUFBUSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO1FBQ3pDLElBQUksQ0FBQyxPQUFPLEdBQUcsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDbEMsQ0FBQztJQUVELElBQUksQ0FBQztRQUNELE1BQU0sYUFBYSxHQUFHLGtCQUFrQixFQUFFLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3ZELE1BQU0sRUFBRSxPQUFPLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxFQUFFLEdBQUcsYUFBYSxDQUFDO1FBRWhELE1BQU0sYUFBYSxHQUFHLE1BQU0sYUFBYSxDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBQ3pELE1BQU0sTUFBTSxHQUFHLGFBQWEsRUFBRSxPQUFpQixJQUFJLEVBQUUsQ0FBQztRQUV0RCxJQUFJLENBQUMsTUFBTSxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDdEIsTUFBTSxDQUFDLEtBQUssQ0FBQyx5RkFBeUYsQ0FBQyxDQUFDO1lBQ3hHLE9BQU87UUFDWCxDQUFDO1FBRUQsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1lBQ1AsTUFBTSxDQUFDLEtBQUssQ0FBQyxvREFBb0QsQ0FBQyxDQUFDO1lBQ25FLE9BQU87UUFDWCxDQUFDO1FBRUQsSUFBSSxXQUFXLEdBQWtCLElBQUksQ0FBQztRQUV0QyxJQUFJLE9BQU8sSUFBSSxPQUFPLENBQUMsT0FBTyxDQUFDLElBQUksT0FBTyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUNwRCxnQkFBZ0I7WUFDaEIsS0FBSyxNQUFNLFNBQVMsSUFBSSxPQUFPLEVBQUUsQ0FBQztnQkFDOUIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsRUFBRSxDQUFDO29CQUNyQixNQUFNLENBQUMsS0FBSyxDQUFDLDZCQUE2QixTQUFTLEVBQUUsQ0FBQyxDQUFDO29CQUN2RCxPQUFPO2dCQUNYLENBQUM7WUFDTCxDQUFDO1lBQ0QsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO2dCQUNWLE1BQU0sQ0FBQyxLQUFLLENBQUMseUNBQXlDLENBQUMsQ0FBQztnQkFDeEQsT0FBTztZQUNYLENBQUM7WUFDRCxNQUFNLENBQUMsSUFBSSxDQUFDLHFCQUFxQixPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxtQkFBbUIsTUFBTSxHQUFHLENBQUMsQ0FBQztZQUNqRixXQUFXLEdBQUcsTUFBTSxTQUFTLENBQUMsTUFBTSxFQUFFLE9BQU8sRUFBRSxhQUFhLENBQUMsQ0FBQztRQUNsRSxDQUFDO2FBQU0sSUFBSSxNQUFNLEVBQUUsQ0FBQztZQUNoQixpQkFBaUI7WUFDakIsTUFBTSxDQUFDLElBQUksQ0FBQyxnQ0FBZ0MsTUFBTSxHQUFHLENBQUMsQ0FBQztZQUN2RCxXQUFXLEdBQUcsTUFBTSxXQUFXLENBQUMsTUFBTSxFQUFFLGFBQWEsQ0FBQyxDQUFDO1FBQzNELENBQUM7UUFFRCxJQUFJLFdBQVcsRUFBRSxDQUFDO1lBQ2QsTUFBTSxJQUFJLEdBQUcsU0FBUyxDQUFDLGFBQWEsQ0FBQyxDQUFDO1lBQ3RDLE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRSxhQUFhLENBQUMsR0FBRyxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUM7WUFDcEUsS0FBSyxDQUFDLE9BQU8sRUFBRSxXQUFXLENBQUMsQ0FBQztZQUM1QixNQUFNLENBQUMsSUFBSSxDQUFDLG1CQUFtQixPQUFPLEVBQUUsQ0FBQyxDQUFDO1FBQzlDLENBQUM7YUFBTSxDQUFDO1lBQ0osTUFBTSxDQUFDLEtBQUssQ0FBQywyQkFBMkIsQ0FBQyxDQUFDO1FBQzlDLENBQUM7SUFFTCxDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNiLE1BQU0sQ0FBQyxLQUFLLENBQUMsNENBQTRDLEVBQUUsS0FBSyxDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUMsTUFBTSxFQUFFLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUN6RyxDQUFDO0FBQ0wsQ0FBQyxDQUFDIn0= \ No newline at end of file diff --git a/packages/kbot/dist-in/lib/images-google.js b/packages/kbot/dist-in/lib/images-google.js index c788b4a9..a9d1d965 100644 --- a/packages/kbot/dist-in/lib/images-google.js +++ b/packages/kbot/dist-in/lib/images-google.js @@ -11,7 +11,6 @@ const createGoogleGenAIClient = (options) => { return undefined; } let apiKey = options.api_key || config?.google?.key; - logger.debug(`Google API Key: ${apiKey}`); if (!apiKey) { logger.error(`No gemini key found. Please provide an "api_key", set it in the config, or pass it via JSON config.`); return undefined; @@ -69,4 +68,4 @@ export const editImage = async (prompt, imagePaths, options) => { } return null; }; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1hZ2VzLWdvb2dsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9saWIvaW1hZ2VzLWdvb2dsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsa0JBQWtCLEVBQVEsTUFBTSx1QkFBdUIsQ0FBQztBQUNqRSxPQUFPLEtBQUssRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUU5QixPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQzFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDckMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLFlBQVksQ0FBQTtBQUVuQyxNQUFNLHVCQUF1QixHQUFHLENBQUMsT0FBcUIsRUFBRSxFQUFFO0lBQ3RELE1BQU0sTUFBTSxHQUFHLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUNuQyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7UUFDVixNQUFNLENBQUMsS0FBSyxDQUNSLDhDQUE4QztZQUM5Qyx3RUFBd0UsQ0FDM0UsQ0FBQztRQUNGLE9BQU8sU0FBUyxDQUFDO0lBQ3JCLENBQUM7SUFFRCxJQUFJLE1BQU0sR0FBRyxPQUFPLENBQUMsT0FBTyxJQUFJLE1BQU0sRUFBRSxNQUFNLEVBQUUsR0FBRyxDQUFDO0lBQ3BELE1BQU0sQ0FBQyxLQUFLLENBQUMsbUJBQW1CLE1BQU0sRUFBRSxDQUFDLENBQUM7SUFFMUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ1YsTUFBTSxDQUFDLEtBQUssQ0FBQyxxR0FBcUcsQ0FBQyxDQUFDO1FBQ3BILE9BQU8sU0FBUyxDQUFDO0lBQ3JCLENBQUM7SUFFRCxPQUFPLElBQUksa0JBQWtCLENBQUMsTUFBTSxDQUFDLENBQUM7QUFDMUMsQ0FBQyxDQUFBO0FBRUQsTUFBTSxDQUFDLE1BQU0sV0FBVyxHQUFHLEtBQUssRUFBRSxNQUFjLEVBQUUsT0FBcUIsRUFBMEIsRUFBRTtJQUMvRixNQUFNLEVBQUUsR0FBRyx1QkFBdUIsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUM1QyxJQUFJLENBQUMsRUFBRSxFQUFFLENBQUM7UUFDTixPQUFPLElBQUksQ0FBQztJQUNoQixDQUFDO0lBRUQsTUFBTSxLQUFLLEdBQUcsRUFBRSxDQUFDLGtCQUFrQixDQUFDLEVBQUUsS0FBSyxFQUFFLE9BQU8sQ0FBQyxLQUFLLElBQUksZ0NBQWdDLEVBQUUsQ0FBQyxDQUFDO0lBRWxHLE1BQU0sTUFBTSxHQUFHLE1BQU0sS0FBSyxDQUFDLGVBQWUsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUVuRCxNQUFNLFFBQVEsR0FBRyxNQUFNLENBQUMsUUFBUSxDQUFDO0lBQ2pDLE1BQU0sS0FBSyxHQUFHLFFBQVEsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQztJQUNuRCxLQUFLLE1BQU0sSUFBSSxJQUFJLEtBQUssRUFBRSxDQUFDO1FBQ3ZCLElBQUksWUFBWSxJQUFJLElBQUksRUFBRSxDQUFDO1lBQ3ZCLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUM7WUFDbkMsSUFBSSxVQUFVLEVBQUUsQ0FBQztnQkFDYixPQUFPLE1BQU0sQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsQ0FBQztZQUNsRCxDQUFDO1FBQ0wsQ0FBQztJQUNMLENBQUM7SUFFRCxPQUFPLElBQUksQ0FBQztBQUNoQixDQUFDLENBQUE7QUFFRCxNQUFNLENBQUMsTUFBTSxTQUFTLEdBQUcsS0FBSyxFQUFFLE1BQWMsRUFBRSxVQUFvQixFQUFFLE9BQXFCLEVBQTBCLEVBQUU7SUFDbkgsTUFBTSxFQUFFLEdBQUcsdUJBQXVCLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDNUMsSUFBSSxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQ04sT0FBTyxJQUFJLENBQUM7SUFDaEIsQ0FBQztJQUVELE1BQU0sS0FBSyxHQUFHLEVBQUUsQ0FBQyxrQkFBa0IsQ0FBQyxFQUFFLEtBQUssRUFBRSxPQUFPLENBQUMsS0FBSyxJQUFJLGdDQUFnQyxFQUFFLENBQUMsQ0FBQztJQUVsRyxNQUFNLFVBQVUsR0FBVyxVQUFVLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxFQUFFO1FBQ2xELE1BQU0sU0FBUyxHQUFHLEVBQUUsQ0FBQyxZQUFZLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDN0MsTUFBTSxXQUFXLEdBQUcsU0FBUyxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUNqRCxNQUFNLFFBQVEsR0FBRyxNQUFNLENBQUMsU0FBUyxDQUFDLElBQUksV0FBVyxDQUFDO1FBQ2xELE9BQU87WUFDSCxVQUFVLEVBQUU7Z0JBQ1IsUUFBUTtnQkFDUixJQUFJLEVBQUUsV0FBVzthQUNwQjtTQUNKLENBQUM7SUFDTixDQUFDLENBQUMsQ0FBQztJQUVILE1BQU0sUUFBUSxHQUFTLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxDQUFDO0lBRXhDLE1BQU0sV0FBVyxHQUFHLENBQUMsR0FBRyxVQUFVLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFFOUMsTUFBTSxNQUFNLEdBQUcsTUFBTSxLQUFLLENBQUMsZUFBZSxDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBRXhELE1BQU0sUUFBUSxHQUFHLE1BQU0sQ0FBQyxRQUFRLENBQUM7SUFDakMsTUFBTSxLQUFLLEdBQUcsUUFBUSxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDO0lBQ25ELEtBQUssTUFBTSxJQUFJLElBQUksS0FBSyxFQUFFLENBQUM7UUFDdkIsSUFBSSxZQUFZLElBQUksSUFBSSxFQUFFLENBQUM7WUFDdkIsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQztZQUNuQyxJQUFJLFVBQVUsRUFBRSxDQUFDO2dCQUNiLE9BQU8sTUFBTSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1lBQ2xELENBQUM7UUFDTCxDQUFDO0lBQ0wsQ0FBQztJQUVELE9BQU8sSUFBSSxDQUFDO0FBQ2hCLENBQUMsQ0FBQSJ9 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1hZ2VzLWdvb2dsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9saWIvaW1hZ2VzLWdvb2dsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsa0JBQWtCLEVBQVEsTUFBTSx1QkFBdUIsQ0FBQztBQUNqRSxPQUFPLEtBQUssRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUU5QixPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQzFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDckMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLFlBQVksQ0FBQTtBQUVuQyxNQUFNLHVCQUF1QixHQUFHLENBQUMsT0FBcUIsRUFBRSxFQUFFO0lBQ3RELE1BQU0sTUFBTSxHQUFHLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUNuQyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7UUFDVixNQUFNLENBQUMsS0FBSyxDQUNSLDhDQUE4QztZQUM5Qyx3RUFBd0UsQ0FDM0UsQ0FBQztRQUNGLE9BQU8sU0FBUyxDQUFDO0lBQ3JCLENBQUM7SUFFRCxJQUFJLE1BQU0sR0FBRyxPQUFPLENBQUMsT0FBTyxJQUFJLE1BQU0sRUFBRSxNQUFNLEVBQUUsR0FBRyxDQUFDO0lBRXBELElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUNWLE1BQU0sQ0FBQyxLQUFLLENBQUMscUdBQXFHLENBQUMsQ0FBQztRQUNwSCxPQUFPLFNBQVMsQ0FBQztJQUNyQixDQUFDO0lBRUQsT0FBTyxJQUFJLGtCQUFrQixDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBQzFDLENBQUMsQ0FBQTtBQUVELE1BQU0sQ0FBQyxNQUFNLFdBQVcsR0FBRyxLQUFLLEVBQUUsTUFBYyxFQUFFLE9BQXFCLEVBQTBCLEVBQUU7SUFDL0YsTUFBTSxFQUFFLEdBQUcsdUJBQXVCLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDNUMsSUFBSSxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQ04sT0FBTyxJQUFJLENBQUM7SUFDaEIsQ0FBQztJQUVELE1BQU0sS0FBSyxHQUFHLEVBQUUsQ0FBQyxrQkFBa0IsQ0FBQyxFQUFFLEtBQUssRUFBRSxPQUFPLENBQUMsS0FBSyxJQUFJLGdDQUFnQyxFQUFFLENBQUMsQ0FBQztJQUVsRyxNQUFNLE1BQU0sR0FBRyxNQUFNLEtBQUssQ0FBQyxlQUFlLENBQUMsTUFBTSxDQUFDLENBQUM7SUFFbkQsTUFBTSxRQUFRLEdBQUcsTUFBTSxDQUFDLFFBQVEsQ0FBQztJQUNqQyxNQUFNLEtBQUssR0FBRyxRQUFRLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUM7SUFDbkQsS0FBSyxNQUFNLElBQUksSUFBSSxLQUFLLEVBQUUsQ0FBQztRQUN2QixJQUFJLFlBQVksSUFBSSxJQUFJLEVBQUUsQ0FBQztZQUN2QixNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDO1lBQ25DLElBQUksVUFBVSxFQUFFLENBQUM7Z0JBQ2IsT0FBTyxNQUFNLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUM7WUFDbEQsQ0FBQztRQUNMLENBQUM7SUFDTCxDQUFDO0lBRUQsT0FBTyxJQUFJLENBQUM7QUFDaEIsQ0FBQyxDQUFBO0FBRUQsTUFBTSxDQUFDLE1BQU0sU0FBUyxHQUFHLEtBQUssRUFBRSxNQUFjLEVBQUUsVUFBb0IsRUFBRSxPQUFxQixFQUEwQixFQUFFO0lBQ25ILE1BQU0sRUFBRSxHQUFHLHVCQUF1QixDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQzVDLElBQUksQ0FBQyxFQUFFLEVBQUUsQ0FBQztRQUNOLE9BQU8sSUFBSSxDQUFDO0lBQ2hCLENBQUM7SUFFRCxNQUFNLEtBQUssR0FBRyxFQUFFLENBQUMsa0JBQWtCLENBQUMsRUFBRSxLQUFLLEVBQUUsT0FBTyxDQUFDLEtBQUssSUFBSSxnQ0FBZ0MsRUFBRSxDQUFDLENBQUM7SUFFbEcsTUFBTSxVQUFVLEdBQVcsVUFBVSxDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsRUFBRTtRQUNsRCxNQUFNLFNBQVMsR0FBRyxFQUFFLENBQUMsWUFBWSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzdDLE1BQU0sV0FBVyxHQUFHLFNBQVMsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDakQsTUFBTSxRQUFRLEdBQUcsTUFBTSxDQUFDLFNBQVMsQ0FBQyxJQUFJLFdBQVcsQ0FBQztRQUNsRCxPQUFPO1lBQ0gsVUFBVSxFQUFFO2dCQUNSLFFBQVE7Z0JBQ1IsSUFBSSxFQUFFLFdBQVc7YUFDcEI7U0FDSixDQUFDO0lBQ04sQ0FBQyxDQUFDLENBQUM7SUFFSCxNQUFNLFFBQVEsR0FBUyxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsQ0FBQztJQUV4QyxNQUFNLFdBQVcsR0FBRyxDQUFDLEdBQUcsVUFBVSxFQUFFLFFBQVEsQ0FBQyxDQUFDO0lBRTlDLE1BQU0sTUFBTSxHQUFHLE1BQU0sS0FBSyxDQUFDLGVBQWUsQ0FBQyxXQUFXLENBQUMsQ0FBQztJQUV4RCxNQUFNLFFBQVEsR0FBRyxNQUFNLENBQUMsUUFBUSxDQUFDO0lBQ2pDLE1BQU0sS0FBSyxHQUFHLFFBQVEsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQztJQUNuRCxLQUFLLE1BQU0sSUFBSSxJQUFJLEtBQUssRUFBRSxDQUFDO1FBQ3ZCLElBQUksWUFBWSxJQUFJLElBQUksRUFBRSxDQUFDO1lBQ3ZCLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUM7WUFDbkMsSUFBSSxVQUFVLEVBQUUsQ0FBQztnQkFDYixPQUFPLE1BQU0sQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsQ0FBQztZQUNsRCxDQUFDO1FBQ0wsQ0FBQztJQUNMLENBQUM7SUFFRCxPQUFPLElBQUksQ0FBQztBQUNoQixDQUFDLENBQUEifQ== \ No newline at end of file diff --git a/packages/kbot/dist/win-64/cat.png b/packages/kbot/dist/win-64/cat.png new file mode 100644 index 00000000..87113636 Binary files /dev/null and b/packages/kbot/dist/win-64/cat.png differ diff --git a/packages/kbot/dist/win-64/tauri-app.exe b/packages/kbot/dist/win-64/tauri-app.exe new file mode 100644 index 00000000..d76f5f07 Binary files /dev/null and b/packages/kbot/dist/win-64/tauri-app.exe differ diff --git a/packages/kbot/gui/app/.cargo/audit.toml b/packages/kbot/gui/app/.cargo/audit.toml new file mode 100644 index 00000000..268d3716 --- /dev/null +++ b/packages/kbot/gui/app/.cargo/audit.toml @@ -0,0 +1,11 @@ +[advisories] +ignore = [ + # time 0.1 + "RUSTSEC-2020-0071", + # needs sqlx 0.7 (still in alpha) + "RUSTSEC-2022-0090", + # wry needs kuchiki on Android + "RUSTSEC-2023-0019", + # atty is only used when the `colored` feature is enabled on tauri-plugin-log + "RUSTSEC-2021-0145", +] diff --git a/packages/kbot/gui/app/.changes/config.json b/packages/kbot/gui/app/.changes/config.json new file mode 100644 index 00000000..93ed46e3 --- /dev/null +++ b/packages/kbot/gui/app/.changes/config.json @@ -0,0 +1,355 @@ +{ + "gitSiteUrl": "https://github.com/tauri-apps/plugins-workspace/", + "pkgManagers": { + "javascript": { + "version": true, + "getPublishedVersion": { + "use": "fetch:check", + "options": { + "url": "https://registry.npmjs.com/${ pkg.pkgFile.pkg.name }/${ pkg.pkgFile.version }" + } + }, + "publish": [ + { + "command": "pnpm build", + "dryRunCommand": "pnpm build" + }, + { + "command": "echo '
\n

PNPM Publish

\n\n```'", + "dryRunCommand": true, + "pipe": true + }, + { + "command": "npm publish --provenance --access public", + "dryRunCommand": "npm publish --provenance --access public --dry-run", + "pipe": true + }, + { + "command": "echo '```\n\n
\n'", + "dryRunCommand": true, + "pipe": true + } + ] + }, + "rust": { + "version": true, + "getPublishedVersion": { + "use": "fetch:check", + "options": { + "url": "https://crates.io/api/v1/crates/${ pkg.pkgFile.pkg.package.name }/${ pkg.pkgFile.version }" + } + }, + "publish": [ + { + "command": "echo '
\n

Cargo Publish

\n\n```'", + "dryRunCommand": true, + "pipe": true + }, + { + "command": "cargo publish --no-verify", + "dryRunCommand": "cargo publish --dry-run", + "pipe": true + }, + { + "command": "echo '```\n\n
\n'", + "dryRunCommand": true, + "pipe": true + } + ] + } + }, + "packages": { + "api-example": { + "path": "./examples/api/src-tauri", + "manager": "rust", + "publish": false, + "dependencies": [ + "barcode-scanner", + "biometric", + "log", + "cli", + "clipboard-manager", + "dialog", + "fs", + "global-shortcut", + "opener", + "http", + "nfc", + "notification", + "os", + "process", + "shell", + "store", + "updater", + "geolocation", + "haptics" + ] + }, + "api-example-js": { + "path": "./examples/api", + "manager": "javascript", + "publish": false, + "dependencies": [ + "barcode-scanner-js", + "biometric-js", + "log-js", + "cli-js", + "clipboard-manager-js", + "dialog-js", + "fs-js", + "global-shortcut-js", + "opener-js", + "http-js", + "nfc-js", + "notification-js", + "os-js", + "process-js", + "shell-js", + "store-js", + "updater-js" + ], + "postversion": "pnpm install --no-frozen-lockfile" + }, + "deep-link-example-js": { + "path": "./plugins/deep-link/examples/app", + "manager": "javascript", + "publish": false, + "dependencies": ["deep-link-js"], + "postversion": "pnpm install --no-frozen-lockfile" + }, + "autostart": { + "path": "./plugins/autostart", + "manager": "rust" + }, + "autostart-js": { + "path": "./plugins/autostart", + "manager": "javascript" + }, + "barcode-scanner": { + "path": "./plugins/barcode-scanner", + "manager": "rust" + }, + "barcode-scanner-js": { + "path": "./plugins/barcode-scanner", + "manager": "javascript" + }, + "biometric": { + "path": "./plugins/biometric", + "manager": "rust" + }, + "biometric-js": { + "path": "./plugins/biometric", + "manager": "javascript" + }, + "cli": { + "path": "./plugins/cli", + "manager": "rust" + }, + "cli-js": { + "path": "./plugins/cli", + "manager": "javascript" + }, + "clipboard-manager": { + "path": "./plugins/clipboard-manager", + "manager": "rust" + }, + "clipboard-manager-js": { + "path": "./plugins/clipboard-manager", + "manager": "javascript" + }, + "deep-link": { + "path": "./plugins/deep-link", + "manager": "rust" + }, + "deep-link-js": { + "path": "./plugins/deep-link", + "manager": "javascript" + }, + "fs": { + "path": "./plugins/fs", + "manager": "rust" + }, + "fs-js": { + "path": "./plugins/fs", + "manager": "javascript" + }, + "dialog": { + "path": "./plugins/dialog", + "manager": "rust", + "dependencies": ["fs"] + }, + "dialog-js": { + "path": "./plugins/dialog", + "manager": "javascript", + "dependencies": ["fs-js"] + }, + "geolocation": { + "path": "./plugins/geolocation", + "manager": "rust" + }, + "geolocation-js": { + "path": "./plugins/geolocation", + "manager": "javascript" + }, + "global-shortcut": { + "path": "./plugins/global-shortcut", + "manager": "rust" + }, + "global-shortcut-js": { + "path": "./plugins/global-shortcut", + "manager": "javascript" + }, + "opener": { + "path": "./plugins/opener", + "manager": "rust" + }, + "opener-js": { + "path": "./plugins/opener", + "manager": "javascript" + }, + "haptics": { + "path": "./plugins/haptics", + "manager": "rust" + }, + "haptics-js": { + "path": "./plugins/haptics", + "manager": "javascript" + }, + "http": { + "path": "./plugins/http", + "manager": "rust", + "dependencies": ["fs"] + }, + "http-js": { + "path": "./plugins/http", + "manager": "javascript", + "dependencies": ["fs-js"] + }, + "localhost": { + "path": "./plugins/localhost", + "manager": "rust" + }, + "log": { + "path": "./plugins/log", + "manager": "rust" + }, + "log-js": { + "path": "./plugins/log", + "manager": "javascript" + }, + "nfc": { + "path": "./plugins/nfc", + "manager": "rust" + }, + "nfc-js": { + "path": "./plugins/nfc", + "manager": "javascript" + }, + "notification": { + "path": "./plugins/notification", + "manager": "rust" + }, + "notification-js": { + "path": "./plugins/notification", + "manager": "javascript" + }, + "os": { + "path": "./plugins/os", + "manager": "rust" + }, + "os-js": { + "path": "./plugins/os", + "manager": "javascript" + }, + "persisted-scope": { + "path": "./plugins/persisted-scope", + "manager": "rust", + "dependencies": ["fs"] + }, + "positioner": { + "path": "./plugins/positioner", + "manager": "rust" + }, + "positioner-js": { + "path": "./plugins/positioner", + "manager": "javascript" + }, + "process": { + "path": "./plugins/process", + "manager": "rust" + }, + "process-js": { + "path": "./plugins/process", + "manager": "javascript" + }, + "shell": { + "path": "./plugins/shell", + "manager": "rust" + }, + "shell-js": { + "path": "./plugins/shell", + "manager": "javascript" + }, + "single-instance": { + "path": "./plugins/single-instance", + "manager": "rust", + "dependencies": ["deep-link"] + }, + "sql": { + "path": "./plugins/sql", + "manager": "rust" + }, + "sql-js": { + "path": "./plugins/sql", + "manager": "javascript" + }, + "store": { + "path": "./plugins/store", + "manager": "rust" + }, + "store-js": { + "path": "./plugins/store", + "manager": "javascript" + }, + "stronghold": { + "path": "./plugins/stronghold", + "manager": "rust" + }, + "stronghold-js": { + "path": "./plugins/stronghold", + "manager": "javascript" + }, + "updater": { + "path": "./plugins/updater", + "manager": "rust" + }, + "updater-js": { + "path": "./plugins/updater", + "manager": "javascript" + }, + "upload": { + "path": "./plugins/upload", + "manager": "rust" + }, + "upload-js": { + "path": "./plugins/upload", + "manager": "javascript" + }, + "websocket": { + "path": "./plugins/websocket", + "manager": "rust" + }, + "websocket-js": { + "path": "./plugins/websocket", + "manager": "javascript" + }, + "window-state": { + "path": "./plugins/window-state", + "manager": "rust" + }, + "window-state-js": { + "path": "./plugins/window-state", + "manager": "javascript" + } + } +} diff --git a/packages/kbot/gui/app/.changes/readme.md b/packages/kbot/gui/app/.changes/readme.md new file mode 100644 index 00000000..6d9396ba --- /dev/null +++ b/packages/kbot/gui/app/.changes/readme.md @@ -0,0 +1,32 @@ +# Changes + +##### via https://github.com/jbolda/covector + +As you create PRs and make changes that require a version bump, please add a new markdown file in this folder. You do not note the version _number_, but rather the type of bump that you expect: major, minor, or patch. The filename is not important, as long as it is a `.md`, but we recommend that it represents the overall change for organizational purposes. + +When you select the version bump required, you do _not_ need to consider dependencies. Only note the package with the actual change, and any packages that depend on that package will be bumped automatically in the process. + +**Note, that in this repository, even if only the Rust code or only the JavaScript code of a plugin changed, both packages need to be bumped with the same increment!** + +Use the following format: + +```md +--- +"package-a": patch +"package-b": minor:feat +--- + +Change summary goes here +``` + +Summaries do not have a specific character limit, but are text only. These summaries are used within the (future implementation of) changelogs. They will give context to the change and also point back to the original PR if more details and context are needed. + +Changes will be designated as a `major`, `minor` or `patch` as further described in [semver](https://semver.org/). + +Given a version number MAJOR.MINOR.PATCH, increment the: + +- MAJOR version when you make incompatible API changes, +- MINOR version when you add functionality in a backwards compatible manner, and +- PATCH version when you make backwards compatible bug fixes. + +Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format, but will be discussed prior to usage (as extra steps will be necessary in consideration of merging and publishing). diff --git a/packages/kbot/gui/app/.changes/updater-new-bundle-support.md b/packages/kbot/gui/app/.changes/updater-new-bundle-support.md new file mode 100644 index 00000000..99cf2e88 --- /dev/null +++ b/packages/kbot/gui/app/.changes/updater-new-bundle-support.md @@ -0,0 +1,6 @@ +--- +"updater": minor +"updater-js": minor +--- + +Updater plugin now supports all bundle types: Deb, Rpm and AppImage for Linux; NSiS, MSI for Windows. diff --git a/packages/kbot/gui/app/.github/CODEOWNERS b/packages/kbot/gui/app/.github/CODEOWNERS new file mode 100644 index 00000000..83ba0c42 --- /dev/null +++ b/packages/kbot/gui/app/.github/CODEOWNERS @@ -0,0 +1,5 @@ +# Order is important; the last matching pattern takes the most precedence. +* @tauri-apps/plugin-maintainers + +# Currently CI/CD for plugins are in heavy flux, and the plugin team manages it themselves. +# .github @tauri-apps/wg-devops diff --git a/packages/kbot/gui/app/.github/CONTRIBUTING.md b/packages/kbot/gui/app/.github/CONTRIBUTING.md new file mode 100644 index 00000000..8bd5da0a --- /dev/null +++ b/packages/kbot/gui/app/.github/CONTRIBUTING.md @@ -0,0 +1,62 @@ +# Tauri Contributing Guide + +Hi! We, the maintainers, are really excited that you are interested in contributing to Tauri. Before submitting your contribution though, please make sure to take a moment and read through the [Code of Conduct](CODE_OF_CONDUCT.md), as well as the appropriate section for the contribution you intend to make: + +- [Issue Reporting Guidelines](#issue-reporting-guidelines) +- [Pull Request Guidelines](#pull-request-guidelines) +- [Development Guide](#development-guide) + +## Issue Reporting Guidelines + +- The issue list of this repo is **exclusively** for bug reports and feature requests. Non-conforming issues will be closed immediately. + +- If you have a question, you can get quick answers from the [Tauri Discord chat](https://discord.com/invite/tauri). + +- Try to search for your issue, it may have already been answered or even fixed in the development branch (`v2`). + +- Check if the issue is reproducible with the latest stable version of Tauri. If you are using a pre-release, please indicate the specific version you are using. + +- It is **required** that you clearly describe the steps necessary to reproduce the issue you are running into. Although we would love to help our users as much as possible, diagnosing issues without clear reproduction steps is extremely time-consuming and simply not sustainable. + +- Use only the minimum amount of code necessary to reproduce the unexpected behavior. A good bug report should isolate specific methods that exhibit unexpected behavior and precisely define how expectations were violated. What did you expect the method or methods to do, and how did the observed behavior differ? The more precisely you isolate the issue, the faster we can investigate. + +- Issues with no clear repro steps will not be triaged. If an issue labeled "need repro" receives no further input from the issue author for more than 5 days, it will be closed. + +- If your issue is resolved but still open, don't hesitate to close it. In case you found a solution by yourself, it could be helpful for others to explain how you fixed it. + +- Most importantly, we beg your patience: the team must balance your request against many other responsibilities — fixing other bugs, answering other questions, new features, new documentation, etc. The issue list is not paid support and we cannot make guarantees about how fast your issue can be resolved. + +## Pull Request Guidelines + +- You have to [sign your commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits). + +- It's OK to have multiple small commits as you work on the PR - we will let GitHub automatically squash it before merging. + +- If adding new feature: + - Provide convincing reason to add this feature. Ideally you should open a suggestion issue first and have it greenlighted before working on it. + +- If fixing a bug: + - If you are resolving a special issue, add `(fix: #xxxx[,#xxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `fix: update entities encoding/decoding (fix #3899)`. + - Provide detailed description of the bug in the PR, or link to an issue that does. + +- If the PR is meant to be released, follow the instructions in `.changes/readme.md` to log your changes. ie. [readme.md](https://github.com/tauri-apps/plugins-workspace/blob/v2/.changes/readme.md) + +## Development Guide + +**NOTE: If you have any question don't hesitate to ask in our Discord server. We try to keep this guide to up guide, but if something doesn't work let us know.** + +### General Setup + +First, [join our Discord server](https://discord.com/invite/tauri) and let us know that you want to contribute. This way we can point you in the right direction and help ensure your contribution will be as helpful as possible. + +To set up your machine for development, follow the [Tauri setup guide](https://v2.tauri.app/start/prerequisites/) to get all the tools you need to develop Tauri apps. The only additional tool you may need is [PNPM](https://pnpm.io/). + +Next, [fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) and clone [this repository](https://github.com/tauri-apps/plugins-workspace/tree/v2). + +### Developing and testing + +The easiest way to test your changes is to use the [example app](https://github.com/tauri-apps/plugins-workspace/tree/v2/examples/api) example app. It automatically rebuilds and uses your local copy of the plugins. To run it follow the instructions inside [README.md](https://github.com/tauri-apps/plugins-workspace/blob/v2/examples/api/README.md). + +To test local changes against your own application simply point the plugin create to your local repository, for example: + +`tauri-plugin-sample = { path = "path/to/local/tauri-plugin-sample/" }` diff --git a/packages/kbot/gui/app/.github/sponsors/crabnebula.svg b/packages/kbot/gui/app/.github/sponsors/crabnebula.svg new file mode 100644 index 00000000..40e24131 --- /dev/null +++ b/packages/kbot/gui/app/.github/sponsors/crabnebula.svg @@ -0,0 +1,31 @@ + \ No newline at end of file diff --git a/packages/kbot/gui/app/.github/sponsors/rescue.png b/packages/kbot/gui/app/.github/sponsors/rescue.png new file mode 100644 index 00000000..2b5916f4 Binary files /dev/null and b/packages/kbot/gui/app/.github/sponsors/rescue.png differ diff --git a/packages/kbot/gui/app/.github/sync-to-mirrors.sh b/packages/kbot/gui/app/.github/sync-to-mirrors.sh new file mode 100644 index 00000000..4baf55d2 --- /dev/null +++ b/packages/kbot/gui/app/.github/sync-to-mirrors.sh @@ -0,0 +1,136 @@ +#!/bin/bash +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +## Environment used by this script: +# +# Current directory must be within the repo being mirrored from, so the commit message can be read. +# +# Required: +# - BUILD_BASE: Path to the build directory, which contains "mirrors.txt" and directories for each repo to mirror to. +# - GITHUB_ACTOR: GitHub username for the commit being mirrored. +# - GITHUB_REF: Git ref being mirrored from, e.g. "refs/heads/main". Must begin with "refs/heads/". +# +# Other: +# - API_TOKEN_GITHUB: Personal access token to use when accessing GitHub. +# - CI: If unset or empty, the commits will be prepared but the actual push will not happen. +# - COMMIT_MESSAGE: Commit message to use for the mirror commits. Will be read from HEAD in `SOURCE_DIR` if not specified. +# - GITHUB_REPOSITORY: GH repository, used in the commit message if `COMMIT_MESSAGE` is not specified. +# - GITHUB_RUN_ID: GH Actions run ID, used in the commit message if `COMMIT_MESSAGE` is not specified. +# - GITHUB_SHA: Head SHA1 from which to fetch the commit message for the commit being mirrored. HEAD will be assumed if not specified. +# - SOURCE_DIR: Source directory, used when `COMMIT_MESSAGE` is not specified. + +# Halt on error +set -eo pipefail + +if [[ -n "$CI" ]]; then + export GIT_AUTHOR_NAME="tauri-bot" + export GIT_AUTHOR_EMAIL="tauri-bot@users.noreply.github.com" + export GIT_COMMITTER_NAME="tauri-bot" + export GIT_COMMITTER_EMAIL="tauri-bot@users.noreply.github.com" +fi + +if [[ -z "$BUILD_BASE" ]]; then + echo "::error::BUILD_BASE must be set" + exit 1 +elif [[ ! -d "$BUILD_BASE" ]]; then + echo "::error::$BUILD_BASE does not exist or is not a directory" + exit 1 +fi + +if [[ -z "$COMMIT_MESSAGE" ]]; then + MONOREPO_COMMIT_MESSAGE=$(cd "${SOURCE_DIR:-.}" && git show -s --format=%B $GITHUB_SHA) + COMMIT_MESSAGE=$( printf "%s\n\nCommitted via a GitHub action: https://github.com/%s/actions/runs/%s" "$MONOREPO_COMMIT_MESSAGE" "$GITHUB_REPOSITORY" "$GITHUB_RUN_ID" ) +fi +COMMIT_ACTOR="${GITHUB_ACTOR} <${GITHUB_ACTOR}@users.noreply.github.com>" +COMMIT_AUTHOR=$(cd "${SOURCE_DIR:-.}" &&git show -s --format="%an <%ae>" $GITHUB_SHA) + +if [[ "$GITHUB_REF" =~ ^refs/heads/ ]]; then + BRANCH=${GITHUB_REF#refs/heads/} +else + echo "::error::Could not determine branch name from $GITHUB_REF" + exit 1 +fi + +if [[ ! -f "$BUILD_BASE/mirrors.txt" ]]; then + echo "::error::File $BUILD_BASE/mirrors.txt does not exist or is not a file" + exit 1 +elif [[ ! -s "$BUILD_BASE/mirrors.txt" ]]; then + echo "Nothing to do, $BUILD_BASE/mirrors.txt is empty." + exit 0 +fi + +# : > "$BUILD_BASE/changes.diff" + +# Collect tags of current commit +readarray -t COMMIT_TAGS < <(git tag --points-at HEAD) + +EXIT=0 +while read -r PLUGIN_NAME; do + printf "\n\n\e[7m Mirror: %s \e[0m\n" "$PLUGIN_NAME" + CLONE_DIR="${BUILD_BASE}/${PLUGIN_NAME}" + cd "${CLONE_DIR}" + + # Initialize the directory as a git repo, and set the remote + git init -b "$BRANCH" . + git remote add origin "https://github.com/tauri-apps/tauri-plugin-${PLUGIN_NAME}" + if [[ -n "$API_TOKEN_GITHUB" ]]; then + git config --local http.https://github.com/.extraheader "AUTHORIZATION: basic $(printf "x-access-token:%s" "$API_TOKEN_GITHUB" | base64)" + fi + + # Check if a remote exists for that mirror. + if ! git ls-remote -h origin >/dev/null 2>&1; then + echo "::error::Mirror repo for ${PLUGIN_NAME} does not exist." + echo "Skipping." + EXIT=1 + continue + fi + + echo "::group::Fetching ${PLUGIN_NAME}" + FORCE_COMMIT= + if git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin "$BRANCH"; then + git reset --soft FETCH_HEAD + echo "Fetched revision $(git rev-parse HEAD)" + elif [[ -n "$DEFAULT_BRANCH" ]] && git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin "$DEFAULT_BRANCH"; then + FORCE_COMMIT=--allow-empty + git reset --soft FETCH_HEAD + echo "Fetched revision $(git rev-parse HEAD)" + else + echo "Failed to find a branch to branch from, just creating an empty one." + FORCE_COMMIT=--allow-empty + fi + git add -A + echo "::endgroup::" + + if [[ -n "$FORCE_COMMIT" || -n "$(git status --porcelain)" ]]; then + echo "Committing to $PLUGIN_NAME" + GIT_CLI_COMMIT_MESSAGE=$( printf "%s \n\nCo-authored-by: %s" "$COMMIT_MESSAGE" "$COMMIT_ACTOR" ) + if git commit $FORCE_COMMIT --author="${COMMIT_AUTHOR}" -m "${GIT_CLI_COMMIT_MESSAGE}" && + { [[ -z "$CI" ]] || git push origin "$BRANCH"; } # Only do the actual push from the GitHub Action + then + # echo "$BUILD_BASE/changes.diff" + # git show --pretty= --src-prefix="a/$PLUGIN_NAME/" --dst-prefix="b/$PLUGIN_NAME/" >> "$BUILD_BASE/changes.diff" + echo "https://github.com/tauri-apps/tauri-plugin-$PLUGIN_NAME/commit/$(git rev-parse HEAD)" + + # Add new tags + for FULL_TAG in "${COMMIT_TAGS[@]}"; do + if [[ "$FULL_TAG" =~ ^"$PLUGIN_NAME-js-v" ]]; then + TAG_NAME="${FULL_TAG#"$PLUGIN_NAME-js-"}" + echo "Creating tag $TAG_NAME" + git tag "${TAG_NAME}" -m "${GIT_CLI_COMMIT_MESSAGE}" + git push origin "${TAG_NAME}" + fi + done + + echo "Completed $PLUGIN_NAME" + else + echo "::error::Commit of ${PLUGIN_NAME} failed" + EXIT=1 + fi + else + echo "No changes, skipping $PLUGIN_NAME" + fi +done < "$BUILD_BASE/mirrors.txt" + +exit $EXIT diff --git a/packages/kbot/gui/app/.github/workflows/audit-javascript.yml b/packages/kbot/gui/app/.github/workflows/audit-javascript.yml new file mode 100644 index 00000000..f1a3ec3f --- /dev/null +++ b/packages/kbot/gui/app/.github/workflows/audit-javascript.yml @@ -0,0 +1,52 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: Audit JavaScript + +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' + push: + branches: + - v1 + - v2 + paths: + - '.github/workflows/audit-javascript.yml' + - '**/pnpm-lock.yaml' + - '**/package.json' + pull_request: + branches: + - v1 + - v2 + paths: + - '.github/workflows/audit-javascript.yml' + - '**/pnpm-lock.yaml' + - '**/package.json' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + audit-js: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Cache pnpm modules + uses: actions/cache@v4 + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}- + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + - uses: pnpm/action-setup@v4 + with: + version: 10.x.x + run_install: true + - name: audit + run: pnpm audit diff --git a/packages/kbot/gui/app/.github/workflows/audit-rust.yml b/packages/kbot/gui/app/.github/workflows/audit-rust.yml new file mode 100644 index 00000000..e0c72a89 --- /dev/null +++ b/packages/kbot/gui/app/.github/workflows/audit-rust.yml @@ -0,0 +1,41 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: Audit Rust + +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' + push: + branches: + - v1 + - v2 + paths: + - '.github/workflows/audit-rust.yml' + - '**/Cargo.lock' + - '**/Cargo.toml' + pull_request: + branches: + - v1 + - v2 + paths: + - '.github/workflows/audit-rust.yml' + - '**/Cargo.lock' + - '**/Cargo.toml' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + audit-rust: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: rustsec/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + # https://github.com/tauri-apps/plugins-workspace/issues/774 + ignore: ${{ github.event_name != 'schedule' && 'RUSTSEC-2023-0071' || '' }} diff --git a/packages/kbot/gui/app/.github/workflows/check-change-files.yml b/packages/kbot/gui/app/.github/workflows/check-change-files.yml new file mode 100644 index 00000000..898f265f --- /dev/null +++ b/packages/kbot/gui/app/.github/workflows/check-change-files.yml @@ -0,0 +1,44 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: check change files + +on: + pull_request: + paths: + - '.changes/*.md' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: check change files end with .md + run: | + for file in .changes/* + do + if [[ ! "$file" =~ \.(md|json)$ ]]; then + echo ".changes directory should only contain files that end with .md" + echo "found an invalid file in .changes directory:" + echo "$file" + exit 1 + fi + done + + - uses: dorny/paths-filter@v3 + id: filter + with: + list-files: shell + filters: | + changes: + - added|modified: '.changes/*.md' + + - name: check + run: node ./.scripts/ci/check-change-files.js ${{ steps.filter.outputs.changes_files }} + if: ${{ steps.filter.outputs.changes == 'true' }} diff --git a/packages/kbot/gui/app/.github/workflows/check-generated-files.yml b/packages/kbot/gui/app/.github/workflows/check-generated-files.yml new file mode 100644 index 00000000..6a513b23 --- /dev/null +++ b/packages/kbot/gui/app/.github/workflows/check-generated-files.yml @@ -0,0 +1,179 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: check generated files + +on: + pull_request: + paths: + - '.github/workflows/check-generated-files.yml' + - pnpm-lock.yaml + - '**/guest-js/**' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + changes: + runs-on: ubuntu-latest + outputs: + packages: ${{ steps.filter.outputs.changes }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + autostart: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/autostart/guest-js/** + - plugins/autostart/src/api-iife.js + cli: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/cli/guest-js/** + - plugins/cli/src/api-iife.js + clipboard-manager: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/clipboard-manager/guest-js/** + - plugins/clipboard-manager/src/api-iife.js + dialog: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/dialog/guest-js/** + - plugins/dialog/src/api-iife.js + fs: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/fs/guest-js/** + - plugins/fs/src/api-iife.js + geolocation: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/geolocation/guest-js/** + - plugins/geolocation/src/api-iife.js + global-shortcut: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/global-shortcut/guest-js/** + - plugins/global-shortcut/src/api-iife.js + opener: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/opener/guest-js/** + - plugins/opener/src/api-iife.js + haptics: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/haptics/guest-js/** + - plugins/haptics/src/api-iife.js + http: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/http/guest-js/** + - plugins/http/src/api-iife.js + log: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/log/guest-js/** + - plugins/log/src/api-iife.js + notification: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/notification/guest-js/** + - plugins/notification/src/api-iife.js + os: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/os/guest-js/** + - plugins/os/src/api-iife.js + positioner: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/positioner/guest-js/** + - plugins/positioner/src/api-iife.js + process: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/process/guest-js/** + - plugins/process/src/api-iife.js + shell: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/shell/guest-js/** + - plugins/shell/src/api-iife.js + sql: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/sql/guest-js/** + - plugins/sql/src/api-iife.js + store: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/store/guest-js/** + - plugins/store/src/api-iife.js + stronghold: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/stronghold/guest-js/** + - plugins/stronghold/src/api-iife.js + updater: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/updater/guest-js/** + - plugins/updater/src/api-iife.js + upload: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/upload/guest-js/** + - plugins/upload/src/api-iife.js + websocket: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/websocket/guest-js/** + - plugins/websocket/src/api-iife.js + window-state: + - .github/workflows/check-generated-files.yml + - pnpm-lock.yaml + - plugins/window-state/guest-js/** + - plugins/window-state/src/api-iife.js + + test: + needs: changes + if: ${{ needs.changes.outputs.packages != '[]' && needs.changes.outputs.packages != '' }} + strategy: + fail-fast: false + matrix: + package: ${{ fromJSON(needs.changes.outputs.packages) }} + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Cache pnpm modules + uses: actions/cache@v4 + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}- + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + - uses: pnpm/action-setup@v4 + with: + version: 10.x.x + run_install: true + + - name: build api + working-directory: plugins/${{ matrix.package }} + run: pnpm install && pnpm build + + - name: check diff + run: | + ./.scripts/ci/has-diff.sh diff --git a/packages/kbot/gui/app/.github/workflows/check-license-header.yml b/packages/kbot/gui/app/.github/workflows/check-license-header.yml new file mode 100644 index 00000000..338a0041 --- /dev/null +++ b/packages/kbot/gui/app/.github/workflows/check-license-header.yml @@ -0,0 +1,28 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: check license header + +on: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v2 + id: filter + with: + list-files: shell + filters: | + added: + - added: '**' + - name: check header license on new files + if: ${{ steps.filter.outputs.added == 'true' }} + run: node .scripts/ci/check-license-header.js ${{ steps.filter.outputs.added_files }} diff --git a/packages/kbot/gui/app/.github/workflows/covector-comment-on-fork.yml b/packages/kbot/gui/app/.github/workflows/covector-comment-on-fork.yml new file mode 100644 index 00000000..494a5348 --- /dev/null +++ b/packages/kbot/gui/app/.github/workflows/covector-comment-on-fork.yml @@ -0,0 +1,30 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: covector comment +on: + workflow_run: + workflows: [covector status] # the `name` of the workflow run on `pull_request` running `status` with `comment: true` + types: + - completed + +# note all other permissions are set to none if not specified +# and these set the permissions for `secrets.GITHUB_TOKEN` +permissions: + # to read the action artifacts on `covector status` workflows + actions: read + # to write the comment + pull-requests: write + +jobs: + download: + runs-on: ubuntu-latest + if: github.event.workflow_run.conclusion == 'success' && + (github.event.workflow_run.head_repository.full_name != github.repository || github.actor == 'dependabot[bot]') + steps: + - name: covector status + uses: jbolda/covector/packages/action@covector-v0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + command: 'status' diff --git a/packages/kbot/gui/app/.github/workflows/covector-status.yml b/packages/kbot/gui/app/.github/workflows/covector-status.yml new file mode 100644 index 00000000..7eeda427 --- /dev/null +++ b/packages/kbot/gui/app/.github/workflows/covector-status.yml @@ -0,0 +1,22 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: covector status +on: [pull_request] + +jobs: + covector: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # required for use of git history + - name: covector status + uses: jbolda/covector/packages/action@covector-v0 + id: covector + with: + command: 'status' + token: ${{ secrets.GITHUB_TOKEN }} + comment: true diff --git a/packages/kbot/gui/app/.github/workflows/covector-version-or-publish.yml b/packages/kbot/gui/app/.github/workflows/covector-version-or-publish.yml new file mode 100644 index 00000000..f19605cf --- /dev/null +++ b/packages/kbot/gui/app/.github/workflows/covector-version-or-publish.yml @@ -0,0 +1,86 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: version or publish + +on: + push: + branches: + - v1 + - v2 + +permissions: + # required for npm provenance + id-token: write + # required to create the GitHub Release + contents: write + # required for creating the Version Packages Release + pull-requests: write + +jobs: + version-or-publish: + runs-on: ubuntu-latest + timeout-minutes: 65 + outputs: + change: ${{ steps.covector.outputs.change }} + commandRan: ${{ steps.covector.outputs.commandRan }} + successfulPublish: ${{ steps.covector.outputs.successfulPublish }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # required for use of git history + + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + registry-url: 'https://registry.npmjs.org' + + - uses: pnpm/action-setup@v4 + with: + version: 10.x.x + run_install: true + + - name: cargo login + run: cargo login ${{ secrets.ORG_CRATES_IO_TOKEN }} + + - name: git config + run: | + git config --global user.name "${{ github.event.pusher.name }}" + git config --global user.email "${{ github.event.pusher.email }}" + + - name: Setup target dir on /mnt + # This directory has a larger partition size + run: | + sudo mkdir /mnt/target + WORKSPACE_OWNER="$(stat -c '%U:%G' "${GITHUB_WORKSPACE}")" + sudo chown -R "${WORKSPACE_OWNER}" /mnt/target + + - name: covector version or publish (publish when no change files present) + uses: jbolda/covector/packages/action@covector-v0 + id: covector + env: + CARGO_TARGET_DIR: /mnt/target + NODE_AUTH_TOKEN: ${{ secrets.ORG_NPM_TOKEN }} + with: + token: ${{ secrets.GITHUB_TOKEN }} + command: 'version-or-publish' + createRelease: true + recognizeContributors: true + + - name: Sync Cargo.lock + if: steps.covector.outputs.commandRan == 'version' + run: cargo tree --depth 0 + + - name: Create Pull Request With Versions Bumped + id: cpr + uses: peter-evans/create-pull-request@dd2324fc52d5d43c699a5636bcf19fceaa70c284 # 7.0.7 + if: steps.covector.outputs.commandRan == 'version' + with: + title: 'Publish New Versions (${{ github.ref_name }})' + commit-message: 'publish new versions' + labels: 'version updates' + branch: 'ci/release-${{ github.ref_name }}' + body: ${{ steps.covector.outputs.change }} + sign-commits: true diff --git a/packages/kbot/gui/app/.github/workflows/fmt.yml b/packages/kbot/gui/app/.github/workflows/fmt.yml new file mode 100644 index 00000000..087b7674 --- /dev/null +++ b/packages/kbot/gui/app/.github/workflows/fmt.yml @@ -0,0 +1,59 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: check formatting + +on: + pull_request: + +jobs: + rustfmt: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: install Rust stable and rustfmt + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + + - name: run cargo fmt + run: cargo fmt --all -- --check + + prettier: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Cache pnpm modules + uses: actions/cache@v4 + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}- + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + - uses: pnpm/action-setup@v4 + with: + version: 10.x.x + run_install: true + - run: pnpm format:check + + taplo: + name: taplo (.toml files) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: install Rust stable + uses: dtolnay/rust-toolchain@stable + + - name: install taplo-cli + uses: taiki-e/install-action@v2 + with: + tool: taplo-cli + + - run: taplo fmt --check --diff diff --git a/packages/kbot/gui/app/.github/workflows/integration-tests.yml b/packages/kbot/gui/app/.github/workflows/integration-tests.yml new file mode 100644 index 00000000..fbbca96a --- /dev/null +++ b/packages/kbot/gui/app/.github/workflows/integration-tests.yml @@ -0,0 +1,52 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: integration tests + +on: + push: + branches: + - v1 + - v2 + paths: + - '.github/workflows/integration-tests.yml' + - 'plugins/updater/src/**' + pull_request: + branches: + - v1 + - v2 + paths: + - '.github/workflows/integration-tests.yml' + - 'plugins/updater/src/**' + +jobs: + run-integration-tests: + runs-on: ${{ matrix.platform }} + + strategy: + fail-fast: false + matrix: + platform: [ubuntu-22.04, macos-latest, windows-latest] + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: install stable + uses: dtolnay/rust-toolchain@stable + + - name: install Linux dependencies + if: matrix.platform == 'ubuntu-22.04' + run: | + sudo apt-get update + sudo apt-get install -y webkit2gtk-4.0 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev libfuse2 + + - uses: Swatinem/rust-cache@v2 + + - name: install Tauri CLI + run: cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch dev + + - name: run integration tests + run: cargo test --test '*' -- --ignored diff --git a/packages/kbot/gui/app/.github/workflows/lint-javascript.yml b/packages/kbot/gui/app/.github/workflows/lint-javascript.yml new file mode 100644 index 00000000..4c9db35e --- /dev/null +++ b/packages/kbot/gui/app/.github/workflows/lint-javascript.yml @@ -0,0 +1,55 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: Lint JavaScript + +on: + push: + branches: + - v1 + - v2 + paths: + - '.github/workflows/lint-javascript.yml' + - 'plugins/*/guest-js/**' + - '.eslintignore' + - '.eslintrc.json' + - '.prettierignore' + - '**/package.json' + pull_request: + branches: + - v1 + - v2 + paths: + - '.github/workflows/lint-javascript.yml' + - 'plugins/*/guest-js/**' + - '.eslintignore' + - '.eslintrc.json' + - '.prettierignore' + - '**/package.json' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + eslint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Cache pnpm modules + uses: actions/cache@v4 + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}- + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + - uses: pnpm/action-setup@v4 + with: + version: 10.x.x + run_install: true + - name: eslint + run: pnpm lint diff --git a/packages/kbot/gui/app/.github/workflows/lint-rust.yml b/packages/kbot/gui/app/.github/workflows/lint-rust.yml new file mode 100644 index 00000000..db922ef1 --- /dev/null +++ b/packages/kbot/gui/app/.github/workflows/lint-rust.yml @@ -0,0 +1,157 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: Lint Rust + +on: + push: + branches: + - v1 + - v2 + paths: + - '.github/workflows/lint-rust.yml' + - 'plugins/*/src/**' + - '!plugins/*/src/api-iife.js' + - '**/Cargo.toml' + pull_request: + branches: + - v1 + - v2 + paths: + - '.github/workflows/lint-rust.yml' + - 'plugins/*/src/**' + - '!plugins/*/src/api-iife.js' + - '**/Cargo.toml' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + changes: + runs-on: ubuntu-latest + permissions: + pull-requests: read + outputs: + packages: ${{ steps.filter.outputs.changes }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + tauri-plugin-autostart: + - .github/workflows/lint-rust.yml + - plugins/autostart/** + tauri-plugin-cli: + - .github/workflows/lint-rust.yml + - plugins/cli/** + tauri-plugin-clipboard-manager: + - .github/workflows/lint-rust.yml + - plugins/clipboard-manager/** + tauri-plugin-deep-link: + - .github/workflows/lint-rust.yml + - plugins/deep-link/** + tauri-plugin-dialog: + - .github/workflows/lint-rust.yml + - plugins/dialog/** + - plugins/fs/** + tauri-plugin-fs: + - .github/workflows/lint-rust.yml + - plugins/fs/** + tauri-plugin-geolocation: + - .github/workflows/lint-rust.yml + - plugins/geolocation/** + tauri-plugin-global-shortcut: + - .github/workflows/lint-rust.yml + - plugins/global-shortcut/** + tauri-plugin-opener: + - .github/workflows/lint-rust.yml + - plugins/opener/** + tauri-plugin-haptics: + - .github/workflows/lint-rust.yml + - plugins/haptics/** + tauri-plugin-http: + - .github/workflows/lint-rust.yml + - plugins/http/** + - plugins/fs/** + tauri-plugin-localhost: + - .github/workflows/lint-rust.yml + - plugins/localhost/** + tauri-plugin-log: + - .github/workflows/lint-rust.yml + - plugins/log/** + tauri-plugin-notification: + - .github/workflows/lint-rust.yml + - plugins/notification/** + tauri-plugin-os: + - .github/workflows/lint-rust.yml + - plugins/os/** + tauri-plugin-persisted-scope: + - .github/workflows/lint-rust.yml + - plugins/persisted-scope/** + - plugins/fs/** + tauri-plugin-positioner: + - .github/workflows/lint-rust.yml + - plugins/positioner/** + tauri-plugin-process: + - .github/workflows/lint-rust.yml + - plugins/process/** + tauri-plugin-shell: + - .github/workflows/lint-rust.yml + - plugins/shell/** + tauri-plugin-single-instance: + - .github/workflows/lint-rust.yml + - plugins/single-instance/** + tauri-plugin-sql: + - .github/workflows/lint-rust.yml + - plugins/sql/** + tauri-plugin-store: + - .github/workflows/lint-rust.yml + - plugins/store/** + tauri-plugin-stronghold: + - .github/workflows/lint-rust.yml + - plugins/stronghold/** + tauri-plugin-updater: + - .github/workflows/lint-rust.yml + - plugins/updater/** + tauri-plugin-upload: + - .github/workflows/lint-rust.yml + - plugins/upload/** + tauri-plugin-websocket: + - .github/workflows/lint-rust.yml + - plugins/websocket/** + tauri-plugin-window-state: + - .github/workflows/lint-rust.yml + - plugins/window-state/** + + clippy: + needs: changes + if: ${{ needs.changes.outputs.packages != '[]' && needs.changes.outputs.packages != '' }} + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + package: ${{ fromJSON(needs.changes.outputs.packages) }} + + steps: + - uses: actions/checkout@v4 + + - name: install webkit2gtk + run: | + sudo apt-get update + sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev + + - name: Install clippy with stable toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + + - uses: Swatinem/rust-cache@v2 + + - name: clippy ${{ matrix.package }} + run: cargo clippy --package ${{ matrix.package }} --all-targets -- -D warnings + + - name: clippy ${{ matrix.package }} --all-features + run: cargo clippy --package ${{ matrix.package }} --all-targets --all-features -- -D warnings diff --git a/packages/kbot/gui/app/.github/workflows/sync.yml b/packages/kbot/gui/app/.github/workflows/sync.yml new file mode 100644 index 00000000..6764cf8a --- /dev/null +++ b/packages/kbot/gui/app/.github/workflows/sync.yml @@ -0,0 +1,51 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: Sync + +on: + workflow_dispatch: + push: + branches: + - v1 + - v2 + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + sync-to-mirrors: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Fetch git tags + run: git fetch origin 'refs/tags/*:refs/tags/*' + + - name: Cache pnpm modules + uses: actions/cache@v4 + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}- + + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + + - uses: pnpm/action-setup@v4 + with: + version: 10.x.x + run_install: true + + - name: Build packages + run: pnpm build + + - name: Sync + run: .github/sync-to-mirrors.sh + env: + BUILD_BASE: ${{ github.workspace }}/plugins + API_TOKEN_GITHUB: ${{ secrets.ORG_TAURI_BOT_PAT }} diff --git a/packages/kbot/gui/app/.github/workflows/test-rust.yml b/packages/kbot/gui/app/.github/workflows/test-rust.yml new file mode 100644 index 00000000..496efe6e --- /dev/null +++ b/packages/kbot/gui/app/.github/workflows/test-rust.yml @@ -0,0 +1,254 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: Test Rust + +on: + push: + branches: + - v1 + - v2 + paths: + - '.github/workflows/test-rust.yml' + - 'plugins/*/src/**' + - '!plugins/*/src/api-iife.js' + - '**/Cargo.toml' + - '**/Cargo.lock' + pull_request: + branches: + - v1 + - v2 + paths: + - '.github/workflows/test-rust.yml' + - 'plugins/*/src/**' + - '!plugins/*/src/api-iife.js' + - '**/Cargo.toml' + - '**/Cargo.lock' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + changes: + runs-on: ubuntu-latest + permissions: + pull-requests: read + outputs: + packages: ${{ steps.filter.outputs.changes }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v2 + id: filter + with: + base: v2 + filters: | + tauri-plugin-autostart: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/autostart/** + tauri-plugin-cli: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/cli/** + tauri-plugin-clipboard-manager: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/clipboard-manager/** + tauri-plugin-deep-link: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/deep-link/** + tauri-plugin-dialog: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/dialog/** + - plugins/fs/** + tauri-plugin-fs: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/fs/** + tauri-plugin-geolocation: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/geolocation/** + tauri-plugin-global-shortcut: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/global-shortcut/** + tauri-plugin-opener: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/opener/** + tauri-plugin-haptics: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/haptics/** + tauri-plugin-http: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/http/** + - plugins/fs/** + tauri-plugin-localhost: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/localhost/** + tauri-plugin-log: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/log/** + tauri-plugin-notification: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/notification/** + tauri-plugin-os: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/os/** + tauri-plugin-persisted-scope: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/persisted-scope/** + - plugins/fs/** + tauri-plugin-positioner: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/positioner/** + tauri-plugin-process: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/process/** + tauri-plugin-shell: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/shell/** + tauri-plugin-single-instance: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/single-instance/** + tauri-plugin-sql: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/sql/** + tauri-plugin-store: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/store/** + tauri-plugin-stronghold: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/stronghold/** + tauri-plugin-updater: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/updater/** + tauri-plugin-upload: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/upload/** + tauri-plugin-websocket: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/websocket/** + tauri-plugin-window-state: + - .github/workflows/test-rust.yml + - Cargo.toml + - Cargo.lock + - plugins/window-state/** + + test: + needs: changes + if: ${{ needs.changes.outputs.packages != '[]' && needs.changes.outputs.packages != '' }} + strategy: + fail-fast: false + matrix: + package: ${{ fromJSON(needs.changes.outputs.packages) }} + platform: + - { + target: x86_64-pc-windows-msvc, + os: windows-latest, + runner: 'cargo', + command: 'test' + } + - { + target: x86_64-unknown-linux-gnu, + os: ubuntu-22.04, + runner: 'cargo', + command: 'test' + } + - { + target: aarch64-apple-darwin, + os: macos-latest, + runner: 'cargo', + command: 'test' + } + - { + target: aarch64-apple-ios, + os: macos-latest, + runner: 'cargo', + command: 'build' + } + - { + target: aarch64-linux-android, + os: ubuntu-latest, + runner: 'cross', + command: 'build' + } + + runs-on: ${{ matrix.platform.os }} + + steps: + - uses: actions/checkout@v4 + + - name: install webkit2gtk + if: contains(matrix.platform.target, 'unknown-linux') + run: | + sudo apt-get update + sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev + + - uses: dtolnay/rust-toolchain@1.77.2 + with: + targets: ${{ matrix.platform.target }} + + - uses: Swatinem/rust-cache@v2 + with: + key: cache-${{ matrix.package }}-${{ matrix.platform.target }} + + - name: install cross + if: ${{ matrix.platform.runner == 'cross' }} + run: cargo +stable install cross --git https://github.com/cross-rs/cross + + - name: test ${{ matrix.package }} + if: matrix.package != 'tauri-plugin-http' + run: ${{ matrix.platform.runner }} ${{ matrix.platform.command }} --package ${{ matrix.package }} --target ${{ matrix.platform.target }} --all-targets --all-features + + - name: test ${{ matrix.package }} + if: matrix.package == 'tauri-plugin-http' + run: ${{ matrix.platform.runner }} ${{ matrix.platform.command }} --package ${{ matrix.package }} --target ${{ matrix.platform.target }} --all-targets diff --git a/packages/kbot/gui/app/.gitignore b/packages/kbot/gui/app/.gitignore new file mode 100644 index 00000000..41022b01 --- /dev/null +++ b/packages/kbot/gui/app/.gitignore @@ -0,0 +1,61 @@ +# dependency directories +node_modules/ +target/ + +# Optional npm and yarn cache directory +.npm/ +.yarn/ + +# Output of 'npm pack' +*.tgz + +# dotenv environment variables file +.env + +# .vscode workspace settings file +.vscode/settings.json +.vscode/launch.json +.vscode/tasks.json + +# npm, yarn and bun lock files +package-lock.json +yarn.lock +bun.lockb + +# rust compiled folders +target/ + +# compiled plugins +dist-js/ + +# plugins .tauri directory +/plugins/*/.tauri + +# examples +examples/*/dist +plugins/*/examples/*/dist +examples/*/src-tauri/gen/schemas +plugins/*/examples/*/src-tauri/gen/schemas + +# logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# runtime data +pids +*.pid +*.seed +*.pid.lock + +# miscellaneous +/.vs +.DS_Store +.Thumbs.db +*.sublime* +.idea +debug.log +TODO.md +.aider.* diff --git a/packages/kbot/gui/app/.npmrc b/packages/kbot/gui/app/.npmrc new file mode 100644 index 00000000..30ab299d --- /dev/null +++ b/packages/kbot/gui/app/.npmrc @@ -0,0 +1 @@ +link-workspace-packages=true diff --git a/packages/kbot/gui/app/.prettierignore b/packages/kbot/gui/app/.prettierignore new file mode 100644 index 00000000..bc4fca6d --- /dev/null +++ b/packages/kbot/gui/app/.prettierignore @@ -0,0 +1,27 @@ +/.changes +/.vscode + +# dependcies and artifacts directories +node_modules/ +target/ +dist-js/ +dist/ + +# lock files +pnpm-lock.yaml + +# examples gen directory +examples/*/src-tauri/gen/ +plugins/*/examples/*/src-tauri/gen/ + +# autogenerated files +**/autogenerated/**/*.md +api-iife.js +init-iife.js +CHANGELOG.md +*schema.json + +# mobile build +**/ios/.build +**/.tauri +plugins/*/android/build diff --git a/packages/kbot/gui/app/.prettierrc b/packages/kbot/gui/app/.prettierrc new file mode 100644 index 00000000..6ca6fdd2 --- /dev/null +++ b/packages/kbot/gui/app/.prettierrc @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "semi": false, + "trailingComma": "none", + "experimentalOperatorPosition": "start" +} diff --git a/packages/kbot/gui/app/.scripts/ci/check-change-files.js b/packages/kbot/gui/app/.scripts/ci/check-change-files.js new file mode 100644 index 00000000..c9ff7e9b --- /dev/null +++ b/packages/kbot/gui/app/.scripts/ci/check-change-files.js @@ -0,0 +1,86 @@ +#!/usr/bin/env node + +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { readFileSync, readdirSync } from 'fs' +import { join } from 'path' + +/* const ignorePackages = [ + 'api-example', + 'api-example-js', + 'deep-link-example', + 'deep-link-example-js' +] */ + +const rsOnly = ['localhost', 'persisted-scope', 'single-instance'] + +function checkChangeFiles(changeFiles) { + let code = 0 + + for (const file of changeFiles) { + const content = readFileSync(file, 'utf8') + const [frontMatter] = /^---[\s\S.]*---\n/i.exec(content) + const packages = frontMatter + .split('\n') + .filter((l) => !(l === '---' || !l)) + .map((l) => l.replace(/('|")/g, '').split(':')) + + const rsPackages = Object.fromEntries( + packages + .filter((v) => !v[0].endsWith('-js')) + .map((v) => [v[0], v[1].trim()]) + ) + const jsPackages = Object.fromEntries( + packages + .filter((v) => v[0].endsWith('-js')) + .map((v) => [v[0].slice(0, -3), v[1].trim()]) + ) + + for (const pkg in rsPackages) { + if (rsOnly.includes(pkg)) continue + + if (!jsPackages[pkg]) { + console.error( + `Missing "${rsPackages[pkg]}" bump for JS package "${pkg}-js" in ${file}.` + ) + code = 1 + } else if (rsPackages[pkg] != jsPackages[pkg]) { + console.error( + `"${pkg}" and "${pkg}-js" have different version bumps in ${file}.` + ) + code = 1 + } + } + + for (const pkg in jsPackages) { + if (!rsPackages[pkg]) { + console.error( + `Missing "${jsPackages[pkg]}" bump for Rust package "${pkg}" in ${file}.` + ) + code = 1 + } else if (rsPackages[pkg] != jsPackages[pkg]) { + console.error( + `"${pkg}" and "${pkg}-js" have different version bumps in ${file}.` + ) + code = 1 + } + } + } + + process.exit(code) +} + +const [_bin, _script, ...files] = process.argv + +if (files.length > 0) { + checkChangeFiles( + files.filter((f) => f.toLowerCase() !== '.changes/readme.md') + ) +} else { + const changeFiles = readdirSync('.changes') + .filter((f) => f.endsWith('.md') && f.toLowerCase() !== 'readme.md') + .map((p) => join('.changes', p)) + checkChangeFiles(changeFiles) +} diff --git a/packages/kbot/gui/app/.scripts/ci/check-license-header.js b/packages/kbot/gui/app/.scripts/ci/check-license-header.js new file mode 100644 index 00000000..4341e5a2 --- /dev/null +++ b/packages/kbot/gui/app/.scripts/ci/check-license-header.js @@ -0,0 +1,130 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import fs from 'fs' +import path from 'path' +import readline from 'readline' + +const header = `Copyright 2019-2023 Tauri Programme within The Commons Conservancy +SPDX-License-Identifier: Apache-2.0 +SPDX-License-Identifier: MIT` +const ignoredLicenses = [ + '// Copyright 2021 Flavio Oliveira', + '// Copyright 2021 Jonas Kruckenberg', + '// Copyright 2018-2023 the Deno authors.' +] + +const extensions = ['.rs', '.js', '.ts', '.yml', '.swift', '.kt'] +const ignore = [ + 'target', + 'templates', + 'node_modules', + 'gen', + 'dist', + 'dist-js', + '.svelte-kit', + 'api-iife.js', + 'init-iife.js', + '.build', + 'notify_rust' +] + +async function checkFile(file) { + if ( + extensions.some((e) => file.endsWith(e)) + && !ignore.some((i) => file.includes(`${path.sep}${i}`)) + ) { + const fileStream = fs.createReadStream(file) + const rl = readline.createInterface({ + input: fileStream, + crlfDelay: Infinity + }) + + let contents = `` + let i = 0 + for await (let line of rl) { + // ignore empty lines, allow shebang, swift-tools-version and bundler license + if ( + line.length === 0 + || line.startsWith('#!') + || line.startsWith('// swift-tools-version:') + || ignoredLicenses.includes(line) + ) { + continue + } + + // strip comment marker + if (line.startsWith('// ')) { + line = line.substring(3) + } else if (line.startsWith('# ')) { + line = line.substring(2) + } + + contents += line + if (++i === 3) { + break + } + contents += '\n' + } + if (contents !== header) { + return true + } + } + return false +} + +async function check(src) { + const missingHeader = [] + + for (const entry of fs.readdirSync(src, { + withFileTypes: true + })) { + const p = path.join(src, entry.name) + + if (entry.isSymbolicLink() || ignore.includes(entry.name)) { + continue + } + + if (entry.isDirectory()) { + const missing = await check(p) + missingHeader.push(...missing) + } else { + const isMissing = await checkFile(p) + if (isMissing) { + missingHeader.push(p) + } + } + } + + return missingHeader +} + +const [_bin, _script, ...files] = process.argv + +if (files.length > 0) { + async function run() { + const missing = [] + for (const f of files) { + const isMissing = await checkFile(f) + if (isMissing) { + missing.push(f) + } + } + if (missing.length > 0) { + console.log(missing.join('\n')) + process.exit(1) + } + } + + run() +} else { + check(path.resolve(new URL(import.meta.url).pathname, '../../..')).then( + (missing) => { + if (missing.length > 0) { + console.log(missing.join('\n')) + process.exit(1) + } + } + ) +} diff --git a/packages/kbot/gui/app/.scripts/ci/has-diff.sh b/packages/kbot/gui/app/.scripts/ci/has-diff.sh new file mode 100644 index 00000000..1f18ac64 --- /dev/null +++ b/packages/kbot/gui/app/.scripts/ci/has-diff.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +if git diff --quiet --ignore-submodules HEAD +then + echo "working directory is clean" +else + echo "found diff" + git diff --name-status HEAD + exit 1 +fi diff --git a/packages/kbot/gui/app/.taurignore b/packages/kbot/gui/app/.taurignore new file mode 100644 index 00000000..28a49db3 --- /dev/null +++ b/packages/kbot/gui/app/.taurignore @@ -0,0 +1,2 @@ +plugins/*/permissions/autogenerated/ +plugins/*/android/.tauri/tauri-api/build/ diff --git a/packages/kbot/gui/app/.vscode/extensions.json b/packages/kbot/gui/app/.vscode/extensions.json new file mode 100644 index 00000000..68acfc90 --- /dev/null +++ b/packages/kbot/gui/app/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "rust-lang.rust-analyzer", + "EditorConfig.EditorConfig", + "esbenp.prettier-vscode", + "tamasfe.even-better-toml" + ] +} diff --git a/packages/kbot/gui/app/Cargo.lock b/packages/kbot/gui/app/Cargo.lock new file mode 100644 index 00000000..591a7349 --- /dev/null +++ b/packages/kbot/gui/app/Cargo.lock @@ -0,0 +1,9207 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.15", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy 0.7.35", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_log-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" + +[[package]] +name = "android_logger" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f39be698127218cca460cb624878c9aa4e2b47dba3b277963d2bf00bad263b" +dependencies = [ + "android_log-sys", + "env_filter", + "log", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "api" +version = "2.0.36" +dependencies = [ + "log", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-barcode-scanner", + "tauri-plugin-biometric", + "tauri-plugin-cli", + "tauri-plugin-clipboard-manager", + "tauri-plugin-dialog", + "tauri-plugin-fs", + "tauri-plugin-geolocation", + "tauri-plugin-global-shortcut", + "tauri-plugin-haptics", + "tauri-plugin-http", + "tauri-plugin-log", + "tauri-plugin-nfc", + "tauri-plugin-notification", + "tauri-plugin-opener", + "tauri-plugin-os", + "tauri-plugin-process", + "tauri-plugin-shell", + "tauri-plugin-store", + "tauri-plugin-updater", + "tauri-plugin-upload", + "tauri-plugin-window-state", + "time", + "tiny_http", +] + +[[package]] +name = "app-updater" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-updater", + "time", + "tiny_http", +] + +[[package]] +name = "app-updater-v2" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-updater", + "tiny_http", +] + +[[package]] +name = "app_settings_manager" +version = "0.0.0" +dependencies = [ + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-store", +] + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arboard" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1df21f715862ede32a0c525ce2ca4d52626bb0007f8c18b87a384503ac33e70" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.0", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.0", + "parking_lot", + "percent-encoding", + "windows-sys 0.59.0", + "wl-clipboard-rs", + "x11rb", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + +[[package]] +name = "ashpd" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" +dependencies = [ + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.0", + "raw-window-handle", + "serde", + "serde_repr", + "tokio", + "url", + "zbus", +] + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40f6024f3f856663b45fd0c9b6f2024034a702f453549449e0d84a305900dad4" +dependencies = [ + "brotli", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "zstd", + "zstd-safe", +] + +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 0.38.44", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 0.38.44", + "tracing", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.44", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "auto-launch" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f012b8cc0c850f34117ec8252a44418f2e34a2cf501de89e29b241ae5f79471" +dependencies = [ + "dirs 4.0.0", + "thiserror 1.0.69", + "winreg 0.10.1", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +dependencies = [ + "serde", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq 0.3.1", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d59b4c170e16f0405a2e95aff44432a0d41aa97675f3d52623effe95792a037" +dependencies = [ + "objc2 0.6.0", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "borsh" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +dependencies = [ + "once_cell", + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "brotli" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "byte-unit" +version = "5.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cd29c3c585209b0cbc7309bfe3ed7efd8c84c21b7af29c8bfae908f8777174" +dependencies = [ + "rust_decimal", + "serde", + "utf8-width", +] + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bytemuck" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.9.0", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc309ed89476c8957c50fb818f56fe894db857866c3e163335faa91dc34eb85" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "cargo_toml" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257" +dependencies = [ + "serde", + "toml 0.8.20", +] + +[[package]] +name = "cc" +version = "1.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "chunked_transfer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clap" +version = "4.5.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", +] + +[[package]] +name = "color-backtrace" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2123a5984bd52ca861c66f66a9ab9883b27115c607f801f86c1bc2a84eb69f0f" +dependencies = [ + "backtrace", + "termcolor", +] + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.59.0", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.15", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.10.0", + "core-graphics-types", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.10.0", + "libc", +] + +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.29.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "matches", + "phf 0.10.1", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.100", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.100", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.100", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "dary_heap" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04d2cd9c18b9f454ed67da600630b021a8a80bf33f8c95896ab33aaf1c26b728" + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + +[[package]] +name = "deep-link-example" +version = "0.0.0" +dependencies = [ + "log", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-deep-link", + "tauri-plugin-log", + "tauri-plugin-single-instance", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "derive_more" +version = "0.99.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.100", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys 0.3.7", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users 0.4.6", + "winapi", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.0", + "windows-sys 0.60.2", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users 0.4.6", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a0d569e003ff27784e0e14e4a594048698e0c0f0b66cabcb51511be55a7caa0" +dependencies = [ + "bitflags 2.9.0", + "block2 0.6.0", + "libc", + "objc2 0.6.0", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "dlopen2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b54f373ccf864bf587a89e880fb7610f8d73f3045f13580948ccbcaff26febff" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-zebra" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "hashbrown 0.14.5", + "hex", + "rand_core 0.6.4", + "sha2", + "zeroize", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "embed-resource" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbc6e0d8e0c03a655b53ca813f0463d2c956bc4db8138dbc89f120b066551e3" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 0.8.20", + "vswhom", + "winreg 0.52.0", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "error-code" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "fern" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29" +dependencies = [ + "colored", + "log", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset 0.9.1", + "rustc_version", +] + +[[package]] +name = "file-id" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc6a637b6dc58414714eddd9170ff187ecb0933d4c7024d1abbd23a3cc26e9" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + +[[package]] +name = "gethostname" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7131e57abbde63513e0e6636f76668a1ca9798dcae2df4e283cae9ee83859e" +dependencies = [ + "rustix 1.0.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.9.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "global-hotkey" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9247516746aa8e53411a0db9b62b0e24efbcf6a76e0ba73e5a91b512ddabed7" +dependencies = [ + "crossbeam-channel", + "keyboard-types", + "objc2 0.6.0", + "objc2-app-kit", + "once_cell", + "serde", + "thiserror 2.0.12", + "windows-sys 0.59.0", + "x11rb", + "xkeysym", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "h2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.9.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.2", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "html5ever" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever", + "match_token", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "num-traits", + "png", + "tiff", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", + "serde", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "inotify" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +dependencies = [ + "bitflags 2.9.0", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "iota-crypto" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98a38db844c910d78825e173c083f2ef416b69cb091bba8ac1055763c6db065b" +dependencies = [ + "aead", + "aes", + "aes-gcm", + "autocfg", + "base64 0.21.7", + "blake2", + "chacha20poly1305", + "cipher", + "curve25519-dalek", + "digest", + "ed25519-zebra", + "generic-array", + "getrandom 0.2.15", + "hkdf", + "hmac", + "iterator-sorted", + "k256", + "pbkdf2", + "rand 0.8.5", + "scrypt", + "serde", + "sha2", + "tiny-keccak", + "unicode-normalization", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "iota_stronghold" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c0d301c7edbc31494d183b7d24c1bb51d3fb10fce2f3793df1baf45b6988e10" +dependencies = [ + "bincode", + "hkdf", + "iota-crypto", + "rust-argon2 1.0.0", + "serde", + "stronghold-derive", + "stronghold-utils", + "stronghold_engine", + "thiserror 1.0.69", + "zeroize", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "iterator-sorted" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d101775d2bc8f99f4ac18bf29b9ed70c0dd138b9a1e88d7b80179470cbbe8bd2" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.2", + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.9.0", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "kuchikiki" +version = "0.8.8-speedreader" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 2.9.0", + "selectors", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "libflate" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45d9dfdc14ea4ef0900c1cddbc8dcd553fbaacd8a4a282cf4018ae9dd04fb21e" +dependencies = [ + "adler32", + "core2", + "crc32fast", + "dary_heap", + "libflate_lz77", +] + +[[package]] +name = "libflate_lz77" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e0d73b369f386f1c44abd9c570d5318f55ccde816ff4b562fa452e5182863d" +dependencies = [ + "core2", + "hashbrown 0.14.5", + "rle-decode-fast", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.9.0", + "libc", + "redox_syscall", +] + +[[package]] +name = "libsodium-sys-stable" +version = "1.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b023d38f2afdfe36f81e15a9d7232097701d7b107e3a93ba903083985e235239" +dependencies = [ + "cc", + "libc", + "libflate", + "minisign-verify", + "pkg-config", + "tar", + "ureq", + "vcpkg", + "zip 2.6.1", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +dependencies = [ + "value-bag", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "mac-notification-sys" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b95dfb34071d1592b45622bf93e315e3a72d414b6782aca9a015c12bec367ef" +dependencies = [ + "cc", + "objc2 0.6.0", + "objc2-foundation 0.3.0", + "time", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "markup5ever" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "minisign-verify" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6367d84fb54d4242af283086402907277715b8fe46976963af5ebf173f8efba3" + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "mockito" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7760e0e418d9b7e5777c0374009ca4c93861b9066f18cb334a20ce50ab63aa48" +dependencies = [ + "assert-json-diff", + "bytes", + "colored", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "log", + "rand 0.9.0", + "regex", + "serde_json", + "serde_urlencoded", + "similar", + "tokio", +] + +[[package]] +name = "muda" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2 0.6.0", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.0", + "once_cell", + "png", + "serde", + "thiserror 2.0.12", + "windows-sys 0.60.2", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.9.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset 0.9.1", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.9.0", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-debouncer-full" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375bd3a138be7bfeff3480e4a623df4cbfb55b79df617c055cd810ba466fa078" +dependencies = [ + "file-id", + "log", + "notify", + "notify-types", + "walkdir", +] + +[[package]] +name = "notify-rust" +version = "4.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6442248665a5aa2514e794af3b39661a8e73033b1cc5e59899e1276117ee4400" +dependencies = [ + "futures-lite", + "log", + "mac-notification-sys", + "serde", + "tauri-winrt-notification", + "zbus", +] + +[[package]] +name = "notify-types" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" +dependencies = [ + "serde", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3531f65190d9cff863b77a99857e74c314dd16bf56c538c4b57c7cbc3f3a6e59" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5906f93257178e2f7ae069efb89fbd6ee94f0592740b5f8a1512ca498814d0fb" +dependencies = [ + "bitflags 2.9.0", + "block2 0.6.0", + "libc", + "objc2 0.6.0", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-foundation 0.3.0", + "objc2-quartz-core 0.3.0", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c1948a9be5f469deadbd6bcb86ad7ff9e47b4f632380139722f7d9840c0d42c" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", + "objc2-foundation 0.3.0", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f860f8e841f6d32f754836f51e6bc7777cd7e7053cf18528233f6811d3eceb4" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", + "objc2-foundation 0.3.0", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dca602628b65356b6513290a21a6405b4d4027b8b250f0b98dddbb28b7de02" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ffa6bea72bf42c78b0b34e89c0bafac877d5f80bf91e159a5d96ea7f693ca56" +dependencies = [ + "objc2 0.6.0", + "objc2-foundation 0.3.0", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.9.0", + "block2 0.5.1", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998" +dependencies = [ + "bitflags 2.9.0", + "block2 0.6.0", + "libc", + "objc2 0.6.0", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "161a8b87e32610086e1a7a9e9ec39f84459db7b3a0881c1f16ca5a2605581c19" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.9.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-osa-kit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ac59da3ceebc4a82179b35dc550431ad9458f9cc326e053f49ba371ce76c5a" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", + "objc2-app-kit", + "objc2-foundation 0.3.0", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.9.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb3794501bb1bee12f08dcad8c61f2a5875791ad1c6f47faa71a0f033f20071" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", + "objc2-foundation 0.3.0", +] + +[[package]] +name = "objc2-security" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3126341c65c5d5728423ae95d788e1b660756486ad0592307ab87ba02d9a7268" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777a571be14a42a3990d4ebedaeb8b54cd17377ec21b92e8200ac03797b3bee1" +dependencies = [ + "bitflags 2.9.0", + "objc2 0.6.0", + "objc2-core-foundation", + "objc2-foundation 0.3.0", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b717127e4014b0f9f3e8bba3d3f2acec81f1bde01f656823036e823ed2c94dce" +dependencies = [ + "bitflags 2.9.0", + "block2 0.6.0", + "objc2 0.6.0", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.0", + "objc2-security", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "open" +version = "5.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +dependencies = [ + "dunce", + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "openssl" +version = "0.10.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-src" +version = "300.5.0+3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8ce546f549326b0e6052b649198487d91320875da901e7bd11a06d1ee3f9c2f" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "os_info" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a604e53c24761286860eba4e2c8b23a0161526476b1de520139d69cdb85a6b5" +dependencies = [ + "log", + "serde", + "windows-sys 0.52.0", +] + +[[package]] +name = "os_pipe" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "osakit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" +dependencies = [ + "objc2 0.6.0", + "objc2-foundation 0.3.0", + "objc2-osa-kit", + "serde", + "serde_json", + "thiserror 2.0.12", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.9.0", +] + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared 0.8.0", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.1", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plist" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d" +dependencies = [ + "base64 0.22.1", + "indexmap 2.9.0", + "quick-xml 0.32.0", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 0.38.44", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy 0.8.24", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +dependencies = [ + "toml_edit 0.20.7", +] + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit 0.22.24", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "publicsuffix" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" +dependencies = [ + "idna", + "psl-types", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-xml" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +dependencies = [ + "memchr", +] + +[[package]] +name = "quick-xml" +version = "0.37.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ce8c88de324ff838700f36fb6ab86c96df0e3c4ab6ef3a9b2044465cce1369" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.12", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc" +dependencies = [ + "bytes", + "getrandom 0.3.2", + "rand 0.9.0", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "541d0f57c6ec747a90738a52741d3221f7960e8ac2f0ff4b1a63680e033b4ab5" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "zerocopy 0.8.24", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "read-progress-stream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6435842fc2fea44b528719eb8c32203bbc1bb2f5b619fbe0c0a3d8350fd8d2a8" +dependencies = [ + "bytes", + "futures", + "pin-project-lite", +] + +[[package]] +name = "redox_syscall" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror 2.0.12", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "reqwest" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +dependencies = [ + "async-compression", + "base64 0.22.1", + "bytes", + "cookie", + "cookie_store", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tokio-socks", + "tokio-util", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", + "windows-registry 0.4.0", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rfd" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80c844748fdc82aae252ee4594a89b6e7ebef1063de7951545564cbc4e57075d" +dependencies = [ + "ashpd", + "block2 0.6.0", + "dispatch2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2 0.6.0", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.0", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rle-decode-fast" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" + +[[package]] +name = "rsa" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rust-argon2" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50162d19404029c1ceca6f6980fe40d45c8b369f6f44446fa14bb39573b5bb9" +dependencies = [ + "base64 0.13.1", + "blake2b_simd", + "constant_time_eq 0.1.5", + "crossbeam-utils", +] + +[[package]] +name = "rust-argon2" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d9848531d60c9cbbcf9d166c885316c24bc0e2a9d3eba0956bb6cbbd79bc6e8" +dependencies = [ + "base64 0.21.7", + "blake2b_simd", + "constant_time_eq 0.3.1", +] + +[[package]] +name = "rust-ini" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e310ef0e1b6eeb79169a1171daf9abcb87a2e17c03bee2c4bb100b55c75409f" +dependencies = [ + "cfg-if", + "ordered-multimap", + "trim-in-place", +] + +[[package]] +name = "rust_decimal" +version = "1.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faa7de2ba56ac291bd90c6b9bece784a52ae1411f9506544b3eae36dd2356d50" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.2.0", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +dependencies = [ + "web-time", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.100", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.10.0", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "selectors" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" +dependencies = [ + "bitflags 1.3.2", + "cssparser", + "derive_more", + "fxhash", + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299d9c19d7d466db4ab10addd5703e4c615dec2a5a16dbbafe191045e87ee66e" +dependencies = [ + "erased-serde", + "serde", + "typeid", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.9.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "servo_arc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shared_child" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09fa9338aed9a1df411814a5b2252f7cd206c55ae9bf2fa763f8de84603aa60c" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + +[[package]] +name = "single-instance-example" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-cli", + "tauri-plugin-single-instance", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "softbuffer" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" +dependencies = [ + "bytemuck", + "cfg_aliases", + "core-graphics", + "foreign-types 0.5.0", + "js-sys", + "log", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", + "raw-window-handle", + "redox_syscall", + "wasm-bindgen", + "web-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "specta" +version = "2.0.0-rc.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab7f01e9310a820edd31c80fde3cae445295adde21a3f9416517d7d65015b971" +dependencies = [ + "paste", + "specta-macros", + "thiserror 1.0.69", +] + +[[package]] +name = "specta-macros" +version = "2.0.0-rc.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0074b9e30ed84c6924eb63ad8d2fe71cdc82628525d84b1fcb1f2fd40676517" +dependencies = [ + "Inflector", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3a85280daca669cfd3bcb68a337882a8bc57ec882f72c5d13a430613a738e" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f743f2a3cea30a58cd479013f75550e879009e3a02f616f18ca699335aa248c3" +dependencies = [ + "base64 0.22.1", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.2", + "hashlink", + "indexmap 2.9.0", + "log", + "memchr", + "once_cell", + "percent-encoding", + "rustls", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.12", + "time", + "tokio", + "tokio-stream", + "tracing", + "url", + "webpki-roots", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4200e0fde19834956d4252347c12a083bdcb237d7a1a1446bffd8768417dce" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.100", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882ceaa29cade31beca7129b6beeb05737f44f82dbe2a9806ecea5a7093d00b7" +dependencies = [ + "dotenvy", + "either", + "heck 0.5.0", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.100", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0afdd3aa7a629683c2d750c2df343025545087081ab5942593a5288855b1b7a7" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.9.0", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.12", + "time", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bedbe1bbb5e2615ef347a5e9d8cd7680fb63e77d9dafc0f29be15e53f1ebe6" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.9.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.12", + "time", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c26083e9a520e8eb87a06b12347679b142dc2ea29e6e409f805644a7a979a5bc" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.12", + "time", + "tracing", + "url", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "stronghold-derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2835db23c4724c05a2f85b81c4681f4aa8ea158edc8a7f4ad791c916fb766c2e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "stronghold-runtime" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18db7cc51450cefdab5f4990e128dd02c98da6d2992b93ffef8992ac0d2f3ddf" +dependencies = [ + "dirs 4.0.0", + "iota-crypto", + "libc", + "libsodium-sys-stable", + "log", + "nix 0.24.3", + "rand 0.8.5", + "serde", + "thiserror 1.0.69", + "windows 0.36.1", + "zeroize", +] + +[[package]] +name = "stronghold-utils" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8300214898af5e153e7f66e49dbd1c6a21585f2d592d9f24f58b969792475ed6" +dependencies = [ + "rand 0.8.5", + "stronghold-derive", +] + +[[package]] +name = "stronghold_engine" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd7371c42e557dd71a7f860bb2ec6b6fdb32f97a97987ccc2435fdd1f3a8615" +dependencies = [ + "anyhow", + "dirs-next", + "hex", + "iota-crypto", + "once_cell", + "paste", + "serde", + "stronghold-runtime", + "thiserror 1.0.69", + "zeroize", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.20", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.34.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4daa814018fecdfb977b59a094df4bd43b42e8e21f88fddfc05807e6f46efaaf" +dependencies = [ + "bitflags 2.9.0", + "block2 0.6.0", + "core-foundation 0.10.0", + "core-graphics", + "crossbeam-channel", + "dispatch", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2 0.6.0", + "objc2-app-kit", + "objc2-foundation 0.3.0", + "once_cell", + "parking_lot", + "raw-window-handle", + "scopeguard", + "tao-macros", + "unicode-segmentation", + "url", + "windows 0.61.1", + "windows-core", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tar" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d545ccf7b60dcd44e07c6fb5aeb09140966f0aabd5d2aa14a6821df7bc99348" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs 6.0.0", + "dunce", + "embed_plist", + "getrandom 0.3.2", + "glob", + "gtk", + "heck 0.5.0", + "http", + "http-range", + "image", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2 0.6.0", + "objc2-app-kit", + "objc2-foundation 0.3.0", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "specta", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.12", + "tokio", + "tray-icon", + "url", + "urlpattern", + "uuid", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows 0.61.1", +] + +[[package]] +name = "tauri-build" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67945dbaf8920dbe3a1e56721a419a0c3d085254ab24cff5b9ad55e2b0016e0b" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs 6.0.0", + "glob", + "heck 0.5.0", + "json-patch", + "quote", + "schemars", + "semver", + "serde", + "serde_json", + "tauri-codegen", + "tauri-utils", + "tauri-winres", + "toml 0.9.5", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab3a62cf2e6253936a8b267c2e95839674e7439f104fa96ad0025e149d54d8a" +dependencies = [ + "base64 0.22.1", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.100", + "tauri-utils", + "thiserror 2.0.12", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4368ea8094e7045217edb690f493b55b30caf9f3e61f79b4c24b6db91f07995e" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.100", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9946a3cede302eac0c6eb6c6070ac47b1768e326092d32efbb91f21ed58d978f" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars", + "serde", + "serde_json", + "tauri-utils", + "toml 0.9.5", + "walkdir", +] + +[[package]] +name = "tauri-plugin-autostart" +version = "2.5.0" +dependencies = [ + "auto-launch", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", +] + +[[package]] +name = "tauri-plugin-barcode-scanner" +version = "2.4.0" +dependencies = [ + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", +] + +[[package]] +name = "tauri-plugin-biometric" +version = "2.3.0" +dependencies = [ + "log", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", +] + +[[package]] +name = "tauri-plugin-cli" +version = "2.4.0" +dependencies = [ + "clap", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", +] + +[[package]] +name = "tauri-plugin-clipboard-manager" +version = "2.3.0" +dependencies = [ + "arboard", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", +] + +[[package]] +name = "tauri-plugin-deep-link" +version = "2.4.3" +dependencies = [ + "dunce", + "plist", + "rust-ini", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.12", + "tracing", + "url", + "windows-registry 0.5.1", + "windows-result", +] + +[[package]] +name = "tauri-plugin-dialog" +version = "2.4.0" +dependencies = [ + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.12", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.4.2" +dependencies = [ + "anyhow", + "dunce", + "glob", + "notify", + "notify-debouncer-full", + "percent-encoding", + "schemars", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.12", + "toml 0.9.5", + "url", +] + +[[package]] +name = "tauri-plugin-geolocation" +version = "2.3.0" +dependencies = [ + "log", + "serde", + "serde_json", + "specta", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", +] + +[[package]] +name = "tauri-plugin-global-shortcut" +version = "2.3.0" +dependencies = [ + "global-hotkey", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", +] + +[[package]] +name = "tauri-plugin-haptics" +version = "2.3.0" +dependencies = [ + "log", + "serde", + "serde_json", + "specta", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", +] + +[[package]] +name = "tauri-plugin-http" +version = "2.5.2" +dependencies = [ + "bytes", + "cookie_store", + "data-url", + "http", + "regex", + "reqwest", + "schemars", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.12", + "tokio", + "tracing", + "url", + "urlpattern", +] + +[[package]] +name = "tauri-plugin-localhost" +version = "2.3.0" +dependencies = [ + "http", + "log", + "serde", + "serde_json", + "tauri", + "thiserror 2.0.12", + "tiny_http", +] + +[[package]] +name = "tauri-plugin-log" +version = "2.7.0" +dependencies = [ + "android_logger", + "byte-unit", + "fern", + "log", + "objc2 0.6.0", + "objc2-foundation 0.3.0", + "serde", + "serde_json", + "serde_repr", + "swift-rs", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", + "time", + "tracing", +] + +[[package]] +name = "tauri-plugin-nfc" +version = "2.3.1" +dependencies = [ + "log", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", +] + +[[package]] +name = "tauri-plugin-notification" +version = "2.3.1" +dependencies = [ + "color-backtrace", + "ctor", + "log", + "maplit", + "notify-rust", + "rand 0.9.0", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", + "time", + "url", + "win7-notifications", + "windows-version", +] + +[[package]] +name = "tauri-plugin-opener" +version = "2.5.0" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation 0.3.0", + "open", + "schemars", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", + "url", + "windows 0.61.1", + "zbus", +] + +[[package]] +name = "tauri-plugin-os" +version = "2.3.1" +dependencies = [ + "gethostname 1.0.1", + "log", + "os_info", + "serde", + "serde_json", + "serialize-to-javascript", + "sys-locale", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", +] + +[[package]] +name = "tauri-plugin-persisted-scope" +version = "2.3.2" +dependencies = [ + "aho-corasick", + "bincode", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin-fs", + "thiserror 2.0.12", +] + +[[package]] +name = "tauri-plugin-positioner" +version = "2.3.0" +dependencies = [ + "log", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", +] + +[[package]] +name = "tauri-plugin-process" +version = "2.3.0" +dependencies = [ + "tauri", + "tauri-plugin", +] + +[[package]] +name = "tauri-plugin-shell" +version = "2.3.1" +dependencies = [ + "encoding_rs", + "log", + "open", + "os_pipe", + "regex", + "schemars", + "serde", + "serde_json", + "shared_child", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", + "tokio", +] + +[[package]] +name = "tauri-plugin-single-instance" +version = "2.3.4" +dependencies = [ + "semver", + "serde", + "serde_json", + "tauri", + "tauri-plugin-deep-link", + "thiserror 2.0.12", + "tracing", + "windows-sys 0.60.2", + "zbus", +] + +[[package]] +name = "tauri-plugin-sql" +version = "2.3.0" +dependencies = [ + "futures-core", + "indexmap 2.9.0", + "log", + "serde", + "serde_json", + "sqlx", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", + "time", + "tokio", +] + +[[package]] +name = "tauri-plugin-store" +version = "2.4.0" +dependencies = [ + "dunce", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", + "tokio", + "tracing", +] + +[[package]] +name = "tauri-plugin-stronghold" +version = "2.3.0" +dependencies = [ + "hex", + "iota-crypto", + "iota_stronghold", + "log", + "rand 0.9.0", + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "rust-argon2 2.1.0", + "rusty-fork", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", + "zeroize", +] + +[[package]] +name = "tauri-plugin-updater" +version = "2.9.0" +dependencies = [ + "base64 0.22.1", + "dirs 6.0.0", + "flate2", + "futures-util", + "http", + "infer", + "log", + "minisign-verify", + "osakit", + "percent-encoding", + "reqwest", + "semver", + "serde", + "serde_json", + "tar", + "tauri", + "tauri-plugin", + "tempfile", + "thiserror 2.0.12", + "time", + "tokio", + "url", + "windows-sys 0.60.2", + "zip 4.0.0", +] + +[[package]] +name = "tauri-plugin-upload" +version = "2.3.1" +dependencies = [ + "futures-util", + "log", + "mockito", + "read-progress-stream", + "reqwest", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", + "tokio", + "tokio-util", +] + +[[package]] +name = "tauri-plugin-websocket" +version = "2.4.0" +dependencies = [ + "futures-util", + "http", + "log", + "rand 0.9.0", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", + "tokio", + "tokio-tungstenite", +] + +[[package]] +name = "tauri-plugin-window-state" +version = "2.4.0" +dependencies = [ + "bitflags 2.9.0", + "log", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.12", +] + +[[package]] +name = "tauri-runtime" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4cfc9ad45b487d3fded5a4731a567872a4812e9552e3964161b08edabf93846" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2 0.6.0", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.12", + "url", + "webkit2gtk", + "webview2-com", + "windows 0.61.1", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fe9d48bd122ff002064e88cfcd7027090d789c4302714e68fcccba0f4b7807" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2 0.6.0", + "objc2-app-kit", + "objc2-foundation 0.3.0", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows 0.61.1", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a3852fdf9a4f8fbeaa63dc3e9a85284dd6ef7200751f0bd66ceee30c93f212" +dependencies = [ + "aes-gcm", + "anyhow", + "cargo_metadata", + "ctor", + "dunce", + "getrandom 0.3.2", + "glob", + "html5ever", + "http", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.3", + "proc-macro2", + "quote", + "regex", + "schemars", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "serialize-to-javascript", + "swift-rs", + "thiserror 2.0.12", + "toml 0.9.5", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56eaa45f707bedf34d19312c26d350bc0f3c59a47e58e8adbeecdc850d2c13a0" +dependencies = [ + "embed-resource", + "toml 0.8.20", +] + +[[package]] +name = "tauri-winrt-notification" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9" +dependencies = [ + "quick-xml 0.37.4", + "thiserror 2.0.12", + "windows 0.61.1", + "windows-version", +] + +[[package]] +name = "tempfile" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +dependencies = [ + "fastrand", + "getrandom 0.3.2", + "once_cell", + "rustix 1.0.5", + "windows-sys 0.59.0", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tiny_http" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" +dependencies = [ + "ascii", + "chunked_transfer", + "httpdate", + "log", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-socks" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" +dependencies = [ + "either", + "futures-util", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489a59b6730eda1b0171fcfda8b121f4bee2b35cba8645ca35c5f7ba3eb736c1" +dependencies = [ + "futures-util", + "log", + "native-tls", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tungstenite", + "webpki-roots", +] + +[[package]] +name = "tokio-util" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +dependencies = [ + "serde", + "serde_spanned 0.6.8", + "toml_datetime 0.6.8", + "toml_edit 0.22.24", +] + +[[package]] +name = "toml" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +dependencies = [ + "indexmap 2.9.0", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow 0.7.12", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.9.0", + "toml_datetime 0.6.8", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap 2.9.0", + "toml_datetime 0.6.8", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +dependencies = [ + "indexmap 2.9.0", + "serde", + "serde_spanned 0.6.8", + "toml_datetime 0.6.8", + "winnow 0.7.12", +] + +[[package]] +name = "toml_parser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +dependencies = [ + "winnow 0.7.12", +] + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d92153331e7d02ec09137538996a7786fe679c629c279e82a6be762b7e6fe2" +dependencies = [ + "crossbeam-channel", + "dirs 6.0.0", + "libappindicator", + "muda", + "objc2 0.6.0", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.0", + "once_cell", + "png", + "serde", + "thiserror 2.0.12", + "windows-sys 0.59.0", +] + +[[package]] +name = "tree_magic_mini" +version = "3.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac5e8971f245c3389a5a76e648bfc80803ae066a1243a75db0064d7c1129d63" +dependencies = [ + "fnv", + "memchr", + "nom", + "once_cell", + "petgraph", +] + +[[package]] +name = "trim-in-place" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadc29d668c91fcc564941132e17b28a7ceb2f3ebf0b9dae3e03fd7a6748eb0d" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "native-tls", + "rand 0.9.0", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 2.0.12", + "utf-8", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset 0.9.1", + "tempfile", + "winapi", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "updater-migration-test" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-updater", + "time", + "tiny_http", +] + +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64 0.22.1", + "log", + "once_cell", + "url", +] + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +dependencies = [ + "getrandom 0.3.2", + "serde", +] + +[[package]] +name = "value-bag" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.100", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wayland-backend" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf" +dependencies = [ + "cc", + "downcast-rs", + "rustix 0.38.44", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f" +dependencies = [ + "bitflags 2.9.0", + "rustix 0.38.44", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0781cf46869b37e36928f7b432273c0995aa8aed9552c556fb18754420541efc" +dependencies = [ + "bitflags 2.9.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248a02e6f595aad796561fa82d25601bd2c8c3b145b1c7453fc8f94c1a58f8b2" +dependencies = [ + "bitflags 2.9.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" +dependencies = [ + "proc-macro2", + "quick-xml 0.37.4", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webpki-roots" +version = "0.26.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "websocket-example" +version = "0.1.0" +dependencies = [ + "futures-util", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-websocket", + "tokio", + "tokio-tungstenite", +] + +[[package]] +name = "webview2-com" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows 0.61.1", + "windows-core", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" +dependencies = [ + "thiserror 2.0.12", + "windows 0.61.1", + "windows-core", +] + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "whoami" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" +dependencies = [ + "redox_syscall", + "wasite", +] + +[[package]] +name = "win7-notifications" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b4745047a00800bd8f2b8fb4b0eb6f7d96822084127f0ff7d68d07f692fe38" +dependencies = [ + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2 0.6.0", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.0", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53b97a83176b369b0eb2fd8158d4ae215357d02df9d40c1e1bf1879c5482c80" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings 0.4.0", +] + +[[package]] +name = "windows-future" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result", + "windows-strings 0.3.1", + "windows-targets 0.53.2", +] + +[[package]] +name = "windows-registry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1da3e436dc7653dfdf3da67332e22bff09bb0e28b0239e1624499c7830842e" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings 0.4.0", +] + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows-version" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04a5c6627e310a23ad2358483286c7df260c964eb2d003d8efd6d0f4e79265c" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "wl-clipboard-rs" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5ff8d0e60065f549fafd9d6cb626203ea64a798186c80d8e7df4f8af56baeb" +dependencies = [ + "libc", + "log", + "os_pipe", + "rustix 0.38.44", + "tempfile", + "thiserror 2.0.12", + "tree_magic_mini", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-wlr", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "wry" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b6763512fe4b51c80b3ce9b50939d682acb4de335dfabbdb20d7a2642199b7" +dependencies = [ + "base64 0.22.1", + "block2 0.6.0", + "cookie", + "crossbeam-channel", + "dirs 6.0.0", + "dpi", + "dunce", + "gdkx11", + "gtk", + "html5ever", + "http", + "javascriptcore-rs", + "jni", + "kuchikiki", + "libc", + "ndk", + "objc2 0.6.0", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.0", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.12", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows 0.61.1", + "windows-core", + "windows-version", + "x11-dl", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "gethostname 0.4.3", + "rustix 0.38.44", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "zeroize", +] + +[[package]] +name = "xattr" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e" +dependencies = [ + "libc", + "rustix 1.0.5", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb4f9a464286d42851d18a605f7193b8febaf5b0919d71c6399b7b26e5b0aad" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "nix 0.30.1", + "ordered-stream", + "serde", + "serde_repr", + "tokio", + "tracing", + "uds_windows", + "windows-sys 0.59.0", + "winnow 0.7.12", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef9859f68ee0c4ee2e8cde84737c78e3f4c54f946f2a38645d0d4c7a95327659" +dependencies = [ + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.100", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +dependencies = [ + "serde", + "static_assertions", + "winnow 0.7.12", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +dependencies = [ + "zerocopy-derive 0.8.24", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "serde", + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "zip" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dcb24d0152526ae49b9b96c1dcf71850ca1e0b882e4e28ed898a93c41334744" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "flate2", + "indexmap 2.9.0", + "memchr", + "zopfli", +] + +[[package]] +name = "zip" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "153a6fff49d264c4babdcfa6b4d534747f520e56e8f0f384f3b808c4b64cc1fd" +dependencies = [ + "arbitrary", + "crc32fast", + "indexmap 2.9.0", + "memchr", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "zvariant" +version = "5.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91b3680bb339216abd84714172b5138a4edac677e641ef17e1d8cb1b3ca6e6f" +dependencies = [ + "endi", + "enumflags2", + "serde", + "url", + "winnow 0.7.12", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8c68501be459a8dbfffbe5d792acdd23b4959940fc87785fb013b32edbc208" +dependencies = [ + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.100", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "static_assertions", + "syn 2.0.100", + "winnow 0.7.12", +] diff --git a/packages/kbot/gui/app/Cargo.toml b/packages/kbot/gui/app/Cargo.toml new file mode 100644 index 00000000..071f54ff --- /dev/null +++ b/packages/kbot/gui/app/Cargo.toml @@ -0,0 +1,41 @@ +[workspace] +members = [ + "plugins/*", + "plugins/*/tests/*", + "plugins/updater/tests/updater-migration/v2-app", + "plugins/*/examples/*/src-tauri", + "examples/*/src-tauri", +] +resolver = "2" + +[workspace.dependencies] +serde = { version = "1", features = ["derive"] } +tracing = "0.1" +log = "0.4" +tauri = { version = "2.8.2", default-features = false } +tauri-build = "2.4" +tauri-plugin = "2.4" +tauri-utils = "2.7" +serde_json = "1" +thiserror = "2" +url = "2" +schemars = "0.8" +dunce = "1" +specta = "^2.0.0-rc.16" +glob = "0.3" +zbus = "5.9" + +[workspace.package] +edition = "2021" +authors = ["Tauri Programme within The Commons Conservancy"] +license = "Apache-2.0 OR MIT" +rust-version = "1.77.2" +repository = "https://github.com/tauri-apps/plugins-workspace" + +# default to small, optimized release binaries +[profile.release] +panic = "abort" +codegen-units = 1 +lto = true +incremental = false +opt-level = "s" diff --git a/packages/kbot/gui/app/LICENSE.spdx b/packages/kbot/gui/app/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/LICENSE_MIT b/packages/kbot/gui/app/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/README.md b/packages/kbot/gui/app/README.md new file mode 100644 index 00000000..cdf239bc --- /dev/null +++ b/packages/kbot/gui/app/README.md @@ -0,0 +1,62 @@ +# Official Tauri Plugins + +This repo and all plugins require a Rust version of at least **1.77.2** + +## Plugins Found Here + +| | | Win | Mac | Lin | iOS | And | +| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- | --- | --- | --- | --- | +| [autostart](plugins/autostart) | Automatically launch your app at system startup. | ✅ | ✅ | ✅ | ❌ | ❌ | +| [barcode-scanner](plugins/barcode-scanner) | Allows your mobile application to use the camera to scan QR codes, EAN-13 and other kinds of barcodes. | ? | ? | ? | ✅ | ✅ | +| [biometric](plugins/biometric) | Prompt the user for biometric authentication on Android and iOS. | ? | ? | ? | ✅ | ✅ | +| [cli](plugins/cli) | Parse arguments from your Command Line Interface | ✅ | ✅ | ✅ | ❌ | ❌ | +| [clipboard-manager](plugins/clipboard-manager) | Read and write to the system clipboard. | ✅ | ✅ | ✅ | ✅ | ✅ | +| [deep-link](plugins/deep-link) | Set your Tauri application as the default handler for an URL. | ✅ | ✅ | ✅ | ✅ | ✅ | +| [dialog](plugins/dialog) | Native system dialogs for opening and saving files along with message dialogs. | ✅ | ✅ | ✅ | ✅ | ✅ | +| [fs](plugins/fs) | Access the file system. | ✅ | ✅ | ✅ | ? | ? | +| [geolocation](plugins/geolocation) | Get and track current device position. | ? | ? | ? | ✅ | ✅ | +| [global-shortcut](plugins/global-shortcut) | Register global shortcuts. | ✅ | ✅ | ✅ | ? | ? | +| [haptics](plugins/haptics) | Haptic feedback and vibrations. | ? | ? | ? | ✅ | ✅ | +| [http](plugins/http) | Access the HTTP client written in Rust. | ✅ | ✅ | ✅ | ✅ | ✅ | +| [localhost](plugins/localhost) | Use a localhost server in production apps. | ✅ | ✅ | ✅ | ? | ? | +| [log](plugins/log) | Configurable logging. | ✅ | ✅ | ✅ | ✅ | ✅ | +| [nfc](plugins/nfc) | Read and write NFC tags on Android and iOS. | ? | ? | ? | ✅ | ✅ | +| [notification](plugins/notification) | Send message notifications (brief auto-expiring OS window element) to your user. Can also be used with the Notification Web API. | ✅ | ✅ | ✅ | ✅ | ✅ | +| [opener](plugins/opener) | Open files and URLs using their default application. | ✅ | ✅ | ✅ | ? | ? | +| [os](plugins/os) | Read information about the operating system. | ✅ | ✅ | ✅ | ✅ | ✅ | +| [persisted-scope](plugins/persisted-scope) | Persist runtime scope changes on the filesystem. | ✅ | ✅ | ✅ | ? | ? | +| [positioner](plugins/positioner) | Move windows to common locations. | ✅ | ✅ | ✅ | ❌ | ❌ | +| [process](plugins/process) | This plugin provides APIs to access the current process. To spawn child processes, see the [`shell`](https://github.com/tauri-apps/tauri-plugin-shell) plugin. | ✅ | ✅ | ✅ | ? | ? | +| [shell](plugins/shell) | Access the system shell. Allows you to spawn child processes and manage files and URLs using their default application. | ✅ | ✅ | ✅ | ? | ? | +| [single-instance](plugins/single-instance) | Ensure a single instance of your tauri app is running. | ✅ | ✅ | ✅ | ❌ | ❌ | +| [sql](plugins/sql) | Interface with SQL databases. | ✅ | ✅ | ✅ | ✅ | ✅ | +| [store](plugins/store) | Persistent key value storage. | ✅ | ✅ | ✅ | ✅ | ✅ | +| [stronghold](plugins/stronghold) | Encrypted, secure database. | ✅ | ✅ | ✅ | ? | ? | +| [updater](plugins/updater) | In-app updates for Tauri applications. | ✅ | ✅ | ✅ | ❌ | ❌ | +| [upload](plugins/upload) | Tauri plugin for file uploads through HTTP. | ✅ | ✅ | ✅ | ✅ | ✅ | +| [websocket](plugins/websocket) | Open a WebSocket connection using a Rust client in JS. | ✅ | ✅ | ✅ | ? | ? | +| [window-state](plugins/window-state) | Persist window sizes and positions. | ✅ | ✅ | ✅ | ❌ | ❌ | + +- ✅: (Partially) Supported +- ❌: Not supported +- `?` : Unknown/Untested or Planned + +## Contributing + +PRs accepted. Please make sure to read the [Contributing Guide](https://github.com/tauri-apps/tauri/blob/dev/.github/CONTRIBUTING.md) before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). diff --git a/packages/kbot/gui/app/SECURITY.md b/packages/kbot/gui/app/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/eslint.config.js b/packages/kbot/gui/app/eslint.config.js new file mode 100644 index 00000000..2500c686 --- /dev/null +++ b/packages/kbot/gui/app/eslint.config.js @@ -0,0 +1,39 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import eslint from '@eslint/js' +import { defineConfig } from 'eslint/config' +import eslintConfigPrettier from 'eslint-config-prettier' +import eslintPluginSecurity from 'eslint-plugin-security' +import tseslint from 'typescript-eslint' + +export default defineConfig( + { + ignores: [ + '**/target', + '**/node_modules', + '**/examples', + '**/dist', + '**/dist-js', + '**/build', + '**/api-iife.js', + '**/init-iife.js', + '**/init.js', + '**/rollup.config.js', + '**/bindings.ts', + '**/.test-server', + '.scripts', + 'eslint.config.js' + ] + }, + eslint.configs.recommended, + eslintConfigPrettier, + eslintPluginSecurity.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + { + languageOptions: { + parserOptions: { project: true, tsconfigRootDir: import.meta.dirname } + } + } +) diff --git a/packages/kbot/gui/app/examples/api/.setup-cross.sh b/packages/kbot/gui/app/examples/api/.setup-cross.sh new file mode 100644 index 00000000..a9e4f867 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/.setup-cross.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +export ICONS_VOLUME="$(realpath icons)" +export DIST_VOLUME="$(realpath dist)" +export ISOLATION_VOLUME="$(realpath isolation-dist)" diff --git a/packages/kbot/gui/app/examples/api/.taurignore b/packages/kbot/gui/app/examples/api/.taurignore new file mode 100644 index 00000000..848a0f2a --- /dev/null +++ b/packages/kbot/gui/app/examples/api/.taurignore @@ -0,0 +1,3 @@ +src-tauri/locales/ +src-tauri/Cross.toml +src-tauri/.gitignore diff --git a/packages/kbot/gui/app/examples/api/CHANGELOG.md b/packages/kbot/gui/app/examples/api/CHANGELOG.md new file mode 100644 index 00000000..cd296557 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/CHANGELOG.md @@ -0,0 +1,699 @@ +# Changelog + +## \[2.0.32] + +### Dependencies + +- Upgraded to `dialog-js@2.4.0` +- Upgraded to `log-js@2.7.0` + +## \[2.0.31] + +### Dependencies + +- Upgraded to `shell-js@2.3.1` + +## \[2.0.30] + +### Dependencies + +- Upgraded to `notification-js@2.3.1` + +## \[2.0.29] + +### Dependencies + +- Upgraded to `fs-js@2.4.2` +- Upgraded to `nfc-js@2.3.1` +- Upgraded to `opener-js@2.5.0` +- Upgraded to `os-js@2.3.1` +- Upgraded to `store-js@2.4.0` +- Upgraded to `dialog-js@2.3.3` +- Upgraded to `http-js@2.5.2` + +## \[2.0.28] + +### Dependencies + +- Upgraded to `dialog-js@2.3.2` + +## \[2.0.27] + +### Dependencies + +- Upgraded to `barcode-scanner-js@2.4.0` +- Upgraded to `fs-js@2.4.1` +- Upgraded to `dialog-js@2.3.1` +- Upgraded to `http-js@2.5.1` + +## \[2.0.26] + +### Dependencies + +- Upgraded to `barcode-scanner-js@2.3.0` +- Upgraded to `biometric-js@2.3.0` +- Upgraded to `cli-js@2.4.0` +- Upgraded to `clipboard-manager-js@2.3.0` +- Upgraded to `fs-js@2.4.0` +- Upgraded to `dialog-js@2.3.0` +- Upgraded to `global-shortcut-js@2.3.0` +- Upgraded to `opener-js@2.4.0` +- Upgraded to `http-js@2.5.0` +- Upgraded to `log-js@2.6.0` +- Upgraded to `nfc-js@2.3.0` +- Upgraded to `notification-js@2.3.0` +- Upgraded to `os-js@2.3.0` +- Upgraded to `process-js@2.3.0` +- Upgraded to `shell-js@2.3.0` +- Upgraded to `store-js@2.3.0` +- Upgraded to `updater-js@2.9.0` + +## \[2.0.25] + +### Dependencies + +- Upgraded to `cli-js@2.3.0` +- Upgraded to `log-js@2.5.1` +- Upgraded to `opener-js@2.3.1` + +## \[2.0.24] + +### Dependencies + +- Upgraded to `updater-js@2.8.1` + +## \[2.0.23] + +### Dependencies + +- Upgraded to `updater-js@2.8.0` +- Upgraded to `barcode-scanner-js@2.2.1` +- Upgraded to `biometric-js@2.2.2` +- Upgraded to `cli-js@2.2.1` +- Upgraded to `clipboard-manager-js@2.2.3` +- Upgraded to `nfc-js@2.2.1` +- Upgraded to `notification-js@2.2.3` +- Upgraded to `os-js@2.2.2` +- Upgraded to `process-js@2.2.2` +- Upgraded to `shell-js@2.2.2` +- Upgraded to `store-js@2.2.1` +- Upgraded to `log-js@2.5.0` +- Upgraded to `opener-js@2.3.0` + +## \[2.0.22] + +### Dependencies + +- Upgraded to `fs-js@2.3.0` +- Upgraded to `global-shortcut-js@2.2.1` +- Upgraded to `http-js@2.4.4` +- Upgraded to `opener-js@2.2.7` +- Upgraded to `dialog-js@2.2.2` + +## \[2.0.21] + +### Dependencies + +- Upgraded to `log-js@2.4.0` +- Upgraded to `biometric-js@2.2.1` +- Upgraded to `updater-js@2.7.1` + +## \[2.0.20] + +### Dependencies + +- Upgraded to `http-js@2.4.3` +- Upgraded to `shell-js@2.2.1` +- Upgraded to `fs-js@2.2.1` +- Upgraded to `process-js@2.2.1` +- Upgraded to `updater-js@2.7.0` +- Upgraded to `dialog-js@2.2.1` + +## \[2.0.19] + +### Dependencies + +- Upgraded to `http-js@2.4.2` +- Upgraded to `updater-js@2.6.1` + +## \[2.0.18] + +### Dependencies + +- Upgraded to `http-js@2.4.1` + +## \[2.0.17] + +### Dependencies + +- Upgraded to `log-js@2.3.1` + +## \[2.0.16] + +### Dependencies + +- Upgraded to `clipboard-manager-js@2.2.2` +- Upgraded to `notification-js@2.2.2` +- Upgraded to `os-js@2.2.1` +- Upgraded to `http-js@2.4.0` +- Upgraded to `log-js@2.3.0` +- Upgraded to `updater-js@2.6.0` + +## \[2.0.15] + +### Dependencies + +- Upgraded to `log-js@2.2.3` +- Upgraded to `opener-js@2.2.6` + +## \[2.0.14] + +### Dependencies + +- Upgraded to `log-js@2.2.2` +- Upgraded to `updater-js@2.5.1` + +## \[2.0.13] + +### Dependencies + +- Upgraded to `updater-js@2.5.0` + +## \[2.0.12] + +### Dependencies + +- Upgraded to `clipboard-manager-js@2.2.1` +- Upgraded to `http-js@2.3.0` +- Upgraded to `log-js@2.2.1` +- Upgraded to `updater-js@2.4.0` + +## \[2.0.11] + +### Dependencies + +- Upgraded to `opener-js@2.2.5` + +## \[2.0.10] + +### Dependencies + +- Upgraded to `notification-js@2.2.1` +- Upgraded to `opener-js@2.2.4` + +## \[2.0.9] + +### Dependencies + +- Upgraded to `opener-js@2.2.3` +- Upgraded to `updater-js@2.3.1` + +## \[2.0.8] + +### Dependencies + +- Upgraded to `opener-js@2.2.2` + +## \[2.0.7] + +### Dependencies + +- Upgraded to `updater-js@2.3.0` +- Upgraded to `opener-js@2.2.1` + +## \[2.0.6] + +### Dependencies + +- Upgraded to `barcode-scanner-js@2.1.0` +- Upgraded to `biometric-js@2.1.0` +- Upgraded to `cli-js@2.1.0` +- Upgraded to `clipboard-manager-js@2.1.0` +- Upgraded to `dialog-js@2.1.0` +- Upgraded to `fs-js@2.1.0` +- Upgraded to `global-shortcut-js@2.1.0` +- Upgraded to `http-js@2.1.0` +- Upgraded to `log-js@2.1.0` +- Upgraded to `nfc-js@2.1.0` +- Upgraded to `notification-js@2.1.0` +- Upgraded to `opener-js@2.1.0` +- Upgraded to `os-js@2.1.0` +- Upgraded to `process-js@2.1.0` +- Upgraded to `shell-js@2.1.0` +- Upgraded to `store-js@2.2.0` +- Upgraded to `updater-js@2.1.0` + +## \[2.0.5] + +### Dependencies + +- Upgraded to `fs-js@2.0.4` +- Upgraded to `dialog-js@2.0.2` +- Upgraded to `http-js@2.0.2` + +## \[2.0.4] + +### Dependencies + +- Upgraded to `log-js@2.0.2` + +## \[2.0.3] + +### Dependencies + +- Upgraded to `clipboard-manager-js@2.0.1` +- Upgraded to `log-js@2.0.1` +- Upgraded to `fs-js@2.0.3` +- Upgraded to `opener-js@2.0.0` + +## \[2.0.2] + +### Dependencies + +- Upgraded to `fs-js@2.0.2` + +## \[2.0.1] + +### Dependencies + +- Upgraded to `dialog-js@2.0.1` +- Upgraded to `fs-js@2.0.1` +- Upgraded to `http-js@2.0.1` +- Upgraded to `shell-js@2.0.1` +- Upgraded to `store-js@2.1.0` + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +### Dependencies + +- Upgraded to `barcode-scanner-js@2.0.0` +- Upgraded to `biometric-js@2.0.0` +- Upgraded to `cli-js@2.0.0` +- Upgraded to `clipboard-manager-js@2.0.0` +- Upgraded to `fs-js@2.0.0` +- Upgraded to `dialog-js@2.0.0` +- Upgraded to `global-shortcut-js@2.0.0` +- Upgraded to `http-js@2.0.0` +- Upgraded to `log-js@2.0.0` +- Upgraded to `nfc-js@2.0.0` +- Upgraded to `notification-js@2.0.0` +- Upgraded to `os-js@2.0.0` +- Upgraded to `process-js@2.0.0` +- Upgraded to `shell-js@2.0.0` +- Upgraded to `store-js@2.0.0` +- Upgraded to `updater-js@2.0.0` + +## \[2.0.0-rc.5] + +### Dependencies + +- Upgraded to `store-js@2.0.0-rc.2` + +## \[2.0.0-rc.4] + +### Dependencies + +- Upgraded to `barcode-scanner-js@2.0.0-rc.2` +- Upgraded to `clipboard-manager-js@2.0.0-rc.2` + +## \[2.0.0-rc.3] + +### Dependencies + +- Upgraded to `updater-js@2.0.0-rc.2` + +## \[2.0.0-rc.2] + +### Dependencies + +- Upgraded to `barcode-scanner-js@2.0.0-rc.1` +- Upgraded to `notification-js@2.0.0-rc.1` +- Upgraded to `dialog-js@2.0.0-rc.1` +- Upgraded to `biometric-js@2.0.0-rc.1` +- Upgraded to `cli-js@2.0.0-rc.1` +- Upgraded to `clipboard-manager-js@2.0.0-rc.1` +- Upgraded to `fs-js@2.0.0-rc.2` +- Upgraded to `global-shortcut-js@2.0.0-rc.1` +- Upgraded to `http-js@2.0.0-rc.2` +- Upgraded to `log-js@2.0.0-rc.1` +- Upgraded to `nfc-js@2.0.0-rc.1` +- Upgraded to `os-js@2.0.0-rc.1` +- Upgraded to `process-js@2.0.0-rc.1` +- Upgraded to `shell-js@2.0.0-rc.1` +- Upgraded to `store-js@2.0.0-rc.1` +- Upgraded to `updater-js@2.0.0-rc.1` + +## \[2.0.0-rc.1] + +### Dependencies + +- Upgraded to `http-js@2.0.0-rc.1` +- Upgraded to `fs-js@2.0.0-rc.1` + +## \[2.0.0-rc.0] + +### Dependencies + +- Upgraded to `barcode-scanner-js@2.0.0-rc.0` +- Upgraded to `biometric-js@2.0.0-rc.0` +- Upgraded to `cli-js@2.0.0-rc.0` +- Upgraded to `clipboard-manager-js@2.0.0-rc.0` +- Upgraded to `dialog-js@2.0.0-rc.0` +- Upgraded to `fs-js@2.0.0-rc.0` +- Upgraded to `global-shortcut-js@2.0.0-rc.0` +- Upgraded to `http-js@2.0.0-rc.0` +- Upgraded to `log-js@2.0.0-rc.0` +- Upgraded to `nfc-js@2.0.0-rc.0` +- Upgraded to `notification-js@2.0.0-rc.0` +- Upgraded to `os-js@2.0.0-rc.0` +- Upgraded to `process-js@2.0.0-rc.0` +- Upgraded to `shell-js@2.0.0-rc.0` +- Upgraded to `updater-js@2.0.0-rc.0` + +## \[2.0.0-beta.12] + +### Dependencies + +- Upgraded to `barcode-scanner-js@2.0.0-beta.8` +- Upgraded to `biometric-js@2.0.0-beta.8` +- Upgraded to `cli-js@2.0.0-beta.8` +- Upgraded to `clipboard-manager-js@2.1.0-beta.6` +- Upgraded to `dialog-js@2.0.0-beta.8` +- Upgraded to `fs-js@2.0.0-beta.8` +- Upgraded to `global-shortcut-js@2.0.0-beta.8` +- Upgraded to `http-js@2.0.0-beta.9` +- Upgraded to `log-js@2.0.0-beta.9` +- Upgraded to `nfc-js@2.0.0-beta.8` +- Upgraded to `notification-js@2.0.0-beta.8` +- Upgraded to `os-js@2.0.0-beta.8` +- Upgraded to `process-js@2.0.0-beta.8` +- Upgraded to `shell-js@2.0.0-beta.9` +- Upgraded to `updater-js@2.0.0-beta.8` + +## \[2.0.0-beta.11] + +### Dependencies + +- Upgraded to `global-shortcut-js@2.0.0-beta.7` +- Upgraded to `http-js@2.0.0-beta.8` +- Upgraded to `os-js@2.0.0-beta.7` +- Upgraded to `barcode-scanner-js@2.0.0-beta.7` +- Upgraded to `biometric-js@2.0.0-beta.7` +- Upgraded to `cli-js@2.0.0-beta.7` +- Upgraded to `clipboard-manager-js@2.1.0-beta.5` +- Upgraded to `dialog-js@2.0.0-beta.7` +- Upgraded to `fs-js@2.0.0-beta.7` +- Upgraded to `log-js@2.0.0-beta.8` +- Upgraded to `nfc-js@2.0.0-beta.7` +- Upgraded to `notification-js@2.0.0-beta.7` +- Upgraded to `process-js@2.0.0-beta.7` +- Upgraded to `shell-js@2.0.0-beta.8` +- Upgraded to `updater-js@2.0.0-beta.7` + +## \[2.0.0-beta.10] + +### Dependencies + +- Upgraded to `os-js@2.0.0-beta.6` +- Upgraded to `barcode-scanner-js@2.0.0-beta.6` +- Upgraded to `biometric-js@2.0.0-beta.6` +- Upgraded to `cli-js@2.0.0-beta.6` +- Upgraded to `clipboard-manager-js@2.1.0-beta.4` +- Upgraded to `dialog-js@2.0.0-beta.6` +- Upgraded to `fs-js@2.0.0-beta.6` +- Upgraded to `global-shortcut-js@2.0.0-beta.6` +- Upgraded to `http-js@2.0.0-beta.7` +- Upgraded to `log-js@2.0.0-beta.7` +- Upgraded to `nfc-js@2.0.0-beta.6` +- Upgraded to `notification-js@2.0.0-beta.6` +- Upgraded to `process-js@2.0.0-beta.6` +- Upgraded to `shell-js@2.0.0-beta.7` +- Upgraded to `updater-js@2.0.0-beta.6` + +## \[2.0.0-beta.9] + +### Dependencies + +- Upgraded to `http-js@2.0.0-beta.6` + +## \[2.0.0-beta.8] + +### Dependencies + +- Upgraded to `barcode-scanner-js@2.0.0-beta.5` +- Upgraded to `biometric-js@2.0.0-beta.5` +- Upgraded to `cli-js@2.0.0-beta.5` +- Upgraded to `clipboard-manager-js@2.1.0-beta.3` +- Upgraded to `dialog-js@2.0.0-beta.5` +- Upgraded to `fs-js@2.0.0-beta.5` +- Upgraded to `global-shortcut-js@2.0.0-beta.5` +- Upgraded to `http-js@2.0.0-beta.5` +- Upgraded to `log-js@2.0.0-beta.6` +- Upgraded to `nfc-js@2.0.0-beta.5` +- Upgraded to `notification-js@2.0.0-beta.5` +- Upgraded to `os-js@2.0.0-beta.5` +- Upgraded to `process-js@2.0.0-beta.5` +- Upgraded to `shell-js@2.0.0-beta.6` +- Upgraded to `updater-js@2.0.0-beta.5` + +## \[2.0.0-beta.7] + +### Dependencies + +- Upgraded to `http-js@2.0.0-beta.4` +- Upgraded to `barcode-scanner-js@2.0.0-beta.4` +- Upgraded to `biometric-js@2.0.0-beta.4` +- Upgraded to `cli-js@2.0.0-beta.4` +- Upgraded to `clipboard-manager-js@2.1.0-beta.2` +- Upgraded to `dialog-js@2.0.0-beta.4` +- Upgraded to `fs-js@2.0.0-beta.4` +- Upgraded to `global-shortcut-js@2.0.0-beta.4` +- Upgraded to `log-js@2.0.0-beta.5` +- Upgraded to `nfc-js@2.0.0-beta.4` +- Upgraded to `notification-js@2.0.0-beta.4` +- Upgraded to `os-js@2.0.0-beta.4` +- Upgraded to `process-js@2.0.0-beta.4` +- Upgraded to `shell-js@2.0.0-beta.5` +- Upgraded to `updater-js@2.0.0-beta.4` + +## \[2.0.0-beta.6] + +### Dependencies + +- Upgraded to `shell-js@2.0.0-beta.4` + +## \[2.0.0-beta.5] + +### Dependencies + +- Upgraded to `global-shortcut-js@2.0.0-beta.3` +- Upgraded to `barcode-scanner-js@2.0.0-beta.3` +- Upgraded to `biometric-js@2.0.0-beta.3` +- Upgraded to `cli-js@2.0.0-beta.3` +- Upgraded to `clipboard-manager-js@2.1.0-beta.1` +- Upgraded to `dialog-js@2.0.0-beta.3` +- Upgraded to `fs-js@2.0.0-beta.3` +- Upgraded to `http-js@2.0.0-beta.3` +- Upgraded to `log-js@2.0.0-beta.4` +- Upgraded to `nfc-js@2.0.0-beta.3` +- Upgraded to `notification-js@2.0.0-beta.3` +- Upgraded to `os-js@2.0.0-beta.3` +- Upgraded to `process-js@2.0.0-beta.3` +- Upgraded to `shell-js@2.0.0-beta.3` +- Upgraded to `updater-js@2.0.0-beta.3` + +## \[2.0.0-beta.4] + +### Dependencies + +- Upgraded to `log-js@2.0.0-beta.3` + +## \[2.0.0-beta.3] + +### Dependencies + +- Upgraded to `clipboard-manager-js@2.1.0-beta.0` + +## \[2.0.0-beta.2] + +### Dependencies + +- Upgraded to `clipboard-manager-js@2.0.0-beta.2` +- Upgraded to `dialog-js@2.0.0-beta.2` +- Upgraded to `fs-js@2.0.0-beta.2` +- Upgraded to `http-js@2.0.0-beta.2` +- Upgraded to `shell-js@2.0.0-beta.2` +- Upgraded to `barcode-scanner-js@2.0.0-beta.2` +- Upgraded to `biometric-js@2.0.0-beta.2` +- Upgraded to `cli-js@2.0.0-beta.2` +- Upgraded to `global-shortcut-js@2.0.0-beta.2` +- Upgraded to `log-js@2.0.0-beta.2` +- Upgraded to `nfc-js@2.0.0-beta.2` +- Upgraded to `notification-js@2.0.0-beta.2` +- Upgraded to `os-js@2.0.0-beta.2` +- Upgraded to `process-js@2.0.0-beta.2` +- Upgraded to `updater-js@2.0.0-beta.2` + +## \[2.0.0-beta.1] + +### Dependencies + +- Upgraded to `barcode-scanner-js@2.0.0-beta.1` +- Upgraded to `biometric-js@2.0.0-beta.1` +- Upgraded to `cli-js@2.0.0-beta.1` +- Upgraded to `clipboard-manager-js@2.0.0-beta.1` +- Upgraded to `dialog-js@2.0.0-beta.1` +- Upgraded to `fs-js@2.0.0-beta.1` +- Upgraded to `global-shortcut-js@2.0.0-beta.1` +- Upgraded to `http-js@2.0.0-beta.1` +- Upgraded to `log-js@2.0.0-beta.1` +- Upgraded to `nfc-js@2.0.0-beta.1` +- Upgraded to `notification-js@2.0.0-beta.1` +- Upgraded to `os-js@2.0.0-beta.1` +- Upgraded to `process-js@2.0.0-beta.1` +- Upgraded to `shell-js@2.0.0-beta.1` +- Upgraded to `updater-js@2.0.0-beta.1` + +## \[2.0.0-beta.0] + +### Dependencies + +- Upgraded to `barcode-scanner-js@2.0.0-beta.0` +- Upgraded to `biometric-js@2.0.0-beta.0` +- Upgraded to `cli-js@2.0.0-beta.0` +- Upgraded to `clipboard-manager-js@2.0.0-beta.0` +- Upgraded to `dialog-js@2.0.0-beta.0` +- Upgraded to `fs-js@2.0.0-beta.0` +- Upgraded to `global-shortcut-js@2.0.0-beta.0` +- Upgraded to `http-js@2.0.0-beta.0` +- Upgraded to `log-js@2.0.0-beta.0` +- Upgraded to `nfc-js@2.0.0-beta.0` +- Upgraded to `notification-js@2.0.0-beta.0` +- Upgraded to `os-js@2.0.0-beta.0` +- Upgraded to `process-js@2.0.0-beta.0` +- Upgraded to `shell-js@2.0.0-beta.0` +- Upgraded to `updater-js@2.0.0-beta.0` + +## \[2.0.0-alpha.9] + +### Dependencies + +- Upgraded to `fs-js@2.0.0-alpha.6` + +## \[2.0.0-alpha.8] + +### Dependencies + +- Upgraded to `http-js@2.0.0-alpha.6` + +## \[2.0.0-alpha.7] + +### Dependencies + +- Upgraded to `barcode-scanner-js@2.0.0-alpha.4` +- Upgraded to `cli-js@2.0.0-alpha.5` +- Upgraded to `clipboard-manager-js@2.0.0-alpha.5` +- Upgraded to `dialog-js@2.0.0-alpha.5` +- Upgraded to `fs-js@2.0.0-alpha.5` +- Upgraded to `global-shortcut-js@2.0.0-alpha.5` +- Upgraded to `http-js@2.0.0-alpha.5` +- Upgraded to `log-js@2.0.0-alpha.5` +- Upgraded to `notification-js@2.0.0-alpha.5` +- Upgraded to `os-js@2.0.0-alpha.6` +- Upgraded to `process-js@2.0.0-alpha.5` +- Upgraded to `shell-js@2.0.0-alpha.5` +- Upgraded to `updater-js@2.0.0-alpha.5` +- Upgraded to `biometric-js@2.0.0-alpha.0` +- Upgraded to `nfc-js@2.0.0-alpha.0` + +## \[2.0.0-alpha.6] + +### Dependencies + +- Upgraded to `barcode-scanner-js@2.0.0-alpha.3` +- Upgraded to `cli-js@2.0.0-alpha.4` +- Upgraded to `clipboard-manager-js@2.0.0-alpha.4` +- Upgraded to `dialog-js@2.0.0-alpha.4` +- Upgraded to `fs-js@2.0.0-alpha.4` +- Upgraded to `global-shortcut-js@2.0.0-alpha.4` +- Upgraded to `http-js@2.0.0-alpha.4` +- Upgraded to `log-js@2.0.0-alpha.4` +- Upgraded to `notification-js@2.0.0-alpha.4` +- Upgraded to `os-js@2.0.0-alpha.5` +- Upgraded to `process-js@2.0.0-alpha.4` +- Upgraded to `shell-js@2.0.0-alpha.4` +- Upgraded to `updater-js@2.0.0-alpha.4` + +## \[2.0.0-alpha.5] + +### Dependencies + +- Upgraded to `barcode-scanner-js@2.0.0-alpha.2` +- Upgraded to `cli-js@2.0.0-alpha.3` +- Upgraded to `clipboard-manager-js@2.0.0-alpha.3` +- Upgraded to `dialog-js@2.0.0-alpha.3` +- Upgraded to `fs-js@2.0.0-alpha.3` +- Upgraded to `global-shortcut-js@2.0.0-alpha.3` +- Upgraded to `http-js@2.0.0-alpha.3` +- Upgraded to `log-js@2.0.0-alpha.3` +- Upgraded to `notification-js@2.0.0-alpha.3` +- Upgraded to `os-js@2.0.0-alpha.4` +- Upgraded to `process-js@2.0.0-alpha.3` +- Upgraded to `shell-js@2.0.0-alpha.3` +- Upgraded to `updater-js@2.0.0-alpha.3` + +## \[2.0.0-alpha.4] + +### Dependencies + +- Upgraded to `barcode-scanner-js@2.0.0-alpha.1` +- Upgraded to `cli-js@2.0.0-alpha.2` +- Upgraded to `clipboard-manager-js@2.0.0-alpha.2` +- Upgraded to `dialog-js@2.0.0-alpha.2` +- Upgraded to `fs-js@2.0.0-alpha.2` +- Upgraded to `global-shortcut-js@2.0.0-alpha.2` +- Upgraded to `http-js@2.0.0-alpha.2` +- Upgraded to `log-js@2.0.0-alpha.2` +- Upgraded to `notification-js@2.0.0-alpha.2` +- Upgraded to `os-js@2.0.0-alpha.3` +- Upgraded to `process-js@2.0.0-alpha.2` +- Upgraded to `shell-js@2.0.0-alpha.2` +- Upgraded to `updater-js@2.0.0-alpha.2` + +## \[2.0.0-alpha.3] + +### Dependencies + +- Upgraded to `barcode-scanner-js@2.0.0-alpha.0` + +## \[2.0.0-alpha.2] + +### Dependencies + +- Upgraded to `os-js@2.0.0-alpha.2` + +## \[2.0.0-alpha.1] + +### Dependencies + +- Upgraded to `window-js@2.0.0-alpha.1` +- Upgraded to `fs-js@2.0.0-alpha.1` +- Upgraded to `http-js@2.0.0-alpha.1` +- Upgraded to `os-js@2.0.0-alpha.1` +- Upgraded to `app-js@2.0.0-alpha.1` +- Upgraded to `cli-js@2.0.0-alpha.1` +- Upgraded to `dialog-js@2.0.0-alpha.1` +- Upgraded to `global-shortcut-js@2.0.0-alpha.1` +- Upgraded to `log-js@2.0.0-alpha.1` +- Upgraded to `notification-js@2.0.0-alpha.1` +- Upgraded to `process-js@2.0.0-alpha.1` +- Upgraded to `shell-js@2.0.0-alpha.1` +- Upgraded to `updater-js@2.0.0-alpha.1` + +## \[2.0.0-alpha.0] + +### Dependencies + +- Plugins v2 alpha. diff --git a/packages/kbot/gui/app/examples/api/README.md b/packages/kbot/gui/app/examples/api/README.md new file mode 100644 index 00000000..60de2fca --- /dev/null +++ b/packages/kbot/gui/app/examples/api/README.md @@ -0,0 +1,28 @@ +# API example + +This example demonstrates Tauri's API capabilities using the plugins from this repository. It's used as the main validation app, serving as the testbed of our development process. +In the future, this app will be used on Tauri's integration tests. + +![App screenshot](./screenshot.png?raw=true) + +## Running the example + +- Install dependencies and build packages (Run inside of the repository root) + +```bash +$ pnpm install +$ pnpm build +``` + +- Run the app in development mode (Run inside of this folder `examples/api/`) + +```bash +$ pnpm tauri dev +``` + +- Build an run the release app (Run inside of this folder `examples/api/`) + +```bash +$ pnpm tauri build +$ ./src-tauri/target/release/app +``` diff --git a/packages/kbot/gui/app/examples/api/index.html b/packages/kbot/gui/app/examples/api/index.html new file mode 100644 index 00000000..655165ea --- /dev/null +++ b/packages/kbot/gui/app/examples/api/index.html @@ -0,0 +1,16 @@ + + + + + + Svelte + Vite App + + + +
+ + + diff --git a/packages/kbot/gui/app/examples/api/isolation-dist/index.html b/packages/kbot/gui/app/examples/api/isolation-dist/index.html new file mode 100644 index 00000000..a97caa83 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/isolation-dist/index.html @@ -0,0 +1,10 @@ + + + + + Isolation Secure Script + + + + + diff --git a/packages/kbot/gui/app/examples/api/isolation-dist/index.js b/packages/kbot/gui/app/examples/api/isolation-dist/index.js new file mode 100644 index 00000000..7e2df30d --- /dev/null +++ b/packages/kbot/gui/app/examples/api/isolation-dist/index.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +window.__TAURI_ISOLATION_HOOK__ = (payload) => { + return payload +} diff --git a/packages/kbot/gui/app/examples/api/jsconfig.json b/packages/kbot/gui/app/examples/api/jsconfig.json new file mode 100644 index 00000000..5696a2de --- /dev/null +++ b/packages/kbot/gui/app/examples/api/jsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "moduleResolution": "bundler", + "target": "ESNext", + "module": "ESNext", + /** + * svelte-preprocess cannot figure out whether you have + * a value or a type, so tell TypeScript to enforce using + * `import type` instead of `import` for Types. + */ + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true, + /** + * To have warnings / errors of the Svelte compiler at the + * correct position, enable source maps by default. + */ + "sourceMap": true, + "esModuleInterop": true, + "skipLibCheck": true, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable this if you'd like to use dynamic types. + */ + "checkJs": true + }, + /** + * Use global.d.ts instead of compilerOptions.types + * to avoid limiting type declarations. + */ + "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] +} diff --git a/packages/kbot/gui/app/examples/api/package.json b/packages/kbot/gui/app/examples/api/package.json new file mode 100644 index 00000000..17d46b78 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/package.json @@ -0,0 +1,45 @@ +{ + "name": "api", + "private": true, + "version": "2.0.32", + "type": "module", + "scripts": { + "dev": "vite --clearScreen false", + "build": "vite build", + "serve": "vite preview", + "tauri": "tauri" + }, + "dependencies": { + "@tauri-apps/api": "2.8.0", + "@tauri-apps/plugin-barcode-scanner": "^2.4.0", + "@tauri-apps/plugin-biometric": "^2.3.0", + "@tauri-apps/plugin-cli": "^2.4.0", + "@tauri-apps/plugin-clipboard-manager": "^2.3.0", + "@tauri-apps/plugin-dialog": "^2.4.0", + "@tauri-apps/plugin-fs": "^2.4.2", + "@tauri-apps/plugin-geolocation": "^2.2.0", + "@tauri-apps/plugin-global-shortcut": "^2.3.0", + "@tauri-apps/plugin-haptics": "^2.2.0", + "@tauri-apps/plugin-http": "^2.5.2", + "@tauri-apps/plugin-nfc": "^2.3.1", + "@tauri-apps/plugin-notification": "^2.3.1", + "@tauri-apps/plugin-opener": "^2.5.0", + "@tauri-apps/plugin-os": "^2.3.1", + "@tauri-apps/plugin-process": "^2.3.0", + "@tauri-apps/plugin-shell": "^2.3.1", + "@tauri-apps/plugin-store": "^2.4.0", + "@tauri-apps/plugin-updater": "^2.9.0", + "@tauri-apps/plugin-upload": "^2.3.0", + "@zerodevx/svelte-json-view": "1.0.11" + }, + "devDependencies": { + "@iconify-json/codicon": "^1.2.12", + "@iconify-json/ph": "^1.2.2", + "@sveltejs/vite-plugin-svelte": "^6.0.0", + "@tauri-apps/cli": "2.8.4", + "@unocss/extractor-svelte": "^66.3.3", + "svelte": "^5.20.4", + "unocss": "^66.3.3", + "vite": "^7.0.4" + } +} diff --git a/packages/kbot/gui/app/examples/api/public/tauri_logo.png b/packages/kbot/gui/app/examples/api/public/tauri_logo.png new file mode 100644 index 00000000..2c53b8c4 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/public/tauri_logo.png differ diff --git a/packages/kbot/gui/app/examples/api/screenshot.png b/packages/kbot/gui/app/examples/api/screenshot.png new file mode 100644 index 00000000..c51a4699 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/screenshot.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/.gitignore b/packages/kbot/gui/app/examples/api/src-tauri/.gitignore new file mode 100644 index 00000000..99cb2b7c --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/.gitignore @@ -0,0 +1,10 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# cargo-mobile +.cargo/ + +gen/schemas/*.json +!gen/schemas/desktop-schema.json +!gen/schemas/mobile-schema.json diff --git a/packages/kbot/gui/app/examples/api/src-tauri/.taurignore b/packages/kbot/gui/app/examples/api/src-tauri/.taurignore new file mode 100644 index 00000000..cbff5293 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/.taurignore @@ -0,0 +1 @@ +tauri-plugin-sample/ \ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src-tauri/CHANGELOG.md b/packages/kbot/gui/app/examples/api/src-tauri/CHANGELOG.md new file mode 100644 index 00000000..7e4eaae8 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/CHANGELOG.md @@ -0,0 +1,893 @@ +# Changelog + +## \[2.0.36] + +### Dependencies + +- Upgraded to `dialog@2.4.0` +- Upgraded to `log@2.7.0` + +## \[2.0.35] + +### Dependencies + +- Upgraded to `shell@2.3.1` + +## \[2.0.34] + +### Dependencies + +- Upgraded to `notification@2.3.1` + +## \[2.0.33] + +### Dependencies + +- Upgraded to `fs@2.4.2` +- Upgraded to `nfc@2.3.1` +- Upgraded to `opener@2.5.0` +- Upgraded to `os@2.3.1` +- Upgraded to `store@2.4.0` +- Upgraded to `dialog@2.3.3` +- Upgraded to `http@2.5.2` + +## \[2.0.32] + +### Dependencies + +- Upgraded to `dialog@2.3.2` + +## \[2.0.31] + +### Dependencies + +- Upgraded to `barcode-scanner@2.4.0` +- Upgraded to `fs@2.4.1` +- Upgraded to `dialog@2.3.1` +- Upgraded to `http@2.5.1` + +## \[2.0.30] + +### Dependencies + +- Upgraded to `barcode-scanner@2.3.0` +- Upgraded to `biometric@2.3.0` +- Upgraded to `cli@2.4.0` +- Upgraded to `clipboard-manager@2.3.0` +- Upgraded to `fs@2.4.0` +- Upgraded to `dialog@2.3.0` +- Upgraded to `geolocation@2.3.0` +- Upgraded to `global-shortcut@2.3.0` +- Upgraded to `opener@2.4.0` +- Upgraded to `haptics@2.3.0` +- Upgraded to `http@2.5.0` +- Upgraded to `log@2.6.0` +- Upgraded to `nfc@2.3.0` +- Upgraded to `notification@2.3.0` +- Upgraded to `os@2.3.0` +- Upgraded to `process@2.3.0` +- Upgraded to `shell@2.3.0` +- Upgraded to `store@2.3.0` +- Upgraded to `updater@2.9.0` + +## \[2.0.29] + +### Dependencies + +- Upgraded to `cli@2.3.0` +- Upgraded to `log@2.5.1` +- Upgraded to `opener@2.3.1` + +## \[2.0.28] + +### Dependencies + +- Upgraded to `updater@2.8.1` + +## \[2.0.27] + +### Dependencies + +- Upgraded to `updater@2.8.0` +- Upgraded to `barcode-scanner@2.2.1` +- Upgraded to `biometric@2.2.2` +- Upgraded to `cli@2.2.1` +- Upgraded to `clipboard-manager@2.2.3` +- Upgraded to `geolocation@2.2.5` +- Upgraded to `haptics@2.2.5` +- Upgraded to `nfc@2.2.1` +- Upgraded to `notification@2.2.3` +- Upgraded to `os@2.2.2` +- Upgraded to `process@2.2.2` +- Upgraded to `shell@2.2.2` +- Upgraded to `store@2.2.1` +- Upgraded to `log@2.5.0` +- Upgraded to `opener@2.3.0` + +## \[2.0.26] + +### Dependencies + +- Upgraded to `fs@2.3.0` +- Upgraded to `global-shortcut@2.2.1` +- Upgraded to `http@2.4.4` +- Upgraded to `opener@2.2.7` +- Upgraded to `dialog@2.2.2` + +## \[2.0.25] + +### Dependencies + +- Upgraded to `log@2.4.0` +- Upgraded to `biometric@2.2.1` +- Upgraded to `updater@2.7.1` + +## \[2.0.24] + +### Dependencies + +- Upgraded to `http@2.4.3` +- Upgraded to `shell@2.2.1` +- Upgraded to `fs@2.2.1` +- Upgraded to `process@2.2.1` +- Upgraded to `updater@2.7.0` +- Upgraded to `dialog@2.2.1` + +## \[2.0.23] + +### Dependencies + +- Upgraded to `http@2.4.2` +- Upgraded to `updater@2.6.1` + +## \[2.0.22] + +### Dependencies + +- Upgraded to `http@2.4.1` + +## \[2.0.21] + +### Dependencies + +- Upgraded to `log@2.3.1` + +## \[2.0.20] + +### Dependencies + +- Upgraded to `clipboard-manager@2.2.2` +- Upgraded to `geolocation@2.2.4` +- Upgraded to `haptics@2.2.4` +- Upgraded to `notification@2.2.2` +- Upgraded to `os@2.2.1` +- Upgraded to `http@2.4.0` +- Upgraded to `log@2.3.0` +- Upgraded to `updater@2.6.0` + +## \[2.0.19] + +### Dependencies + +- Upgraded to `log@2.2.3` +- Upgraded to `opener@2.2.6` + +## \[2.0.18] + +### Dependencies + +- Upgraded to `log@2.2.2` +- Upgraded to `updater@2.5.1` + +## \[2.0.17] + +### Dependencies + +- Upgraded to `updater@2.5.0` + +## \[2.0.16] + +### Dependencies + +- Upgraded to `clipboard-manager@2.2.1` +- Upgraded to `http@2.3.0` +- Upgraded to `log@2.2.1` +- Upgraded to `updater@2.4.0` + +## \[2.0.15] + +### Dependencies + +- Upgraded to `haptics@2.2.3` +- Upgraded to `geolocation@2.2.3` +- Upgraded to `opener@2.2.5` + +## \[2.0.14] + +### Dependencies + +- Upgraded to `geolocation@2.2.2` +- Upgraded to `haptics@2.2.2` +- Upgraded to `notification@2.2.1` +- Upgraded to `opener@2.2.4` + +## \[2.0.13] + +### Dependencies + +- Upgraded to `geolocation@2.2.1` +- Upgraded to `haptics@2.2.1` + +## \[2.0.12] + +### Dependencies + +- Upgraded to `opener@2.2.3` +- Upgraded to `updater@2.3.1` + +## \[2.0.11] + +### Dependencies + +- Upgraded to `opener@2.2.2` + +## \[2.0.10] + +### Dependencies + +- Upgraded to `updater@2.3.0` +- Upgraded to `opener@2.2.1` + +## \[2.0.9] + +### Dependencies + +- Upgraded to `barcode-scanner@2.1.0` +- Upgraded to `biometric@2.1.0` +- Upgraded to `cli@2.1.0` +- Upgraded to `clipboard-manager@2.1.0` +- Upgraded to `dialog@2.1.0` +- Upgraded to `fs@2.2.0` +- Upgraded to `geolocation@2.1.0` +- Upgraded to `global-shortcut@2.1.0` +- Upgraded to `haptics@2.1.0` +- Upgraded to `http@2.1.0` +- Upgraded to `log@2.1.0` +- Upgraded to `nfc@2.1.0` +- Upgraded to `notification@2.1.0` +- Upgraded to `opener@2.1.0` +- Upgraded to `os@2.1.0` +- Upgraded to `process@2.1.0` +- Upgraded to `shell@2.1.0` +- Upgraded to `store@2.2.0` +- Upgraded to `updater@2.2.0` + +## \[2.0.8] + +### Dependencies + +- Upgraded to `fs@2.1.1` +- Upgraded to `dialog@2.0.5` +- Upgraded to `http@2.0.5` + +## \[2.0.7] + +### Dependencies + +- Upgraded to `log@2.0.4` + +## \[2.0.6] + +### Dependencies + +- Upgraded to `fs@2.1.0` +- Upgraded to `updater@2.1.0` +- Upgraded to `dialog@2.0.4` +- Upgraded to `log-plugin@2.0.3` +- Upgraded to `http@2.0.4` +- Upgraded to `opener@2.0.0` + +## \[2.0.5] + +### Dependencies + +- Upgraded to `clipboard-manager@2.0.2` +- Upgraded to `log-plugin@2.0.2` + +## \[2.0.4] + +### Dependencies + +- Upgraded to `fs@2.0.3` +- Upgraded to `dialog@2.0.3` +- Upgraded to `http@2.0.3` + +## \[2.0.3] + +### Dependencies + +- Upgraded to `dialog@2.0.2` +- Upgraded to `fs@2.0.2` +- Upgraded to `http@2.0.2` +- Upgraded to `shell@2.0.2` +- Upgraded to `store@2.1.0` + +## \[2.0.2] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +### Dependencies + +- Upgraded to `barcode-scanner@2.0.1` +- Upgraded to `biometric@2.0.1` +- Upgraded to `cli@2.0.1` +- Upgraded to `clipboard-manager@2.0.1` +- Upgraded to `fs@2.0.1` +- Upgraded to `dialog@2.0.1` +- Upgraded to `geolocation@2.0.1` +- Upgraded to `global-shortcut@2.0.1` +- Upgraded to `haptics@2.0.1` +- Upgraded to `http@2.0.1` +- Upgraded to `log-plugin@2.0.1` +- Upgraded to `nfc@2.0.1` +- Upgraded to `notification@2.0.1` +- Upgraded to `os@2.0.1` +- Upgraded to `process@2.0.1` +- Upgraded to `shell@2.0.1` +- Upgraded to `store@2.0.1` +- Upgraded to `updater@2.0.2` + +## \[2.0.1] + +### Dependencies + +- Upgraded to `updater@2.0.1` + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +### Dependencies + +- Upgraded to `barcode-scanner@2.0.0` +- Upgraded to `biometric@2.0.0` +- Upgraded to `cli@2.0.0` +- Upgraded to `clipboard-manager@2.0.0` +- Upgraded to `fs@2.0.0` +- Upgraded to `dialog@2.0.0` +- Upgraded to `global-shortcut@2.0.0` +- Upgraded to `http@2.0.0` +- Upgraded to `log-plugin@2.0.0` +- Upgraded to `nfc@2.0.0` +- Upgraded to `notification@2.0.0` +- Upgraded to `os@2.0.0` +- Upgraded to `process@2.0.0` +- Upgraded to `shell@2.0.0` +- Upgraded to `store@2.0.0` +- Upgraded to `updater@2.0.0` + +## \[2.0.0-rc.8] + +### Dependencies + +- Upgraded to `cli@2.0.0-rc.2` +- Upgraded to `dialog@2.0.0-rc.8` +- Upgraded to `fs@2.0.0-rc.6` +- Upgraded to `shell@2.0.0-rc.4` +- Upgraded to `store@2.0.0-rc.4` +- Upgraded to `updater@2.0.0-rc.4` +- Upgraded to `http@2.0.0-rc.6` + +## \[2.0.0-rc.7] + +### Dependencies + +- Upgraded to `clipboard-manager@2.0.0-rc.4` +- Upgraded to `fs@2.0.0-rc.5` +- Upgraded to `notification@2.0.0-rc.5` +- Upgraded to `dialog@2.0.0-rc.7` +- Upgraded to `http@2.0.0-rc.5` + +## \[2.0.0-rc.6] + +### Dependencies + +- Upgraded to `dialog@2.0.0-rc.6` +- Upgraded to `fs@2.0.0-rc.4` +- Upgraded to `http@2.0.0-rc.4` + +## \[2.0.0-rc.5] + +### Dependencies + +- Upgraded to `barcode-scanner@2.0.0-rc.4` +- Upgraded to `notification@2.0.0-rc.4` + +## \[2.0.0-rc.4] + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.3` +- Upgraded to `dialog@2.0.0-rc.5` +- Upgraded to `updater@2.0.0-rc.3` +- Upgraded to `http@2.0.0-rc.3` + +## \[2.0.0-rc.3] + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.2` +- Upgraded to `dialog@2.0.0-rc.4` +- Upgraded to `http@2.0.0-rc.2` + +## \[2.0.0-rc.2] + +### Dependencies + +- Upgraded to `barcode-scanner@2.0.0-rc.3` +- Upgraded to `notification@2.0.0-rc.3` +- Upgraded to `dialog@2.0.0-rc.3` +- Upgraded to `fs@2.0.0-rc.1` +- Upgraded to `global-shortcut@2.0.0-rc.2` +- Upgraded to `store@2.0.0-rc.3` +- Upgraded to `biometric@2.0.0-rc.3` +- Upgraded to `cli@2.0.0-rc.1` +- Upgraded to `clipboard-manager@2.0.0-rc.3` +- Upgraded to `http@2.0.0-rc.1` +- Upgraded to `log-plugin@2.0.0-rc.2` +- Upgraded to `nfc@2.0.0-rc.3` +- Upgraded to `os@2.0.0-rc.1` +- Upgraded to `process@2.0.0-rc.1` +- Upgraded to `shell@2.0.0-rc.3` +- Upgraded to `updater@2.0.0-rc.2` + +## \[2.0.0-rc.1] + +### Dependencies + +- Upgraded to `barcode-scanner@2.0.0-rc.2` +- Upgraded to `biometric@2.0.0-rc.2` +- Upgraded to `clipboard-manager@2.0.0-rc.2` +- Upgraded to `dialog@2.0.0-rc.2` +- Upgraded to `log-plugin@2.0.0-rc.1` +- Upgraded to `nfc@2.0.0-rc.2` +- Upgraded to `notification@2.0.0-rc.2` +- Upgraded to `shell@2.0.0-rc.2` + +## \[2.0.0-rc.0] + +### Dependencies + +- Upgraded to `dialog@2.0.0-rc.1` +- Upgraded to `updater@2.0.0-rc.1` +- Upgraded to `barcode-scanner@2.0.0-rc.1` +- Upgraded to `clipboard-manager@2.0.0-rc.1` +- Upgraded to `global-shortcut@2.0.0-rc.1` +- Upgraded to `biometric@2.0.0-rc.1` +- Upgraded to `nfc@2.0.0-rc.1` +- Upgraded to `notification@2.0.0-rc.1` +- Upgraded to `shell@2.0.0-rc.1` + +## \[2.0.0-beta.17] + +### Dependencies + +- Upgraded to `barcode-scanner@2.0.0-rc.0` +- Upgraded to `biometric@2.0.0-rc.0` +- Upgraded to `cli@2.0.0-rc.0` +- Upgraded to `clipboard-manager@2.0.0-rc.0` +- Upgraded to `dialog@2.0.0-rc.0` +- Upgraded to `fs@2.0.0-rc.0` +- Upgraded to `global-shortcut@2.0.0-rc.0` +- Upgraded to `http@2.0.0-rc.0` +- Upgraded to `log-plugin@2.0.0-rc.0` +- Upgraded to `nfc@2.0.0-rc.0` +- Upgraded to `notification@2.0.0-rc.0` +- Upgraded to `os@2.0.0-rc.0` +- Upgraded to `process@2.0.0-rc.0` +- Upgraded to `shell@2.0.0-rc.0` +- Upgraded to `updater@2.0.0-rc.0` + +## \[2.0.0-beta.16] + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.12` +- Upgraded to `barcode-scanner@2.0.0-beta.10` +- Upgraded to `biometric@2.0.0-beta.9` +- Upgraded to `cli@2.0.0-beta.9` +- Upgraded to `clipboard-manager@2.1.0-beta.7` +- Upgraded to `dialog@2.0.0-beta.12` +- Upgraded to `global-shortcut@2.0.0-beta.9` +- Upgraded to `http@2.0.0-beta.13` +- Upgraded to `log-plugin@2.0.0-beta.10` +- Upgraded to `nfc@2.0.0-beta.9` +- Upgraded to `notification@2.0.0-beta.12` +- Upgraded to `os@2.0.0-beta.9` +- Upgraded to `process@2.0.0-beta.9` +- Upgraded to `shell@2.0.0-beta.10` +- Upgraded to `updater@2.0.0-beta.12` + +## \[2.0.0-beta.15] + +### Dependencies + +- Upgraded to `log-plugin@2.0.0-beta.9` + +## \[2.0.0-beta.14] + +### Dependencies + +- Upgraded to `notification@2.0.0-beta.11` +- Upgraded to `updater@2.0.0-beta.11` + +## \[2.0.0-beta.13] + +### Dependencies + +- Upgraded to `biometric@2.0.0-beta.8` +- Upgraded to `global-shortcut@2.0.0-beta.8` +- Upgraded to `http@2.0.0-beta.12` +- Upgraded to `barcode-scanner@2.0.0-beta.9` +- Upgraded to `cli@2.0.0-beta.8` +- Upgraded to `clipboard-manager@2.1.0-beta.6` +- Upgraded to `dialog@2.0.0-beta.11` +- Upgraded to `fs@2.0.0-beta.11` +- Upgraded to `log-plugin@2.0.0-beta.8` +- Upgraded to `nfc@2.0.0-beta.8` +- Upgraded to `notification@2.0.0-beta.10` +- Upgraded to `os@2.0.0-beta.8` +- Upgraded to `process@2.0.0-beta.8` +- Upgraded to `shell@2.0.0-beta.9` +- Upgraded to `updater@2.0.0-beta.10` + +## \[2.0.0-beta.12] + +### Dependencies + +- Upgraded to `clipboard-manager@2.1.0-beta.5` +- Upgraded to `fs@2.0.0-beta.10` +- Upgraded to `updater@2.0.0-beta.9` +- Upgraded to `notification@2.0.0-beta.9` +- Upgraded to `os@2.0.0-beta.7` +- Upgraded to `barcode-scanner@2.0.0-beta.8` +- Upgraded to `biometric@2.0.0-beta.7` +- Upgraded to `cli@2.0.0-beta.7` +- Upgraded to `dialog@2.0.0-beta.10` +- Upgraded to `global-shortcut@2.0.0-beta.7` +- Upgraded to `http@2.0.0-beta.11` +- Upgraded to `log-plugin@2.0.0-beta.7` +- Upgraded to `nfc@2.0.0-beta.7` +- Upgraded to `process@2.0.0-beta.7` +- Upgraded to `shell@2.0.0-beta.8` + +## \[2.0.0-beta.11] + +### Dependencies + +- Upgraded to `notification@2.0.0-beta.8` +- Upgraded to `http@2.0.0-beta.10` +- Upgraded to `updater@2.0.0-beta.8` + +## \[2.0.0-beta.10] + +### Dependencies + +- Upgraded to `barcode-scanner@2.0.0-beta.7` +- Upgraded to `biometric@2.0.0-beta.6` +- Upgraded to `clipboard-manager@2.1.0-beta.4` +- Upgraded to `nfc@2.0.0-beta.6` +- Upgraded to `notification@2.0.0-beta.7` +- Upgraded to `shell@2.0.0-beta.7` +- Upgraded to `cli@2.0.0-beta.6` +- Upgraded to `dialog@2.0.0-beta.9` +- Upgraded to `fs@2.0.0-beta.9` +- Upgraded to `global-shortcut@2.0.0-beta.6` +- Upgraded to `http@2.0.0-beta.9` +- Upgraded to `log-plugin@2.0.0-beta.6` +- Upgraded to `os@2.0.0-beta.6` +- Upgraded to `process@2.0.0-beta.6` +- Upgraded to `updater@2.0.0-beta.7` + +## \[2.0.0-beta.9] + +### Dependencies + +- Upgraded to `clipboard-manager@2.1.0-beta.3` +- Upgraded to `dialog@2.0.0-beta.8` +- Upgraded to `http@2.0.0-beta.8` +- Upgraded to `notification@2.0.0-beta.6` +- Upgraded to `shell@2.0.0-beta.6` +- Upgraded to `barcode-scanner@2.0.0-beta.6` +- Upgraded to `biometric@2.0.0-beta.5` +- Upgraded to `nfc@2.0.0-beta.5` +- Upgraded to `cli@2.0.0-beta.5` +- Upgraded to `fs@2.0.0-beta.8` +- Upgraded to `global-shortcut@2.0.0-beta.5` +- Upgraded to `log-plugin@2.0.0-beta.5` +- Upgraded to `os@2.0.0-beta.5` +- Upgraded to `process@2.0.0-beta.5` +- Upgraded to `updater@2.0.0-beta.6` + +## \[2.0.0-beta.8] + +### Dependencies + +- Upgraded to `shell@2.0.0-beta.5` + +## \[2.0.0-beta.7] + +### Dependencies + +- Upgraded to `clipboard-manager@2.1.0-beta.2` +- Upgraded to `global-shortcut@2.0.0-beta.4` +- Upgraded to `barcode-scanner@2.0.0-beta.5` +- Upgraded to `biometric@2.0.0-beta.4` +- Upgraded to `cli@2.0.0-beta.4` +- Upgraded to `dialog@2.0.0-beta.7` +- Upgraded to `fs@2.0.0-beta.7` +- Upgraded to `http@2.0.0-beta.7` +- Upgraded to `log-plugin@2.0.0-beta.4` +- Upgraded to `nfc@2.0.0-beta.4` +- Upgraded to `notification@2.0.0-beta.5` +- Upgraded to `os@2.0.0-beta.4` +- Upgraded to `process@2.0.0-beta.4` +- Upgraded to `shell@2.0.0-beta.4` +- Upgraded to `updater@2.0.0-beta.5` + +## \[2.0.0-beta.6] + +### Dependencies + +- Upgraded to `notification@2.0.0-beta.4` +- Upgraded to `barcode-scanner@2.0.0-beta.4` +- Upgraded to `dialog@2.0.0-beta.6` +- Upgraded to `fs@2.0.0-beta.6` +- Upgraded to `http@2.0.0-beta.6` + +## \[2.0.0-beta.5] + +### Dependencies + +- Upgraded to `clipboard-manager@2.1.0-beta.1` +- Upgraded to `http@2.0.0-beta.5` +- Upgraded to `updater@2.0.0-beta.4` +- Upgraded to `dialog@2.0.0-beta.5` +- Upgraded to `fs@2.0.0-beta.5` + +## \[2.0.0-beta.4] + +### Dependencies + +- Upgraded to `dialog@2.0.0-beta.4` +- Upgraded to `fs@2.0.0-beta.4` +- Upgraded to `http@2.0.0-beta.4` + +## \[2.0.0-beta.3] + +### Dependencies + +- Upgraded to `clipboard-manager@2.1.0-beta.0` +- Upgraded to `dialog@2.0.0-beta.3` +- Upgraded to `fs@2.0.0-beta.3` +- Upgraded to `http@2.0.0-beta.3` +- Upgraded to `updater@2.0.0-beta.3` +- Upgraded to `barcode-scanner@2.0.0-beta.3` +- Upgraded to `biometric@2.0.0-beta.3` +- Upgraded to `cli@2.0.0-beta.3` +- Upgraded to `global-shortcut@2.0.0-beta.3` +- Upgraded to `log-plugin@2.0.0-beta.3` +- Upgraded to `nfc@2.0.0-beta.3` +- Upgraded to `notification@2.0.0-beta.3` +- Upgraded to `os@2.0.0-beta.3` +- Upgraded to `process@2.0.0-beta.3` +- Upgraded to `shell@2.0.0-beta.3` + +## \[2.0.0-beta.2] + +### Dependencies + +- Upgraded to `clipboard-manager@2.0.0-beta.2` +- Upgraded to `dialog@2.0.0-beta.2` +- Upgraded to `http@2.0.0-beta.2` +- Upgraded to `fs@2.0.0-beta.2` +- Upgraded to `shell@2.0.0-beta.2` +- Upgraded to `barcode-scanner@2.0.0-beta.2` +- Upgraded to `biometric@2.0.0-beta.2` +- Upgraded to `cli@2.0.0-beta.2` +- Upgraded to `global-shortcut@2.0.0-beta.2` +- Upgraded to `log-plugin@2.0.0-beta.2` +- Upgraded to `nfc@2.0.0-beta.2` +- Upgraded to `notification@2.0.0-beta.2` +- Upgraded to `os@2.0.0-beta.2` +- Upgraded to `process@2.0.0-beta.2` +- Upgraded to `updater@2.0.0-beta.2` + +## \[2.0.0-beta.1] + +### Dependencies + +- Upgraded to `barcode-scanner@2.0.0-beta.1` +- Upgraded to `biometric@2.0.0-beta.1` +- Upgraded to `cli@2.0.0-beta.1` +- Upgraded to `clipboard-manager@2.0.0-beta.1` +- Upgraded to `dialog@2.0.0-beta.1` +- Upgraded to `fs@2.0.0-beta.1` +- Upgraded to `global-shortcut@2.0.0-beta.1` +- Upgraded to `http@2.0.0-beta.1` +- Upgraded to `log-plugin@2.0.0-beta.1` +- Upgraded to `nfc@2.0.0-beta.1` +- Upgraded to `notification@2.0.0-beta.1` +- Upgraded to `os@2.0.0-beta.1` +- Upgraded to `process@2.0.0-beta.1` +- Upgraded to `shell@2.0.0-beta.1` +- Upgraded to `updater@2.0.0-beta.1` + +## \[2.0.0-beta.0] + +### Dependencies + +- Upgraded to `barcode-scanner@2.0.0-beta.0` +- Upgraded to `biometric@2.0.0-beta.0` +- Upgraded to `cli@2.0.0-beta.0` +- Upgraded to `clipboard-manager@2.0.0-beta.0` +- Upgraded to `dialog@2.0.0-beta.0` +- Upgraded to `fs@2.0.0-beta.0` +- Upgraded to `global-shortcut@2.0.0-beta.0` +- Upgraded to `http@2.0.0-beta.0` +- Upgraded to `log@2.0.0-beta.0` +- Upgraded to `nfc@2.0.0-beta.0` +- Upgraded to `notification@2.0.0-beta.0` +- Upgraded to `os@2.0.0-beta.0` +- Upgraded to `process@2.0.0-beta.0` +- Upgraded to `shell@2.0.0-beta.0` +- Upgraded to `updater@2.0.0-beta.0` + +## \[2.0.0-alpha.11] + +### Dependencies + +- Upgraded to `fs@2.0.0-alpha.7` +- Upgraded to `dialog@2.0.0-alpha.7` +- Upgraded to `http@2.0.0-alpha.9` + +## \[2.0.0-alpha.10] + +### Dependencies + +- Upgraded to `http@2.0.0-alpha.8` + +## \[2.0.0-alpha.9] + +### Dependencies + +- Upgraded to `barcode-scanner@2.0.0-alpha.4` +- Upgraded to `cli@2.0.0-alpha.6` +- Upgraded to `clipboard-manager@2.0.0-alpha.6` +- Upgraded to `dialog@2.0.0-alpha.6` +- Upgraded to `fs@2.0.0-alpha.6` +- Upgraded to `global-shortcut@2.0.0-alpha.6` +- Upgraded to `http@2.0.0-alpha.7` +- Upgraded to `log-plugin@2.0.0-alpha.6` +- Upgraded to `notification@2.0.0-alpha.7` +- Upgraded to `os@2.0.0-alpha.6` +- Upgraded to `process@2.0.0-alpha.6` +- Upgraded to `shell@2.0.0-alpha.6` +- Upgraded to `updater@2.0.0-alpha.6` +- Upgraded to `biometric@2.0.0-alpha.0` +- Upgraded to `nfc@2.0.0-alpha.0` + +## \[2.0.0-alpha.8] + +### Dependencies + +- Upgraded to `barcode-scanner@2.0.0-alpha.3` +- Upgraded to `cli@2.0.0-alpha.5` +- Upgraded to `clipboard-manager@2.0.0-alpha.5` +- Upgraded to `dialog@2.0.0-alpha.5` +- Upgraded to `fs@2.0.0-alpha.5` +- Upgraded to `global-shortcut@2.0.0-alpha.5` +- Upgraded to `http@2.0.0-alpha.6` +- Upgraded to `log-plugin@2.0.0-alpha.5` +- Upgraded to `notification@2.0.0-alpha.6` +- Upgraded to `os@2.0.0-alpha.5` +- Upgraded to `process@2.0.0-alpha.5` +- Upgraded to `shell@2.0.0-alpha.5` +- Upgraded to `updater@2.0.0-alpha.5` + +## \[2.0.0-alpha.7] + +### Dependencies + +- Upgraded to `barcode-scanner@2.0.0-alpha.2` +- Upgraded to `cli@2.0.0-alpha.4` +- Upgraded to `clipboard-manager@2.0.0-alpha.4` +- Upgraded to `dialog@2.0.0-alpha.4` +- Upgraded to `fs@2.0.0-alpha.4` +- Upgraded to `global-shortcut@2.0.0-alpha.4` +- Upgraded to `http@2.0.0-alpha.5` +- Upgraded to `log-plugin@2.0.0-alpha.4` +- Upgraded to `notification@2.0.0-alpha.5` +- Upgraded to `os@2.0.0-alpha.4` +- Upgraded to `process@2.0.0-alpha.4` +- Upgraded to `shell@2.0.0-alpha.4` +- Upgraded to `updater@2.0.0-alpha.4` + +## \[2.0.0-alpha.6] + +### Dependencies + +- Upgraded to `log-plugin@2.0.0-alpha.3` +- Upgraded to `shell@2.0.0-alpha.3` +- Upgraded to `dialog@2.0.0-alpha.3` +- Upgraded to `notification@2.0.0-alpha.4` +- Upgraded to `updater@2.0.0-alpha.3` +- Upgraded to `global-shortcut@2.0.0-alpha.3` +- Upgraded to `barcode-scanner@2.0.0-alpha.1` +- Upgraded to `cli@2.0.0-alpha.3` +- Upgraded to `clipboard-manager@2.0.0-alpha.3` +- Upgraded to `fs@2.0.0-alpha.3` +- Upgraded to `http@2.0.0-alpha.4` +- Upgraded to `os@2.0.0-alpha.3` +- Upgraded to `process@2.0.0-alpha.3` + +## \[2.0.0-alpha.5] + +### Dependencies + +- Upgraded to `barcode-scanner@2.0.0-alpha.0` + +## \[2.0.0-alpha.4] + +### Dependencies + +- Upgraded to `http@2.0.0-alpha.3` +- Upgraded to `app@2.0.0-alpha.2` +- Upgraded to `cli@2.0.0-alpha.2` +- Upgraded to `clipboard-manager@2.0.0-alpha.2` +- Upgraded to `dialog@2.0.0-alpha.2` +- Upgraded to `fs@2.0.0-alpha.2` +- Upgraded to `global-shortcut@2.0.0-alpha.2` +- Upgraded to `log-plugin@2.0.0-alpha.2` +- Upgraded to `notification@2.0.0-alpha.3` +- Upgraded to `os@2.0.0-alpha.2` +- Upgraded to `process@2.0.0-alpha.2` +- Upgraded to `shell@2.0.0-alpha.2` +- Upgraded to `updater@2.0.0-alpha.2` +- Upgraded to `window@2.0.0-alpha.2` + +## \[2.0.0-alpha.3] + +### Dependencies + +- Upgraded to `http@2.0.0-alpha.2` + +## \[2.0.0-alpha.2] + +### Dependencies + +- Upgraded to `dialog@2.0.0-alpha.1` +- Upgraded to `window@2.0.0-alpha.1` +- Upgraded to `app@2.0.0-alpha.1` +- Upgraded to `cli@2.0.0-alpha.1` +- Upgraded to `fs@2.0.0-alpha.1` +- Upgraded to `global-shortcut@2.0.0-alpha.1` +- Upgraded to `http@2.0.0-alpha.1` +- Upgraded to `log-plugin@2.0.0-alpha.1` +- Upgraded to `notification@2.0.0-alpha.2` +- Upgraded to `os@2.0.0-alpha.1` +- Upgraded to `process@2.0.0-alpha.1` +- Upgraded to `shell@2.0.0-alpha.1` +- Upgraded to `updater@2.0.0-alpha.1` + +## \[2.0.0-alpha.1] + +### Dependencies + +- Updated to latest `notification` + +## \[2.0.0-alpha.0] + +### Dependencies + +- Plugins v2 alpha. diff --git a/packages/kbot/gui/app/examples/api/src-tauri/Cargo.toml b/packages/kbot/gui/app/examples/api/src-tauri/Cargo.toml new file mode 100644 index 00000000..70e52d42 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/Cargo.toml @@ -0,0 +1,71 @@ +[package] +name = "api" +publish = false +version = "2.0.36" +description = "An example Tauri Application showcasing the api" +edition = "2021" +rust-version = { workspace = true } +license = "Apache-2.0 OR MIT" + +[lib] +name = "api_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { workspace = true, features = ["codegen", "isolation"] } + +[dependencies] +serde_json = { workspace = true } +serde = { workspace = true } +tiny_http = "0.12" +time = "0.3" +log = { workspace = true } +tauri-plugin-log = { path = "../../../plugins/log", version = "2.7.0" } +tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.4.2", features = [ + "watch", +] } +tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.3.0" } +tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.4.0" } +tauri-plugin-http = { path = "../../../plugins/http", features = [ + "multipart", + "cookies", +], version = "2.5.2" } +tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.3.1", features = [ + "windows7-compat", +] } +tauri-plugin-os = { path = "../../../plugins/os", version = "2.3.1" } +tauri-plugin-process = { path = "../../../plugins/process", version = "2.3.0" } +tauri-plugin-opener = { path = "../../../plugins/opener", version = "2.5.0" } +tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.3.1" } +tauri-plugin-store = { path = "../../../plugins/store", version = "2.4.0" } +tauri-plugin-upload = { path = "../../../plugins/upload", version = "2.3.0" } + +[dependencies.tauri] +workspace = true +features = [ + "wry", + "common-controls-v6", + "x11", + "image-ico", + "image-png", + "isolation", + "macos-private-api", + "tray-icon", + "protocol-asset", +] + +[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] +tauri-plugin-cli = { path = "../../../plugins/cli", version = "2.4.0" } +tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut", version = "2.3.0" } +tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.9.0" } +tauri-plugin-window-state = { path = "../../../plugins/window-state", version = "2.2.0" } + +[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies] +tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.4.0" } +tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "2.3.1" } +tauri-plugin-biometric = { path = "../../../plugins/biometric/", version = "2.3.0" } +tauri-plugin-geolocation = { path = "../../../plugins/geolocation/", version = "2.3.0" } +tauri-plugin-haptics = { path = "../../../plugins/haptics/", version = "2.3.0" } + +[features] +prod = ["tauri/custom-protocol"] diff --git a/packages/kbot/gui/app/examples/api/src-tauri/Cross.toml b/packages/kbot/gui/app/examples/api/src-tauri/Cross.toml new file mode 100644 index 00000000..23ac27b6 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/Cross.toml @@ -0,0 +1,11 @@ +[build.env] +# must set ICONS_VOLUME, DIST_VOLUME and ISOLATION_VOLUME environment variables +# ICONS_VOLUME: absolute path to the icons folder +# DIST_VOLUME: absolute path to the dist folder +# ISOLATION_VOLUME: absolute path to the isolation dist folder +# this can be done running `$ . .setup-cross.sh` in the examples/api folder +volumes = ["ICONS_VOLUME", "DIST_VOLUME", "ISOLATION_VOLUME"] + +[target.aarch64-unknown-linux-gnu] +image = "aarch64-unknown-linux-gnu:latest" +#image = "ghcr.io/tauri-apps/tauri/aarch64-unknown-linux-gnu:latest" diff --git a/packages/kbot/gui/app/examples/api/src-tauri/Info.plist b/packages/kbot/gui/app/examples/api/src-tauri/Info.plist new file mode 100644 index 00000000..4dcd6aca --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/Info.plist @@ -0,0 +1,14 @@ + + + + + NSCameraUsageDescription + Request camera access for WebRTC + NSMicrophoneUsageDescription + Request microphone access for WebRTC + NSFaceIDUsageDescription + Authenticate with biometrics + NFCReaderUsageDescription + Read and write to NFC tags for testing + + diff --git a/packages/kbot/gui/app/examples/api/src-tauri/build.rs b/packages/kbot/gui/app/examples/api/src-tauri/build.rs new file mode 100644 index 00000000..88537dde --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/build.rs @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +fn main() { + tauri_build::build(); +} diff --git a/packages/kbot/gui/app/examples/api/src-tauri/capabilities/base.json b/packages/kbot/gui/app/examples/api/src-tauri/capabilities/base.json new file mode 100644 index 00000000..09d028da --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/capabilities/base.json @@ -0,0 +1,107 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "run-app-base", + "description": "Base permissions to run the app", + "windows": ["main"], + "permissions": [ + "log:default", + { + "identifier": "http:default", + "allow": [ + "https://tauri.app", + { + "url": "http://localhost:3003" + } + ] + }, + "core:default", + "fs:default", + "core:window:allow-minimize", + "core:window:allow-maximize", + "core:window:allow-unmaximize", + "core:window:allow-close", + "core:window:allow-start-dragging", + "notification:default", + "os:allow-platform", + "dialog:allow-open", + "dialog:allow-ask", + "dialog:allow-save", + "dialog:allow-confirm", + "dialog:allow-message", + { + "identifier": "shell:allow-spawn", + "allow": [ + { + "name": "sh", + "cmd": "sh", + "args": [ + "-c", + { + "validator": ".+" + } + ] + }, + { + "name": "cmd", + "cmd": "cmd", + "args": [ + "/C", + { + "validator": ".+" + } + ] + } + ] + }, + "shell:default", + "shell:allow-kill", + "shell:allow-stdin-write", + "process:allow-exit", + "process:allow-restart", + "clipboard-manager:allow-read-text", + "clipboard-manager:allow-write-text", + "clipboard-manager:allow-read-image", + "clipboard-manager:allow-write-image", + "fs:allow-open", + "fs:allow-write", + "fs:allow-read", + "fs:allow-rename", + "fs:allow-mkdir", + "fs:allow-remove", + "fs:allow-stat", + "fs:allow-fstat", + "fs:allow-lstat", + "fs:allow-write-text-file", + "fs:read-meta", + "fs:scope-download-recursive", + "fs:scope-resource-recursive", + { + "identifier": "fs:scope-appdata-recursive", + "allow": [ + { + "path": "$APPDATA/db/" + }, + { + "path": "$APPDATA/db/**" + } + ], + "deny": ["$APPDATA/db/*.stronghold"] + }, + "store:default", + "opener:default", + { + "identifier": "opener:allow-open-url", + "allow": [ + { + "url": "https://*", + "app": "inAppBrowser" + } + ] + }, + { + "identifier": "opener:allow-open-path", + "allow": [{ "path": "$APPDATA" }, { "path": "$APPDATA/**" }] + }, + "upload:default" + ] +} diff --git a/packages/kbot/gui/app/examples/api/src-tauri/capabilities/desktop.json b/packages/kbot/gui/app/examples/api/src-tauri/capabilities/desktop.json new file mode 100644 index 00000000..82d8354f --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/capabilities/desktop.json @@ -0,0 +1,16 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "run-app-desktop", + "description": "Permissions to run the app (desktop only)", + "windows": ["main"], + "platforms": ["linux", "macOS", "windows"], + "permissions": [ + "cli:default", + "updater:default", + "global-shortcut:allow-unregister", + "global-shortcut:allow-register", + "global-shortcut:allow-unregister-all", + { "identifier": "fs:allow-watch", "allow": ["*", "**/*"] }, + "fs:allow-unwatch" + ] +} diff --git a/packages/kbot/gui/app/examples/api/src-tauri/capabilities/mobile.json b/packages/kbot/gui/app/examples/api/src-tauri/capabilities/mobile.json new file mode 100644 index 00000000..da77f5e5 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/capabilities/mobile.json @@ -0,0 +1,24 @@ +{ + "$schema": "../gen/schemas/mobile-schema.json", + "identifier": "run-app-mobile", + "description": "Permissions to run the app (mobile only)", + "windows": ["main"], + "platforms": ["android", "iOS"], + "permissions": [ + "nfc:allow-write", + "nfc:allow-scan", + "biometric:allow-authenticate", + "barcode-scanner:allow-scan", + "barcode-scanner:allow-cancel", + "barcode-scanner:allow-request-permissions", + "barcode-scanner:allow-check-permissions", + "geolocation:allow-check-permissions", + "geolocation:allow-request-permissions", + "geolocation:allow-watch-position", + "geolocation:allow-get-current-position", + "haptics:allow-impact-feedback", + "haptics:allow-notification-feedback", + "haptics:allow-selection-feedback", + "haptics:allow-vibrate" + ] +} diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/.editorconfig b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/.editorconfig new file mode 100644 index 00000000..ebe51d3b --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = false \ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/.gitignore b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/.gitignore new file mode 100644 index 00000000..b2482031 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/.gitignore @@ -0,0 +1,19 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +build +/captures +.externalNativeBuild +.cxx +local.properties +key.properties + +/.tauri +/tauri.settings.gradle \ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/.gitignore b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/.gitignore new file mode 100644 index 00000000..4008dd74 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/.gitignore @@ -0,0 +1,6 @@ +/src/main/java/com/tauri/api/generated +/src/main/jniLibs/**/*.so +/src/main/assets/tauri.conf.json +/tauri.build.gradle.kts +/proguard-tauri.pro +/tauri.properties \ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/build.gradle.kts b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/build.gradle.kts new file mode 100644 index 00000000..f7047ca0 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/build.gradle.kts @@ -0,0 +1,69 @@ +import java.util.Properties + +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("rust") +} + +val tauriProperties = Properties().apply { + val propFile = file("tauri.properties") + if (propFile.exists()) { + propFile.inputStream().use { load(it) } + } +} + +android { + compileSdk = 34 + namespace = "com.tauri.api" + defaultConfig { + manifestPlaceholders["usesCleartextTraffic"] = "false" + applicationId = "com.tauri.api" + minSdk = 24 + targetSdk = 34 + versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt() + versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0") + } + buildTypes { + getByName("debug") { + manifestPlaceholders["usesCleartextTraffic"] = "true" + isDebuggable = true + isJniDebuggable = true + isMinifyEnabled = false + packaging { jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so") + jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so") + jniLibs.keepDebugSymbols.add("*/x86/*.so") + jniLibs.keepDebugSymbols.add("*/x86_64/*.so") + } + } + getByName("release") { + isMinifyEnabled = true + proguardFiles( + *fileTree(".") { include("**/*.pro") } + .plus(getDefaultProguardFile("proguard-android-optimize.txt")) + .toList().toTypedArray() + ) + } + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + buildConfig = true + } +} + +rust { + rootDirRel = "../../../" +} + +dependencies { + implementation("androidx.webkit:webkit:1.6.1") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.8.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.4") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") +} + +apply(from = "tauri.build.gradle.kts") \ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/proguard-rules.pro b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/AndroidManifest.xml b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8ebe7c4b --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/java/com/tauri/api/MainActivity.kt b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/java/com/tauri/api/MainActivity.kt new file mode 100644 index 00000000..0a05f42b --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/java/com/tauri/api/MainActivity.kt @@ -0,0 +1,3 @@ +package com.tauri.api + +class MainActivity : TauriActivity() \ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..07d5da9c --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..4fc24441 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..28f1aa11 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..85d0c88a Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..28f1aa11 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..73e48dbf Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..13dd2147 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..73e48dbf Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..1d98044f Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..a888b336 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..1d98044f Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..08183246 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..a2a838e7 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..08183246 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..b18bceb6 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..3f8a57f3 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..b18bceb6 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/values-night/themes.xml b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/values-night/themes.xml new file mode 100644 index 00000000..f0378527 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/values-night/themes.xml @@ -0,0 +1,6 @@ + + + + diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/values/colors.xml b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..f8c6127d --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/values/strings.xml b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..6f4305e0 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + Tauri API + Tauri API + \ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/values/themes.xml b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/values/themes.xml new file mode 100644 index 00000000..f0378527 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/values/themes.xml @@ -0,0 +1,6 @@ + + + + diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 00000000..782d63b9 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/build.gradle.kts b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/build.gradle.kts new file mode 100644 index 00000000..c5ef452a --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/build.gradle.kts @@ -0,0 +1,22 @@ +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.5.1") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25") + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +tasks.register("clean").configure { + delete("build") +} + diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/buildSrc/build.gradle.kts b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/buildSrc/build.gradle.kts new file mode 100644 index 00000000..39e90b05 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/buildSrc/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + `kotlin-dsl` +} + +gradlePlugin { + plugins { + create("pluginsForCoolKids") { + id = "rust" + implementationClass = "RustPlugin" + } + } +} + +repositories { + google() + mavenCentral() +} + +dependencies { + compileOnly(gradleApi()) + implementation("com.android.tools.build:gradle:8.5.1") +} + diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/buildSrc/src/main/java/com/tauri/api/kotlin/BuildTask.kt b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/buildSrc/src/main/java/com/tauri/api/kotlin/BuildTask.kt new file mode 100644 index 00000000..f9874825 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/buildSrc/src/main/java/com/tauri/api/kotlin/BuildTask.kt @@ -0,0 +1,52 @@ +import java.io.File +import org.apache.tools.ant.taskdefs.condition.Os +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.logging.LogLevel +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction + +open class BuildTask : DefaultTask() { + @Input + var rootDirRel: String? = null + @Input + var target: String? = null + @Input + var release: Boolean? = null + + @TaskAction + fun assemble() { + val executable = """pnpm"""; + try { + runTauriCli(executable) + } catch (e: Exception) { + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + runTauriCli("$executable.cmd") + } else { + throw e; + } + } + } + + fun runTauriCli(executable: String) { + val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null") + val target = target ?: throw GradleException("target cannot be null") + val release = release ?: throw GradleException("release cannot be null") + val args = listOf("tauri", "android", "android-studio-script"); + + project.exec { + workingDir(File(project.projectDir, rootDirRel)) + executable(executable) + args(args) + if (project.logger.isEnabled(LogLevel.DEBUG)) { + args("-vv") + } else if (project.logger.isEnabled(LogLevel.INFO)) { + args("-v") + } + if (release) { + args("--release") + } + args(listOf("--target", target)) + }.assertNormalExitValue() + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/buildSrc/src/main/java/com/tauri/api/kotlin/RustPlugin.kt b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/buildSrc/src/main/java/com/tauri/api/kotlin/RustPlugin.kt new file mode 100644 index 00000000..4aa7fcaf --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/buildSrc/src/main/java/com/tauri/api/kotlin/RustPlugin.kt @@ -0,0 +1,85 @@ +import com.android.build.api.dsl.ApplicationExtension +import org.gradle.api.DefaultTask +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.get + +const val TASK_GROUP = "rust" + +open class Config { + lateinit var rootDirRel: String +} + +open class RustPlugin : Plugin { + private lateinit var config: Config + + override fun apply(project: Project) = with(project) { + config = extensions.create("rust", Config::class.java) + + val defaultAbiList = listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64"); + val abiList = (findProperty("abiList") as? String)?.split(',') ?: defaultAbiList + + val defaultArchList = listOf("arm64", "arm", "x86", "x86_64"); + val archList = (findProperty("archList") as? String)?.split(',') ?: defaultArchList + + val targetsList = (findProperty("targetList") as? String)?.split(',') ?: listOf("aarch64", "armv7", "i686", "x86_64") + + extensions.configure { + @Suppress("UnstableApiUsage") + flavorDimensions.add("abi") + productFlavors { + create("universal") { + dimension = "abi" + ndk { + abiFilters += abiList + } + } + defaultArchList.forEachIndexed { index, arch -> + create(arch) { + dimension = "abi" + ndk { + abiFilters.add(defaultAbiList[index]) + } + } + } + } + } + + afterEvaluate { + for (profile in listOf("debug", "release")) { + val profileCapitalized = profile.replaceFirstChar { it.uppercase() } + val buildTask = tasks.maybeCreate( + "rustBuildUniversal$profileCapitalized", + DefaultTask::class.java + ).apply { + group = TASK_GROUP + description = "Build dynamic library in $profile mode for all targets" + } + + tasks["mergeUniversal${profileCapitalized}JniLibFolders"].dependsOn(buildTask) + + for (targetPair in targetsList.withIndex()) { + val targetName = targetPair.value + val targetArch = archList[targetPair.index] + val targetArchCapitalized = targetArch.replaceFirstChar { it.uppercase() } + val targetBuildTask = project.tasks.maybeCreate( + "rustBuild$targetArchCapitalized$profileCapitalized", + BuildTask::class.java + ).apply { + group = TASK_GROUP + description = "Build dynamic library in $profile mode for $targetArch" + rootDirRel = config.rootDirRel + target = targetName + release = profile == "release" + } + + buildTask.dependsOn(targetBuildTask) + tasks["merge$targetArchCapitalized${profileCapitalized}JniLibFolders"].dependsOn( + targetBuildTask + ) + } + } + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/gradle.properties b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/gradle.properties new file mode 100644 index 00000000..2a7ec695 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/gradle.properties @@ -0,0 +1,24 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true +android.nonFinalResIds=false \ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..e708b1c0 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..0df10d55 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue May 10 19:22:52 CST 2022 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/gradlew b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/gradlew new file mode 100644 index 00000000..4f906e0c --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/gradlew.bat b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/android/settings.gradle b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/settings.gradle new file mode 100644 index 00000000..39391166 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/android/settings.gradle @@ -0,0 +1,3 @@ +include ':app' + +apply from: 'tauri.settings.gradle' diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/.gitignore b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/.gitignore new file mode 100644 index 00000000..6726e2f8 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/.gitignore @@ -0,0 +1,3 @@ +xcuserdata/ +build/ +Externals/ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png new file mode 100644 index 00000000..a6ac2a8c Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png new file mode 100644 index 00000000..2869541f Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png new file mode 100644 index 00000000..2869541f Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png new file mode 100644 index 00000000..cf265a45 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png new file mode 100644 index 00000000..29c9746c Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png new file mode 100644 index 00000000..a4e68c8d Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png new file mode 100644 index 00000000..a4e68c8d Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png new file mode 100644 index 00000000..e4adcbce Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png new file mode 100644 index 00000000..2869541f Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png new file mode 100644 index 00000000..a414e65b Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png new file mode 100644 index 00000000..a414e65b Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png new file mode 100644 index 00000000..a0807e5d Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png new file mode 100644 index 00000000..704c9291 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png new file mode 100644 index 00000000..a0807e5d Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png new file mode 100644 index 00000000..2a9fbc26 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png new file mode 100644 index 00000000..2cdf1848 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png new file mode 100644 index 00000000..4723e4b4 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png new file mode 100644 index 00000000..f26fee45 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..90eea7ec --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "AppIcon-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "AppIcon-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "AppIcon-29x29@2x-1.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "AppIcon-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "AppIcon-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "AppIcon-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "AppIcon-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "AppIcon-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "AppIcon-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "AppIcon-20x20@2x-1.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "AppIcon-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "AppIcon-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "AppIcon-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "AppIcon-40x40@2x-1.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "AppIcon-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "AppIcon-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "AppIcon-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "AppIcon-512@2x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/Contents.json b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/ExportOptions.plist b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/ExportOptions.plist new file mode 100644 index 00000000..0428a171 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/ExportOptions.plist @@ -0,0 +1,8 @@ + + + + + method + debugging + + diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/LaunchScreen.storyboard b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/LaunchScreen.storyboard new file mode 100644 index 00000000..81b5f90e --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/LaunchScreen.storyboard @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Podfile b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Podfile new file mode 100644 index 00000000..24605d24 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Podfile @@ -0,0 +1,21 @@ +# Uncomment the next line to define a global platform for your project + +target 'api_iOS' do +platform :ios, '14.0' + # Pods for api_iOS +end + +target 'api_macOS' do +platform :osx, '11.0' + # Pods for api_macOS +end + +# Delete the deployment target for iOS and macOS, causing it to be inherited from the Podfile +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' + config.build_settings.delete 'MACOSX_DEPLOYMENT_TARGET' + end + end +end diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Sources/api/bindings/bindings.h b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Sources/api/bindings/bindings.h new file mode 100644 index 00000000..51522007 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Sources/api/bindings/bindings.h @@ -0,0 +1,8 @@ +#pragma once + +namespace ffi { + extern "C" { + void start_app(); + } +} + diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Sources/api/main.mm b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Sources/api/main.mm new file mode 100644 index 00000000..7793a9d5 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/Sources/api/main.mm @@ -0,0 +1,6 @@ +#include "bindings/bindings.h" + +int main(int argc, char * argv[]) { + ffi::start_app(); + return 0; +} diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/api.xcodeproj/project.pbxproj b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/api.xcodeproj/project.pbxproj new file mode 100644 index 00000000..11be7126 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/api.xcodeproj/project.pbxproj @@ -0,0 +1,469 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 3043432501C9BC2DB6B4CB95 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 71EB788DE4662CFC0D97F567 /* CoreGraphics.framework */; }; + 328B4ADB3700C1873BEB7B10 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 90D3B673AFAB8D8AB561F616 /* main.mm */; }; + 6F379F15DA085785BA2624D4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6B7E79E23E646BA7968B457C /* Assets.xcassets */; }; + 832F9A55FEDEF3D807D8C40A /* libapp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 248286BAA086BB1A5F98B2B2 /* libapp.a */; }; + 9AADB041D25772D04E543F15 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62601E25FA39E62BE119B74D /* Metal.framework */; }; + 9DDA3BE70DD0E4013973FE38 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6082E363D51372A7658C351 /* UIKit.framework */; }; + AC8BDC2C7A63FA3FDC5967F4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4B2D1B108AE002010BDEC6D2 /* LaunchScreen.storyboard */; }; + AFA0CA286325FD7A34968CA2 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 384966E551417F94A02D2706 /* Security.framework */; }; + B60763BD194DFACA215EC7DA /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC377692DC31A070A0188C9D /* QuartzCore.framework */; }; + C6D80743F168BDF017B7769E /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 59CFE20DCF760BE67D9CE3D6 /* WebKit.framework */; }; + DFFF888045C8D9D9FB69E8FD /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 338E66700FD330B99D434DD7 /* MetalKit.framework */; }; + F86717F05E27C72C9FA1FB27 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 74A8FDFB350B966F5AAD4A24 /* assets */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0E96CE07CD20273DD46BF325 /* main.rs */ = {isa = PBXFileReference; path = main.rs; sourceTree = ""; }; + 1C1AB1B414CA2795AFBEDDB9 /* tray.rs */ = {isa = PBXFileReference; path = tray.rs; sourceTree = ""; }; + 248286BAA086BB1A5F98B2B2 /* libapp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libapp.a; sourceTree = ""; }; + 2F63E2AA460089BB58D40C79 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 338E66700FD330B99D434DD7 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; + 384966E551417F94A02D2706 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + 4B2D1B108AE002010BDEC6D2 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + 59CFE20DCF760BE67D9CE3D6 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; + 5AC703CEBA41A121596066F3 /* api_iOS.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = api_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 62601E25FA39E62BE119B74D /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; + 6B7E79E23E646BA7968B457C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 71EB788DE4662CFC0D97F567 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 74A8FDFB350B966F5AAD4A24 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = SOURCE_ROOT; }; + 785D025E9542F7E098BF22B5 /* lib.rs */ = {isa = PBXFileReference; path = lib.rs; sourceTree = ""; }; + 879941AE3DAA14534BBC6391 /* api_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = api_iOS.entitlements; sourceTree = ""; }; + 90D3B673AFAB8D8AB561F616 /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; }; + B6082E363D51372A7658C351 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + DC377692DC31A070A0188C9D /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + EC8C7948C50C3C9B5D96CB61 /* bindings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bindings.h; sourceTree = ""; }; + F835F52713CE8F029D5D252C /* cmd.rs */ = {isa = PBXFileReference; path = cmd.rs; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 11E18DCDB3ADFE87C18915EF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 832F9A55FEDEF3D807D8C40A /* libapp.a in Frameworks */, + 3043432501C9BC2DB6B4CB95 /* CoreGraphics.framework in Frameworks */, + 9AADB041D25772D04E543F15 /* Metal.framework in Frameworks */, + DFFF888045C8D9D9FB69E8FD /* MetalKit.framework in Frameworks */, + B60763BD194DFACA215EC7DA /* QuartzCore.framework in Frameworks */, + AFA0CA286325FD7A34968CA2 /* Security.framework in Frameworks */, + 9DDA3BE70DD0E4013973FE38 /* UIKit.framework in Frameworks */, + C6D80743F168BDF017B7769E /* WebKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0677CEAF1F282F38CBA0F140 = { + isa = PBXGroup; + children = ( + 74A8FDFB350B966F5AAD4A24 /* assets */, + 6B7E79E23E646BA7968B457C /* Assets.xcassets */, + 4B2D1B108AE002010BDEC6D2 /* LaunchScreen.storyboard */, + F2116A6428EED18BE2A07E2B /* api_iOS */, + 86D903732E10FAC4D300E8DF /* Externals */, + 7A9A7AC155D9E22E54D6D847 /* Sources */, + CF9AA87D2F6E9C389B7AB70B /* src */, + 10C9FC3FA3E12D6A4A67999D /* Frameworks */, + 4AC51E67B71E27F15B02C5CD /* Products */, + ); + sourceTree = ""; + }; + 07051859D6E2D8109C8FB128 /* bindings */ = { + isa = PBXGroup; + children = ( + EC8C7948C50C3C9B5D96CB61 /* bindings.h */, + ); + path = bindings; + sourceTree = ""; + }; + 10C9FC3FA3E12D6A4A67999D /* Frameworks */ = { + isa = PBXGroup; + children = ( + 71EB788DE4662CFC0D97F567 /* CoreGraphics.framework */, + 248286BAA086BB1A5F98B2B2 /* libapp.a */, + 62601E25FA39E62BE119B74D /* Metal.framework */, + 338E66700FD330B99D434DD7 /* MetalKit.framework */, + DC377692DC31A070A0188C9D /* QuartzCore.framework */, + 384966E551417F94A02D2706 /* Security.framework */, + B6082E363D51372A7658C351 /* UIKit.framework */, + 59CFE20DCF760BE67D9CE3D6 /* WebKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 4AC51E67B71E27F15B02C5CD /* Products */ = { + isa = PBXGroup; + children = ( + 5AC703CEBA41A121596066F3 /* api_iOS.app */, + ); + name = Products; + sourceTree = ""; + }; + 7A9A7AC155D9E22E54D6D847 /* Sources */ = { + isa = PBXGroup; + children = ( + A3574F52DBC5463B9C3D043D /* api */, + ); + path = Sources; + sourceTree = ""; + }; + 86D903732E10FAC4D300E8DF /* Externals */ = { + isa = PBXGroup; + children = ( + ); + path = Externals; + sourceTree = ""; + }; + A3574F52DBC5463B9C3D043D /* api */ = { + isa = PBXGroup; + children = ( + 90D3B673AFAB8D8AB561F616 /* main.mm */, + 07051859D6E2D8109C8FB128 /* bindings */, + ); + path = api; + sourceTree = ""; + }; + CF9AA87D2F6E9C389B7AB70B /* src */ = { + isa = PBXGroup; + children = ( + F835F52713CE8F029D5D252C /* cmd.rs */, + 785D025E9542F7E098BF22B5 /* lib.rs */, + 0E96CE07CD20273DD46BF325 /* main.rs */, + 1C1AB1B414CA2795AFBEDDB9 /* tray.rs */, + ); + name = src; + path = ../../src; + sourceTree = ""; + }; + F2116A6428EED18BE2A07E2B /* api_iOS */ = { + isa = PBXGroup; + children = ( + 879941AE3DAA14534BBC6391 /* api_iOS.entitlements */, + 2F63E2AA460089BB58D40C79 /* Info.plist */, + ); + path = api_iOS; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 54DC6E273C78071F3BA12EF3 /* api_iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 01CBC40275452376830D79B1 /* Build configuration list for PBXNativeTarget "api_iOS" */; + buildPhases = ( + FF948951157DE71465B5BD5F /* Build Rust Code */, + 71E73CC9AB5F1323EC1F6365 /* Sources */, + CA2BEC44B6EDA1F21B6155CD /* Resources */, + 11E18DCDB3ADFE87C18915EF /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = api_iOS; + packageProductDependencies = ( + ); + productName = api_iOS; + productReference = 5AC703CEBA41A121596066F3 /* api_iOS.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 9BC88C3717DA5D4B78A51C15 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + TargetAttributes = { + 54DC6E273C78071F3BA12EF3 = { + DevelopmentTeam = Q93MBH6S2F; + }; + }; + }; + buildConfigurationList = 8FA67D0F928A09CD639137D1 /* Build configuration list for PBXProject "api" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = 0677CEAF1F282F38CBA0F140; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 54; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 54DC6E273C78071F3BA12EF3 /* api_iOS */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + CA2BEC44B6EDA1F21B6155CD /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6F379F15DA085785BA2624D4 /* Assets.xcassets in Resources */, + AC8BDC2C7A63FA3FDC5967F4 /* LaunchScreen.storyboard in Resources */, + F86717F05E27C72C9FA1FB27 /* assets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + FF948951157DE71465B5BD5F /* Build Rust Code */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Build Rust Code"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a", + "$(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "pnpm tauri ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --framework-search-paths \"${FRAMEWORK_SEARCH_PATHS:?}\" --header-search-paths \"${HEADER_SEARCH_PATHS:?}\" --gcc-preprocessor-definitions \"${GCC_PREPROCESSOR_DEFINITIONS:-}\" --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?}"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 71E73CC9AB5F1323EC1F6365 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 328B4ADB3700C1873BEB7B10 /* main.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A83F70B4C02DD0222038C7F1 /* release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + }; + name = release; + }; + B6AD77E490F315562F75D3D7 /* debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DEBUG=1", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = debug; + }; + BF284FE6E7AE0C8DDCCE398B /* debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ARCHS = ( + arm64, + ); + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = api_iOS/api_iOS.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + DEVELOPMENT_TEAM = "Q93MBH6S2F"; + ENABLE_BITCODE = NO; + "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\".\"", + ); + INFOPLIST_FILE = api_iOS/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + "LIBRARY_SEARCH_PATHS[arch=arm64]" = "$(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; + "LIBRARY_SEARCH_PATHS[arch=x86_64]" = "$(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; + PRODUCT_BUNDLE_IDENTIFIER = com.tauri.api; + PRODUCT_NAME = "Tauri API"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = arm64; + }; + name = debug; + }; + DB0E254D0FD84970B57F6410 /* release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ARCHS = ( + arm64, + ); + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = api_iOS/api_iOS.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + DEVELOPMENT_TEAM = "Q93MBH6S2F"; + ENABLE_BITCODE = NO; + "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\".\"", + ); + INFOPLIST_FILE = api_iOS/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + "LIBRARY_SEARCH_PATHS[arch=arm64]" = "$(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; + "LIBRARY_SEARCH_PATHS[arch=x86_64]" = "$(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; + PRODUCT_BUNDLE_IDENTIFIER = com.tauri.api; + PRODUCT_NAME = "Tauri API"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = arm64; + }; + name = release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 01CBC40275452376830D79B1 /* Build configuration list for PBXNativeTarget "api_iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BF284FE6E7AE0C8DDCCE398B /* debug */, + DB0E254D0FD84970B57F6410 /* release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = debug; + }; + 8FA67D0F928A09CD639137D1 /* Build configuration list for PBXProject "api" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B6AD77E490F315562F75D3D7 /* debug */, + A83F70B4C02DD0222038C7F1 /* release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = 9BC88C3717DA5D4B78A51C15 /* Project object */; +} diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/api.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/api.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/api.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/api.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/api.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..ac90d5ac --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/api.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,10 @@ + + + + + BuildSystemType + Original + DisableBuildSystemDeprecationDiagnostic + + + diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/api.xcodeproj/xcshareddata/xcschemes/api_iOS.xcscheme b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/api.xcodeproj/xcshareddata/xcschemes/api_iOS.xcscheme new file mode 100644 index 00000000..2a9f5045 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/api.xcodeproj/xcshareddata/xcschemes/api_iOS.xcscheme @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/api_iOS/Info.plist b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/api_iOS/Info.plist new file mode 100644 index 00000000..7d35e702 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/api_iOS/Info.plist @@ -0,0 +1,52 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 2.0.0 + CFBundleVersion + 2.0.0 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + arm64 + metal + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSCameraUsageDescription + Request camera access for WebRTC + NSMicrophoneUsageDescription + Request microphone access for WebRTC + NSFaceIDUsageDescription + Authenticate with biometrics + NFCReaderUsageDescription + Read and write to NFC tags for testing + + \ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/api_iOS/api_iOS.entitlements b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/api_iOS/api_iOS.entitlements new file mode 100644 index 00000000..0c67376e --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/api_iOS/api_iOS.entitlements @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/project.yml b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/project.yml new file mode 100644 index 00000000..d789500e --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/gen/apple/project.yml @@ -0,0 +1,89 @@ +name: api +options: + bundleIdPrefix: com.tauri.api + deploymentTarget: + iOS: 14.0 +fileGroups: [../../src] +configs: + debug: debug + release: release +settingGroups: + app: + base: + PRODUCT_NAME: Tauri API + PRODUCT_BUNDLE_IDENTIFIER: com.tauri.api + DEVELOPMENT_TEAM: Q93MBH6S2F +targetTemplates: + app: + type: application + sources: + - path: Sources + scheme: + environmentVariables: + RUST_BACKTRACE: full + RUST_LOG: info + settings: + groups: [app] +targets: + api_iOS: + type: application + platform: iOS + sources: + - path: Sources + - path: Assets.xcassets + - path: Externals + - path: api_iOS + - path: assets + buildPhase: resources + type: folder + - path: LaunchScreen.storyboard + info: + path: api_iOS/Info.plist + properties: + LSRequiresIPhoneOS: true + UILaunchStoryboardName: LaunchScreen + UIRequiredDeviceCapabilities: [arm64, metal] + UISupportedInterfaceOrientations: + - UIInterfaceOrientationPortrait + - UIInterfaceOrientationLandscapeLeft + - UIInterfaceOrientationLandscapeRight + UISupportedInterfaceOrientations~ipad: + - UIInterfaceOrientationPortrait + - UIInterfaceOrientationPortraitUpsideDown + - UIInterfaceOrientationLandscapeLeft + - UIInterfaceOrientationLandscapeRight + CFBundleShortVersionString: 2.0.0 + CFBundleVersion: 2.0.0 + entitlements: + path: api_iOS/api_iOS.entitlements + scheme: + environmentVariables: + RUST_BACKTRACE: full + RUST_LOG: info + settings: + base: + ENABLE_BITCODE: false + ARCHS: [arm64] + VALID_ARCHS: arm64 + LIBRARY_SEARCH_PATHS[arch=x86_64]: $(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME) + LIBRARY_SEARCH_PATHS[arch=arm64]: $(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME) + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES: true + EXCLUDED_ARCHS[sdk=iphoneos*]: x86_64 + groups: [app] + dependencies: + - framework: libapp.a + embed: false + - sdk: CoreGraphics.framework + - sdk: Metal.framework + - sdk: MetalKit.framework + - sdk: QuartzCore.framework + - sdk: Security.framework + - sdk: UIKit.framework + - sdk: WebKit.framework + preBuildScripts: + - script: pnpm tauri ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --framework-search-paths "${FRAMEWORK_SEARCH_PATHS:?}" --header-search-paths "${HEADER_SEARCH_PATHS:?}" --gcc-preprocessor-definitions "${GCC_PREPROCESSOR_DEFINITIONS:-}" --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?} + name: Build Rust Code + basedOnDependencyAnalysis: false + outputFiles: + - $(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a + - $(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a \ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src-tauri/icons/128x128.png b/packages/kbot/gui/app/examples/api/src-tauri/icons/128x128.png new file mode 100644 index 00000000..77e7d233 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/icons/128x128.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/icons/128x128@2x.png b/packages/kbot/gui/app/examples/api/src-tauri/icons/128x128@2x.png new file mode 100644 index 00000000..0f7976f1 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/icons/128x128@2x.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/icons/32x32.png b/packages/kbot/gui/app/examples/api/src-tauri/icons/32x32.png new file mode 100644 index 00000000..98fda06f Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/icons/32x32.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/icons/icon.icns b/packages/kbot/gui/app/examples/api/src-tauri/icons/icon.icns new file mode 100644 index 00000000..5594104c Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/icons/icon.icns differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/icons/icon.ico b/packages/kbot/gui/app/examples/api/src-tauri/icons/icon.ico new file mode 100644 index 00000000..06c23c82 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/icons/icon.ico differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/icons/icon.png b/packages/kbot/gui/app/examples/api/src-tauri/icons/icon.png new file mode 100644 index 00000000..d1756ce4 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/icons/icon.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/icons/tray_icon.png b/packages/kbot/gui/app/examples/api/src-tauri/icons/tray_icon.png new file mode 100644 index 00000000..e037c514 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/icons/tray_icon.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/icons/tray_icon_with_transparency.ico b/packages/kbot/gui/app/examples/api/src-tauri/icons/tray_icon_with_transparency.ico new file mode 100644 index 00000000..b3990240 Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/icons/tray_icon_with_transparency.ico differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/icons/tray_icon_with_transparency.png b/packages/kbot/gui/app/examples/api/src-tauri/icons/tray_icon_with_transparency.png new file mode 100644 index 00000000..99d8ff2b Binary files /dev/null and b/packages/kbot/gui/app/examples/api/src-tauri/icons/tray_icon_with_transparency.png differ diff --git a/packages/kbot/gui/app/examples/api/src-tauri/locales/pt-BR.wxl b/packages/kbot/gui/app/examples/api/src-tauri/locales/pt-BR.wxl new file mode 100644 index 00000000..8f58c074 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/locales/pt-BR.wxl @@ -0,0 +1,6 @@ + + Executar Tauri API + Uma versão mais recente de Tauri API está instalada. + Adiciona o caminho do executável de Tauri API para a variável de ambiente PATH. Isso permite Tauri API ser executado pela linha de comando. + Instala Tauri API. + diff --git a/packages/kbot/gui/app/examples/api/src-tauri/src/cmd.rs b/packages/kbot/gui/app/examples/api/src-tauri/src/cmd.rs new file mode 100644 index 00000000..734552c6 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/src/cmd.rs @@ -0,0 +1,24 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::Deserialize; +use tauri::command; + +#[derive(Debug, Deserialize)] +#[allow(unused)] +pub struct RequestBody { + id: i32, + name: String, +} + +#[command] +pub fn log_operation(event: String, payload: Option) { + log::info!("{} {:?}", event, payload); +} + +#[command] +pub fn perform_request(endpoint: String, body: RequestBody) -> String { + println!("{} {:?}", endpoint, body); + "message response".into() +} diff --git a/packages/kbot/gui/app/examples/api/src-tauri/src/lib.rs b/packages/kbot/gui/app/examples/api/src-tauri/src/lib.rs new file mode 100644 index 00000000..3c58f2c8 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/src/lib.rs @@ -0,0 +1,182 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +mod cmd; +#[cfg(desktop)] +mod tray; + +use serde::Serialize; +use tauri::{ + webview::{PageLoadEvent, WebviewWindowBuilder}, + App, AppHandle, Emitter, Listener, RunEvent, WebviewUrl, +}; + +#[derive(Clone, Serialize)] +struct Reply { + data: String, +} + +pub type SetupHook = Box Result<(), Box> + Send>; +pub type OnEvent = Box; + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + #[allow(unused_mut)] + let mut builder = tauri::Builder::default() + .plugin( + tauri_plugin_log::Builder::default() + .level(log::LevelFilter::Info) + .build(), + ) + .plugin(tauri_plugin_fs::init()) + .plugin(tauri_plugin_clipboard_manager::init()) + .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_http::init()) + .plugin(tauri_plugin_notification::init()) + .plugin(tauri_plugin_os::init()) + .plugin(tauri_plugin_process::init()) + .plugin(tauri_plugin_opener::init()) + .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_store::Builder::default().build()) + .plugin(tauri_plugin_upload::init()) + .setup(move |app| { + #[cfg(desktop)] + { + tray::create_tray(app.handle())?; + app.handle().plugin(tauri_plugin_cli::init())?; + app.handle() + .plugin(tauri_plugin_global_shortcut::Builder::new().build())?; + app.handle() + .plugin(tauri_plugin_window_state::Builder::new().build())?; + app.handle() + .plugin(tauri_plugin_updater::Builder::new().build())?; + } + #[cfg(mobile)] + { + app.handle().plugin(tauri_plugin_barcode_scanner::init())?; + app.handle().plugin(tauri_plugin_nfc::init())?; + app.handle().plugin(tauri_plugin_biometric::init())?; + app.handle().plugin(tauri_plugin_geolocation::init())?; + app.handle().plugin(tauri_plugin_haptics::init())?; + } + + let mut webview_window_builder = + WebviewWindowBuilder::new(app, "main", WebviewUrl::default()); + #[cfg(desktop)] + { + webview_window_builder = webview_window_builder + .user_agent(&format!("Tauri API - {}", std::env::consts::OS)) + .title("Tauri API Validation") + .inner_size(1000., 800.) + .min_inner_size(600., 400.) + .visible(false); + } + + #[cfg(target_os = "windows")] + { + webview_window_builder = webview_window_builder + .transparent(true) + .shadow(true) + .decorations(false); + } + + #[cfg(target_os = "macos")] + { + webview_window_builder = webview_window_builder.transparent(true); + } + + let webview = webview_window_builder.build().unwrap(); + + #[cfg(debug_assertions)] + webview.open_devtools(); + + std::thread::spawn(|| { + let server = match tiny_http::Server::http("localhost:3003") { + Ok(s) => s, + Err(e) => { + eprintln!("{}", e); + std::process::exit(1); + } + }; + loop { + if let Ok(mut request) = server.recv() { + let mut body = Vec::new(); + let _ = request.as_reader().read_to_end(&mut body); + let mut headers = request.headers().to_vec(); + + if !headers.iter().any(|header| header.field == tiny_http::HeaderField::from_bytes(b"Cookie").unwrap()) { + let expires = time::OffsetDateTime::now_utc() + time::Duration::days(1); + // RFC 1123 format + let format = time::macros::format_description!( + "[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT" + ); + let expires_str = expires.format(format).unwrap(); + headers.push( + tiny_http::Header::from_bytes( + &b"Set-Cookie"[..], + format!("session-token=test-value; Secure; Path=/; Expires={expires_str}") + .as_bytes(), + ) + .unwrap(), + ); + } + + let response = tiny_http::Response::new( + tiny_http::StatusCode(200), + headers, + std::io::Cursor::new(body), + request.body_length(), + None, + ); + let _ = request.respond(response); + } + } + }); + + Ok(()) + }) + .on_page_load(|webview, payload| { + if payload.event() == PageLoadEvent::Finished { + let webview_ = webview.clone(); + webview.listen("js-event", move |event| { + println!("got js-event with message '{:?}'", event.payload()); + let reply = Reply { + data: "something else".to_string(), + }; + + webview_ + .emit("rust-event", Some(reply)) + .expect("failed to emit"); + }); + } + }); + + #[cfg(target_os = "macos")] + { + builder = builder.menu(tauri::menu::Menu::default); + } + + #[allow(unused_mut)] + let mut app = builder + .invoke_handler(tauri::generate_handler![ + cmd::log_operation, + cmd::perform_request, + ]) + .build(tauri::generate_context!()) + .expect("error while building tauri application"); + + #[cfg(target_os = "macos")] + app.set_activation_policy(tauri::ActivationPolicy::Regular); + + app.run(move |_app_handle, _event| { + #[cfg(desktop)] + if let RunEvent::ExitRequested { code, api, .. } = &_event { + if code.is_none() { + // Keep the event loop running even if all windows are closed + // This allow us to catch system tray events when there is no window + api.prevent_exit(); + } + } + }) +} diff --git a/packages/kbot/gui/app/examples/api/src-tauri/src/main.rs b/packages/kbot/gui/app/examples/api/src-tauri/src/main.rs new file mode 100644 index 00000000..bbed006b --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/src/main.rs @@ -0,0 +1,10 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + api_lib::run(); +} diff --git a/packages/kbot/gui/app/examples/api/src-tauri/src/tray.rs b/packages/kbot/gui/app/examples/api/src-tauri/src/tray.rs new file mode 100644 index 00000000..7b1321a5 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/src/tray.rs @@ -0,0 +1,126 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::sync::atomic::{AtomicBool, Ordering}; +use tauri::{ + menu::{Menu, MenuItem}, + tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, + Manager, Runtime, WebviewUrl, WebviewWindowBuilder, +}; + +pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { + let toggle_i = MenuItem::with_id(app, "toggle", "Toggle", true, None::<&str>)?; + let new_window_i = MenuItem::with_id(app, "new-window", "New window", true, None::<&str>)?; + let icon_i_1 = MenuItem::with_id(app, "icon-1", "Icon 1", true, None::<&str>)?; + let icon_i_2 = MenuItem::with_id(app, "icon-2", "Icon 2", true, None::<&str>)?; + #[cfg(target_os = "macos")] + let set_title_i = MenuItem::with_id(app, "set-title", "Set Title", true, None::<&str>)?; + let switch_i = MenuItem::with_id(app, "switch-menu", "Switch Menu", true, None::<&str>)?; + let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?; + let remove_tray_i = + MenuItem::with_id(app, "remove-tray", "Remove Tray icon", true, None::<&str>)?; + let menu1 = Menu::with_items( + app, + &[ + &toggle_i, + &new_window_i, + &icon_i_1, + &icon_i_2, + #[cfg(target_os = "macos")] + &set_title_i, + &switch_i, + &quit_i, + &remove_tray_i, + ], + )?; + let menu2 = Menu::with_items( + app, + &[&toggle_i, &new_window_i, &switch_i, &quit_i, &remove_tray_i], + )?; + + let is_menu1 = AtomicBool::new(true); + + let _ = TrayIconBuilder::with_id("tray-1") + .tooltip("Tauri") + .icon(app.default_window_icon().unwrap().clone()) + .menu(&menu1) + .show_menu_on_left_click(false) + .on_menu_event(move |app, event| match event.id.as_ref() { + "quit" => { + app.exit(0); + } + "remove-tray" => { + app.remove_tray_by_id("tray-1"); + } + "toggle" => { + if let Some(window) = app.get_webview_window("main") { + let new_title = if window.is_visible().unwrap_or_default() { + let _ = window.hide(); + "Show" + } else { + let _ = window.show(); + let _ = window.set_focus(); + "Hide" + }; + toggle_i.set_text(new_title).unwrap(); + } + } + "new-window" => { + let _ = WebviewWindowBuilder::new(app, "new", WebviewUrl::App("index.html".into())) + .title("Tauri") + .build(); + } + #[cfg(target_os = "macos")] + "set-title" => { + if let Some(tray) = app.tray_by_id("tray-1") { + let _ = tray.set_title(Some("Tauri")); + } + } + i @ "icon-1" | i @ "icon-2" => { + if let Some(tray) = app.tray_by_id("tray-1") { + let _ = tray.set_icon(Some(if i == "icon-1" { + tauri::image::Image::from_bytes(include_bytes!("../icons/icon.ico")) + .unwrap() + } else { + tauri::image::Image::from_bytes(include_bytes!( + "../icons/tray_icon_with_transparency.png" + )) + .unwrap() + })); + } + } + "switch-menu" => { + let flag = is_menu1.load(Ordering::Relaxed); + let (menu, tooltip) = if flag { + (menu2.clone(), "Menu 2") + } else { + (menu1.clone(), "Tauri") + }; + if let Some(tray) = app.tray_by_id("tray-1") { + let _ = tray.set_menu(Some(menu)); + let _ = tray.set_tooltip(Some(tooltip)); + } + is_menu1.store(!flag, Ordering::Relaxed); + } + + _ => {} + }) + .on_tray_icon_event(|tray, event| { + if let TrayIconEvent::Click { + button_state: MouseButtonState::Down, + button: MouseButton::Left, + .. + } = event + { + let app = tray.app_handle(); + if let Some(window) = app.get_webview_window("main") { + let _ = window.show(); + let _ = window.set_focus(); + } + } + }) + .build(app); + + Ok(()) +} diff --git a/packages/kbot/gui/app/examples/api/src-tauri/tauri.conf.json b/packages/kbot/gui/app/examples/api/src-tauri/tauri.conf.json new file mode 100644 index 00000000..00b095be --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src-tauri/tauri.conf.json @@ -0,0 +1,108 @@ +{ + "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", + "productName": "Tauri API", + "version": "2.0.0", + "identifier": "com.tauri.api", + "build": { + "devUrl": "http://localhost:5173", + "frontendDist": "../dist", + "beforeDevCommand": "pnpm dev", + "beforeBuildCommand": "pnpm build" + }, + "app": { + "withGlobalTauri": true, + "macOSPrivateApi": true, + "security": { + "pattern": { + "use": "isolation", + "options": { + "dir": "../isolation-dist/" + } + }, + "csp": { + "default-src": "'self' customprotocol: asset:", + "connect-src": "ipc: http://ipc.localhost", + "font-src": ["https://fonts.gstatic.com"], + "img-src": "'self' asset: http://asset.localhost blob: data:", + "style-src": "'unsafe-inline' 'self' http://fonts.googleapis.com" + }, + "freezePrototype": true, + "assetProtocol": { + "enable": true, + "scope": { + "allow": ["$APPDATA/db/**", "$RESOURCE/**"], + "deny": ["$APPDATA/db/*.stronghold"] + } + } + } + }, + "plugins": { + "cli": { + "description": "Tauri API example", + "args": [ + { + "short": "c", + "name": "config", + "takesValue": true, + "description": "Config path" + }, + { + "short": "t", + "name": "theme", + "takesValue": true, + "description": "App theme", + "possibleValues": ["light", "dark", "system"] + }, + { + "short": "v", + "name": "verbose", + "description": "Verbosity level" + } + ], + "subcommands": { + "update": { + "description": "Updates the app", + "args": [ + { + "short": "b", + "name": "background", + "description": "Update in background" + } + ] + } + } + }, + "shell": { + "open": true + }, + "updater": { + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK", + "endpoints": [ + "https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}" + ] + } + }, + "bundle": { + "active": true, + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "windows": { + "wix": { + "language": { + "en-US": {}, + "pt-BR": { + "localePath": "locales/pt-BR.wxl" + } + } + } + }, + "iOS": { + "minimumSystemVersion": "14.0" + } + } +} diff --git a/packages/kbot/gui/app/examples/api/src/App.svelte b/packages/kbot/gui/app/examples/api/src/App.svelte new file mode 100644 index 00000000..8e114c4b --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/App.svelte @@ -0,0 +1,534 @@ + + + +{#if isWindows} +
+ Tauri API Validation + + + + + + +
+{/if} + + + + +
+ +
+
+

{selected.label}

+
+
+ +
+
+
+ +
+ +
+
+

Console

+ +
+
+ {#each $messages as r} + {@html r.html} + {/each} +
+
+
+
diff --git a/packages/kbot/gui/app/examples/api/src/app.css b/packages/kbot/gui/app/examples/api/src/app.css new file mode 100644 index 00000000..6e8ae62d --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/app.css @@ -0,0 +1,61 @@ +*:not(h1, h2, h3, h4, h5, h6) { + margin: 0; + padding: 0; +} + +* { + box-sizing: border-box; + font-family: 'Rubik', sans-serif; +} + +::-webkit-scrollbar { + width: 0.25rem; + height: 3px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + border-radius: 0.25rem; +} + +code { + padding: 0.05rem 0.25rem; +} + +code.code-block { + padding: 0.5rem; +} + +#sidebar { + width: 18.75rem; +} + +@media screen and (max-width: 640px) { + #sidebar { + --translate-x: -18.75rem; + transform: translateX(var(--translate-x)); + } +} + +.sidebar-toggle { + margin-top: 0.5rem; + margin-left: 0.5rem; +} + +body { + overflow: hidden; + padding: env(safe-area-inset-top) env(safe-area-inset-right) + env(safe-area-inset-bottom) env(safe-area-inset-left); +} + +#sidebar, +#console { + padding-bottom: calc(env(safe-area-inset-bottom) + 24px); +} + +.transparent { + background-color: transparent; +} diff --git a/packages/kbot/gui/app/examples/api/src/lib/utils.js b/packages/kbot/gui/app/examples/api/src/lib/utils.js new file mode 100644 index 00000000..a5ddc058 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/lib/utils.js @@ -0,0 +1,15 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +export function arrayBufferToBase64(buffer, callback) { + const blob = new Blob([buffer], { + type: 'application/octet-binary' + }) + const reader = new FileReader() + reader.onload = function (evt) { + const dataurl = evt.target.result + callback(dataurl.substr(dataurl.indexOf(',') + 1)) + } + reader.readAsDataURL(blob) +} diff --git a/packages/kbot/gui/app/examples/api/src/main.js b/packages/kbot/gui/app/examples/api/src/main.js new file mode 100644 index 00000000..5b7473f6 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/main.js @@ -0,0 +1,14 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import 'uno.css' +import './app.css' +import App from './App.svelte' +import { mount } from 'svelte' + +const app = mount(App, { + target: document.querySelector('#app') +}) + +export default app diff --git a/packages/kbot/gui/app/examples/api/src/views/Biometric.svelte b/packages/kbot/gui/app/examples/api/src/views/Biometric.svelte new file mode 100644 index 00000000..3f287b0d --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/Biometric.svelte @@ -0,0 +1,30 @@ + + +
+ + +
+ diff --git a/packages/kbot/gui/app/examples/api/src/views/Cli.svelte b/packages/kbot/gui/app/examples/api/src/views/Cli.svelte new file mode 100644 index 00000000..06f38e02 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/Cli.svelte @@ -0,0 +1,29 @@ + + +
+ This binary can be run from the terminal and takes the following arguments: + +
+  --config <PATH>
+  --theme <light|dark|system>
+  --verbose
+
+ Additionally, it has a update --background subcommand. +
+
+
+ Note that the arguments are only parsed, not implemented. +
+
+
+ diff --git a/packages/kbot/gui/app/examples/api/src/views/Clipboard.svelte b/packages/kbot/gui/app/examples/api/src/views/Clipboard.svelte new file mode 100644 index 00000000..f16a7d71 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/Clipboard.svelte @@ -0,0 +1,69 @@ + + +
+ + + + +
diff --git a/packages/kbot/gui/app/examples/api/src/views/Communication.svelte b/packages/kbot/gui/app/examples/api/src/views/Communication.svelte new file mode 100644 index 00000000..f34dcd3e --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/Communication.svelte @@ -0,0 +1,52 @@ + + +
+ + + +
diff --git a/packages/kbot/gui/app/examples/api/src/views/Dialog.svelte b/packages/kbot/gui/app/examples/api/src/views/Dialog.svelte new file mode 100644 index 00000000..5aadad5a --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/Dialog.svelte @@ -0,0 +1,159 @@ + + +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ + + + + + + +
\ No newline at end of file diff --git a/packages/kbot/gui/app/examples/api/src/views/FileSystem.svelte b/packages/kbot/gui/app/examples/api/src/views/FileSystem.svelte new file mode 100644 index 00000000..fbec8628 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/FileSystem.svelte @@ -0,0 +1,272 @@ + + +
+ {#if isMobile} +
+ On mobile, paths outside of App* paths require the use of dialogs + regardless of Tauri's scope mechanism. +
+
+ {/if} +
+ + +
+
+
+ + + + +
+ + +
+ +
+ {#if file} +
+ + + +
+ {/if} + +

Watch

+ + +
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ +
+ + diff --git a/packages/kbot/gui/app/examples/api/src/views/Geolocation.svelte b/packages/kbot/gui/app/examples/api/src/views/Geolocation.svelte new file mode 100644 index 00000000..cd181dfb --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/Geolocation.svelte @@ -0,0 +1,29 @@ + + + diff --git a/packages/kbot/gui/app/examples/api/src/views/Haptics.svelte b/packages/kbot/gui/app/examples/api/src/views/Haptics.svelte new file mode 100644 index 00000000..9ddf15c1 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/Haptics.svelte @@ -0,0 +1,46 @@ + + +
+ + + + + +
+ +
+ +

+ Depending on your device settings for haptic feedback some of the buttons may + not work. +

diff --git a/packages/kbot/gui/app/examples/api/src/views/Http.svelte b/packages/kbot/gui/app/examples/api/src/views/Http.svelte new file mode 100644 index 00000000..5c7c3c49 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/Http.svelte @@ -0,0 +1,103 @@ + + +
+ +
+ +
+ +
+ +
+ +

HTTP Form

+ +
+ + +
+
+
+ +
+
+ diff --git a/packages/kbot/gui/app/examples/api/src/views/Nfc.svelte b/packages/kbot/gui/app/examples/api/src/views/Nfc.svelte new file mode 100644 index 00000000..4d7ea2c3 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/Nfc.svelte @@ -0,0 +1,126 @@ + + +
+
+
+ + +
+ + +
+ + {#if isAndroid} +
+

Filters

+
+ + + +
+
+ +
+
+ {/if} + +
+

Write Records

+
+ + +
+
+ + +
diff --git a/packages/kbot/gui/app/examples/api/src/views/Notifications.svelte b/packages/kbot/gui/app/examples/api/src/views/Notifications.svelte new file mode 100644 index 00000000..11273fdc --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/Notifications.svelte @@ -0,0 +1,44 @@ + + + + diff --git a/packages/kbot/gui/app/examples/api/src/views/Opener.svelte b/packages/kbot/gui/app/examples/api/src/views/Opener.svelte new file mode 100644 index 00000000..e20e7658 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/Opener.svelte @@ -0,0 +1,68 @@ + + +
+
+ + + with + +
+ +
+ + + with + +
+ +
+ + +
+
diff --git a/packages/kbot/gui/app/examples/api/src/views/Scanner.svelte b/packages/kbot/gui/app/examples/api/src/views/Scanner.svelte new file mode 100644 index 00000000..ea0609e3 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/Scanner.svelte @@ -0,0 +1,163 @@ + + +
+
+
+ + +
+
+ +
+ +
+
+
+ +
+
+
+
+

Aim your camera at a QR code

+ +
+
+
+
+
+
+
+
+
+
+ + diff --git a/packages/kbot/gui/app/examples/api/src/views/Shell.svelte b/packages/kbot/gui/app/examples/api/src/views/Shell.svelte new file mode 100644 index 00000000..faaa8c4c --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/Shell.svelte @@ -0,0 +1,100 @@ + + +
+
+ Script: + +
+
+ Encoding: + +
+
+ Working directory: + +
+
+ Arguments: + +
+
+ + +
+ {#if child} +
+ + + {/if} +
diff --git a/packages/kbot/gui/app/examples/api/src/views/Shortcuts.svelte b/packages/kbot/gui/app/examples/api/src/views/Shortcuts.svelte new file mode 100644 index 00000000..41d0271d --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/Shortcuts.svelte @@ -0,0 +1,72 @@ + + +
+ + +
+
+
+ {#each $shortcuts as savedShortcut} +
+ {savedShortcut} + +
+ {/each} + {#if $shortcuts.length > 1} +
+ + {/if} +
diff --git a/packages/kbot/gui/app/examples/api/src/views/Store.svelte b/packages/kbot/gui/app/examples/api/src/views/Store.svelte new file mode 100644 index 00000000..b03f563d --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/Store.svelte @@ -0,0 +1,114 @@ + + +
+
+
+ Key: + +
+ +
+ Value: + +
+ +
+ + + + + +
+
Store at {path} on disk
+
+ +
+

Store Values

+ {#each Object.entries(cache) as [k, v]} +
{k} = {v}
+ {/each} +
+
diff --git a/packages/kbot/gui/app/examples/api/src/views/Updater.svelte b/packages/kbot/gui/app/examples/api/src/views/Updater.svelte new file mode 100644 index 00000000..26d074a6 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/Updater.svelte @@ -0,0 +1,90 @@ + + +
+ {#if !isChecking && !newUpdate} + + {:else if !isInstalling && newUpdate} + + {:else} +
+ {progress}% +
+
+ {/if} +
+ + diff --git a/packages/kbot/gui/app/examples/api/src/views/Upload.svelte b/packages/kbot/gui/app/examples/api/src/views/Upload.svelte new file mode 100644 index 00000000..6c1c3852 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/Upload.svelte @@ -0,0 +1,376 @@ + + +
+
+

File Download

+ +
+
+ + +
+ +
+ +
+ + +
+
+ + {#if downloadPath} +
+
+ File will be saved as: +
{downloadPath}
+
+
+ {/if} + + + + {#if downloadProgress} +
+
+ Progress: {downloadProgress.percentage}% + Speed: {Math.round(downloadProgress.transferSpeed / 1024)} KB/s +
+
+
+
+
+ {Math.round(downloadProgress.progressTotal / 1024)} KB / {Math.round(downloadProgress.total / 1024)} KB +
+
+ {/if} + + {#if downloadResult} +
+ +
+ {/if} +
+
+ +
+

File Upload

+ +
+
+ + +
+ +
+ +
+ + +
+
+ + + + {#if uploadProgress} +
+
+ Progress: {uploadProgress.percentage}% + Speed: {Math.round(uploadProgress.transferSpeed / 1024)} KB/s +
+
+
+
+
+ {Math.round(uploadProgress.progressTotal / 1024)} KB / {Math.round(uploadProgress.total / 1024)} KB +
+
+ {/if} + + {#if uploadResult} +
+ +
+ {/if} +
+
+
diff --git a/packages/kbot/gui/app/examples/api/src/views/WebRTC.svelte b/packages/kbot/gui/app/examples/api/src/views/WebRTC.svelte new file mode 100644 index 00000000..e0f9862a --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/WebRTC.svelte @@ -0,0 +1,56 @@ + + +
+
Not available for Linux
+ +
diff --git a/packages/kbot/gui/app/examples/api/src/views/Welcome.svelte b/packages/kbot/gui/app/examples/api/src/views/Welcome.svelte new file mode 100644 index 00000000..5d42aaad --- /dev/null +++ b/packages/kbot/gui/app/examples/api/src/views/Welcome.svelte @@ -0,0 +1,47 @@ + + +

+ This is a demo of Tauri's API capabilities using the @tauri-apps/api package. It's used as the main validation app, serving as the test bed of our + development process. In the future, this app will be used on Tauri's integration + tests. +

+ +
+
+
+App name: {appName}
+App version: {version}
+Tauri version: {tauriVersion}
+
+
+
+ + +
diff --git a/packages/kbot/gui/app/examples/api/unocss.config.js b/packages/kbot/gui/app/examples/api/unocss.config.js new file mode 100644 index 00000000..d2069a49 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/unocss.config.js @@ -0,0 +1,100 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { defineConfig, presetIcons, presetUno, presetWebFonts } from 'unocss' +import extractorSvelte from '@unocss/extractor-svelte' + +export default defineConfig({ + theme: { + colors: { + primary: '#FFFFFF', + primaryLighter: '#e9ecef', + darkPrimary: '#1B1B1D', + darkPrimaryLighter: '#242526', + primaryText: '#1C1E21', + darkPrimaryText: '#E3E3E3', + secondaryText: '#858A91', + darkSecondaryText: '#C2C5CA', + accent: '#3578E5', + accentDark: '#306cce', + accentDarker: '#2d66c3', + accentDarkest: '#2554a0', + accentLight: '#538ce9', + accentLighter: '#72a1ed', + accentLightest: '#9abcf2', + accentText: '#FFFFFF', + darkAccent: '#67d6ed', + darkAccentDark: '#49cee9', + darkAccentDarker: '#39cae8', + darkAccentDarkest: '#19b5d5', + darkAccentLight: '#85def1', + darkAccentLighter: '#95e2f2', + darkAccentLightest: '#c2eff8', + darkAccentText: '#1C1E21', + code: '#d6d8da', + codeDark: '#282a2e', + hoverOverlay: 'rgba(0,0,0,.05)', + hoverOverlayDarker: 'rgba(0,0,0,.1)', + darkHoverOverlay: 'hsla(0,0%,100%,.05)', + darkHoverOverlayDarker: 'hsla(0,0%,100%,.1)' + } + }, + preflights: [ + { + getCSS: ({ theme }) => ` + ::-webkit-scrollbar-thumb { + background-color: ${theme.colors.accent}; + } + + .dark ::-webkit-scrollbar-thumb { + background-color: ${theme.colors.darkAccent}; + } + + code { + font-size: ${theme.fontSize.xs[0]}; + font-family: ${theme.fontFamily.mono}; + border-radius: ${theme.borderRadius['DEFAULT']}; + background-color: ${theme.colors.code}; + } + + .code-block { + font-family: ${theme.fontFamily.mono}; + font-size: ${theme.fontSize.sm[0]}; + } + + .dark code { + background-color: ${theme.colors.codeDark}; + } + ` + } + ], + shortcuts: { + btn: `select-none outline-none shadow-md p-2 rd-1 text-primaryText border-none font-400 dark:font-600 + bg-accent hover:bg-accentDarker active:bg-accentDarkest text-accentText + dark:bg-darkAccent dark:hover:bg-darkAccentDarker dark:active:bg-darkAccentDarkest dark:text-darkAccentText`, + nv: `decoration-none flex items-center relative p-2 rd-1 transition-all-125 ease + text-darkSecondaryText + hover:text-accent dark:hover:text-darkAccent + hover:bg-darkHoverOverlay hover:border-l-4`, + nv_selected: `nv bg-darkHoverOverlay text-accent dark:text-darkAccent border-l-4`, + note: `decoration-none flex-inline items-center relative p-2 rd-1 + border-l-4 border-accent dark:border-darkAccent + bg-accent/10 dark:bg-darkAccent/10`, + 'note-red': + 'note bg-red-700/10 dark:bg-red-700/10 after:bg-red-700 dark:after:bg-red-700', + input: + 'h-10 flex items-center outline-none border-none p-2 rd-1 shadow-md bg-primaryLighter dark:bg-darkPrimaryLighter text-primaryText dark:text-darkPrimaryText' + }, + presets: [ + presetUno(), + presetIcons(), + presetWebFonts({ + fonts: { + sans: 'Rubik', + mono: ['Fira Code', 'Fira Mono:400,700'] + } + }) + ], + extractors: [extractorSvelte] +}) diff --git a/packages/kbot/gui/app/examples/api/vite.config.js b/packages/kbot/gui/app/examples/api/vite.config.js new file mode 100644 index 00000000..f7d87db8 --- /dev/null +++ b/packages/kbot/gui/app/examples/api/vite.config.js @@ -0,0 +1,34 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { defineConfig } from 'vite' +import Unocss from 'unocss/vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import process from 'process' + +const host = process.env.TAURI_DEV_HOST + +// https://vitejs.dev/config/ +export default defineConfig(async () => { + return { + plugins: [Unocss(), svelte()], + build: { + rollupOptions: { + output: { + entryFileNames: `assets/[name].js`, + chunkFileNames: `assets/[name].js`, + assetFileNames: `assets/[name].[ext]` + } + } + }, + server: { + host: host || false, + port: 5173, + strictPort: true, + fs: { + allow: ['.', '../../tooling/api/dist'] + } + } + } +}) diff --git a/packages/kbot/gui/app/package.json b/packages/kbot/gui/app/package.json new file mode 100644 index 00000000..91f55916 --- /dev/null +++ b/packages/kbot/gui/app/package.json @@ -0,0 +1,39 @@ +{ + "name": "plugins-workspace", + "private": true, + "license": "MIT OR Apache-2.0", + "type": "module", + "scripts": { + "build": "pnpm run -r --parallel --filter !plugins-workspace --filter !\"./plugins/*/examples/**\" --filter !\"./examples/*\" build", + "lint": "eslint .", + "format": "prettier --write .", + "format:check": "prettier --check .", + "example:api:dev": "pnpm run --filter \"api\" tauri dev" + }, + "devDependencies": { + "@eslint/js": "9.35.0", + "@rollup/plugin-node-resolve": "16.0.1", + "@rollup/plugin-terser": "0.4.4", + "@rollup/plugin-typescript": "12.1.4", + "covector": "^0.12.4", + "eslint": "9.35.0", + "eslint-config-prettier": "10.1.8", + "eslint-plugin-security": "3.0.1", + "prettier": "3.6.2", + "rollup": "4.50.1", + "tslib": "2.8.1", + "typescript": "5.9.2", + "typescript-eslint": "8.42.0" + }, + "pnpm": { + "overrides": { + "esbuild@<0.25.0": ">=0.25.0" + }, + "onlyBuiltDependencies": [ + "esbuild" + ] + }, + "engines": { + "pnpm": "^10.0.0" + } +} diff --git a/packages/kbot/gui/app/plugins/autostart/CHANGELOG.md b/packages/kbot/gui/app/plugins/autostart/CHANGELOG.md new file mode 100644 index 00000000..a14a40e1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/CHANGELOG.md @@ -0,0 +1,106 @@ +# Changelog + +## \[2.5.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.4.0] + +- [`764e8f77`](https://github.com/tauri-apps/plugins-workspace/commit/764e8f7719247da515243d9c9cafa6d087d21769) ([#2707](https://github.com/tauri-apps/plugins-workspace/pull/2707)) Added a new builder method app_name() to allow customizing the application name in the autostart entry. + +## \[2.3.0] + +- [`8ecb418a`](https://github.com/tauri-apps/plugins-workspace/commit/8ecb418a1a35d7f234dc5d833746ac2d8e062aec) ([#2569](https://github.com/tauri-apps/plugins-workspace/pull/2569)) Add a `Builder` for more flexible settings + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +## \[2.0.0-beta.8] + +- [`99d6ac0f`](https://github.com/tauri-apps/plugins-workspace/commit/99d6ac0f9506a6a4a1aa59c728157190a7441af6) ([#1606](https://github.com/tauri-apps/plugins-workspace/pull/1606) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The JS packages now specify the *minimum* `@tauri-apps/api` version instead of a single exact version. +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.7] + +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.6] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.5] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.4] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.3] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.4] + +- [`a233919`](https://github.com/tauri-apps/plugins-workspace/commit/a2339195aa940bff86d76375fd05087595bf06ce)([#1118](https://github.com/tauri-apps/plugins-workspace/pull/1118)) Fix LaunchAgent-based autostart for macOS. + +## \[2.0.0-beta.3] + +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. + +## \[2.0.0-beta.2] + +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. + +## \[2.0.0-alpha.5] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.13. + +## \[2.0.0-alpha.4] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.12. + +## \[2.0.0-alpha.3] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to @tauri-apps/api v2.0.0-alpha.11. + +## \[2.0.0-alpha.2] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update to @tauri-apps/api v2.0.0-alpha.9. + +## \[2.0.0-alpha.2] + +- [`4e2cef9`](https://github.com/tauri-apps/plugins-workspace/commit/4e2cef9b702bbbb9cf4ee17de50791cb21f1b2a4)([#593](https://github.com/tauri-apps/plugins-workspace/pull/593)) Update to alpha.12. + +## \[2.0.0-alpha.1] + +- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11. + +## \[2.0.0-alpha.0] + +- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! diff --git a/packages/kbot/gui/app/plugins/autostart/Cargo.toml b/packages/kbot/gui/app/plugins/autostart/Cargo.toml new file mode 100644 index 00000000..5a0f0395 --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "tauri-plugin-autostart" +version = "2.5.0" +description = "Automatically launch your application at startup." +authors = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-autostart" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.platforms.support] +windows = { level = "full", notes = "" } +linux = { level = "full", notes = "" } +macos = { level = "full", notes = "" } +android = { level = "none", notes = "" } +ios = { level = "none", notes = "" } + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +thiserror = { workspace = true } +auto-launch = "0.5" diff --git a/packages/kbot/gui/app/plugins/autostart/LICENSE.spdx b/packages/kbot/gui/app/plugins/autostart/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/autostart/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/autostart/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/autostart/LICENSE_MIT b/packages/kbot/gui/app/plugins/autostart/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/autostart/README.md b/packages/kbot/gui/app/plugins/autostart/README.md new file mode 100644 index 00000000..f9f9dce1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/README.md @@ -0,0 +1,98 @@ +![plugin-autostart](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/autostart/banner.png) + +Automatically launch your application at startup. + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | x | +| iOS | x | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-autostart = "2.0.0" +# alternatively with Git: +tauri-plugin-autostart = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +```sh +pnpm add @tauri-apps/plugin-autostart +# or +npm add @tauri-apps/plugin-autostart +# or +yarn add @tauri-apps/plugin-autostart +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_autostart::Builder::new() + .args(["--flag1", "--flag2"]) + .app_name("My Custom Name") + .build()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { enable, isEnabled, disable } from '@tauri-apps/plugin-autostart' + +await enable() + +console.log(`registered for autostart? ${await isEnabled()}`) + +disable() +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/autostart/SECURITY.md b/packages/kbot/gui/app/plugins/autostart/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/autostart/api-iife.js b/packages/kbot/gui/app/plugins/autostart/api-iife.js new file mode 100644 index 00000000..77a12c92 --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_AUTOSTART__=function(n){"use strict";async function t(n,t={},a){return window.__TAURI_INTERNALS__.invoke(n,t,a)}return"function"==typeof SuppressedError&&SuppressedError,n.disable=async function(){await t("plugin:autostart|disable")},n.enable=async function(){await t("plugin:autostart|enable")},n.isEnabled=async function(){return await t("plugin:autostart|is_enabled")},n}({});Object.defineProperty(window.__TAURI__,"autostart",{value:__TAURI_PLUGIN_AUTOSTART__})} diff --git a/packages/kbot/gui/app/plugins/autostart/banner.png b/packages/kbot/gui/app/plugins/autostart/banner.png new file mode 100644 index 00000000..952d552a Binary files /dev/null and b/packages/kbot/gui/app/plugins/autostart/banner.png differ diff --git a/packages/kbot/gui/app/plugins/autostart/build.rs b/packages/kbot/gui/app/plugins/autostart/build.rs new file mode 100644 index 00000000..1460469b --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/build.rs @@ -0,0 +1,11 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +const COMMANDS: &[&str] = &["enable", "disable", "is_enabled"]; + +fn main() { + tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .build(); +} diff --git a/packages/kbot/gui/app/plugins/autostart/guest-js/index.ts b/packages/kbot/gui/app/plugins/autostart/guest-js/index.ts new file mode 100644 index 00000000..fca8344f --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/guest-js/index.ts @@ -0,0 +1,17 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { invoke } from '@tauri-apps/api/core' + +export async function isEnabled(): Promise { + return await invoke('plugin:autostart|is_enabled') +} + +export async function enable(): Promise { + await invoke('plugin:autostart|enable') +} + +export async function disable(): Promise { + await invoke('plugin:autostart|disable') +} diff --git a/packages/kbot/gui/app/plugins/autostart/package.json b/packages/kbot/gui/app/plugins/autostart/package.json new file mode 100644 index 00000000..5f3d2adf --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/package.json @@ -0,0 +1,29 @@ +{ + "name": "@tauri-apps/plugin-autostart", + "version": "2.5.0", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/autostart/permissions/autogenerated/commands/disable.toml b/packages/kbot/gui/app/plugins/autostart/permissions/autogenerated/commands/disable.toml new file mode 100644 index 00000000..849c50a2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/permissions/autogenerated/commands/disable.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-disable" +description = "Enables the disable command without any pre-configured scope." +commands.allow = ["disable"] + +[[permission]] +identifier = "deny-disable" +description = "Denies the disable command without any pre-configured scope." +commands.deny = ["disable"] diff --git a/packages/kbot/gui/app/plugins/autostart/permissions/autogenerated/commands/enable.toml b/packages/kbot/gui/app/plugins/autostart/permissions/autogenerated/commands/enable.toml new file mode 100644 index 00000000..f931a9e5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/permissions/autogenerated/commands/enable.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-enable" +description = "Enables the enable command without any pre-configured scope." +commands.allow = ["enable"] + +[[permission]] +identifier = "deny-enable" +description = "Denies the enable command without any pre-configured scope." +commands.deny = ["enable"] diff --git a/packages/kbot/gui/app/plugins/autostart/permissions/autogenerated/commands/is_enabled.toml b/packages/kbot/gui/app/plugins/autostart/permissions/autogenerated/commands/is_enabled.toml new file mode 100644 index 00000000..88a6a282 --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/permissions/autogenerated/commands/is_enabled.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-is-enabled" +description = "Enables the is_enabled command without any pre-configured scope." +commands.allow = ["is_enabled"] + +[[permission]] +identifier = "deny-is-enabled" +description = "Denies the is_enabled command without any pre-configured scope." +commands.deny = ["is_enabled"] diff --git a/packages/kbot/gui/app/plugins/autostart/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/autostart/permissions/autogenerated/reference.md new file mode 100644 index 00000000..50bc1a94 --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/permissions/autogenerated/reference.md @@ -0,0 +1,104 @@ +## Default Permission + +This permission set configures if your +application can enable or disable auto +starting the application on boot. + +#### Granted Permissions + +It allows all to check, enable and +disable the automatic start on boot. + +#### This default permission set includes the following: + +- `allow-enable` +- `allow-disable` +- `allow-is-enabled` + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`autostart:allow-disable` + + + +Enables the disable command without any pre-configured scope. + +
+ +`autostart:deny-disable` + + + +Denies the disable command without any pre-configured scope. + +
+ +`autostart:allow-enable` + + + +Enables the enable command without any pre-configured scope. + +
+ +`autostart:deny-enable` + + + +Denies the enable command without any pre-configured scope. + +
+ +`autostart:allow-is-enabled` + + + +Enables the is_enabled command without any pre-configured scope. + +
+ +`autostart:deny-is-enabled` + + + +Denies the is_enabled command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/autostart/permissions/default.toml b/packages/kbot/gui/app/plugins/autostart/permissions/default.toml new file mode 100644 index 00000000..a2ac766f --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/permissions/default.toml @@ -0,0 +1,15 @@ +"$schema" = "schemas/schema.json" +[default] +description = """ +This permission set configures if your +application can enable or disable auto +starting the application on boot. + +#### Granted Permissions + +It allows all to check, enable and +disable the automatic start on boot. + +""" + +permissions = ["allow-enable", "allow-disable", "allow-is-enabled"] diff --git a/packages/kbot/gui/app/plugins/autostart/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/autostart/permissions/schemas/schema.json new file mode 100644 index 00000000..af681221 --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/permissions/schemas/schema.json @@ -0,0 +1,342 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the disable command without any pre-configured scope.", + "type": "string", + "const": "allow-disable", + "markdownDescription": "Enables the disable command without any pre-configured scope." + }, + { + "description": "Denies the disable command without any pre-configured scope.", + "type": "string", + "const": "deny-disable", + "markdownDescription": "Denies the disable command without any pre-configured scope." + }, + { + "description": "Enables the enable command without any pre-configured scope.", + "type": "string", + "const": "allow-enable", + "markdownDescription": "Enables the enable command without any pre-configured scope." + }, + { + "description": "Denies the enable command without any pre-configured scope.", + "type": "string", + "const": "deny-enable", + "markdownDescription": "Denies the enable command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "This permission set configures if your\napplication can enable or disable auto\nstarting the application on boot.\n\n#### Granted Permissions\n\nIt allows all to check, enable and\ndisable the automatic start on boot.\n\n\n#### This default permission set includes:\n\n- `allow-enable`\n- `allow-disable`\n- `allow-is-enabled`", + "type": "string", + "const": "default", + "markdownDescription": "This permission set configures if your\napplication can enable or disable auto\nstarting the application on boot.\n\n#### Granted Permissions\n\nIt allows all to check, enable and\ndisable the automatic start on boot.\n\n\n#### This default permission set includes:\n\n- `allow-enable`\n- `allow-disable`\n- `allow-is-enabled`" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/autostart/rollup.config.js b/packages/kbot/gui/app/plugins/autostart/rollup.config.js new file mode 100644 index 00000000..1f349ec8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/rollup.config.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' + +export default createConfig() diff --git a/packages/kbot/gui/app/plugins/autostart/src/lib.rs b/packages/kbot/gui/app/plugins/autostart/src/lib.rs new file mode 100644 index 00000000..4b4c7c23 --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/src/lib.rs @@ -0,0 +1,250 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Automatically launch your application at startup. Supports Windows, Mac (via AppleScript or Launch Agent), and Linux. + +#![doc( + html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", + html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" +)] +#![cfg(not(any(target_os = "android", target_os = "ios")))] + +use auto_launch::{AutoLaunch, AutoLaunchBuilder}; +use serde::{ser::Serializer, Serialize}; +use tauri::{ + command, + plugin::{Builder as PluginBuilder, TauriPlugin}, + Manager, Runtime, State, +}; + +use std::env::current_exe; + +type Result = std::result::Result; + +#[derive(Debug, Default, Copy, Clone)] +pub enum MacosLauncher { + #[default] + LaunchAgent, + AppleScript, +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[error("{0}")] + Anyhow(String), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} + +pub struct AutoLaunchManager(AutoLaunch); + +impl AutoLaunchManager { + pub fn enable(&self) -> Result<()> { + self.0 + .enable() + .map_err(|e| e.to_string()) + .map_err(Error::Anyhow) + } + + pub fn disable(&self) -> Result<()> { + self.0 + .disable() + .map_err(|e| e.to_string()) + .map_err(Error::Anyhow) + } + + pub fn is_enabled(&self) -> Result { + self.0 + .is_enabled() + .map_err(|e| e.to_string()) + .map_err(Error::Anyhow) + } +} + +pub trait ManagerExt { + /// TODO: Rename these to `autostart` or `auto_start` in v3 + fn autolaunch(&self) -> State<'_, AutoLaunchManager>; +} + +impl> ManagerExt for T { + /// TODO: Rename these to `autostart` or `auto_start` in v3 + fn autolaunch(&self) -> State<'_, AutoLaunchManager> { + self.state::() + } +} + +#[command] +async fn enable(manager: State<'_, AutoLaunchManager>) -> Result<()> { + manager.enable() +} + +#[command] +async fn disable(manager: State<'_, AutoLaunchManager>) -> Result<()> { + manager.disable() +} + +#[command] +async fn is_enabled(manager: State<'_, AutoLaunchManager>) -> Result { + manager.is_enabled() +} + +#[derive(Default)] +pub struct Builder { + #[cfg(target_os = "macos")] + macos_launcher: MacosLauncher, + args: Vec, + app_name: Option, +} + +impl Builder { + /// Create a new auto start builder with default settings + pub fn new() -> Self { + Self::default() + } + + /// Adds an argument to pass to your app on startup. + /// + /// ## Examples + /// + /// ```no_run + /// Builder::new() + /// .arg("--from-autostart") + /// .arg("--hey") + /// .build(); + /// ``` + pub fn arg>(mut self, arg: S) -> Self { + self.args.push(arg.into()); + self + } + + /// Adds multiple arguments to pass to your app on startup. + /// + /// ## Examples + /// + /// ```no_run + /// Builder::new() + /// .args(["--from-autostart", "--hey"]) + /// .build(); + /// ``` + pub fn args(mut self, args: I) -> Self + where + I: IntoIterator, + S: Into, + { + for arg in args { + self = self.arg(arg); + } + self + } + + /// Sets whether to use launch agent or apple script to be used to enable auto start, + /// the builder's default is [`MacosLauncher::LaunchAgent`] + #[cfg(target_os = "macos")] + pub fn macos_launcher(mut self, macos_launcher: MacosLauncher) -> Self { + self.macos_launcher = macos_launcher; + self + } + + /// Sets the app name to be used for the auto start entry. + /// + /// ## Examples + /// + /// ```no_run + /// Builder::new() + /// .app_name("My Custom Name")) + /// .build(); + /// ``` + pub fn app_name>(mut self, app_name: S) -> Self { + self.app_name = Some(app_name.into()); + self + } + + pub fn build(self) -> TauriPlugin { + PluginBuilder::new("autostart") + .invoke_handler(tauri::generate_handler![enable, disable, is_enabled]) + .setup(move |app, _api| { + let mut builder = AutoLaunchBuilder::new(); + + let app_name = self + .app_name + .as_ref() + .unwrap_or_else(|| &app.package_info().name); + builder.set_app_name(app_name); + + builder.set_args(&self.args); + + let current_exe = current_exe()?; + + #[cfg(windows)] + builder.set_app_path(¤t_exe.display().to_string()); + + #[cfg(target_os = "macos")] + { + builder.set_use_launch_agent(matches!( + self.macos_launcher, + MacosLauncher::LaunchAgent + )); + // on macOS, current_exe gives path to /Applications/Example.app/MacOS/Example + // but this results in seeing a Unix Executable in macOS login items + // It must be: /Applications/Example.app + // If it didn't find exactly a single occurance of .app, it will default to + // exe path to not break it. + let exe_path = current_exe.canonicalize()?.display().to_string(); + let parts: Vec<&str> = exe_path.split(".app/").collect(); + let app_path = if parts.len() == 2 + && matches!(self.macos_launcher, MacosLauncher::AppleScript) + { + format!("{}.app", parts.first().unwrap()) + } else { + exe_path + }; + builder.set_app_path(&app_path); + } + + #[cfg(target_os = "linux")] + if let Some(appimage) = app + .env() + .appimage + .and_then(|p| p.to_str().map(|s| s.to_string())) + { + builder.set_app_path(&appimage); + } else { + builder.set_app_path(¤t_exe.display().to_string()); + } + + app.manage(AutoLaunchManager( + builder.build().map_err(|e| e.to_string())?, + )); + Ok(()) + }) + .build() + } +} + +/// Initializes the plugin. +/// +/// `args` - are passed to your app on startup. +pub fn init( + #[allow(unused)] macos_launcher: MacosLauncher, + args: Option>, +) -> TauriPlugin { + let mut builder = Builder::new(); + if let Some(args) = args { + builder = builder.args(args) + } + #[cfg(target_os = "macos")] + { + builder = builder.macos_launcher(macos_launcher); + } + builder.build() +} diff --git a/packages/kbot/gui/app/plugins/autostart/tsconfig.json b/packages/kbot/gui/app/plugins/autostart/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/autostart/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/CHANGELOG.md b/packages/kbot/gui/app/plugins/barcode-scanner/CHANGELOG.md new file mode 100644 index 00000000..543acbd8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/CHANGELOG.md @@ -0,0 +1,127 @@ +# Changelog + +## \[2.4.0] + +- [`aa9140e1`](https://github.com/tauri-apps/plugins-workspace/commit/aa9140e1ac239ab9f015f92b2ed52bbf0eda7c12) ([#2437](https://github.com/tauri-apps/plugins-workspace/pull/2437) by [@enkhjile](https://github.com/tauri-apps/plugins-workspace/../../enkhjile)) Added support for GS1 DataBar on iOS 15.4+ + +## \[2.3.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.2.1] + +- [`f634e524`](https://github.com/tauri-apps/plugins-workspace/commit/f634e5248ebe428f8305a59f74c13fc15147fb8e) This is an "empty" release to update the plugins' source files on crates.io and docs.rs. This should fix docs.rs build failures for projects using tauri plugins as dependencies. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.2] + +- [`79d6e19c`](https://github.com/tauri-apps/plugins-workspace/commit/79d6e19c4b38bae0cab29eb88df379e2237d9aac) ([#1777](https://github.com/tauri-apps/plugins-workspace/pull/1777)) Fixed an issue which caused checkPermission and requestPermission to be mixed up. + +## \[2.0.0-rc.4] + +- [`713c54ef`](https://github.com/tauri-apps/plugins-workspace/commit/713c54ef8365d36afd84585dcabed2fbb751223d) ([#1749](https://github.com/tauri-apps/plugins-workspace/pull/1749) by [@olivierlemasle](https://github.com/tauri-apps/plugins-workspace/../../olivierlemasle)) Remove unused Android dependencies. +- [`8c3a6a25`](https://github.com/tauri-apps/plugins-workspace/commit/8c3a6a253d7029d370659d2102f91a458745d345) ([#1758](https://github.com/tauri-apps/plugins-workspace/pull/1758) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Validate missing `NSCameraUsageDescription` Info.plist value. + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Use `PermissionState` from the `tauri` crate, which now also includes a "prompt with rationale" variant for Android (returned when your app must explain to the user why it needs the permission). +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.2] + +- [`b9147758`](https://github.com/tauri-apps/plugins-workspace/commit/b914775898c2bee7ceb20bd17ee595005cd17a64) ([#1679](https://github.com/tauri-apps/plugins-workspace/pull/1679) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Explicitly set a minimum macOS version for the Swift package. + +## \[2.0.0-rc.1] + +- [`2c00c029`](https://github.com/tauri-apps/plugins-workspace/commit/2c00c0292c9127b81567de46691e8c0f73557261) ([#1630](https://github.com/tauri-apps/plugins-workspace/pull/1630) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused multi-word IIFE names to not be formatted correctly. For example the `barcode-scanner` was defined as `window.__TAURI_PLUGIN_CLIPBOARDMANAGER__` instead of `window.__TAURI_PLUGIN_CLIPBOARD_MANAGER__`. + +### changes + +- [`6b079cfd`](https://github.com/tauri-apps/plugins-workspace/commit/6b079cfdd107c94abc2c7300f6af00bac3ff4040) ([#1649](https://github.com/tauri-apps/plugins-workspace/pull/1649) by [@ahqsoftwares](https://github.com/tauri-apps/plugins-workspace/../../ahqsoftwares)) Remove targetSdk from build.kts files as it is deprecated and will be removed from DSL v9.0 + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +## \[2.0.0-beta.8] + +- [`99d6ac0f`](https://github.com/tauri-apps/plugins-workspace/commit/99d6ac0f9506a6a4a1aa59c728157190a7441af6) ([#1606](https://github.com/tauri-apps/plugins-workspace/pull/1606) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The JS packages now specify the *minimum* `@tauri-apps/api` version instead of a single exact version. +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.7] + +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.6] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.5] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.4] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.3] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.4] + +- [`326df688`](https://github.com/tauri-apps/plugins-workspace/commit/326df6883998d416fc0837583ed972854628bb52)([#1236](https://github.com/tauri-apps/plugins-workspace/pull/1236)) Fixes command argument parsing on iOS. + +## \[2.0.0-beta.3] + +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. + +## \[2.0.0-beta.2] + +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Add permissions. + +## \[2.0.0-alpha.4] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.13. + +## \[2.0.0-alpha.3] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.12. + +## \[2.0.0-alpha.2] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to @tauri-apps/api v2.0.0-alpha.11. + +## \[2.0.0-alpha.1] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update to @tauri-apps/api v2.0.0-alpha.9. + +## \[2.0.0-alpha.0] + +- [`454428c`](https://github.com/tauri-apps/plugins-workspace/commit/454428cd50ce4962f0bad8e355aebc68af8cc61f)([#536](https://github.com/tauri-apps/plugins-workspace/pull/536)) Initial release. + commit/454428cd50ce4962f0bad8e355aebc68af8cc61f)([#536](https://github.com/tauri-apps/plugins-workspace/pull/536)) Initial release. + 36]\(https://github.com/tauri-apps/plugins-workspace/pull/536)) Initial release. + commit/454428cd50ce4962f0bad8e355aebc68af8cc61f)([#536](https://github.com/tauri-apps/plugins-workspace/pull/536)) Initial release. + . + commit/454428cd50ce4962f0bad8e355aebc68af8cc61f)([#536](https://github.com/tauri-apps/plugins-workspace/pull/536)) Initial release. + github.com/tauri-apps/plugins-workspace/pull/536)) Initial release. diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/Cargo.toml b/packages/kbot/gui/app/plugins/barcode-scanner/Cargo.toml new file mode 100644 index 00000000..01679162 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "tauri-plugin-barcode-scanner" +version = "2.4.0" +description = "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-barcode-scanner" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] +targets = ["x86_64-linux-android"] + +[package.metadata.platforms.support] +windows = { level = "none", notes = "" } +linux = { level = "none", notes = "" } +macos = { level = "none", notes = "" } +android = { level = "full", notes = "" } +ios = { level = "full", notes = "" } + + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } + +[target.'cfg(target_os = "ios")'.dependencies] +tauri = { workspace = true, features = ["wry"] } diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/LICENSE.spdx b/packages/kbot/gui/app/plugins/barcode-scanner/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/barcode-scanner/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/LICENSE_MIT b/packages/kbot/gui/app/plugins/barcode-scanner/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/README.md b/packages/kbot/gui/app/plugins/barcode-scanner/README.md new file mode 100644 index 00000000..5da47abd --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/README.md @@ -0,0 +1,113 @@ +![Barcode Scanner](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/barcode-scanner/banner.png) + +Allows your mobile application to use the camera to scan QR codes, EAN-13 and other kinds of barcodes. + +| Platform | Supported | +| -------- | --------- | +| Linux | x | +| Windows | x | +| macOS | x | +| Android | ✓ | +| iOS | ✓ | + +## Install + +_This plugin requires a Rust version of at least **1.64**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-barcode-scanner = "2.0.0" +# alternatively with Git: +tauri-plugin-barcode-scanner = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +```sh +pnpm add @tauri-apps/plugin-barcode-scanner +# or +npm add @tauri-apps/plugin-barcode-scanner +# or +yarn add @tauri-apps/plugin-barcode-scanner +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_barcode_scanner::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { scan } from '@tauri-apps/plugin-barcode-scanner' + +// `windowed: true` actually sets the webview to transparent +// instead of opening a separate view for the camera +// make sure your user interface is ready to show what is underneath with a transparent element +scan({ windowed: true, formats: [''] }) +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Contributed By + + + + + + + + +
+ + CrabNebula + + + + Impierce + +
+ +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/SECURITY.md b/packages/kbot/gui/app/plugins/barcode-scanner/SECURITY.md new file mode 100644 index 00000000..135504ec --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/SECURITY.md @@ -0,0 +1,65 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). + +## Threat Model + +As there are only the `scan` and `cancel` commands exposed to the frontend, +there is no additional risk or exposure of additional information. +Only barcodes are passed and no raw camera access is used, which means no images are available to the frontend. + +The application is only usable on iOS and Android and therefore the specific mobile operating system security boundaries need to be considered. + +### Security Assumptions + +- The QR code parsing into a link/text is trusted and correctly handled by the mobile operating system +- The link itself is untrusted and additional validation/sanitization needs to be handled by the app developer +- The camera is not passing images to the app +- The camera permission is granted at first use by the user and can be revoked at any time +- The Android manifest also states that the camera permission is required + +### Threats + +#### Silent Interaction + +##### When is it possible? + +The following threat is either caused by a malicious developer, which has further implications and should be considered as a full compromise of an application or system, or by +compromise of the application frontend. In the second case there are several impact minimization methods (e.g. the CSP) and if all of these fail the possible risk could occur. +Therefore it is unlikely to occur in most cases but should be considered when using this plugin. + +##### What is possible? + +The camera has two modes. The first one is where the user can see the background camera image and no further interaction is possible. +The second mode allows the developer to assist the user and add a transparent overlay to the image, providing hints or additional information (like a link preview). +The overlay could be made non-transparent by the application frontend and as long as the app is open (and in some cases) it could read QR codes in range of the camera lense. + +#### Out Of Scope + +- Exploits in the operating system QR code parsing functionality +- Exploits based on the string passed to the application using this plugin +- Continous camera/QR scan usage even when application is in background + +## Best Practices + +There is no additional exposure aside from reading barcodes in the webview and there are no specific best practices for secure usage. diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/android/.gitignore b/packages/kbot/gui/app/plugins/barcode-scanner/android/.gitignore new file mode 100644 index 00000000..c0f21ec2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/android/.gitignore @@ -0,0 +1,2 @@ +/build +/.tauri diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/android/build.gradle.kts b/packages/kbot/gui/app/plugins/barcode-scanner/android/build.gradle.kts new file mode 100644 index 00000000..f3ecd6c7 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/android/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "app.tauri.barcodescanner" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.0") + implementation("com.google.android.material:material:1.7.0") + implementation("androidx.camera:camera-core:1.1.0") + implementation("androidx.camera:camera-view:1.1.0") + implementation("androidx.camera:camera-lifecycle:1.1.0") + implementation("androidx.camera:camera-camera2:1.1.0") + implementation("androidx.camera:camera-lifecycle:1.1.0") + implementation("androidx.camera:camera-view:1.1.0") + implementation("com.google.android.gms:play-services-mlkit-barcode-scanning:18.1.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + implementation(project(":tauri-android")) +} diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/android/proguard-rules.pro b/packages/kbot/gui/app/plugins/barcode-scanner/android/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/android/settings.gradle b/packages/kbot/gui/app/plugins/barcode-scanner/android/settings.gradle new file mode 100644 index 00000000..14a752e4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/android/settings.gradle @@ -0,0 +1,2 @@ +include ':tauri-android' +project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/android/src/androidTest/java/ExampleInstrumentedTest.kt b/packages/kbot/gui/app/plugins/barcode-scanner/android/src/androidTest/java/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..4ccd5f66 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/android/src/androidTest/java/ExampleInstrumentedTest.kt @@ -0,0 +1,28 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.barcodescanner + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("app.tauri.barcodescanner", appContext.packageName) + } +} diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/android/src/main/AndroidManifest.xml b/packages/kbot/gui/app/plugins/barcode-scanner/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..750a724b --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/android/src/main/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/android/src/main/java/BarcodeScannerPlugin.kt b/packages/kbot/gui/app/plugins/barcode-scanner/android/src/main/java/BarcodeScannerPlugin.kt new file mode 100644 index 00000000..ef2eeb34 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/android/src/main/java/BarcodeScannerPlugin.kt @@ -0,0 +1,439 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.barcodescanner + +import android.Manifest +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context +import android.content.Context.MODE_PRIVATE +import android.content.Intent +import android.content.SharedPreferences +import android.content.pm.PackageManager +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Build +import android.os.VibrationEffect +import android.os.Vibrator +import android.provider.Settings +import android.util.Size +import android.view.ViewGroup +import android.webkit.WebView +import android.widget.FrameLayout +import androidx.activity.result.ActivityResult +import androidx.camera.core.Camera +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageProxy +import androidx.camera.core.Preview +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.view.PreviewView +import androidx.core.content.ContextCompat +import androidx.lifecycle.LifecycleOwner +import app.tauri.Logger +import app.tauri.PermissionState +import app.tauri.annotation.ActivityCallback +import app.tauri.annotation.Command +import app.tauri.annotation.InvokeArg +import app.tauri.annotation.Permission +import app.tauri.annotation.PermissionCallback +import app.tauri.annotation.TauriPlugin +import app.tauri.plugin.Invoke +import app.tauri.plugin.JSObject +import app.tauri.plugin.Plugin +import com.google.common.util.concurrent.ListenableFuture +import com.google.mlkit.vision.barcode.BarcodeScannerOptions +import com.google.mlkit.vision.barcode.BarcodeScanning +import com.google.mlkit.vision.barcode.common.Barcode +import com.google.mlkit.vision.common.InputImage +import java.util.Collections +import java.util.concurrent.ExecutionException + +private const val PERMISSION_ALIAS_CAMERA = "camera" +private const val PERMISSION_NAME = Manifest.permission.CAMERA +private const val PREFS_PERMISSION_FIRST_TIME_ASKING = "PREFS_PERMISSION_FIRST_TIME_ASKING" + +@InvokeArg +class ScanOptions { + var formats: Array? = null + var windowed: Boolean = false + var cameraDirection: String? = null +} + +@TauriPlugin( + permissions = [ + Permission(strings = [Manifest.permission.CAMERA], alias = "camera") + ] +) +class BarcodeScannerPlugin(private val activity: Activity) : Plugin(activity), + ImageAnalysis.Analyzer { + private lateinit var webView: WebView + private var previewView: PreviewView? = null + private var cameraProviderFuture: ListenableFuture? = null + private var cameraProvider: ProcessCameraProvider? = null + private var graphicOverlay: GraphicOverlay? = null + private var camera: Camera? = null + private var vibrator: Vibrator? = null + + private var scannerOptions: BarcodeScannerOptions? = null + private var scanner: com.google.mlkit.vision.barcode.BarcodeScanner? = null + + private var requestPermissionResponse: JSObject? = null + private var windowed = false + + // declare a map constant for allowed barcode formats + private val supportedFormats = supportedFormats() + + private var savedInvoke: Invoke? = null + private var webViewBackground: Drawable? = null + + override fun load(webView: WebView) { + super.load(webView) + this.webView = webView + } + + private fun supportedFormats(): Map { + val map: MutableMap = HashMap() + map["UPC_A"] = Barcode.FORMAT_UPC_A + map["UPC_E"] = Barcode.FORMAT_UPC_E + map["EAN_8"] = Barcode.FORMAT_EAN_8 + map["EAN_13"] = Barcode.FORMAT_EAN_13 + map["CODE_39"] = Barcode.FORMAT_CODE_39 + map["CODE_93"] = Barcode.FORMAT_CODE_93 + map["CODE_128"] = Barcode.FORMAT_CODE_128 + map["CODABAR"] = Barcode.FORMAT_CODABAR + map["ITF"] = Barcode.FORMAT_ITF + map["AZTEC"] = Barcode.FORMAT_AZTEC + map["DATA_MATRIX"] = Barcode.FORMAT_DATA_MATRIX + map["PDF_417"] = Barcode.FORMAT_PDF417 + map["QR_CODE"] = Barcode.FORMAT_QR_CODE + return Collections.unmodifiableMap(map) + } + + private fun hasCamera(): Boolean { + return activity.packageManager + .hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) + } + + private fun setupCamera(cameraDirection: String, windowed: Boolean) { + activity + .runOnUiThread { + val previewView = PreviewView(activity) + previewView.layoutParams = FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + this.previewView = previewView + + val graphicOverlay = GraphicOverlay(activity) + graphicOverlay.layoutParams = FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + this.graphicOverlay = graphicOverlay + + val parent = webView.parent as ViewGroup + parent.addView(previewView) + parent.addView(graphicOverlay) + + this.windowed = windowed + if (windowed) { + webView.bringToFront() + webViewBackground = webView.background + webView.setBackgroundColor(Color.TRANSPARENT) + } + + val cameraProviderFuture = ProcessCameraProvider.getInstance(activity) + cameraProviderFuture.addListener( + { + try { + val cameraProvider = cameraProviderFuture.get() + bindPreview( + cameraProvider, + if (cameraDirection == "front") CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK + ) + this.cameraProvider = cameraProvider + } catch (e: InterruptedException) { + // ignored + } catch (_: ExecutionException) { + // ignored + } + }, + ContextCompat.getMainExecutor(activity) + ) + this.cameraProviderFuture = cameraProviderFuture + } + } + + private fun bindPreview(cameraProvider: ProcessCameraProvider, cameraDirection: Int) { + activity + .runOnUiThread { + val preview = Preview.Builder().build() + val cameraSelector = + CameraSelector.Builder().requireLensFacing(cameraDirection).build() + preview.setSurfaceProvider(previewView?.surfaceProvider) + val imageAnalysis = ImageAnalysis.Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .setTargetResolution(Size(1280, 720)) + .build() + imageAnalysis.setAnalyzer( + ContextCompat.getMainExecutor(activity), + this + ) + + try { + camera = cameraProvider.bindToLifecycle( + activity as LifecycleOwner, + cameraSelector, + preview, + imageAnalysis + ) + } catch (e: Exception) { + // TODO + } + } + } + + private fun dismantleCamera() { + activity + .runOnUiThread { + if (cameraProvider != null) { + cameraProvider?.unbindAll() + val parent = webView.parent as ViewGroup + parent.removeView(previewView) + parent.removeView(graphicOverlay) + camera = null + previewView = null + graphicOverlay = null + } + } + } + + private fun getFormats(args: ScanOptions): List { + val formats = ArrayList() + for (format in args.formats ?: arrayOf()) { + val targetedBarcodeFormat = supportedFormats[format] + if (targetedBarcodeFormat != null) { + formats.add(targetedBarcodeFormat) + } + } + return formats + } + + private fun prepare(direction: String, windowed: Boolean) { + dismantleCamera() + setupCamera(direction, windowed) + } + + private fun destroy() { + dismantleCamera() + savedInvoke = null + if (windowed) { + if (webViewBackground != null) { + webView.background = webViewBackground + webViewBackground = null + } else { + webView.setBackgroundColor(Color.WHITE) + } + } + } + + @Suppress("DEPRECATION") + private fun configureCamera(formats: List) { + activity + .runOnUiThread { + val vibrator = + activity.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + this.vibrator = vibrator + if (previewView == null) { + throw Exception("Something went wrong configuring the BarcodeScanner") + } + + if (formats.isNotEmpty()) { + val mappedFormats = mapFormats(formats) + val options = + BarcodeScannerOptions.Builder() + .setBarcodeFormats(Barcode.FORMAT_QR_CODE, *mappedFormats).build() + scannerOptions = options + scanner = BarcodeScanning.getClient(options) + } else { + val options = BarcodeScannerOptions.Builder() + .setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS).build() + scannerOptions = options + scanner = BarcodeScanning.getClient(options) + } + } + } + + private fun mapFormats(integers: List): IntArray { + val ret = IntArray(integers.size) + for (i in ret.indices) { + if (integers[i] != Barcode.FORMAT_QR_CODE) ret[i] = integers[i] + } + return ret + } + + override fun analyze(image: ImageProxy) { + @SuppressLint("UnsafeOptInUsageError") val mediaImage = image.image + if (mediaImage != null) { + val inputImage = + InputImage.fromMediaImage(mediaImage, image.imageInfo.rotationDegrees) + scanner + ?.process(inputImage) + ?.addOnSuccessListener { barcodes -> + if (barcodes.isNotEmpty()) { + val barcode = barcodes[0] + val bounds = barcode.boundingBox + val rawValue = barcode.rawValue ?: "" + val rawFormat = barcode.format + var format: String? = null + + for (entry in supportedFormats.entries) { + if (entry.value == rawFormat) { + format = entry.key + break + } + } + + val s = bounds?.flattenToString() + val jsObject = JSObject() + jsObject.put("content", rawValue) + jsObject.put("format", format) + jsObject.put("bounds", s) + + savedInvoke?.resolve(jsObject) + destroy() + } + } + ?.addOnFailureListener { e -> + Logger.error(e.message ?: e.toString()) + } + ?.addOnCompleteListener { + image.close() + mediaImage.close() + } + } + } + + @Command + fun vibrate(invoke: Invoke) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + vibrator!!.vibrate( + VibrationEffect.createOneShot( + 50, + VibrationEffect.DEFAULT_AMPLITUDE + ) + ) + } + invoke.resolve() + } + + @Command + fun cancel(invoke: Invoke) { + destroy() + savedInvoke?.reject("cancelled") + invoke.resolve() + } + + @Command + fun scan(invoke: Invoke) { + val args = invoke.parseArgs(ScanOptions::class.java) + + savedInvoke = invoke + if (hasCamera()) { + if (getPermissionState("camera") != PermissionState.GRANTED) { + throw Exception("No permission to use camera. Did you request it yet?") + } else { + webViewBackground = null + prepare(args.cameraDirection ?: "back", args.windowed) + configureCamera(getFormats(args)) + } + } + } + + private fun markFirstPermissionRequest() { + val sharedPreference: SharedPreferences = + activity.getSharedPreferences(PREFS_PERMISSION_FIRST_TIME_ASKING, MODE_PRIVATE) + sharedPreference.edit().putBoolean(PERMISSION_NAME, false).apply() + } + + private fun firstPermissionRequest(): Boolean { + return activity.getSharedPreferences(PREFS_PERMISSION_FIRST_TIME_ASKING, MODE_PRIVATE) + .getBoolean(PERMISSION_NAME, true) + } + + @SuppressLint("ObsoleteSdkInt") + @PermissionCallback + fun cameraPermissionCallback(invoke: Invoke) { + if (requestPermissionResponse == null) { + return + } + + val requestPermissionResponse = requestPermissionResponse!! + + val granted = getPermissionState(PERMISSION_ALIAS_CAMERA) === PermissionState.GRANTED + + if (granted) { + requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED) + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (!activity.shouldShowRequestPermissionRationale(PERMISSION_NAME)) { + requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.DENIED) + } + } else { + requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED) + } + } + + invoke.resolve(requestPermissionResponse) + this.requestPermissionResponse = null + } + + @SuppressLint("ObsoleteSdkInt") + @Command + override fun requestPermissions(invoke: Invoke) { + val requestPermissionResponse = JSObject() + this.requestPermissionResponse = requestPermissionResponse + if (getPermissionState(PERMISSION_ALIAS_CAMERA) === PermissionState.GRANTED) { + requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED) + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (firstPermissionRequest() || activity.shouldShowRequestPermissionRationale( + PERMISSION_NAME + ) + ) { + markFirstPermissionRequest() + requestPermissionForAlias( + PERMISSION_ALIAS_CAMERA, + invoke, + "cameraPermissionCallback" + ) + return + } else { + requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.DENIED) + } + } else { + requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED) + } + } + invoke.resolve(requestPermissionResponse) + } + + @Command + fun openAppSettings(invoke: Invoke) { + val intent = Intent( + Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.fromParts("package", activity.packageName, null) + ) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + startActivityForResult(invoke, intent, "openSettingsResult") + } + + @ActivityCallback + private fun openSettingsResult(invoke: Invoke, result: ActivityResult) { + invoke.resolve() + } +} diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/android/src/main/java/GraphicOverlay.kt b/packages/kbot/gui/app/plugins/barcode-scanner/android/src/main/java/GraphicOverlay.kt new file mode 100644 index 00000000..1b1d3b2c --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/android/src/main/java/GraphicOverlay.kt @@ -0,0 +1,180 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.barcodescanner + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Matrix +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import com.google.android.gms.common.internal.Preconditions + +class GraphicOverlay: View { + private val lock = Any() + private val graphics: MutableList = ArrayList() + + private val transformationMatrix = Matrix() + + private var imageWidth = 0 + private var imageHeight = 0 + + private var scaleFactor = 1.0f + + private var postScaleWidthOffset = 0f + + private var postScaleHeightOffset = 0f + private var isImageFlipped = false + private var needUpdateTransformation = true + + abstract class Graphic(private val overlay: GraphicOverlay) { + abstract fun draw(canvas: Canvas?) + protected fun drawRect( + canvas: Canvas, + left: Float, + top: Float, + right: Float, + bottom: Float, + paint: Paint? + ) { + canvas.drawRect(left, top, right, bottom, paint!!) + } + + protected fun drawText(canvas: Canvas, text: String?, x: Float, y: Float, paint: Paint?) { + canvas.drawText(text!!, x, y, paint!!) + } + + /** Adjusts the supplied value from the image scale to the view scale. */ + fun scale(imagePixel: Float): Float { + return imagePixel * overlay.scaleFactor + } + + val applicationContext + get() = overlay.context.applicationContext + + fun isImageFlipped(): Boolean { + return overlay.isImageFlipped + } + + fun translateX(x: Float): Float { + return if (overlay.isImageFlipped) { + overlay.width - (scale(x) - overlay.postScaleWidthOffset) + } else { + scale(x) - overlay.postScaleWidthOffset + } + } + + fun translateY(y: Float): Float { + return scale(y) - overlay.postScaleHeightOffset + } + + fun getTransformationMatrix(): Matrix { + return overlay.transformationMatrix + } + + fun postInvalidate() { + overlay.postInvalidate() + } + + fun updatePaintColorByZValue( + paint: Paint, + canvas: Canvas, + visualizeZ: Boolean, + rescaleZForVisualization: Boolean, + zInImagePixel: Float, + zMin: Float, + zMax: Float + ) { + if (!visualizeZ) { + return + } + + val zInScreenPixel = scale(zInImagePixel) + if (zInScreenPixel < 0) { + paint.setARGB(0, 0, 255, 0) + } else { + paint.setARGB(0, 0, 255, 0) + } + } + } + + constructor(context: Context): super(context) + + constructor(context: Context, attrs: AttributeSet): super(context, attrs) { + addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> + needUpdateTransformation = true + } + } + + fun clear() { + synchronized(lock) { graphics.clear() } + postInvalidate() + } + + fun add(graphic: Graphic) { + synchronized(lock) { graphics.add(graphic) } + } + + fun remove(graphic: Graphic) { + synchronized(lock) { graphics.remove(graphic) } + postInvalidate() + } + + fun setImageSourceInfo(imageWidth: Int, imageHeight: Int, isFlipped: Boolean) { + Preconditions.checkState(imageWidth > 0, "image width must be positive") + Preconditions.checkState(imageHeight > 0, "image height must be positive") + synchronized(lock) { + this.imageWidth = imageWidth + this.imageHeight = imageHeight + isImageFlipped = isFlipped + needUpdateTransformation = true + } + postInvalidate() + } + + fun getImageWidth(): Int { + return imageWidth + } + + fun getImageHeight(): Int { + return imageHeight + } + + private fun updateTransformationIfNeeded() { + if (!needUpdateTransformation || imageWidth <= 0 || imageHeight <= 0) { + return + } + val viewAspectRatio = width.toFloat() / height + val imageAspectRatio = imageWidth.toFloat() / imageHeight + postScaleWidthOffset = 0f + postScaleHeightOffset = 0f + if (viewAspectRatio > imageAspectRatio) { + // The image needs to be vertically cropped to be displayed in this view. + scaleFactor = width.toFloat() / imageWidth + postScaleHeightOffset = (width.toFloat() / imageAspectRatio - height) / 2 + } else { + // The image needs to be horizontally cropped to be displayed in this view. + scaleFactor = height.toFloat() / imageHeight + postScaleWidthOffset = (height.toFloat() * imageAspectRatio - width) / 2 + } + transformationMatrix.reset() + transformationMatrix.setScale(scaleFactor, scaleFactor) + transformationMatrix.postTranslate(-postScaleWidthOffset, -postScaleHeightOffset) + if (isImageFlipped) { + transformationMatrix.postScale(-1f, 1f, width / 2f, height / 2f) + } + needUpdateTransformation = false + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + synchronized(lock) { + updateTransformationIfNeeded() + for (graphic in graphics) { + graphic.draw(canvas) + } + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/android/src/test/java/ExampleUnitTest.kt b/packages/kbot/gui/app/plugins/barcode-scanner/android/src/test/java/ExampleUnitTest.kt new file mode 100644 index 00000000..895a8ddb --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/android/src/test/java/ExampleUnitTest.kt @@ -0,0 +1,21 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.barcodescanner + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/api-iife.js b/packages/kbot/gui/app/plugins/barcode-scanner/api-iife.js new file mode 100644 index 00000000..be48fae9 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_BARCODE_SCANNER__=function(n){"use strict";async function a(n,a={},e){return window.__TAURI_INTERNALS__.invoke(n,a,e)}var e;return"function"==typeof SuppressedError&&SuppressedError,n.Format=void 0,(e=n.Format||(n.Format={})).QRCode="QR_CODE",e.UPC_A="UPC_A",e.UPC_E="UPC_E",e.EAN8="EAN_8",e.EAN13="EAN_13",e.Code39="CODE_39",e.Code93="CODE_93",e.Code128="CODE_128",e.Codabar="CODABAR",e.ITF="ITF",e.Aztec="AZTEC",e.DataMatrix="DATA_MATRIX",e.PDF417="PDF_417",e.GS1DataBar="GS1_DATA_BAR",e.GS1DataBarLimited="GS1_DATA_BAR_LIMITED",e.GS1DataBarExpanded="GS1_DATA_BAR_EXPANDED",n.cancel=async function(){await a("plugin:barcode-scanner|cancel")},n.checkPermissions=async function(){return await async function(n){return a(`plugin:${n}|check_permissions`)}("barcode-scanner").then((n=>n.camera))},n.openAppSettings=async function(){await a("plugin:barcode-scanner|open_app_settings")},n.requestPermissions=async function(){return await async function(n){return a(`plugin:${n}|request_permissions`)}("barcode-scanner").then((n=>n.camera))},n.scan=async function(n){return await a("plugin:barcode-scanner|scan",{...n})},n}({});Object.defineProperty(window.__TAURI__,"barcodeScanner",{value:__TAURI_PLUGIN_BARCODE_SCANNER__})} diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/banner.png b/packages/kbot/gui/app/plugins/barcode-scanner/banner.png new file mode 100644 index 00000000..491e9864 Binary files /dev/null and b/packages/kbot/gui/app/plugins/barcode-scanner/banner.png differ diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/build.rs b/packages/kbot/gui/app/plugins/barcode-scanner/build.rs new file mode 100644 index 00000000..25896b57 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/build.rs @@ -0,0 +1,25 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +const COMMANDS: &[&str] = &[ + "scan", + "cancel", + "request_permissions", + "check_permissions", + "open_app_settings", + "vibrate", +]; + +fn main() { + let result = tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .android_path("android") + .ios_path("ios") + .try_build(); + + // when building documentation for Android the plugin build result is always Err() and is irrelevant to the crate documentation build + if !(cfg!(docsrs) && std::env::var("TARGET").unwrap().contains("android")) { + result.unwrap(); + } +} diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/contributors/crabnebula.svg b/packages/kbot/gui/app/plugins/barcode-scanner/contributors/crabnebula.svg new file mode 100644 index 00000000..a9bb4609 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/contributors/crabnebula.svg @@ -0,0 +1,31 @@ + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/contributors/impierce.svg b/packages/kbot/gui/app/plugins/barcode-scanner/contributors/impierce.svg new file mode 100644 index 00000000..9d2a510b --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/contributors/impierce.svg @@ -0,0 +1,21 @@ + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/guest-js/index.ts b/packages/kbot/gui/app/plugins/barcode-scanner/guest-js/index.ts new file mode 100644 index 00000000..9781b4ba --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/guest-js/index.ts @@ -0,0 +1,97 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { + invoke, + requestPermissions as requestPermissions_, + checkPermissions as checkPermissions_ +} from '@tauri-apps/api/core' + +export type { PermissionState } from '@tauri-apps/api/core' + +export enum Format { + QRCode = 'QR_CODE', + /** + * Not supported on iOS. + */ + UPC_A = 'UPC_A', + UPC_E = 'UPC_E', + EAN8 = 'EAN_8', + EAN13 = 'EAN_13', + Code39 = 'CODE_39', + Code93 = 'CODE_93', + Code128 = 'CODE_128', + /** + * Not supported on iOS. + */ + Codabar = 'CODABAR', + ITF = 'ITF', + Aztec = 'AZTEC', + DataMatrix = 'DATA_MATRIX', + PDF417 = 'PDF_417', + /** + * Not supported on Android. Requires iOS 15.4+ + */ + GS1DataBar = 'GS1_DATA_BAR', + /** + * Not supported on Android. Requires iOS 15.4+ + */ + GS1DataBarLimited = 'GS1_DATA_BAR_LIMITED', + /** + * Not supported on Android. Requires iOS 15.4+ + */ + GS1DataBarExpanded = 'GS1_DATA_BAR_EXPANDED' +} + +export interface ScanOptions { + cameraDirection?: 'back' | 'front' + formats?: Format[] + windowed?: boolean +} + +export interface Scanned { + content: string + format: Format + bounds: unknown +} + +/** + * Start scanning. + * @param options + */ +export async function scan(options?: ScanOptions): Promise { + return await invoke('plugin:barcode-scanner|scan', { ...options }) +} + +/** + * Cancel the current scan process. + */ +export async function cancel(): Promise { + await invoke('plugin:barcode-scanner|cancel') +} + +/** + * Get permission state. + */ +export async function checkPermissions(): Promise { + return await checkPermissions_<{ camera: PermissionState }>( + 'barcode-scanner' + ).then((r) => r.camera) +} + +/** + * Request permissions to use the camera. + */ +export async function requestPermissions(): Promise { + return await requestPermissions_<{ camera: PermissionState }>( + 'barcode-scanner' + ).then((r) => r.camera) +} + +/** + * Open application settings. Useful if permission was denied and the user must manually enable it. + */ +export async function openAppSettings(): Promise { + await invoke('plugin:barcode-scanner|open_app_settings') +} diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/ios/.gitignore b/packages/kbot/gui/app/plugins/barcode-scanner/ios/.gitignore new file mode 100644 index 00000000..5922fdaa --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/ios/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Package.resolved diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/ios/Package.swift b/packages/kbot/gui/app/plugins/barcode-scanner/ios/Package.swift new file mode 100644 index 00000000..cf39b812 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/ios/Package.swift @@ -0,0 +1,36 @@ +// swift-tools-version:5.3 +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "tauri-plugin-barcode-scanner", + platforms: [ + .macOS(.v10_13), + .iOS(.v13), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "tauri-plugin-barcode-scanner", + type: .static, + targets: ["tauri-plugin-barcode-scanner"]) + ], + dependencies: [ + .package(name: "Tauri", path: "../.tauri/tauri-api") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "tauri-plugin-barcode-scanner", + dependencies: [ + .byName(name: "Tauri") + ], + path: "Sources") + ] +) diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/ios/README.md b/packages/kbot/gui/app/plugins/barcode-scanner/ios/README.md new file mode 100644 index 00000000..d954848c --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/ios/README.md @@ -0,0 +1,3 @@ +# Tauri Plugin Barcode Scanner + +A description of this package. diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift b/packages/kbot/gui/app/plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift new file mode 100644 index 00000000..7271b7f6 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift @@ -0,0 +1,335 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import AVFoundation +import Tauri +import UIKit +import WebKit + +struct ScanOptions: Decodable { + var formats: [SupportedFormat]? + var windowed: Bool? + var cameraDirection: String? +} + +enum SupportedFormat: String, CaseIterable, Decodable { + // UPC_A not supported + case UPC_E + case EAN_8 + case EAN_13 + case CODE_39 + case CODE_93 + case CODE_128 + // CODABAR not supported + case ITF + case AZTEC + case DATA_MATRIX + case PDF_417 + case QR_CODE + case GS1_DATA_BAR + case GS1_DATA_BAR_LIMITED + case GS1_DATA_BAR_EXPANDED + + var value: AVMetadataObject.ObjectType? { + switch self { + case .UPC_E: return AVMetadataObject.ObjectType.upce + case .EAN_8: return AVMetadataObject.ObjectType.ean8 + case .EAN_13: return AVMetadataObject.ObjectType.ean13 + case .CODE_39: return AVMetadataObject.ObjectType.code39 + case .CODE_93: return AVMetadataObject.ObjectType.code93 + case .CODE_128: return AVMetadataObject.ObjectType.code128 + case .ITF: return AVMetadataObject.ObjectType.interleaved2of5 + case .AZTEC: return AVMetadataObject.ObjectType.aztec + case .DATA_MATRIX: return AVMetadataObject.ObjectType.dataMatrix + case .PDF_417: return AVMetadataObject.ObjectType.pdf417 + case .QR_CODE: return AVMetadataObject.ObjectType.qr + case .GS1_DATA_BAR: + if #available(iOS 15.4, *) { + return AVMetadataObject.ObjectType.gs1DataBar + } else { + return nil + } + case .GS1_DATA_BAR_LIMITED: + if #available(iOS 15.4, *) { + return AVMetadataObject.ObjectType.gs1DataBarLimited + } else { + return nil + } + case .GS1_DATA_BAR_EXPANDED: + if #available(iOS 15.4, *) { + return AVMetadataObject.ObjectType.gs1DataBarExpanded + } else { + return nil + } + } + } +} + +enum CaptureError: Error { + case backCameraUnavailable + case frontCameraUnavailable + case couldNotCaptureInput(error: NSError) +} + +class BarcodeScannerPlugin: Plugin, AVCaptureMetadataOutputObjectsDelegate { + var webView: WKWebView! + var cameraView: CameraView! + var captureSession: AVCaptureSession? + var captureVideoPreviewLayer: AVCaptureVideoPreviewLayer? + var metaOutput: AVCaptureMetadataOutput? + + var currentCamera = 0 + var frontCamera: AVCaptureDevice? + var backCamera: AVCaptureDevice? + + var isScanning = false + + var windowed = false + var previousBackgroundColor: UIColor? = UIColor.white + + var invoke: Invoke? = nil + + var scanFormats = [AVMetadataObject.ObjectType]() + + public override func load(webview: WKWebView) { + self.webView = webview + loadCamera() + } + + private func loadCamera() { + cameraView = CameraView( + frame: CGRect( + x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)) + cameraView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + } + + public func metadataOutput( + _ captureOutput: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], + from connection: AVCaptureConnection + ) { + if metadataObjects.count == 0 || !self.isScanning { + // while nothing is detected, or if scanning is false, do nothing. + return + } + + let found = metadataObjects[0] as! AVMetadataMachineReadableCodeObject + if scanFormats.contains(found.type) { + var jsObject: JsonObject = [:] + + jsObject["format"] = formatStringFromMetadata(found.type) + if found.stringValue != nil { + jsObject["content"] = found.stringValue + } + + invoke?.resolve(jsObject) + destroy() + + } + } + + private func setupCamera(direction: String, windowed: Bool) { + do { + var cameraDirection = direction + cameraView.backgroundColor = UIColor.clear + if windowed { + webView.superview?.insertSubview(cameraView, belowSubview: webView) + } else { + webView.superview?.insertSubview(cameraView, aboveSubview: webView) + } + + let availableVideoDevices = discoverCaptureDevices() + for device in availableVideoDevices { + if device.position == AVCaptureDevice.Position.back { + backCamera = device + } else if device.position == AVCaptureDevice.Position.front { + frontCamera = device + } + } + + // older iPods have no back camera + if cameraDirection == "back" { + if backCamera == nil { + cameraDirection = "front" + } + } else { + if frontCamera == nil { + cameraDirection = "back" + } + } + + let input: AVCaptureDeviceInput + input = try createCaptureDeviceInput( + cameraDirection: cameraDirection, backCamera: backCamera, frontCamera: frontCamera) + captureSession = AVCaptureSession() + captureSession!.addInput(input) + metaOutput = AVCaptureMetadataOutput() + captureSession!.addOutput(metaOutput!) + metaOutput!.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) + captureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!) + cameraView.addPreviewLayer(captureVideoPreviewLayer) + + self.windowed = windowed + if windowed { + self.previousBackgroundColor = self.webView.backgroundColor + self.webView.isOpaque = false + self.webView.backgroundColor = UIColor.clear + self.webView.scrollView.backgroundColor = UIColor.clear + } + } catch CaptureError.backCameraUnavailable { + // + } catch CaptureError.frontCameraUnavailable { + // + } catch CaptureError.couldNotCaptureInput { + // + } catch { + // + } + } + + private func dismantleCamera() { + if self.captureSession != nil { + self.captureSession!.stopRunning() + self.cameraView.removePreviewLayer() + self.captureVideoPreviewLayer = nil + self.metaOutput = nil + self.captureSession = nil + self.frontCamera = nil + self.backCamera = nil + } + + self.isScanning = false + } + + private func destroy() { + dismantleCamera() + invoke = nil + if windowed { + let backgroundColor = previousBackgroundColor ?? UIColor.white + webView.isOpaque = true + webView.backgroundColor = backgroundColor + webView.scrollView.backgroundColor = backgroundColor + } + } + + private func getPermissionState() -> String { + var permissionState: String + + switch AVCaptureDevice.authorizationStatus(for: .video) { + case .authorized: + permissionState = "granted" + case .denied: + permissionState = "denied" + default: + permissionState = "prompt" + } + + return permissionState + } + + @objc override func checkPermissions(_ invoke: Invoke) { + let permissionState = getPermissionState() + invoke.resolve(["camera": permissionState]) + } + + @objc override func requestPermissions(_ invoke: Invoke) { + let state = getPermissionState() + if state == "prompt" { + AVCaptureDevice.requestAccess(for: .video) { (authorized) in + invoke.resolve(["camera": authorized ? "granted" : "denied"]) + } + } else { + invoke.resolve(["camera": state]) + } + } + + @objc func openAppSettings(_ invoke: Invoke) { + guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else { + return + } + + DispatchQueue.main.async { + if UIApplication.shared.canOpenURL(settingsUrl) { + UIApplication.shared.open( + settingsUrl, + completionHandler: { (success) in + invoke.resolve() + }) + } + } + } + + private func runScanner(_ invoke: Invoke, args: ScanOptions) { + scanFormats = [AVMetadataObject.ObjectType]() + + (args.formats ?? []).forEach { format in + if let formatValue = format.value { + scanFormats.append(formatValue) + } else { + invoke.reject("Unsupported barcode format on this iOS version: \(format)") + return + } + } + + if scanFormats.isEmpty { + for supportedFormat in SupportedFormat.allCases { + if let formatValue = supportedFormat.value { + scanFormats.append(formatValue) + } + } + } + + self.metaOutput!.metadataObjectTypes = self.scanFormats + self.captureSession!.startRunning() + + self.isScanning = true + } + + @objc private func scan(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(ScanOptions.self) + + self.invoke = invoke + + let entry = Bundle.main.infoDictionary?["NSCameraUsageDescription"] as? String + + if entry == nil || entry?.count == 0 { + invoke.reject("NSCameraUsageDescription is not in the app Info.plist") + return + } + + var iOS14min: Bool = false + if #available(iOS 14.0, *) { iOS14min = true } + if !iOS14min && self.getPermissionState() != "granted" { + var authorized = false + AVCaptureDevice.requestAccess(for: .video) { (isAuthorized) in + authorized = isAuthorized + } + if !authorized { + invoke.reject("denied by the user") + return + } + } + + DispatchQueue.main.async { [self] in + self.loadCamera() + self.dismantleCamera() + self.setupCamera( + direction: args.cameraDirection ?? "back", + windowed: args.windowed ?? false + ) + self.runScanner(invoke, args: args) + } + } + + @objc private func cancel(_ invoke: Invoke) { + self.invoke?.reject("cancelled") + + destroy() + invoke.resolve() + } +} + +@_cdecl("init_plugin_barcode_scanner") +func initPlugin() -> Plugin { + return BarcodeScannerPlugin() +} diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/ios/Sources/CameraView.swift b/packages/kbot/gui/app/plugins/barcode-scanner/ios/Sources/CameraView.swift new file mode 100644 index 00000000..7f7b0cda --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/ios/Sources/CameraView.swift @@ -0,0 +1,57 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import AVFoundation +import UIKit + +class CameraView: UIView { + var videoPreviewLayer: AVCaptureVideoPreviewLayer? + + func interfaceOrientationToVideoOrientation(_ orientation: UIInterfaceOrientation) + -> AVCaptureVideoOrientation + { + switch orientation { + case UIInterfaceOrientation.portrait: + return AVCaptureVideoOrientation.portrait + case UIInterfaceOrientation.portraitUpsideDown: + return AVCaptureVideoOrientation.portraitUpsideDown + case UIInterfaceOrientation.landscapeLeft: + return AVCaptureVideoOrientation.landscapeLeft + case UIInterfaceOrientation.landscapeRight: + return AVCaptureVideoOrientation.landscapeRight + default: + return AVCaptureVideoOrientation.portraitUpsideDown + } + } + + override func layoutSubviews() { + super.layoutSubviews() + if let sublayers = self.layer.sublayers { + for layer in sublayers { + layer.frame = self.bounds + } + } + + if let interfaceOrientation = UIApplication.shared.windows.first(where: { $0.isKeyWindow })? + .windowScene?.interfaceOrientation + { + self.videoPreviewLayer?.connection?.videoOrientation = interfaceOrientationToVideoOrientation( + interfaceOrientation) + } + } + + func addPreviewLayer(_ previewLayer: AVCaptureVideoPreviewLayer?) { + previewLayer!.videoGravity = AVLayerVideoGravity.resizeAspectFill + previewLayer!.frame = self.bounds + self.layer.addSublayer(previewLayer!) + self.videoPreviewLayer = previewLayer + } + + func removePreviewLayer() { + if self.videoPreviewLayer != nil { + self.videoPreviewLayer!.removeFromSuperlayer() + self.videoPreviewLayer = nil + } + } +} diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/ios/Sources/Utils.swift b/packages/kbot/gui/app/plugins/barcode-scanner/ios/Sources/Utils.swift new file mode 100644 index 00000000..18daa280 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/ios/Sources/Utils.swift @@ -0,0 +1,90 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import AVFoundation + +func createCaptureDeviceInput( + cameraDirection: String, backCamera: AVCaptureDevice?, frontCamera: AVCaptureDevice? +) throws + -> AVCaptureDeviceInput +{ + var captureDevice: AVCaptureDevice + if cameraDirection == "back" { + if backCamera != nil { + captureDevice = backCamera! + } else { + throw CaptureError.backCameraUnavailable + } + } else { + if frontCamera != nil { + captureDevice = frontCamera! + } else { + throw CaptureError.frontCameraUnavailable + } + } + let captureDeviceInput: AVCaptureDeviceInput + do { + captureDeviceInput = try AVCaptureDeviceInput(device: captureDevice) + } catch let error as NSError { + throw CaptureError.couldNotCaptureInput(error: error) + } + return captureDeviceInput +} + +func discoverCaptureDevices() -> [AVCaptureDevice] { + if #available(iOS 13.0, *) { + return AVCaptureDevice.DiscoverySession( + deviceTypes: [ + .builtInTripleCamera, .builtInDualCamera, .builtInTelephotoCamera, + .builtInTrueDepthCamera, + .builtInUltraWideCamera, .builtInDualWideCamera, .builtInWideAngleCamera, + ], mediaType: .video, position: .unspecified + ).devices + } else { + return AVCaptureDevice.DiscoverySession( + deviceTypes: [ + .builtInDualCamera, .builtInWideAngleCamera, .builtInTelephotoCamera, + .builtInTrueDepthCamera, + ], mediaType: .video, position: .unspecified + ).devices + } +} + +func formatStringFromMetadata(_ type: AVMetadataObject.ObjectType) -> String { + if #available(iOS 15.4, *) { + if type == .gs1DataBar { + return "GS1_DATA_BAR" + } else if type == .gs1DataBarLimited { + return "GS1_DATA_BAR_LIMITED" + } else if type == .gs1DataBarExpanded { + return "GS1_DATA_BAR_EXPANDED" + } + } + switch type { + case AVMetadataObject.ObjectType.upce: + return "UPC_E" + case AVMetadataObject.ObjectType.ean8: + return "EAN_8" + case AVMetadataObject.ObjectType.ean13: + return "EAN_13" + case AVMetadataObject.ObjectType.code39: + return "CODE_39" + case AVMetadataObject.ObjectType.code93: + return "CODE_93" + case AVMetadataObject.ObjectType.code128: + return "CODE_128" + case AVMetadataObject.ObjectType.interleaved2of5: + return "ITF" + case AVMetadataObject.ObjectType.aztec: + return "AZTEC" + case AVMetadataObject.ObjectType.dataMatrix: + return "DATA_MATRIX" + case AVMetadataObject.ObjectType.pdf417: + return "PDF_417" + case AVMetadataObject.ObjectType.qr: + return "QR_CODE" + default: + return type.rawValue + } +} diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/ios/Tests/PluginTests/PluginTests.swift b/packages/kbot/gui/app/plugins/barcode-scanner/ios/Tests/PluginTests/PluginTests.swift new file mode 100644 index 00000000..9693b5c5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/ios/Tests/PluginTests/PluginTests.swift @@ -0,0 +1,13 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import XCTest + +@testable import ExamplePlugin + +final class ExamplePluginTests: XCTestCase { + func testExample() throws { + let plugin = ExamplePlugin() + } +} diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/package.json b/packages/kbot/gui/app/plugins/barcode-scanner/package.json new file mode 100644 index 00000000..87b6dc71 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/package.json @@ -0,0 +1,30 @@ +{ + "name": "@tauri-apps/plugin-barcode-scanner", + "version": "2.4.0", + "description": "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/commands/cancel.toml b/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/commands/cancel.toml new file mode 100644 index 00000000..91efeaa0 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/commands/cancel.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-cancel" +description = "Enables the cancel command without any pre-configured scope." +commands.allow = ["cancel"] + +[[permission]] +identifier = "deny-cancel" +description = "Denies the cancel command without any pre-configured scope." +commands.deny = ["cancel"] diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/commands/check_permissions.toml b/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/commands/check_permissions.toml new file mode 100644 index 00000000..f5af08b1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/commands/check_permissions.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-check-permissions" +description = "Enables the check_permissions command without any pre-configured scope." +commands.allow = ["check_permissions"] + +[[permission]] +identifier = "deny-check-permissions" +description = "Denies the check_permissions command without any pre-configured scope." +commands.deny = ["check_permissions"] diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/commands/open_app_settings.toml b/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/commands/open_app_settings.toml new file mode 100644 index 00000000..9da98d85 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/commands/open_app_settings.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-open-app-settings" +description = "Enables the open_app_settings command without any pre-configured scope." +commands.allow = ["open_app_settings"] + +[[permission]] +identifier = "deny-open-app-settings" +description = "Denies the open_app_settings command without any pre-configured scope." +commands.deny = ["open_app_settings"] diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/commands/request_permissions.toml b/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/commands/request_permissions.toml new file mode 100644 index 00000000..02dcd627 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/commands/request_permissions.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-request-permissions" +description = "Enables the request_permissions command without any pre-configured scope." +commands.allow = ["request_permissions"] + +[[permission]] +identifier = "deny-request-permissions" +description = "Denies the request_permissions command without any pre-configured scope." +commands.deny = ["request_permissions"] diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/commands/scan.toml b/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/commands/scan.toml new file mode 100644 index 00000000..efa621dd --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/commands/scan.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-scan" +description = "Enables the scan command without any pre-configured scope." +commands.allow = ["scan"] + +[[permission]] +identifier = "deny-scan" +description = "Denies the scan command without any pre-configured scope." +commands.deny = ["scan"] diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/commands/vibrate.toml b/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/commands/vibrate.toml new file mode 100644 index 00000000..4c2fe94e --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/commands/vibrate.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-vibrate" +description = "Enables the vibrate command without any pre-configured scope." +commands.allow = ["vibrate"] + +[[permission]] +identifier = "deny-vibrate" +description = "Denies the vibrate command without any pre-configured scope." +commands.deny = ["vibrate"] diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/reference.md new file mode 100644 index 00000000..0b5f760e --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/permissions/autogenerated/reference.md @@ -0,0 +1,183 @@ +## Default Permission + +This permission set configures which +barcode scanning features are by default exposed. + +#### Granted Permissions + +It allows all barcode related features. + +#### This default permission set includes the following: + +- `allow-cancel` +- `allow-check-permissions` +- `allow-open-app-settings` +- `allow-request-permissions` +- `allow-scan` +- `allow-vibrate` + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`barcode-scanner:allow-cancel` + + + +Enables the cancel command without any pre-configured scope. + +
+ +`barcode-scanner:deny-cancel` + + + +Denies the cancel command without any pre-configured scope. + +
+ +`barcode-scanner:allow-check-permissions` + + + +Enables the check_permissions command without any pre-configured scope. + +
+ +`barcode-scanner:deny-check-permissions` + + + +Denies the check_permissions command without any pre-configured scope. + +
+ +`barcode-scanner:allow-open-app-settings` + + + +Enables the open_app_settings command without any pre-configured scope. + +
+ +`barcode-scanner:deny-open-app-settings` + + + +Denies the open_app_settings command without any pre-configured scope. + +
+ +`barcode-scanner:allow-request-permissions` + + + +Enables the request_permissions command without any pre-configured scope. + +
+ +`barcode-scanner:deny-request-permissions` + + + +Denies the request_permissions command without any pre-configured scope. + +
+ +`barcode-scanner:allow-scan` + + + +Enables the scan command without any pre-configured scope. + +
+ +`barcode-scanner:deny-scan` + + + +Denies the scan command without any pre-configured scope. + +
+ +`barcode-scanner:allow-vibrate` + + + +Enables the vibrate command without any pre-configured scope. + +
+ +`barcode-scanner:deny-vibrate` + + + +Denies the vibrate command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/permissions/default.toml b/packages/kbot/gui/app/plugins/barcode-scanner/permissions/default.toml new file mode 100644 index 00000000..3b5a2dfd --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/permissions/default.toml @@ -0,0 +1,20 @@ +"$schema" = "schemas/schema.json" +[default] +description = """ +This permission set configures which +barcode scanning features are by default exposed. + +#### Granted Permissions + +It allows all barcode related features. + +""" + +permissions = [ + "allow-cancel", + "allow-check-permissions", + "allow-open-app-settings", + "allow-request-permissions", + "allow-scan", + "allow-vibrate", +] diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/barcode-scanner/permissions/schemas/schema.json new file mode 100644 index 00000000..69fb0d5d --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/permissions/schemas/schema.json @@ -0,0 +1,378 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the cancel command without any pre-configured scope.", + "type": "string", + "const": "allow-cancel", + "markdownDescription": "Enables the cancel command without any pre-configured scope." + }, + { + "description": "Denies the cancel command without any pre-configured scope.", + "type": "string", + "const": "deny-cancel", + "markdownDescription": "Denies the cancel command without any pre-configured scope." + }, + { + "description": "Enables the check_permissions command without any pre-configured scope.", + "type": "string", + "const": "allow-check-permissions", + "markdownDescription": "Enables the check_permissions command without any pre-configured scope." + }, + { + "description": "Denies the check_permissions command without any pre-configured scope.", + "type": "string", + "const": "deny-check-permissions", + "markdownDescription": "Denies the check_permissions command without any pre-configured scope." + }, + { + "description": "Enables the open_app_settings command without any pre-configured scope.", + "type": "string", + "const": "allow-open-app-settings", + "markdownDescription": "Enables the open_app_settings command without any pre-configured scope." + }, + { + "description": "Denies the open_app_settings command without any pre-configured scope.", + "type": "string", + "const": "deny-open-app-settings", + "markdownDescription": "Denies the open_app_settings command without any pre-configured scope." + }, + { + "description": "Enables the request_permissions command without any pre-configured scope.", + "type": "string", + "const": "allow-request-permissions", + "markdownDescription": "Enables the request_permissions command without any pre-configured scope." + }, + { + "description": "Denies the request_permissions command without any pre-configured scope.", + "type": "string", + "const": "deny-request-permissions", + "markdownDescription": "Denies the request_permissions command without any pre-configured scope." + }, + { + "description": "Enables the scan command without any pre-configured scope.", + "type": "string", + "const": "allow-scan", + "markdownDescription": "Enables the scan command without any pre-configured scope." + }, + { + "description": "Denies the scan command without any pre-configured scope.", + "type": "string", + "const": "deny-scan", + "markdownDescription": "Denies the scan command without any pre-configured scope." + }, + { + "description": "Enables the vibrate command without any pre-configured scope.", + "type": "string", + "const": "allow-vibrate", + "markdownDescription": "Enables the vibrate command without any pre-configured scope." + }, + { + "description": "Denies the vibrate command without any pre-configured scope.", + "type": "string", + "const": "deny-vibrate", + "markdownDescription": "Denies the vibrate command without any pre-configured scope." + }, + { + "description": "This permission set configures which\nbarcode scanning features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all barcode related features.\n\n\n#### This default permission set includes:\n\n- `allow-cancel`\n- `allow-check-permissions`\n- `allow-open-app-settings`\n- `allow-request-permissions`\n- `allow-scan`\n- `allow-vibrate`", + "type": "string", + "const": "default", + "markdownDescription": "This permission set configures which\nbarcode scanning features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all barcode related features.\n\n\n#### This default permission set includes:\n\n- `allow-cancel`\n- `allow-check-permissions`\n- `allow-open-app-settings`\n- `allow-request-permissions`\n- `allow-scan`\n- `allow-vibrate`" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/rollup.config.js b/packages/kbot/gui/app/plugins/barcode-scanner/rollup.config.js new file mode 100644 index 00000000..1f349ec8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/rollup.config.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' + +export default createConfig() diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/src/error.rs b/packages/kbot/gui/app/plugins/barcode-scanner/src/error.rs new file mode 100644 index 00000000..339e763b --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/src/error.rs @@ -0,0 +1,25 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{ser::Serializer, Serialize}; + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/src/lib.rs b/packages/kbot/gui/app/plugins/barcode-scanner/src/lib.rs new file mode 100644 index 00000000..2f2e7ee9 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/src/lib.rs @@ -0,0 +1,53 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#![cfg(mobile)] + +use tauri::{ + plugin::{Builder, PluginHandle, TauriPlugin}, + Manager, Runtime, +}; + +pub use models::*; + +mod error; +mod models; + +pub use error::{Error, Result}; + +#[cfg(target_os = "android")] +const PLUGIN_IDENTIFIER: &str = "app.tauri.barcodescanner"; + +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_barcode_scanner); + +/// Access to the scanner APIs. +pub struct BarcodeScanner(PluginHandle); + +impl BarcodeScanner {} + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the barcode scanner APIs. +pub trait BarcodeScannerExt { + fn barcode_scanner(&self) -> &BarcodeScanner; +} + +impl> crate::BarcodeScannerExt for T { + fn barcode_scanner(&self) -> &BarcodeScanner { + self.state::>().inner() + } +} + +/// Initializes the plugin. +pub fn init() -> TauriPlugin { + Builder::new("barcode-scanner") + .setup(|app, api| { + #[cfg(target_os = "android")] + let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "BarcodeScannerPlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_barcode_scanner)?; + app.manage(BarcodeScanner(handle)); + Ok(()) + }) + .build() +} diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/src/mobile.rs b/packages/kbot/gui/app/plugins/barcode-scanner/src/mobile.rs new file mode 100644 index 00000000..a0560385 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/src/mobile.rs @@ -0,0 +1,3 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/src/models.rs b/packages/kbot/gui/app/plugins/barcode-scanner/src/models.rs new file mode 100644 index 00000000..a0560385 --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/src/models.rs @@ -0,0 +1,3 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT diff --git a/packages/kbot/gui/app/plugins/barcode-scanner/tsconfig.json b/packages/kbot/gui/app/plugins/barcode-scanner/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/barcode-scanner/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/biometric/CHANGELOG.md b/packages/kbot/gui/app/plugins/biometric/CHANGELOG.md new file mode 100644 index 00000000..cb8a5fe6 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/CHANGELOG.md @@ -0,0 +1,102 @@ +# Changelog + +## \[2.3.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.2.2] + +- [`f634e524`](https://github.com/tauri-apps/plugins-workspace/commit/f634e5248ebe428f8305a59f74c13fc15147fb8e) This is an "empty" release to update the plugins' source files on crates.io and docs.rs. This should fix docs.rs build failures for projects using tauri plugins as dependencies. + +## \[2.2.1] + +### bug + +- [`10f9e66e`](https://github.com/tauri-apps/plugins-workspace/commit/10f9e66e32141dd35f4bf884fbf9102691187e92) ([#2633](https://github.com/tauri-apps/plugins-workspace/pull/2633) by [@pjf-dev](https://github.com/tauri-apps/plugins-workspace/../../pjf-dev)) Fix biometric plugin ignoring fallback logic when biometry status is unavailable or not enrolled on iOS. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.2] + +- [`b9147758`](https://github.com/tauri-apps/plugins-workspace/commit/b914775898c2bee7ceb20bd17ee595005cd17a64) ([#1679](https://github.com/tauri-apps/plugins-workspace/pull/1679) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Explicitly set a minimum macOS version for the Swift package. + +## \[2.0.0-rc.1] + +### changes + +- [`6b079cfd`](https://github.com/tauri-apps/plugins-workspace/commit/6b079cfdd107c94abc2c7300f6af00bac3ff4040) ([#1649](https://github.com/tauri-apps/plugins-workspace/pull/1649) by [@ahqsoftwares](https://github.com/tauri-apps/plugins-workspace/../../ahqsoftwares)) Remove targetSdk from build.kts files as it is deprecated and will be removed from DSL v9.0 + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +## \[2.0.0-beta.8] + +- [`99d6ac0f`](https://github.com/tauri-apps/plugins-workspace/commit/99d6ac0f9506a6a4a1aa59c728157190a7441af6) ([#1606](https://github.com/tauri-apps/plugins-workspace/pull/1606) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The JS packages now specify the *minimum* `@tauri-apps/api` version instead of a single exact version. +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.7] + +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.6] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.5] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.4] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.3] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.3] + +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. + +## \[2.0.0-beta.2] + +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. + +## \[2.0.0-alpha.0] + +- [`8df28a9`](https://github.com/tauri-apps/plugins-workspace/commit/8df28a987519ecfa03dcb8635443025f8d010362)([#829](https://github.com/tauri-apps/plugins-workspace/pull/829)) Initial release. +- [`8df28a9`](https://github.com/tauri-apps/plugins-workspace/commit/8df28a987519ecfa03dcb8635443025f8d010362)([#829](https://github.com/tauri-apps/plugins-workspace/pull/829)) Initial release. + commit/8df28a987519ecfa03dcb8635443025f8d010362)([#829](https://github.com/tauri-apps/plugins-workspace/pull/829)) Initial release. + 29]\(https://github.com/tauri-apps/plugins-workspace/pull/829)) Initial release. + commit/8df28a987519ecfa03dcb8635443025f8d010362)([#829](https://github.com/tauri-apps/plugins-workspace/pull/829)) Initial release. + . + commit/8df28a987519ecfa03dcb8635443025f8d010362)([#829](https://github.com/tauri-apps/plugins-workspace/pull/829)) Initial release. + itial release. + 29]\(https://github.com/tauri-apps/plugins-workspace/pull/829)) Initial release. + commit/8df28a987519ecfa03dcb8635443025f8d010362)([#829](https://github.com/tauri-apps/plugins-workspace/pull/829)) Initial release. + . + commit/8df28a987519ecfa03dcb8635443025f8d010362)([#829](https://github.com/tauri-apps/plugins-workspace/pull/829)) Initial release. + ithub.com/tauri-apps/plugins-workspace/pull/829)) Initial release. diff --git a/packages/kbot/gui/app/plugins/biometric/Cargo.toml b/packages/kbot/gui/app/plugins/biometric/Cargo.toml new file mode 100644 index 00000000..1da605fb --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "tauri-plugin-biometric" +version = "2.3.0" +description = "Prompt the user for biometric authentication on Android and iOS." +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-biometric" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] +targets = ["x86_64-linux-android"] + +[package.metadata.platforms.support] +windows = { level = "none", notes = "" } +linux = { level = "none", notes = "" } +macos = { level = "none", notes = "" } +android = { level = "full", notes = "" } +ios = { level = "full", notes = "" } + + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } +serde_repr = "0.1" diff --git a/packages/kbot/gui/app/plugins/biometric/LICENSE.spdx b/packages/kbot/gui/app/plugins/biometric/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/biometric/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/biometric/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/biometric/LICENSE_MIT b/packages/kbot/gui/app/plugins/biometric/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/biometric/README.md b/packages/kbot/gui/app/plugins/biometric/README.md new file mode 100644 index 00000000..e2ad7efd --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/README.md @@ -0,0 +1,111 @@ +![biometric](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/biometric/banner.png) + +Prompt the user for biometric authentication on Android and iOS. + +| Platform | Supported | +| -------- | --------- | +| Linux | x | +| Windows | x | +| macOS | x | +| Android | ✓ | +| iOS | ✓ | + +## Install + +_This plugin requires a Rust version of at least **1.65**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-biometric = "2.0.0" +# alternatively with Git: +tauri-plugin-biometric = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + + + +```sh +pnpm add @tauri-apps/plugin-biometric +# or +npm add @tauri-apps/plugin-biometric +# or +yarn add @tauri-apps/plugin-biometric +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_biometric::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { authenticate } from '@tauri-apps/plugin-biometric' +await authenticate('Open your wallet') +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Contributed By + + + + + + + + +
+ + CrabNebula + + + + Impierce + +
+ +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/biometric/SECURITY.md b/packages/kbot/gui/app/plugins/biometric/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/biometric/android/.gitignore b/packages/kbot/gui/app/plugins/biometric/android/.gitignore new file mode 100644 index 00000000..c0f21ec2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/android/.gitignore @@ -0,0 +1,2 @@ +/build +/.tauri diff --git a/packages/kbot/gui/app/plugins/biometric/android/build.gradle.kts b/packages/kbot/gui/app/plugins/biometric/android/build.gradle.kts new file mode 100644 index 00000000..d8833662 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/android/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "app.tauri.biometric" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation("androidx.biometric:biometric:1.1.0") + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.0") + implementation("com.google.android.material:material:1.7.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + implementation(project(":tauri-android")) +} diff --git a/packages/kbot/gui/app/plugins/biometric/android/proguard-rules.pro b/packages/kbot/gui/app/plugins/biometric/android/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/biometric/android/settings.gradle b/packages/kbot/gui/app/plugins/biometric/android/settings.gradle new file mode 100644 index 00000000..14a752e4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/android/settings.gradle @@ -0,0 +1,2 @@ +include ':tauri-android' +project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/packages/kbot/gui/app/plugins/biometric/android/src/androidTest/java/ExampleInstrumentedTest.kt b/packages/kbot/gui/app/plugins/biometric/android/src/androidTest/java/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..5efaa023 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/android/src/androidTest/java/ExampleInstrumentedTest.kt @@ -0,0 +1,28 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.biometric + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("app.tauri.biometric", appContext.packageName) + } +} diff --git a/packages/kbot/gui/app/plugins/biometric/android/src/main/AndroidManifest.xml b/packages/kbot/gui/app/plugins/biometric/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..90328cb7 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/android/src/main/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/packages/kbot/gui/app/plugins/biometric/android/src/main/java/BiometricActivity.kt b/packages/kbot/gui/app/plugins/biometric/android/src/main/java/BiometricActivity.kt new file mode 100644 index 00000000..011de4d5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/android/src/main/java/BiometricActivity.kt @@ -0,0 +1,129 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.biometric + +import android.annotation.SuppressLint +import android.app.Activity +import android.app.KeyguardManager +import android.content.Context +import android.content.Intent +import android.hardware.biometrics.BiometricManager +import android.os.Build +import android.os.Bundle +import android.os.Handler +import androidx.appcompat.app.AppCompatActivity +import androidx.biometric.BiometricPrompt +import java.util.concurrent.Executor + +class BiometricActivity : AppCompatActivity() { + @SuppressLint("WrongConstant") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.auth_activity) + + val executor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + this.mainExecutor + } else { + Executor { command: Runnable? -> + Handler(this.mainLooper).post( + command!! + ) + } + } + + val builder = BiometricPrompt.PromptInfo.Builder() + val intent = intent + var title = intent.getStringExtra(BiometricPlugin.TITLE) + val subtitle = intent.getStringExtra(BiometricPlugin.SUBTITLE) + val description = intent.getStringExtra(BiometricPlugin.REASON) + allowDeviceCredential = false + // Android docs say we should check if the device is secure before enabling device credential fallback + val manager = getSystemService( + Context.KEYGUARD_SERVICE + ) as KeyguardManager + if (manager.isDeviceSecure) { + allowDeviceCredential = + intent.getBooleanExtra(BiometricPlugin.DEVICE_CREDENTIAL, false) + } + + if (title.isNullOrEmpty()) { + title = "Authenticate" + } + + builder.setTitle(title).setSubtitle(subtitle).setDescription(description) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + var authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK + if (allowDeviceCredential) { + authenticators = authenticators or BiometricManager.Authenticators.DEVICE_CREDENTIAL + } + builder.setAllowedAuthenticators(authenticators) + } else { + @Suppress("DEPRECATION") + builder.setDeviceCredentialAllowed(allowDeviceCredential) + } + + // From the Android docs: + // You can't call setNegativeButtonText() and setAllowedAuthenticators(... or DEVICE_CREDENTIAL) + // at the same time on a BiometricPrompt.PromptInfo.Builder instance. + if (!allowDeviceCredential) { + val negativeButtonText = intent.getStringExtra(BiometricPlugin.CANCEL_TITLE) + builder.setNegativeButtonText( + if (negativeButtonText.isNullOrEmpty()) "Cancel" else negativeButtonText + ) + } + builder.setConfirmationRequired( + intent.getBooleanExtra(BiometricPlugin.CONFIRMATION_REQUIRED, true) + ) + val promptInfo = builder.build() + val prompt = BiometricPrompt( + this, + executor, + object : BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationError( + errorCode: Int, + errorMessage: CharSequence + ) { + super.onAuthenticationError(errorCode, errorMessage) + finishActivity( + BiometryResultType.ERROR, + errorCode, + errorMessage as String + ) + } + + override fun onAuthenticationSucceeded( + result: BiometricPrompt.AuthenticationResult + ) { + super.onAuthenticationSucceeded(result) + finishActivity() + } + } + ) + prompt.authenticate(promptInfo) + } + + @JvmOverloads + fun finishActivity( + resultType: BiometryResultType = BiometryResultType.SUCCESS, + errorCode: Int = 0, + errorMessage: String? = "" + ) { + val intent = Intent() + val prefix = BiometricPlugin.RESULT_EXTRA_PREFIX + intent + .putExtra(prefix + BiometricPlugin.RESULT_TYPE, resultType.toString()) + .putExtra(prefix + BiometricPlugin.RESULT_ERROR_CODE, errorCode) + .putExtra( + prefix + BiometricPlugin.RESULT_ERROR_MESSAGE, + errorMessage + ) + setResult(Activity.RESULT_OK, intent) + finish() + } + + companion object { + var allowDeviceCredential = false + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/biometric/android/src/main/java/BiometricPlugin.kt b/packages/kbot/gui/app/plugins/biometric/android/src/main/java/BiometricPlugin.kt new file mode 100644 index 00000000..b3436fd4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/android/src/main/java/BiometricPlugin.kt @@ -0,0 +1,254 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.biometric + +import android.app.Activity +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import android.webkit.WebView +import androidx.activity.result.ActivityResult +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricPrompt +import app.tauri.annotation.ActivityCallback +import app.tauri.annotation.Command +import app.tauri.annotation.InvokeArg +import app.tauri.annotation.TauriPlugin +import app.tauri.plugin.Invoke +import app.tauri.plugin.JSArray +import app.tauri.plugin.JSObject +import app.tauri.plugin.Plugin +import java.util.EnumMap +import java.util.HashMap +import kotlin.math.max + +enum class BiometryResultType { + SUCCESS, FAILURE, ERROR +} + +private const val MAX_ATTEMPTS = "maxAttemps" +private const val BIOMETRIC_FAILURE = "authenticationFailed" +private const val INVALID_CONTEXT_ERROR = "invalidContext" + +@InvokeArg +class AuthOptions { + lateinit var reason: String + var allowDeviceCredential: Boolean = false + var title: String? = null + var subtitle: String? = null + var cancelTitle: String? = null + var confirmationRequired: Boolean? = null + var maxAttemps: Int = 3 +} + +@TauriPlugin +class BiometricPlugin(private val activity: Activity): Plugin(activity) { + private var biometryTypes: ArrayList = arrayListOf() + + companion object { + var RESULT_EXTRA_PREFIX = "" + const val TITLE = "title" + const val SUBTITLE = "subtitle" + const val REASON = "reason" + const val CANCEL_TITLE = "cancelTitle" + const val RESULT_TYPE = "type" + const val RESULT_ERROR_CODE = "errorCode" + const val RESULT_ERROR_MESSAGE = "errorMessage" + const val DEVICE_CREDENTIAL = "allowDeviceCredential" + const val CONFIRMATION_REQUIRED = "confirmationRequired" + + // Maps biometry error numbers to string error codes + private var biometryErrorCodeMap: MutableMap = HashMap() + private var biometryNameMap: MutableMap = EnumMap(BiometryType::class.java) + + init { + biometryErrorCodeMap[BiometricManager.BIOMETRIC_SUCCESS] = "" + biometryErrorCodeMap[BiometricManager.BIOMETRIC_SUCCESS] = "" + biometryErrorCodeMap[BiometricPrompt.ERROR_CANCELED] = "systemCancel" + biometryErrorCodeMap[BiometricPrompt.ERROR_HW_NOT_PRESENT] = "biometryNotAvailable" + biometryErrorCodeMap[BiometricPrompt.ERROR_HW_UNAVAILABLE] = "biometryNotAvailable" + biometryErrorCodeMap[BiometricPrompt.ERROR_LOCKOUT] = "biometryLockout" + biometryErrorCodeMap[BiometricPrompt.ERROR_LOCKOUT_PERMANENT] = "biometryLockout" + biometryErrorCodeMap[BiometricPrompt.ERROR_NEGATIVE_BUTTON] = "userCancel" + biometryErrorCodeMap[BiometricPrompt.ERROR_NO_BIOMETRICS] = "biometryNotEnrolled" + biometryErrorCodeMap[BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL] = "noDeviceCredential" + biometryErrorCodeMap[BiometricPrompt.ERROR_NO_SPACE] = "systemCancel" + biometryErrorCodeMap[BiometricPrompt.ERROR_TIMEOUT] = "systemCancel" + biometryErrorCodeMap[BiometricPrompt.ERROR_UNABLE_TO_PROCESS] = "systemCancel" + biometryErrorCodeMap[BiometricPrompt.ERROR_USER_CANCELED] = "userCancel" + biometryErrorCodeMap[BiometricPrompt.ERROR_VENDOR] = "systemCancel" + + biometryNameMap[BiometryType.NONE] = "No Authentication" + biometryNameMap[BiometryType.FINGERPRINT] = "Fingerprint Authentication" + biometryNameMap[BiometryType.FACE] = "Face Authentication" + biometryNameMap[BiometryType.IRIS] = "Iris Authentication" + } + } + + override fun load(webView: WebView) { + super.load(webView) + + biometryTypes = ArrayList() + val manager = activity.packageManager + if (manager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) { + biometryTypes.add(BiometryType.FINGERPRINT) + } + if (manager.hasSystemFeature(PackageManager.FEATURE_FACE)) { + biometryTypes.add(BiometryType.FACE) + } + if (manager.hasSystemFeature(PackageManager.FEATURE_IRIS)) { + biometryTypes.add(BiometryType.IRIS) + } + if (biometryTypes.size == 0) { + biometryTypes.add(BiometryType.NONE) + } + } + + /** + * Check the device's availability and type of biometric authentication. + */ + @Command + fun status(invoke: Invoke) { + val manager = BiometricManager.from(activity) + val biometryResult = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + manager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) + } else { + @Suppress("DEPRECATION") + manager.canAuthenticate() + } + val ret = JSObject() + + val available = biometryResult == BiometricManager.BIOMETRIC_SUCCESS + ret.put( + "isAvailable", + available + ) + + ret.put("biometryType", biometryTypes[0].type) + + if (!available) { + var reason = "" + when (biometryResult) { + BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> reason = + "Biometry unavailable." + BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> reason = + "Biometrics not enrolled." + BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> reason = + "No biometric on this device." + BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> reason = + "A security update is required." + BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> reason = + "Unsupported biometry." + BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> reason = + "Unknown biometry state." + } + + var errorCode = biometryErrorCodeMap[biometryResult] + if (errorCode == null) { + errorCode = "biometryNotAvailable" + } + ret.put("error", reason) + ret.put("errorCode", errorCode) + } + + invoke.resolve(ret) + } + + /** + * Prompt the user for biometric authentication. + */ + @Command + fun authenticate(invoke: Invoke) { + // The result of an intent is supposed to have the package name as a prefix + RESULT_EXTRA_PREFIX = activity.packageName + "." + val intent = Intent( + activity, + BiometricActivity::class.java + ) + + val args = invoke.parseArgs(AuthOptions::class.java) + + // Pass the options to the activity + intent.putExtra( + TITLE, + args.title ?: (biometryNameMap[biometryTypes[0]] ?: "") + ) + intent.putExtra(SUBTITLE, args.subtitle) + intent.putExtra(REASON, args.reason) + intent.putExtra(CANCEL_TITLE, args.cancelTitle) + intent.putExtra(DEVICE_CREDENTIAL, args.allowDeviceCredential) + args.confirmationRequired?.let { + intent.putExtra(CONFIRMATION_REQUIRED, it) + } + + val maxAttemptsConfig = args.maxAttemps + val maxAttempts = max(maxAttemptsConfig, 1) + intent.putExtra(MAX_ATTEMPTS, maxAttempts) + startActivityForResult(invoke, intent, "authenticateResult") + } + + @ActivityCallback + private fun authenticateResult(invoke: Invoke, result: ActivityResult) { + val resultCode = result.resultCode + + // If the system canceled the activity, we might get RESULT_CANCELED in resultCode. + // In that case return that immediately, because there won't be any data. + if (resultCode == Activity.RESULT_CANCELED) { + invoke.reject( + "The system canceled authentication", + biometryErrorCodeMap[BiometricPrompt.ERROR_CANCELED] + ) + return + } + + // Convert the string result type to an enum + val data = result.data + val resultTypeName = data?.getStringExtra( + RESULT_EXTRA_PREFIX + RESULT_TYPE + ) + if (resultTypeName == null) { + invoke.reject( + "Missing data in the result of the activity", + INVALID_CONTEXT_ERROR + ) + return + } + val resultType = try { + BiometryResultType.valueOf(resultTypeName) + } catch (e: IllegalArgumentException) { + invoke.reject( + "Invalid data in the result of the activity", + INVALID_CONTEXT_ERROR + ) + return + } + val errorCode = data.getIntExtra( + RESULT_EXTRA_PREFIX + RESULT_ERROR_CODE, + 0 + ) + var errorMessage = data.getStringExtra( + RESULT_EXTRA_PREFIX + RESULT_ERROR_MESSAGE + ) + when (resultType) { + BiometryResultType.SUCCESS -> invoke.resolve() + BiometryResultType.FAILURE -> // Biometry was successfully presented but was not recognized + invoke.reject(errorMessage, BIOMETRIC_FAILURE) + + BiometryResultType.ERROR -> { + // The user cancelled, the system cancelled, or some error occurred. + // If the user cancelled, errorMessage is the text of the "negative" button, + // which is not especially descriptive. + if (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) { + errorMessage = "Cancel button was pressed" + } + invoke.reject(errorMessage, biometryErrorCodeMap[errorCode]) + } + } + } + + internal enum class BiometryType(val type: Int) { + NONE(0), FINGERPRINT(1), FACE(2), IRIS(3); + } +} diff --git a/packages/kbot/gui/app/plugins/biometric/android/src/main/res/layout/auth_activity.xml b/packages/kbot/gui/app/plugins/biometric/android/src/main/res/layout/auth_activity.xml new file mode 100644 index 00000000..d88f20da --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/android/src/main/res/layout/auth_activity.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/biometric/android/src/main/res/values/styles.xml b/packages/kbot/gui/app/plugins/biometric/android/src/main/res/values/styles.xml new file mode 100644 index 00000000..3caed83b --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/android/src/main/res/values/styles.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/biometric/android/src/test/java/ExampleUnitTest.kt b/packages/kbot/gui/app/plugins/biometric/android/src/test/java/ExampleUnitTest.kt new file mode 100644 index 00000000..128d8bce --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/android/src/test/java/ExampleUnitTest.kt @@ -0,0 +1,21 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.biometric + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/packages/kbot/gui/app/plugins/biometric/api-iife.js b/packages/kbot/gui/app/plugins/biometric/api-iife.js new file mode 100644 index 00000000..3da2296b --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_BIOMETRIC__=function(e){"use strict";async function i(e,i={},t){return window.__TAURI_INTERNALS__.invoke(e,i,t)}var t;return"function"==typeof SuppressedError&&SuppressedError,e.BiometryType=void 0,(t=e.BiometryType||(e.BiometryType={}))[t.None=0]="None",t[t.TouchID=1]="TouchID",t[t.FaceID=2]="FaceID",t[t.Iris=3]="Iris",e.authenticate=async function(e,t){await i("plugin:biometric|authenticate",{reason:e,...t})},e.checkStatus=async function(){return await i("plugin:biometric|status")},e}({});Object.defineProperty(window.__TAURI__,"biometric",{value:__TAURI_PLUGIN_BIOMETRIC__})} diff --git a/packages/kbot/gui/app/plugins/biometric/build.rs b/packages/kbot/gui/app/plugins/biometric/build.rs new file mode 100644 index 00000000..070986b2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/build.rs @@ -0,0 +1,18 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +const COMMANDS: &[&str] = &["authenticate", "status"]; + +fn main() { + let result = tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .android_path("android") + .ios_path("ios") + .try_build(); + + // when building documentation for Android the plugin build result is always Err() and is irrelevant to the crate documentation build + if !(cfg!(docsrs) && std::env::var("TARGET").unwrap().contains("android")) { + result.unwrap(); + } +} diff --git a/packages/kbot/gui/app/plugins/biometric/contributors/crabnebula.svg b/packages/kbot/gui/app/plugins/biometric/contributors/crabnebula.svg new file mode 100644 index 00000000..a9bb4609 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/contributors/crabnebula.svg @@ -0,0 +1,31 @@ + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/biometric/contributors/impierce.svg b/packages/kbot/gui/app/plugins/biometric/contributors/impierce.svg new file mode 100644 index 00000000..9d2a510b --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/contributors/impierce.svg @@ -0,0 +1,21 @@ + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/biometric/guest-js/index.ts b/packages/kbot/gui/app/plugins/biometric/guest-js/index.ts new file mode 100644 index 00000000..5c3eb8df --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/guest-js/index.ts @@ -0,0 +1,77 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { invoke } from '@tauri-apps/api/core' + +export enum BiometryType { + None = 0, + // Apple TouchID or Android fingerprint + TouchID = 1, + // Apple FaceID or Android face authentication + FaceID = 2, + // Android iris authentication + Iris = 3 +} + +export interface Status { + isAvailable: boolean + biometryType: BiometryType + error?: string + errorCode?: + | 'appCancel' + | 'authenticationFailed' + | 'invalidContext' + | 'notInteractive' + | 'passcodeNotSet' + | 'systemCancel' + | 'userCancel' + | 'userFallback' + | 'biometryLockout' + | 'biometryNotAvailable' + | 'biometryNotEnrolled' +} + +export interface AuthOptions { + allowDeviceCredential?: boolean + cancelTitle?: string + + // iOS options + fallbackTitle?: string + + // android options + title?: string + subtitle?: string + confirmationRequired?: boolean + maxAttemps?: number +} + +/** + * Checks if the biometric authentication is available. + * @returns a promise resolving to an object containing all the information about the status of the biometry. + */ +export async function checkStatus(): Promise { + return await invoke('plugin:biometric|status') +} + +/** + * Prompts the user for authentication using the system interface (touchID, faceID or Android Iris). + * Rejects if the authentication fails. + * + * ```javascript + * import { authenticate } from "@tauri-apps/plugin-biometric"; + * await authenticate('Open your wallet'); + * ``` + * @param reason + * @param options + * @returns + */ +export async function authenticate( + reason: string, + options?: AuthOptions +): Promise { + await invoke('plugin:biometric|authenticate', { + reason, + ...options + }) +} diff --git a/packages/kbot/gui/app/plugins/biometric/ios/.gitignore b/packages/kbot/gui/app/plugins/biometric/ios/.gitignore new file mode 100644 index 00000000..5922fdaa --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/ios/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Package.resolved diff --git a/packages/kbot/gui/app/plugins/biometric/ios/Package.swift b/packages/kbot/gui/app/plugins/biometric/ios/Package.swift new file mode 100644 index 00000000..7860f476 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/ios/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version:5.3 +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import PackageDescription + +let package = Package( + name: "tauri-plugin-biometric", + platforms: [ + .macOS(.v10_13), + .iOS(.v13), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "tauri-plugin-biometric", + type: .static, + targets: ["tauri-plugin-biometric"]) + ], + dependencies: [ + .package(name: "Tauri", path: "../.tauri/tauri-api") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "tauri-plugin-biometric", + dependencies: [ + .byName(name: "Tauri") + ], + path: "Sources") + ] +) diff --git a/packages/kbot/gui/app/plugins/biometric/ios/README.md b/packages/kbot/gui/app/plugins/biometric/ios/README.md new file mode 100644 index 00000000..f4900bdd --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/ios/README.md @@ -0,0 +1,3 @@ +# Tauri Plugin {{ plugin_name_original }} + +A description of this package. diff --git a/packages/kbot/gui/app/plugins/biometric/ios/Sources/BiometricPlugin.swift b/packages/kbot/gui/app/plugins/biometric/ios/Sources/BiometricPlugin.swift new file mode 100644 index 00000000..c295904a --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/ios/Sources/BiometricPlugin.swift @@ -0,0 +1,153 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import LocalAuthentication +import SwiftRs +import Tauri +import UIKit +import WebKit + +class BiometricStatus { + let available: Bool + let biometryType: LABiometryType + let errorReason: String? + let errorCode: String? + + init(available: Bool, biometryType: LABiometryType, errorReason: String?, errorCode: String?) { + self.available = available + self.biometryType = biometryType + self.errorReason = errorReason + self.errorCode = errorCode + } +} + +struct AuthOptions: Decodable { + let reason: String + var allowDeviceCredential: Bool? + var fallbackTitle: String? + var cancelTitle: String? +} + +class BiometricPlugin: Plugin { + let authenticationErrorCodeMap: [Int: String] = [ + 0: "", + LAError.appCancel.rawValue: "appCancel", + LAError.authenticationFailed.rawValue: "authenticationFailed", + LAError.invalidContext.rawValue: "invalidContext", + LAError.notInteractive.rawValue: "notInteractive", + LAError.passcodeNotSet.rawValue: "passcodeNotSet", + LAError.systemCancel.rawValue: "systemCancel", + LAError.userCancel.rawValue: "userCancel", + LAError.userFallback.rawValue: "userFallback", + LAError.biometryLockout.rawValue: "biometryLockout", + LAError.biometryNotAvailable.rawValue: "biometryNotAvailable", + LAError.biometryNotEnrolled.rawValue: "biometryNotEnrolled", + ] + + var status: BiometricStatus! + + public override func load(webview: WKWebView) { + let context = LAContext() + var error: NSError? + var available = context.canEvaluatePolicy( + .deviceOwnerAuthenticationWithBiometrics, error: &error) + var reason: String? = nil + var errorCode: String? = nil + + if available && context.biometryType == .faceID { + let entry = Bundle.main.infoDictionary?["NSFaceIDUsageDescription"] as? String + + if entry == nil || entry?.count == 0 { + available = false + reason = "NSFaceIDUsageDescription is not in the app Info.plist" + errorCode = authenticationErrorCodeMap[LAError.biometryNotAvailable.rawValue] ?? "" + } + } else if !available, let error = error { + reason = error.localizedDescription + if let failureReason = error.localizedFailureReason { + reason = "\(reason ?? ""): \(failureReason)" + } + errorCode = + authenticationErrorCodeMap[error.code] ?? authenticationErrorCodeMap[ + LAError.biometryNotAvailable.rawValue] ?? "" + } + + self.status = BiometricStatus( + available: available, + biometryType: context.biometryType, + errorReason: reason, + errorCode: errorCode + ) + } + + @objc func status(_ invoke: Invoke) { + if self.status.available { + invoke.resolve([ + "isAvailable": self.status.available, + "biometryType": self.status.biometryType.rawValue, + ]) + } else { + invoke.resolve([ + "isAvailable": self.status.available, + "biometryType": self.status.biometryType.rawValue, + "error": self.status.errorReason ?? "", + "errorCode": self.status.errorCode ?? "", + ]) + } + } + + @objc func authenticate(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(AuthOptions.self) + + let allowDeviceCredential = args.allowDeviceCredential ?? false + + guard self.status.available || allowDeviceCredential else { + // Biometry unavailable, fallback disabled + invoke.reject( + self.status.errorReason ?? "", + code: self.status.errorCode ?? "" + ) + return + } + + let context = LAContext() + context.localizedFallbackTitle = args.fallbackTitle + context.localizedCancelTitle = args.cancelTitle + context.touchIDAuthenticationAllowableReuseDuration = 0 + + // force system default fallback title if an empty string is provided (the OS hides the fallback button in this case) + if allowDeviceCredential, + let fallbackTitle = context.localizedFallbackTitle, + fallbackTitle.isEmpty + { + context.localizedFallbackTitle = nil + } + + context.evaluatePolicy( + allowDeviceCredential + ? .deviceOwnerAuthentication : .deviceOwnerAuthenticationWithBiometrics, + localizedReason: args.reason + ) { success, error in + if success { + invoke.resolve() + } else { + if let policyError = error as? LAError { + let code = self.authenticationErrorCodeMap[policyError.code.rawValue] + invoke.reject(policyError.localizedDescription, code: code) + } else { + invoke.reject( + "Unknown error", + code: self.authenticationErrorCodeMap[LAError.authenticationFailed.rawValue] + ) + } + } + } + + } +} + +@_cdecl("init_plugin_biometric") +func initPlugin() -> Plugin { + return BiometricPlugin() +} diff --git a/packages/kbot/gui/app/plugins/biometric/ios/Tests/PluginTests/PluginTests.swift b/packages/kbot/gui/app/plugins/biometric/ios/Tests/PluginTests/PluginTests.swift new file mode 100644 index 00000000..99992ce4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/ios/Tests/PluginTests/PluginTests.swift @@ -0,0 +1,12 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import XCTest +@testable import ExamplePlugin + +final class ExamplePluginTests: XCTestCase { + func testExample() throws { + let plugin = ExamplePlugin() + } +} diff --git a/packages/kbot/gui/app/plugins/biometric/package.json b/packages/kbot/gui/app/plugins/biometric/package.json new file mode 100644 index 00000000..e6c4d988 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/package.json @@ -0,0 +1,30 @@ +{ + "name": "@tauri-apps/plugin-biometric", + "version": "2.3.0", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "!dist-js/**/*.map", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/biometric/permissions/autogenerated/commands/authenticate.toml b/packages/kbot/gui/app/plugins/biometric/permissions/autogenerated/commands/authenticate.toml new file mode 100644 index 00000000..be4c9f9b --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/permissions/autogenerated/commands/authenticate.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-authenticate" +description = "Enables the authenticate command without any pre-configured scope." +commands.allow = ["authenticate"] + +[[permission]] +identifier = "deny-authenticate" +description = "Denies the authenticate command without any pre-configured scope." +commands.deny = ["authenticate"] diff --git a/packages/kbot/gui/app/plugins/biometric/permissions/autogenerated/commands/status.toml b/packages/kbot/gui/app/plugins/biometric/permissions/autogenerated/commands/status.toml new file mode 100644 index 00000000..c8ed433c --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/permissions/autogenerated/commands/status.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-status" +description = "Enables the status command without any pre-configured scope." +commands.allow = ["status"] + +[[permission]] +identifier = "deny-status" +description = "Denies the status command without any pre-configured scope." +commands.deny = ["status"] diff --git a/packages/kbot/gui/app/plugins/biometric/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/biometric/permissions/autogenerated/reference.md new file mode 100644 index 00000000..e1b29bb0 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/permissions/autogenerated/reference.md @@ -0,0 +1,75 @@ +## Default Permission + +This permission set configures which +biometric features are by default exposed. + +#### Granted Permissions + +It allows acccess to all biometric commands. + +#### This default permission set includes the following: + +- `allow-authenticate` +- `allow-status` + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`biometric:allow-authenticate` + + + +Enables the authenticate command without any pre-configured scope. + +
+ +`biometric:deny-authenticate` + + + +Denies the authenticate command without any pre-configured scope. + +
+ +`biometric:allow-status` + + + +Enables the status command without any pre-configured scope. + +
+ +`biometric:deny-status` + + + +Denies the status command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/biometric/permissions/default.toml b/packages/kbot/gui/app/plugins/biometric/permissions/default.toml new file mode 100644 index 00000000..651990ef --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/permissions/default.toml @@ -0,0 +1,13 @@ +"$schema" = "schemas/schema.json" +[default] +description = """ +This permission set configures which +biometric features are by default exposed. + +#### Granted Permissions + +It allows acccess to all biometric commands. + +""" + +permissions = ["allow-authenticate", "allow-status"] diff --git a/packages/kbot/gui/app/plugins/biometric/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/biometric/permissions/schemas/schema.json new file mode 100644 index 00000000..416759b5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/permissions/schemas/schema.json @@ -0,0 +1,330 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the authenticate command without any pre-configured scope.", + "type": "string", + "const": "allow-authenticate", + "markdownDescription": "Enables the authenticate command without any pre-configured scope." + }, + { + "description": "Denies the authenticate command without any pre-configured scope.", + "type": "string", + "const": "deny-authenticate", + "markdownDescription": "Denies the authenticate command without any pre-configured scope." + }, + { + "description": "Enables the status command without any pre-configured scope.", + "type": "string", + "const": "allow-status", + "markdownDescription": "Enables the status command without any pre-configured scope." + }, + { + "description": "Denies the status command without any pre-configured scope.", + "type": "string", + "const": "deny-status", + "markdownDescription": "Denies the status command without any pre-configured scope." + }, + { + "description": "This permission set configures which\nbiometric features are by default exposed.\n\n#### Granted Permissions\n\nIt allows acccess to all biometric commands.\n\n\n#### This default permission set includes:\n\n- `allow-authenticate`\n- `allow-status`", + "type": "string", + "const": "default", + "markdownDescription": "This permission set configures which\nbiometric features are by default exposed.\n\n#### Granted Permissions\n\nIt allows acccess to all biometric commands.\n\n\n#### This default permission set includes:\n\n- `allow-authenticate`\n- `allow-status`" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/biometric/rollup.config.js b/packages/kbot/gui/app/plugins/biometric/rollup.config.js new file mode 100644 index 00000000..1f349ec8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/rollup.config.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' + +export default createConfig() diff --git a/packages/kbot/gui/app/plugins/biometric/src/error.rs b/packages/kbot/gui/app/plugins/biometric/src/error.rs new file mode 100644 index 00000000..339e763b --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/src/error.rs @@ -0,0 +1,25 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{ser::Serializer, Serialize}; + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/packages/kbot/gui/app/plugins/biometric/src/lib.rs b/packages/kbot/gui/app/plugins/biometric/src/lib.rs new file mode 100644 index 00000000..f79a104d --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/src/lib.rs @@ -0,0 +1,71 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#![cfg(mobile)] + +use serde::Serialize; +use tauri::{ + plugin::{Builder, PluginHandle, TauriPlugin}, + Manager, Runtime, +}; + +pub use models::*; + +mod error; +mod models; + +pub use error::{Error, Result}; + +#[cfg(target_os = "android")] +const PLUGIN_IDENTIFIER: &str = "app.tauri.biometric"; + +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_biometric); + +/// Access to the biometric APIs. +pub struct Biometric(PluginHandle); + +#[derive(Serialize)] +struct AuthenticatePayload { + reason: String, + #[serde(flatten)] + options: AuthOptions, +} + +impl Biometric { + pub fn status(&self) -> crate::Result { + self.0.run_mobile_plugin("status", ()).map_err(Into::into) + } + + pub fn authenticate(&self, reason: String, options: AuthOptions) -> crate::Result<()> { + self.0 + .run_mobile_plugin("authenticate", AuthenticatePayload { reason, options }) + .map_err(Into::into) + } +} + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the biometric APIs. +pub trait BiometricExt { + fn biometric(&self) -> &Biometric; +} + +impl> crate::BiometricExt for T { + fn biometric(&self) -> &Biometric { + self.state::>().inner() + } +} + +/// Initializes the plugin. +pub fn init() -> TauriPlugin { + Builder::new("biometric") + .setup(|app, api| { + #[cfg(target_os = "android")] + let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "BiometricPlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_biometric)?; + app.manage(Biometric(handle)); + Ok(()) + }) + .build() +} diff --git a/packages/kbot/gui/app/plugins/biometric/src/models.rs b/packages/kbot/gui/app/plugins/biometric/src/models.rs new file mode 100644 index 00000000..49c84300 --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/src/models.rs @@ -0,0 +1,39 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AuthOptions { + /// Enables authentication using the device's password. This feature is available on both Android and iOS. + pub allow_device_credential: bool, + /// Label for the Cancel button. This feature is available on both Android and iOS. + pub cancel_title: Option, + /// Specifies the text displayed on the fallback button if biometric authentication fails. This feature is available iOS only. + pub fallback_title: Option, + /// Title indicating the purpose of biometric verification. This feature is available Android only. + pub title: Option, + /// SubTitle providing contextual information of biometric verification. This feature is available Android only. + pub subtitle: Option, + /// Specifies whether additional user confirmation is required, such as pressing a button after successful biometric authentication. This feature is available Android only. + pub confirmation_required: Option, +} + +#[derive(Debug, Clone, serde_repr::Deserialize_repr)] +#[repr(u8)] +pub enum BiometryType { + None = 0, + TouchID = 1, + FaceID = 2, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Status { + pub is_available: bool, + pub biometry_type: BiometryType, + pub error: Option, + pub error_code: Option, +} diff --git a/packages/kbot/gui/app/plugins/biometric/tsconfig.json b/packages/kbot/gui/app/plugins/biometric/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/biometric/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/cli/CHANGELOG.md b/packages/kbot/gui/app/plugins/cli/CHANGELOG.md new file mode 100644 index 00000000..45de34e4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/CHANGELOG.md @@ -0,0 +1,110 @@ +# Changelog + +## \[2.4.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.3.0] + +- [`f6e11282`](https://github.com/tauri-apps/plugins-workspace/commit/f6e11282a7f4036dd6d1dbb8f100e777e9e42f11) ([#2787](https://github.com/tauri-apps/plugins-workspace/pull/2787) by [@mikew](https://github.com/tauri-apps/plugins-workspace/../../mikew)) Added `Cli.matches_from(args)`. This can be combined with the `args` passed to the callback of `tauri_plugin_single_instance::init` to parse the command line arguments passed to subsequent instances of the application. +- [`37c2fb41`](https://github.com/tauri-apps/plugins-workspace/commit/37c2fb41201160e85c8dc3ad40f462cd4e17a304) ([#2772](https://github.com/tauri-apps/plugins-workspace/pull/2772) by [@floriskn](https://github.com/tauri-apps/plugins-workspace/../../floriskn)) Added a new `global` boolean flag to the `CliArg` struct to support global CLI arguments. This flag allows arguments like `--verbose` to be marked as global and automatically propagated to all subcommands, enabling consistent availability throughout the CLI. + + The new field is optional and defaults to false. + +## \[2.2.1] + +- [`f634e524`](https://github.com/tauri-apps/plugins-workspace/commit/f634e5248ebe428f8305a59f74c13fc15147fb8e) This is an "empty" release to update the plugins' source files on crates.io and docs.rs. This should fix docs.rs build failures for projects using tauri plugins as dependencies. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.2] + +- [`68579934`](https://github.com/tauri-apps/plugins-workspace/commit/68579934c93f6ed2edbc97474560d6a8a00e8f70) ([#1856](https://github.com/tauri-apps/plugins-workspace/pull/1856) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Expose `Matches`, `SubcommandMatches` and `ArgData` structs. + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +## \[2.0.0-beta.8] + +- [`99d6ac0f`](https://github.com/tauri-apps/plugins-workspace/commit/99d6ac0f9506a6a4a1aa59c728157190a7441af6) ([#1606](https://github.com/tauri-apps/plugins-workspace/pull/1606) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The JS packages now specify the *minimum* `@tauri-apps/api` version instead of a single exact version. +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.7] + +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.6] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.5] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.4] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.3] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.3] + +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. + +## \[2.0.0-beta.2] + +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. + +## \[2.0.0-alpha.5] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.13. + +## \[2.0.0-alpha.4] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.12. + +## \[2.0.0-alpha.3] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to @tauri-apps/api v2.0.0-alpha.11. + +## \[2.0.0-alpha.2] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update to @tauri-apps/api v2.0.0-alpha.9. + +## \[2.0.0-alpha.2] + +- [`4e2cef9`](https://github.com/tauri-apps/plugins-workspace/commit/4e2cef9b702bbbb9cf4ee17de50791cb21f1b2a4)([#593](https://github.com/tauri-apps/plugins-workspace/pull/593)) Update to alpha.12. + +## \[2.0.0-alpha.1] + +- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11. + +## \[2.0.0-alpha.0] + +- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! + te to alpha.11. diff --git a/packages/kbot/gui/app/plugins/cli/Cargo.toml b/packages/kbot/gui/app/plugins/cli/Cargo.toml new file mode 100644 index 00000000..eb63f0e5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "tauri-plugin-cli" +version = "2.4.0" +description = "Parse arguments from your Tauri application's command line interface." +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-cli" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.platforms.support] +windows = { level = "full", notes = "" } +linux = { level = "full", notes = "" } +macos = { level = "full", notes = "" } +android = { level = "none", notes = "" } +ios = { level = "none", notes = "" } + + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } +clap = { version = "4", features = ["string"] } diff --git a/packages/kbot/gui/app/plugins/cli/LICENSE.spdx b/packages/kbot/gui/app/plugins/cli/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/cli/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/cli/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/cli/LICENSE_MIT b/packages/kbot/gui/app/plugins/cli/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/cli/README.md b/packages/kbot/gui/app/plugins/cli/README.md new file mode 100644 index 00000000..30cd3449 --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/README.md @@ -0,0 +1,105 @@ +![plugin-cli](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/cli/banner.png) + +Parse arguments from your Command Line Interface. + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | x | +| iOS | x | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +# you can add the dependencies on the `[dependencies]` section if you do not target mobile +[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies] +tauri-plugin-cli = "2.0.0" +# alternatively with Git: +tauri-plugin-cli = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +```sh +pnpm add @tauri-apps/plugin-cli +# or +npm add @tauri-apps/plugin-cli +# or +yarn add @tauri-apps/plugin-cli +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .setup(|app| { + #[cfg(desktop)] + app.handle().plugin(tauri_plugin_cli::init())?; + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { getMatches } from '@tauri-apps/plugin-cli' +const matches = await getMatches() +if (matches.subcommand?.name === 'run') { + // `./your-app run $ARGS` was executed + const args = matches.subcommand?.matches.args + if ('debug' in args) { + // `./your-app run --debug` was executed + } +} else { + const args = matches.args + // `./your-app $ARGS` was executed +} +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/cli/SECURITY.md b/packages/kbot/gui/app/plugins/cli/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/cli/api-iife.js b/packages/kbot/gui/app/plugins/cli/api-iife.js new file mode 100644 index 00000000..90b2f35f --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_CLI__=function(_){"use strict";return"function"==typeof SuppressedError&&SuppressedError,_.getMatches=async function(){return await async function(_,n={},e){return window.__TAURI_INTERNALS__.invoke(_,n,e)}("plugin:cli|cli_matches")},_}({});Object.defineProperty(window.__TAURI__,"cli",{value:__TAURI_PLUGIN_CLI__})} diff --git a/packages/kbot/gui/app/plugins/cli/banner.png b/packages/kbot/gui/app/plugins/cli/banner.png new file mode 100644 index 00000000..2d1f2cd9 Binary files /dev/null and b/packages/kbot/gui/app/plugins/cli/banner.png differ diff --git a/packages/kbot/gui/app/plugins/cli/build.rs b/packages/kbot/gui/app/plugins/cli/build.rs new file mode 100644 index 00000000..50d88849 --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/build.rs @@ -0,0 +1,11 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +const COMMANDS: &[&str] = &["cli_matches"]; + +fn main() { + tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .build(); +} diff --git a/packages/kbot/gui/app/plugins/cli/guest-js/index.ts b/packages/kbot/gui/app/plugins/cli/guest-js/index.ts new file mode 100644 index 00000000..7a7f4acf --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/guest-js/index.ts @@ -0,0 +1,72 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/** + * Parse arguments from your Command Line Interface. + * + * @module + */ + +import { invoke } from '@tauri-apps/api/core' + +/** + * @since 2.0.0 + */ +interface ArgMatch { + /** + * string if takes value + * boolean if flag + * string[] or null if takes multiple values + */ + value: string | boolean | string[] | null + /** + * Number of occurrences + */ + occurrences: number +} + +/** + * @since 2.0.0 + */ +interface SubcommandMatch { + name: string + matches: CliMatches +} + +/** + * @since 2.0.0 + */ +interface CliMatches { + args: Record + subcommand: SubcommandMatch | null +} + +/** + * Parse the arguments provided to the current process and get the matches using the configuration defined [`tauri.cli`](https://tauri.app/v1/api/config/#tauriconfig.cli) in `tauri.conf.json` + * + * @example + * ```typescript + * import { getMatches } from '@tauri-apps/plugin-cli'; + * const matches = await getMatches(); + * if (matches.subcommand?.name === 'run') { + * // `./your-app run $ARGS` was executed + * const args = matches.subcommand?.matches.args + * if ('debug' in args) { + * // `./your-app run --debug` was executed + * } + * } else { + * const args = matches.args + * // `./your-app $ARGS` was executed + * } + * ``` + * + * @since 2.0.0 + */ +async function getMatches(): Promise { + return await invoke('plugin:cli|cli_matches') +} + +export type { ArgMatch, SubcommandMatch, CliMatches } + +export { getMatches } diff --git a/packages/kbot/gui/app/plugins/cli/package.json b/packages/kbot/gui/app/plugins/cli/package.json new file mode 100644 index 00000000..4aeeb8ec --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/package.json @@ -0,0 +1,29 @@ +{ + "name": "@tauri-apps/plugin-cli", + "version": "2.4.0", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/cli/permissions/autogenerated/commands/cli_matches.toml b/packages/kbot/gui/app/plugins/cli/permissions/autogenerated/commands/cli_matches.toml new file mode 100644 index 00000000..b0a2e8f3 --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/permissions/autogenerated/commands/cli_matches.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-cli-matches" +description = "Enables the cli_matches command without any pre-configured scope." +commands.allow = ["cli_matches"] + +[[permission]] +identifier = "deny-cli-matches" +description = "Denies the cli_matches command without any pre-configured scope." +commands.deny = ["cli_matches"] diff --git a/packages/kbot/gui/app/plugins/cli/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/cli/permissions/autogenerated/reference.md new file mode 100644 index 00000000..cfa83f0a --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/permissions/autogenerated/reference.md @@ -0,0 +1,43 @@ +## Default Permission + +Allows reading the CLI matches + +#### This default permission set includes the following: + +- `allow-cli-matches` + +## Permission Table + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`cli:allow-cli-matches` + + + +Enables the cli_matches command without any pre-configured scope. + +
+ +`cli:deny-cli-matches` + + + +Denies the cli_matches command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/cli/permissions/default.toml b/packages/kbot/gui/app/plugins/cli/permissions/default.toml new file mode 100644 index 00000000..82bf7ca8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/permissions/default.toml @@ -0,0 +1,4 @@ +"$schema" = "schemas/schema.json" +[default] +description = "Allows reading the CLI matches" +permissions = ["allow-cli-matches"] diff --git a/packages/kbot/gui/app/plugins/cli/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/cli/permissions/schemas/schema.json new file mode 100644 index 00000000..45941514 --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/permissions/schemas/schema.json @@ -0,0 +1,318 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the cli_matches command without any pre-configured scope.", + "type": "string", + "const": "allow-cli-matches", + "markdownDescription": "Enables the cli_matches command without any pre-configured scope." + }, + { + "description": "Denies the cli_matches command without any pre-configured scope.", + "type": "string", + "const": "deny-cli-matches", + "markdownDescription": "Denies the cli_matches command without any pre-configured scope." + }, + { + "description": "Allows reading the CLI matches\n#### This default permission set includes:\n\n- `allow-cli-matches`", + "type": "string", + "const": "default", + "markdownDescription": "Allows reading the CLI matches\n#### This default permission set includes:\n\n- `allow-cli-matches`" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/cli/rollup.config.js b/packages/kbot/gui/app/plugins/cli/rollup.config.js new file mode 100644 index 00000000..1f349ec8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/rollup.config.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' + +export default createConfig() diff --git a/packages/kbot/gui/app/plugins/cli/src/config.rs b/packages/kbot/gui/app/plugins/cli/src/config.rs new file mode 100644 index 00000000..bd4a3015 --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/src/config.rs @@ -0,0 +1,184 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::collections::HashMap; + +use serde::Deserialize; + +/// A CLI argument definition. +#[derive(Debug, PartialEq, Eq, Clone, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct Arg { + /// The short version of the argument, without the preceding -. + /// + /// NOTE: Any leading `-` characters will be stripped, and only the first non-character will be used as the short version. + pub short: Option, + /// The unique argument name + pub name: String, + /// The argument description which will be shown on the help information. + /// Typically, this is a short (one line) description of the arg. + pub description: Option, + /// The argument long description which will be shown on the help information. + /// Typically this a more detailed (multi-line) message that describes the argument. + #[serde(alias = "long-description")] + pub long_description: Option, + /// Specifies that the argument takes a value at run time. + /// + /// NOTE: values for arguments may be specified in any of the following methods + /// - Using a space such as -o value or --option value + /// - Using an equals and no space such as -o=value or --option=value + /// - Use a short and no space such as -ovalue + #[serde(default, alias = "takes-value")] + pub takes_value: bool, + /// Specifies that the argument may have an unknown number of multiple values. Without any other settings, this argument may appear only once. + /// + /// For example, --opt val1 val2 is allowed, but --opt val1 val2 --opt val3 is not. + /// + /// NOTE: Setting this requires `takes_value` to be set to true. + #[serde(default)] + pub multiple: bool, + /// Specifies how many values are required to satisfy this argument. For example, if you had a + /// `-f ` argument where you wanted exactly 3 'files' you would set + /// `number_of_values = 3`, and this argument wouldn't be satisfied unless the user provided + /// 3 and only 3 values. + /// + /// **NOTE:** Does *not* require `multiple_occurrences = true` to be set. Setting + /// `multiple_occurrences = true` would allow `-f -f ` where + /// as *not* setting it would only allow one occurrence of this argument. + /// + /// **NOTE:** implicitly sets `takes_value = true` and `multiple_values = true`. + #[serde(alias = "number-of-values")] + pub number_of_values: Option, + /// Specifies a list of possible values for this argument. + /// At runtime, the CLI verifies that only one of the specified values was used, or fails with an error message. + #[serde(alias = "possible-values")] + pub possible_values: Option>, + /// Specifies the minimum number of values for this argument. + /// For example, if you had a -f `` argument where you wanted at least 2 'files', + /// you would set `minValues: 2`, and this argument would be satisfied if the user provided, 2 or more values. + #[serde(alias = "min-values")] + pub min_values: Option, + /// Specifies the maximum number of values are for this argument. + /// For example, if you had a -f `` argument where you wanted up to 3 'files', + /// you would set .max_values(3), and this argument would be satisfied if the user provided, 1, 2, or 3 values. + #[serde(alias = "max-values")] + pub max_values: Option, + /// Sets whether or not the argument is required by default. + /// + /// - Required by default means it is required, when no other conflicting rules have been evaluated + /// - Conflicting rules take precedence over being required. + #[serde(default)] + pub required: bool, + /// Sets an arg that override this arg's required setting + /// i.e. this arg will be required unless this other argument is present. + #[serde(alias = "required-unless-present")] + pub required_unless_present: Option, + /// Sets args that override this arg's required setting + /// i.e. this arg will be required unless all these other arguments are present. + #[serde(alias = "required-unless-present-all")] + pub required_unless_present_all: Option>, + /// Sets args that override this arg's required setting + /// i.e. this arg will be required unless at least one of these other arguments are present. + #[serde(alias = "required-unless-present-any")] + pub required_unless_present_any: Option>, + /// Sets a conflicting argument by name + /// i.e. when using this argument, the following argument can't be present and vice versa. + #[serde(alias = "conflicts-with")] + pub conflicts_with: Option, + /// The same as conflictsWith but allows specifying multiple two-way conflicts per argument. + #[serde(alias = "conflicts-with-all")] + pub conflicts_with_all: Option>, + /// Tets an argument by name that is required when this one is present + /// i.e. when using this argument, the following argument must be present. + pub requires: Option, + /// Sts multiple arguments by names that are required when this one is present + /// i.e. when using this argument, the following arguments must be present. + #[serde(alias = "requires-all")] + pub requires_all: Option>, + /// Allows a conditional requirement with the signature [arg, value] + /// the requirement will only become valid if `arg`'s value equals `${value}`. + #[serde(alias = "requires-if")] + pub requires_if: Option<(String, String)>, + /// Allows specifying that an argument is required conditionally with the signature [arg, value] + /// the requirement will only become valid if the `arg`'s value equals `${value}`. + #[serde(alias = "required-if-eq")] + pub required_if_eq: Option<(String, String)>, + /// Requires that options use the --option=val syntax + /// i.e. an equals between the option and associated value. + #[serde(alias = "requires-equals")] + pub require_equals: Option, + /// The positional argument index, starting at 1. + /// + /// The index refers to position according to other positional argument. + /// It does not define position in the argument list as a whole. When utilized with multiple=true, + /// only the last positional argument may be defined as multiple (i.e. the one with the highest index). + pub index: Option, + /// Specifies whether the argument should be global. + /// + /// Global arguments are propagated to all subcommands automatically, + /// making them available throughout the CLI regardless of where they are defined. + #[serde(default)] + pub global: bool, +} + +/// describes a CLI configuration +#[derive(Debug, PartialEq, Eq, Clone, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct Config { + /// Command description which will be shown on the help information. + pub description: Option, + /// Command long description which will be shown on the help information. + #[serde(alias = "long-description")] + pub long_description: Option, + /// Adds additional help information to be displayed in addition to auto-generated help. + /// This information is displayed before the auto-generated help information. + /// This is often used for header information. + #[serde(alias = "before-help")] + pub before_help: Option, + /// Adds additional help information to be displayed in addition to auto-generated help. + /// This information is displayed after the auto-generated help information. + /// This is often used to describe how to use the arguments, or caveats to be noted. + #[serde(alias = "after-help")] + pub after_help: Option, + /// List of arguments for the command + pub args: Option>, + /// List of subcommands of this command + pub subcommands: Option>, +} + +impl Config { + /// List of arguments for the command + pub fn args(&self) -> Option<&Vec> { + self.args.as_ref() + } + + /// List of subcommands of this command + pub fn subcommands(&self) -> Option<&HashMap> { + self.subcommands.as_ref() + } + + /// Command description which will be shown on the help information. + pub fn description(&self) -> Option<&String> { + self.description.as_ref() + } + + /// Command long description which will be shown on the help information. + pub fn long_description(&self) -> Option<&String> { + self.description.as_ref() + } + + /// Adds additional help information to be displayed in addition to auto-generated help. + /// This information is displayed before the auto-generated help information. + /// This is often used for header information. + pub fn before_help(&self) -> Option<&String> { + self.before_help.as_ref() + } + + /// Adds additional help information to be displayed in addition to auto-generated help. + /// This information is displayed after the auto-generated help information. + /// This is often used to describe how to use the arguments, or caveats to be noted. + pub fn after_help(&self) -> Option<&String> { + self.after_help.as_ref() + } +} diff --git a/packages/kbot/gui/app/plugins/cli/src/error.rs b/packages/kbot/gui/app/plugins/cli/src/error.rs new file mode 100644 index 00000000..2b5e1602 --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/src/error.rs @@ -0,0 +1,22 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{Serialize, Serializer}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("failed to parse arguments: {0}")] + ParseCli(#[from] clap::Error), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} + +pub type Result = std::result::Result; diff --git a/packages/kbot/gui/app/plugins/cli/src/lib.rs b/packages/kbot/gui/app/plugins/cli/src/lib.rs new file mode 100644 index 00000000..e927a348 --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/src/lib.rs @@ -0,0 +1,63 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Parse arguments from your Command Line Interface. +//! +//! - Supported platforms: Windows, Linux and macOS. + +#![doc( + html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", + html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" +)] + +use tauri::{ + plugin::{Builder, PluginApi, TauriPlugin}, + AppHandle, Manager, Runtime, State, +}; + +mod config; +mod error; +mod parser; + +use config::{Arg, Config}; + +pub use error::{Error, Result}; +pub use parser::{ArgData, Matches, SubcommandMatches}; + +pub struct Cli(PluginApi); + +impl Cli { + pub fn matches(&self) -> Result { + parser::get_matches(self.0.config(), self.0.app().package_info(), None) + } + + pub fn matches_from(&self, args: Vec) -> Result { + parser::get_matches(self.0.config(), self.0.app().package_info(), Some(args)) + } +} + +pub trait CliExt { + fn cli(&self) -> &Cli; +} + +impl> CliExt for T { + fn cli(&self) -> &Cli { + self.state::>().inner() + } +} + +#[tauri::command] +fn cli_matches(_app: AppHandle, cli: State<'_, Cli>) -> Result { + cli.matches() +} + +pub fn init() -> TauriPlugin { + Builder::new("cli") + .invoke_handler(tauri::generate_handler![cli_matches]) + .setup(|app, api| { + app.manage(Cli(api)); + Ok(()) + }) + .build() +} diff --git a/packages/kbot/gui/app/plugins/cli/src/parser.rs b/packages/kbot/gui/app/plugins/cli/src/parser.rs new file mode 100644 index 00000000..466885f1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/src/parser.rs @@ -0,0 +1,295 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use clap::{ + builder::{PossibleValue, PossibleValuesParser}, + error::ErrorKind, + Arg as ClapArg, ArgAction, ArgMatches, Command, +}; +use serde::Serialize; +use serde_json::Value; +use tauri::PackageInfo; + +use crate::{Arg, Config}; + +use std::collections::HashMap; + +#[macro_use] +mod macros; + +/// The resolution of a argument match. +#[derive(Default, Debug, Serialize, Clone)] +#[non_exhaustive] +pub struct ArgData { + /// - [`Value::Bool`] if it's a flag, + /// - [`Value::Array`] if it's multiple, + /// - [`Value::String`] if it has value, + /// - [`Value::Null`] otherwise. + pub value: Value, + /// The number of occurrences of the argument. + /// e.g. `./app --arg 1 --arg 2 --arg 2 3 4` results in three occurrences. + pub occurrences: u8, +} + +/// The matched subcommand. +#[derive(Default, Debug, Serialize, Clone)] +#[non_exhaustive] +pub struct SubcommandMatches { + /// The subcommand name. + pub name: String, + /// The subcommand argument matches. + pub matches: Matches, +} + +/// The argument matches of a command. +#[derive(Default, Debug, Serialize, Clone)] +#[non_exhaustive] +pub struct Matches { + /// Data structure mapping each found arg with its resolution. + pub args: HashMap, + /// The matched subcommand if found. + pub subcommand: Option>, +} + +impl Matches { + /// Set a arg match. + pub(crate) fn set_arg(&mut self, name: String, value: ArgData) { + self.args.insert(name, value); + } + + /// Sets the subcommand matches. + pub(crate) fn set_subcommand(&mut self, name: String, matches: Matches) { + self.subcommand = Some(Box::new(SubcommandMatches { name, matches })); + } +} + +/// Gets the argument matches of the CLI definition. +/// +/// This is a low level API. If the application has been built, +/// prefer [`App::get_cli_matches`](`crate::App#method.get_cli_matches`). +/// +/// # Examples +/// +/// ```rust,no_run +/// use tauri_plugin_cli::CliExt; +/// tauri::Builder::default() +/// .setup(|app| { +/// let matches = app.cli().matches()?; +/// Ok(()) +/// }); +/// ``` +pub fn get_matches( + cli: &Config, + package_info: &PackageInfo, + args: Option>, +) -> crate::Result { + let about = cli + .description() + .unwrap_or(&package_info.description.to_string()) + .to_string(); + let version = package_info.version.to_string(); + let app = get_app( + package_info, + version, + package_info.name.clone(), + Some(&about), + cli, + ); + + let matches = if let Some(args) = args { + app.try_get_matches_from(args) + } else { + app.try_get_matches() + }; + + match matches { + Ok(matches) => Ok(get_matches_internal(cli, &matches)), + Err(e) => match e.kind() { + ErrorKind::DisplayHelp => { + let mut matches = Matches::default(); + let help_text = e.to_string(); + matches.args.insert( + "help".to_string(), + ArgData { + value: Value::String(help_text), + occurrences: 0, + }, + ); + Ok(matches) + } + ErrorKind::DisplayVersion => { + let mut matches = Matches::default(); + matches + .args + .insert("version".to_string(), Default::default()); + Ok(matches) + } + _ => Err(e.into()), + }, + } +} + +fn get_matches_internal(config: &Config, matches: &ArgMatches) -> Matches { + let mut cli_matches = Matches::default(); + map_matches(config, matches, &mut cli_matches); + + if let Some((subcommand_name, subcommand_matches)) = matches.subcommand() { + if let Some(subcommand_config) = config + .subcommands + .as_ref() + .and_then(|s| s.get(subcommand_name)) + { + cli_matches.set_subcommand( + subcommand_name.to_string(), + get_matches_internal(subcommand_config, subcommand_matches), + ); + } + } + + cli_matches +} + +fn map_matches(config: &Config, matches: &ArgMatches, cli_matches: &mut Matches) { + if let Some(args) = config.args() { + for arg in args { + let (occurrences, value) = if arg.takes_value { + if arg.multiple { + matches + .get_many::(&arg.name) + .map(|v| { + let mut values = Vec::new(); + for value in v { + values.push(Value::String(value.into())); + } + (values.len() as u8, Value::Array(values)) + }) + .unwrap_or((0, Value::Null)) + } else { + matches + .get_one::(&arg.name) + .map(|v| (1, Value::String(v.clone()))) + .unwrap_or((0, Value::Null)) + } + } else { + let occurrences = matches.get_count(&arg.name); + (occurrences, Value::Bool(occurrences > 0)) + }; + + cli_matches.set_arg(arg.name.clone(), ArgData { value, occurrences }); + } + } +} + +fn get_app( + package_info: &PackageInfo, + version: String, + command_name: String, + about: Option<&String>, + config: &Config, +) -> Command { + let mut app = Command::new(command_name) + .author(package_info.authors) + .version(version.clone()); + + if let Some(about) = about { + app = app.about(about); + } + if let Some(long_description) = config.long_description() { + app = app.long_about(long_description); + } + if let Some(before_help) = config.before_help() { + app = app.before_help(before_help); + } + if let Some(after_help) = config.after_help() { + app = app.after_help(after_help); + } + + if let Some(args) = config.args() { + for arg in args { + app = app.arg(get_arg(arg.name.clone(), arg)); + } + } + + if let Some(subcommands) = config.subcommands() { + for (subcommand_name, subcommand) in subcommands { + let clap_subcommand = get_app( + package_info, + version.clone(), + subcommand_name.to_string(), + subcommand.description(), + subcommand, + ); + app = app.subcommand(clap_subcommand); + } + } + + app +} + +fn get_arg(arg_name: String, arg: &Arg) -> ClapArg { + let mut clap_arg = ClapArg::new(arg_name.clone()); + + if arg.index.is_none() { + clap_arg = clap_arg.long(arg_name); + if let Some(short) = arg.short { + clap_arg = clap_arg.short(short); + } + } + + clap_arg = bind_string_arg!(arg, clap_arg, description, help); + clap_arg = bind_string_arg!(arg, clap_arg, long_description, long_help); + + let action = if arg.multiple { + ArgAction::Append + } else if arg.takes_value { + ArgAction::Set + } else { + ArgAction::Count + }; + + clap_arg = clap_arg.action(action); + + clap_arg = bind_value_arg!(arg, clap_arg, number_of_values); + + if let Some(values) = &arg.possible_values { + clap_arg = clap_arg.value_parser(PossibleValuesParser::new( + values + .iter() + .map(PossibleValue::new) + .collect::>(), + )); + } + + clap_arg = match (arg.min_values, arg.max_values) { + (Some(min), Some(max)) => clap_arg.num_args(min..=max), + (Some(min), None) => clap_arg.num_args(min..), + (None, Some(max)) => clap_arg.num_args(0..max), + (None, None) => clap_arg, + }; + clap_arg = clap_arg.required(arg.required); + clap_arg = bind_string_arg!( + arg, + clap_arg, + required_unless_present, + required_unless_present + ); + clap_arg = bind_string_slice_arg!(arg, clap_arg, required_unless_present_all); + clap_arg = bind_string_slice_arg!(arg, clap_arg, required_unless_present_any); + clap_arg = bind_string_arg!(arg, clap_arg, conflicts_with, conflicts_with); + if let Some(value) = &arg.conflicts_with_all { + clap_arg = clap_arg.conflicts_with_all(value); + } + clap_arg = bind_string_arg!(arg, clap_arg, requires, requires); + if let Some(value) = &arg.requires_all { + clap_arg = clap_arg.requires_all(value); + } + clap_arg = bind_if_arg!(arg, clap_arg, requires_if); + clap_arg = bind_if_arg!(arg, clap_arg, required_if_eq); + clap_arg = bind_value_arg!(arg, clap_arg, require_equals); + clap_arg = bind_value_arg!(arg, clap_arg, index); + + clap_arg = clap_arg.global(arg.global); + + clap_arg +} diff --git a/packages/kbot/gui/app/plugins/cli/src/parser/macros.rs b/packages/kbot/gui/app/plugins/cli/src/parser/macros.rs new file mode 100644 index 00000000..2c3c66ec --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/src/parser/macros.rs @@ -0,0 +1,47 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +macro_rules! bind_string_arg { + ($arg:expr, $clap_arg:expr, $arg_name:ident, $clap_field:ident) => {{ + let arg = $arg; + let mut clap_arg = $clap_arg; + if let Some(value) = &arg.$arg_name { + clap_arg = clap_arg.$clap_field(value); + } + clap_arg + }}; +} + +macro_rules! bind_value_arg { + ($arg:expr, $clap_arg:expr, $field:ident) => {{ + let arg = $arg; + let mut clap_arg = $clap_arg; + if let Some(value) = arg.$field { + clap_arg = clap_arg.$field(value); + } + clap_arg + }}; +} + +macro_rules! bind_string_slice_arg { + ($arg:expr, $clap_arg:expr, $field:ident) => {{ + let arg = $arg; + let mut clap_arg = $clap_arg; + if let Some(value) = &arg.$field { + clap_arg = clap_arg.$field(value); + } + clap_arg + }}; +} + +macro_rules! bind_if_arg { + ($arg:expr, $clap_arg:expr, $field:ident) => {{ + let arg = $arg; + let mut clap_arg = $clap_arg; + if let Some((value, arg)) = &arg.$field { + clap_arg = clap_arg.$field(value, arg); + } + clap_arg + }}; +} diff --git a/packages/kbot/gui/app/plugins/cli/tsconfig.json b/packages/kbot/gui/app/plugins/cli/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/cli/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/CHANGELOG.md b/packages/kbot/gui/app/plugins/clipboard-manager/CHANGELOG.md new file mode 100644 index 00000000..4f0460d2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/CHANGELOG.md @@ -0,0 +1,140 @@ +# Changelog + +## \[2.3.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.2.3] + +- [`f634e524`](https://github.com/tauri-apps/plugins-workspace/commit/f634e5248ebe428f8305a59f74c13fc15147fb8e) This is an "empty" release to update the plugins' source files on crates.io and docs.rs. This should fix docs.rs build failures for projects using tauri plugins as dependencies. + +## \[2.2.2] + +### bug + +- [`d37bbdef`](https://github.com/tauri-apps/plugins-workspace/commit/d37bbdef8dc70e61e59f9fe0bb8b2a48999d0aa1) ([#2507](https://github.com/tauri-apps/plugins-workspace/pull/2507) by [@SquitchYT](https://github.com/tauri-apps/plugins-workspace/../../SquitchYT)) Fix clipboard-manager Wayland support. + +## \[2.2.1] + +- [`ce11079f`](https://github.com/tauri-apps/plugins-workspace/commit/ce11079f19852fbefdecf0e4c7d947af3624fee0) ([#2280](https://github.com/tauri-apps/plugins-workspace/pull/2280) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Explicitly drop `arboard::Clipboard` on exit. Add recommendation to not use read methods on the mainthread. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.1] + +- [`3fa0fc09`](https://github.com/tauri-apps/plugins-workspace/commit/3fa0fc09bbee0d619801e5757af9fb3c09883c97) ([#2099](https://github.com/tauri-apps/plugins-workspace/pull/2099) by [@rasteiner](https://github.com/tauri-apps/plugins-workspace/../../rasteiner)) Fix clipboard manager client side api not copying fallback alternative text when calling `writeHtml`. + +## \[2.0.2] + +- [`d57df4de`](https://github.com/tauri-apps/plugins-workspace/commit/d57df4debe7c75cfbd6d6558fff1beb07dbee54c) ([#1986](https://github.com/tauri-apps/plugins-workspace/pull/1986) by [@RikaKagurasaka](https://github.com/tauri-apps/plugins-workspace/../../RikaKagurasaka)) Fix that `read_image` wrongly set the image rgba data with binary PNG data. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.2] + +- [`341a5320`](https://github.com/tauri-apps/plugins-workspace/commit/341a5320c33d3c7b041abf7eb0ab7ad8009e6c3f) ([#1771](https://github.com/tauri-apps/plugins-workspace/pull/1771)) Fix warnings and clear implementation on Android below SDK 28. + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.2] + +- [`b9147758`](https://github.com/tauri-apps/plugins-workspace/commit/b914775898c2bee7ceb20bd17ee595005cd17a64) ([#1679](https://github.com/tauri-apps/plugins-workspace/pull/1679) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Explicitly set a minimum macOS version for the Swift package. + +## \[2.0.0-rc.1] + +- [`2c00c029`](https://github.com/tauri-apps/plugins-workspace/commit/2c00c0292c9127b81567de46691e8c0f73557261) ([#1630](https://github.com/tauri-apps/plugins-workspace/pull/1630) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused multi-word IIFE names to not be formatted correctly. For example the `barcode-scanner` was defined as `window.__TAURI_PLUGIN_CLIPBOARDMANAGER__` instead of `window.__TAURI_PLUGIN_CLIPBOARD_MANAGER__`. + +### changes + +- [`6b079cfd`](https://github.com/tauri-apps/plugins-workspace/commit/6b079cfdd107c94abc2c7300f6af00bac3ff4040) ([#1649](https://github.com/tauri-apps/plugins-workspace/pull/1649) by [@ahqsoftwares](https://github.com/tauri-apps/plugins-workspace/../../ahqsoftwares)) Remove targetSdk from build.kts files as it is deprecated and will be removed from DSL v9.0 + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +## \[2.1.0-beta.6] + +- [`99d6ac0f`](https://github.com/tauri-apps/plugins-workspace/commit/99d6ac0f9506a6a4a1aa59c728157190a7441af6) ([#1606](https://github.com/tauri-apps/plugins-workspace/pull/1606) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The JS packages now specify the *minimum* `@tauri-apps/api` version instead of a single exact version. +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.1.0-beta.5] + +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.1.0-beta.4] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.1.0-beta.3] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.1.0-beta.2] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.1.0-beta.1] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.1.0-beta.1] + +- [`27b258c`](https://github.com/tauri-apps/plugins-workspace/commit/27b258cf31ae5557c99ae66537fb9196368d4d8b)([#1185](https://github.com/tauri-apps/plugins-workspace/pull/1185)) Expose `Clipboard` struct +- [`e3d41f4`](https://github.com/tauri-apps/plugins-workspace/commit/e3d41f4011bd3ea3ce281bb38bbe31d3709f8e0f)([#1191](https://github.com/tauri-apps/plugins-workspace/pull/1191)) Internally use the webview scoped resources table instead of the app one, so other webviews can't access other webviews resources. +- [`e3d41f4`](https://github.com/tauri-apps/plugins-workspace/commit/e3d41f4011bd3ea3ce281bb38bbe31d3709f8e0f)([#1191](https://github.com/tauri-apps/plugins-workspace/pull/1191)) Update for tauri 2.0.0-beta.15. + +## \[2.1.0-beta.0] + +- [`9dec960`](https://github.com/tauri-apps/plugins-workspace/commit/9dec9605ed1ce19dbef697e55debddf9008ecba1)([#845](https://github.com/tauri-apps/plugins-workspace/pull/845)) Add support for `read_image` and `write_image` to the clipboard plugin (desktop). + +## \[2.0.0-beta.2] + +- [`dc6d332`](https://github.com/tauri-apps/plugins-workspace/commit/dc6d3321e5305fa8b7250553bd179cbee995998a)([#977](https://github.com/tauri-apps/plugins-workspace/pull/977)) Add support for writing HTML content to the clipboard. +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Add permissions. + +## \[2.0.0-alpha.5] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.13. + +## \[2.0.0-alpha.4] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.12. + +## \[2.0.0-alpha.3] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to @tauri-apps/api v2.0.0-alpha.11. + +## \[2.0.0-alpha.2] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update to @tauri-apps/api v2.0.0-alpha.9. + +## \[2.0.0-alpha.2] + +- [`4e2cef9`](https://github.com/tauri-apps/plugins-workspace/commit/4e2cef9b702bbbb9cf4ee17de50791cb21f1b2a4)([#593](https://github.com/tauri-apps/plugins-workspace/pull/593)) Update to alpha.12. + +## \[2.0.0-alpha.1] + +- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11. + +## \[2.0.0-alpha.0] + +- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/Cargo.toml b/packages/kbot/gui/app/plugins/clipboard-manager/Cargo.toml new file mode 100644 index 00000000..624336ec --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "tauri-plugin-clipboard-manager" +version = "2.3.0" +description = "Read and write to the system clipboard." +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-clipboard-manager" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] +targets = ["x86_64-unknown-linux-gnu", "x86_64-linux-android"] + +[package.metadata.platforms.support] +windows = { level = "full", notes = "" } +linux = { level = "full", notes = "" } +macos = { level = "full", notes = "" } +android = { level = "partial", notes = "Only plain-text content support" } +ios = { level = "partial", notes = "Only plain-text content support" } + + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } + +[target.'cfg(target_os = "ios")'.dependencies] +tauri = { workspace = true, features = ["wry"] } + +[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] +arboard = { version = "3", features = ["wayland-data-control"] } diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/LICENSE.spdx b/packages/kbot/gui/app/plugins/clipboard-manager/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/clipboard-manager/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/LICENSE_MIT b/packages/kbot/gui/app/plugins/clipboard-manager/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/README.md b/packages/kbot/gui/app/plugins/clipboard-manager/README.md new file mode 100644 index 00000000..b2353f97 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/README.md @@ -0,0 +1,96 @@ +![plugin-clipboard-manager](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/clipboard-manager/banner.png) + +Read and write to the system clipboard. + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | ✓ | +| iOS | ✓ | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-clipboard-manager = "2.0.0" +# alternatively with Git: +tauri-plugin-clipboard-manager = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +```sh +pnpm add @tauri-apps/plugin-clipboard-manager +# or +npm add @tauri-apps/plugin-clipboard-manager +# or +yarn add @tauri-apps/plugin-clipboard-manager +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_clipboard_manager::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { + writeText, + readText, + writeHtml, + clear +} from '@tauri-apps/plugin-clipboard-manager' +await writeText('Tauri is awesome!') +assert(await readText(), 'Tauri is awesome!') +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/SECURITY.md b/packages/kbot/gui/app/plugins/clipboard-manager/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/android/.gitignore b/packages/kbot/gui/app/plugins/clipboard-manager/android/.gitignore new file mode 100644 index 00000000..c0f21ec2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/android/.gitignore @@ -0,0 +1,2 @@ +/build +/.tauri diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/android/build.gradle.kts b/packages/kbot/gui/app/plugins/clipboard-manager/android/build.gradle.kts new file mode 100644 index 00000000..b12a7482 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/android/build.gradle.kts @@ -0,0 +1,45 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "app.tauri.clipboard" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.0") + implementation("com.google.android.material:material:1.7.0") + implementation("com.fasterxml.jackson.core:jackson-databind:2.15.3") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + implementation(project(":tauri-android")) +} diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/android/proguard-rules.pro b/packages/kbot/gui/app/plugins/clipboard-manager/android/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/android/settings.gradle b/packages/kbot/gui/app/plugins/clipboard-manager/android/settings.gradle new file mode 100644 index 00000000..14a752e4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/android/settings.gradle @@ -0,0 +1,2 @@ +include ':tauri-android' +project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/android/src/androidTest/java/ExampleInstrumentedTest.kt b/packages/kbot/gui/app/plugins/clipboard-manager/android/src/androidTest/java/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..02e6984b --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/android/src/androidTest/java/ExampleInstrumentedTest.kt @@ -0,0 +1,28 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.clipboard + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("app.tauri.clipboard", appContext.packageName) + } +} diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/android/src/main/AndroidManifest.xml b/packages/kbot/gui/app/plugins/clipboard-manager/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..9a40236b --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/android/src/main/java/ClipboardPlugin.kt b/packages/kbot/gui/app/plugins/clipboard-manager/android/src/main/java/ClipboardPlugin.kt new file mode 100644 index 00000000..ebb931b4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/android/src/main/java/ClipboardPlugin.kt @@ -0,0 +1,142 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.clipboard + +import android.app.Activity +import android.content.ClipData +import android.content.ClipDescription +import android.content.ClipboardManager +import android.content.Context +import android.os.Build +import app.tauri.annotation.Command +import app.tauri.annotation.InvokeArg +import app.tauri.annotation.TauriPlugin +import app.tauri.plugin.Invoke +import app.tauri.plugin.Plugin +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import com.fasterxml.jackson.databind.annotation.JsonSerialize +import com.fasterxml.jackson.databind.ser.std.StdSerializer +import java.io.IOException + +@InvokeArg +@JsonDeserialize(using = WriteOptionsDeserializer::class) +sealed class WriteOptions { + @JsonDeserialize + class PlainText: WriteOptions() { + lateinit var text: String + var label: String? = null + } +} + +@JsonSerialize(using = ReadClipDataSerializer::class) +sealed class ReadClipData { + class PlainText: ReadClipData() { + lateinit var text: String + } +} + +internal class ReadClipDataSerializer @JvmOverloads constructor(t: Class? = null) : + StdSerializer(t) { + @Throws(IOException::class, JsonProcessingException::class) + override fun serialize( + value: ReadClipData, jgen: JsonGenerator, provider: SerializerProvider + ) { + jgen.writeStartObject() + when (value) { + is ReadClipData.PlainText -> { + jgen.writeObjectFieldStart("plainText") + + jgen.writeStringField("text", value.text) + + jgen.writeEndObject() + } + else -> { + throw Exception("unimplemented ReadClipData") + } + } + + jgen.writeEndObject() + } +} + +internal class WriteOptionsDeserializer: JsonDeserializer() { + override fun deserialize( + jsonParser: JsonParser, + deserializationContext: DeserializationContext + ): WriteOptions { + val node: JsonNode = jsonParser.codec.readTree(jsonParser) + node.get("plainText")?.let { + return jsonParser.codec.treeToValue(it, WriteOptions.PlainText::class.java) + } ?: run { + throw Error("unknown write options $node") + } + } +} + +@TauriPlugin +class ClipboardPlugin(private val activity: Activity) : Plugin(activity) { + private val manager: ClipboardManager = + activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + + @Command + @Suppress("MoveVariableDeclarationIntoWhen") + fun writeText(invoke: Invoke) { + val args = invoke.parseArgs(WriteOptions::class.java) + + val clipData = when (args) { + is WriteOptions.PlainText -> { + ClipData.newPlainText(args.label, args.text) + } else -> { + invoke.reject("unimplemented WriteOptions") + return + } + + } + + manager.setPrimaryClip(clipData) + + invoke.resolve() + } + + @Command + fun readText(invoke: Invoke) { + val data = if (manager.hasPrimaryClip()) { + if (manager.primaryClipDescription?.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN) == true) { + val item: ClipData.Item = manager.primaryClip!!.getItemAt(0) + val data = ReadClipData.PlainText() + data.text = item.text.toString() + data + } else { + // TODO + invoke.reject("Clipboard content reader not implemented") + return + } + } else { + invoke.reject("Clipboard is empty") + return + } + + invoke.resolveObject(data) + } + + @Command + fun clear(invoke: Invoke) { + if (manager.hasPrimaryClip()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + manager.clearPrimaryClip() + } else { + manager.setPrimaryClip(ClipData.newPlainText("", "")) + } + } + invoke.resolve() + } +} diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/android/src/test/java/ExampleUnitTest.kt b/packages/kbot/gui/app/plugins/clipboard-manager/android/src/test/java/ExampleUnitTest.kt new file mode 100644 index 00000000..282700f2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/android/src/test/java/ExampleUnitTest.kt @@ -0,0 +1,21 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.clipboard + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/api-iife.js b/packages/kbot/gui/app/plugins/clipboard-manager/api-iife.js new file mode 100644 index 00000000..1de1f448 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_CLIPBOARD_MANAGER__=function(e){"use strict";var n;async function t(e,n={},t){return window.__TAURI_INTERNALS__.invoke(e,n,t)}"function"==typeof SuppressedError&&SuppressedError;class r{get rid(){return function(e,n,t,r){if("function"==typeof n?e!==n||!r:!n.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===t?r:"a"===t?r.call(e):r?r.value:n.get(e)}(this,n,"f")}constructor(e){n.set(this,void 0),function(e,n,t){if("function"==typeof n||!n.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");n.set(e,t)}(this,n,e)}async close(){return t("plugin:resources|close",{rid:this.rid})}}n=new WeakMap;class a extends r{constructor(e){super(e)}static async new(e,n,r){return t("plugin:image|new",{rgba:i(e),width:n,height:r}).then((e=>new a(e)))}static async fromBytes(e){return t("plugin:image|from_bytes",{bytes:i(e)}).then((e=>new a(e)))}static async fromPath(e){return t("plugin:image|from_path",{path:e}).then((e=>new a(e)))}async rgba(){return t("plugin:image|rgba",{rid:this.rid}).then((e=>new Uint8Array(e)))}async size(){return t("plugin:image|size",{rid:this.rid})}}function i(e){return null==e?null:"string"==typeof e?e:e instanceof a?e.rid:e}return e.clear=async function(){await t("plugin:clipboard-manager|clear")},e.readImage=async function(){return await t("plugin:clipboard-manager|read_image").then((e=>new a(e)))},e.readText=async function(){return await t("plugin:clipboard-manager|read_text")},e.writeHtml=async function(e,n){await t("plugin:clipboard-manager|write_html",{html:e,altText:n})},e.writeImage=async function(e){await t("plugin:clipboard-manager|write_image",{image:i(e)})},e.writeText=async function(e,n){await t("plugin:clipboard-manager|write_text",{label:n?.label,text:e})},e}({});Object.defineProperty(window.__TAURI__,"clipboardManager",{value:__TAURI_PLUGIN_CLIPBOARD_MANAGER__})} diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/banner.png b/packages/kbot/gui/app/plugins/clipboard-manager/banner.png new file mode 100644 index 00000000..5d304708 Binary files /dev/null and b/packages/kbot/gui/app/plugins/clipboard-manager/banner.png differ diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/build.rs b/packages/kbot/gui/app/plugins/clipboard-manager/build.rs new file mode 100644 index 00000000..9bbeddfc --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/build.rs @@ -0,0 +1,25 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +const COMMANDS: &[&str] = &[ + "write_text", + "read_text", + "write_image", + "read_image", + "write_html", + "clear", +]; + +fn main() { + let result = tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .android_path("android") + .ios_path("ios") + .try_build(); + + // when building documentation for Android the plugin build result is always Err() and is irrelevant to the crate documentation build + if !(cfg!(docsrs) && std::env::var("TARGET").unwrap().contains("android")) { + result.unwrap(); + } +} diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/guest-js/index.ts b/packages/kbot/gui/app/plugins/clipboard-manager/guest-js/index.ts new file mode 100644 index 00000000..a37bbfab --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/guest-js/index.ts @@ -0,0 +1,151 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/** + * Read and write to the system clipboard. + * + * @module + */ + +import { invoke } from '@tauri-apps/api/core' +import { Image, transformImage } from '@tauri-apps/api/image' + +/** + * Writes plain text to the clipboard. + * @example + * ```typescript + * import { writeText, readText } from '@tauri-apps/plugin-clipboard-manager'; + * await writeText('Tauri is awesome!'); + * assert(await readText(), 'Tauri is awesome!'); + * ``` + * + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.0.0 + */ +async function writeText( + text: string, + opts?: { label?: string } +): Promise { + await invoke('plugin:clipboard-manager|write_text', { + label: opts?.label, + text + }) +} + +/** + * Gets the clipboard content as plain text. + * @example + * ```typescript + * import { readText } from '@tauri-apps/plugin-clipboard-manager'; + * const clipboardText = await readText(); + * ``` + * @since 2.0.0 + */ +async function readText(): Promise { + return await invoke('plugin:clipboard-manager|read_text') +} + +/** + * Writes image buffer to the clipboard. + * + * #### Platform-specific + * + * - **Android / iOS:** Not supported. + * + * @example + * ```typescript + * import { writeImage } from '@tauri-apps/plugin-clipboard-manager'; + * const buffer = [ + * // A red pixel + * 255, 0, 0, 255, + * + * // A green pixel + * 0, 255, 0, 255, + * ]; + * await writeImage(buffer); + * ``` + * + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.0.0 + */ +async function writeImage( + image: string | Image | Uint8Array | ArrayBuffer | number[] +): Promise { + await invoke('plugin:clipboard-manager|write_image', { + image: transformImage(image) + }) +} + +/** + * Gets the clipboard content as Uint8Array image. + * + * #### Platform-specific + * + * - **Android / iOS:** Not supported. + * + * @example + * ```typescript + * import { readImage } from '@tauri-apps/plugin-clipboard-manager'; + * + * const clipboardImage = await readImage(); + * const blob = new Blob([await clipboardImage.rgba()], { type: 'image' }) + * const url = URL.createObjectURL(blob) + * ``` + * @since 2.0.0 + */ +async function readImage(): Promise { + return await invoke('plugin:clipboard-manager|read_image').then( + (rid) => new Image(rid) + ) +} + +/** + * * Writes HTML or fallbacks to write provided plain text to the clipboard. + * + * #### Platform-specific + * + * - **Android / iOS:** Not supported. + * + * @example + * ```typescript + * import { writeHtml } from '@tauri-apps/plugin-clipboard-manager'; + * await writeHtml('

Tauri is awesome!

', 'plaintext'); + * // The following will write "

Tauri is awesome

" as plain text + * await writeHtml('

Tauri is awesome!

', '

Tauri is awesome

'); + * // we can read html data only as a string so there's just readText(), no readHtml() + * assert(await readText(), '

Tauri is awesome!

'); + * ``` + * + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.0.0 + */ +async function writeHtml(html: string, altText?: string): Promise { + await invoke('plugin:clipboard-manager|write_html', { + html, + altText + }) +} + +/** + * Clears the clipboard. + * + * #### Platform-specific + * + * - **Android:** Only supported on SDK 28+. For older releases we write an empty string to the clipboard instead. + * + * @example + * ```typescript + * import { clear } from '@tauri-apps/plugin-clipboard-manager'; + * await clear(); + * ``` + * @since 2.0.0 + */ +async function clear(): Promise { + await invoke('plugin:clipboard-manager|clear') +} + +export { writeText, readText, writeHtml, clear, readImage, writeImage } diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/ios/.gitignore b/packages/kbot/gui/app/plugins/clipboard-manager/ios/.gitignore new file mode 100644 index 00000000..5922fdaa --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/ios/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Package.resolved diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/ios/Package.swift b/packages/kbot/gui/app/plugins/clipboard-manager/ios/Package.swift new file mode 100644 index 00000000..6da5303e --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/ios/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version:5.3 +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import PackageDescription + +let package = Package( + name: "tauri-plugin-clipboard-manager", + platforms: [ + .macOS(.v10_13), + .iOS(.v13), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "tauri-plugin-clipboard-manager", + type: .static, + targets: ["tauri-plugin-clipboard-manager"]) + ], + dependencies: [ + .package(name: "Tauri", path: "../.tauri/tauri-api") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "tauri-plugin-clipboard-manager", + dependencies: [ + .byName(name: "Tauri") + ], + path: "Sources") + ] +) diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/ios/README.md b/packages/kbot/gui/app/plugins/clipboard-manager/ios/README.md new file mode 100644 index 00000000..f4900bdd --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/ios/README.md @@ -0,0 +1,3 @@ +# Tauri Plugin {{ plugin_name_original }} + +A description of this package. diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/ios/Sources/ClipboardPlugin.swift b/packages/kbot/gui/app/plugins/clipboard-manager/ios/Sources/ClipboardPlugin.swift new file mode 100644 index 00000000..cb4fc9b2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/ios/Sources/ClipboardPlugin.swift @@ -0,0 +1,52 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import SwiftRs +import Tauri +import UIKit +import WebKit + +enum WriteOptions: Codable { + case plainText(text: String) +} + +enum ReadClipData: Codable { + case plainText(text: String) +} + +class ClipboardPlugin: Plugin { + @objc public func writeText(_ invoke: Invoke) throws { + let options = try invoke.parseArgs(WriteOptions.self) + let clipboard = UIPasteboard.general + switch options { + case .plainText(let text): + clipboard.string = text + default: + invoke.unimplemented() + return + } + invoke.resolve() + + } + + @objc public func readText(_ invoke: Invoke) throws { + let clipboard = UIPasteboard.general + if let text = clipboard.string { + invoke.resolve(ReadClipData.plainText(text: text)) + } else { + invoke.reject("Clipboard is empty") + } + } + + @objc public func clear(_ invoke: Invoke) throws { + let clipboard = UIPasteboard.general + clipboard.items = [] + invoke.resolve() + } +} + +@_cdecl("init_plugin_clipboard") +func initPlugin() -> Plugin { + return ClipboardPlugin() +} diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/ios/Tests/PluginTests/PluginTests.swift b/packages/kbot/gui/app/plugins/clipboard-manager/ios/Tests/PluginTests/PluginTests.swift new file mode 100644 index 00000000..e5d54b38 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/ios/Tests/PluginTests/PluginTests.swift @@ -0,0 +1,12 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import XCTest +@testable import ClipboardPlugin + +final class ClipboardPluginTests: XCTestCase { + func testClipboard() throws { + let plugin = ClipboardPlugin() + } +} diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/package.json b/packages/kbot/gui/app/plugins/clipboard-manager/package.json new file mode 100644 index 00000000..ac6ac49c --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/package.json @@ -0,0 +1,29 @@ +{ + "name": "@tauri-apps/plugin-clipboard-manager", + "version": "2.3.0", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/commands/clear.toml b/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/commands/clear.toml new file mode 100644 index 00000000..83de1819 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/commands/clear.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-clear" +description = "Enables the clear command without any pre-configured scope." +commands.allow = ["clear"] + +[[permission]] +identifier = "deny-clear" +description = "Denies the clear command without any pre-configured scope." +commands.deny = ["clear"] diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/commands/read_image.toml b/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/commands/read_image.toml new file mode 100644 index 00000000..cfed86db --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/commands/read_image.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-read-image" +description = "Enables the read_image command without any pre-configured scope." +commands.allow = ["read_image"] + +[[permission]] +identifier = "deny-read-image" +description = "Denies the read_image command without any pre-configured scope." +commands.deny = ["read_image"] diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/commands/read_text.toml b/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/commands/read_text.toml new file mode 100644 index 00000000..29844892 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/commands/read_text.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-read-text" +description = "Enables the read_text command without any pre-configured scope." +commands.allow = ["read_text"] + +[[permission]] +identifier = "deny-read-text" +description = "Denies the read_text command without any pre-configured scope." +commands.deny = ["read_text"] diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/commands/write_html.toml b/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/commands/write_html.toml new file mode 100644 index 00000000..5e292808 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/commands/write_html.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-write-html" +description = "Enables the write_html command without any pre-configured scope." +commands.allow = ["write_html"] + +[[permission]] +identifier = "deny-write-html" +description = "Denies the write_html command without any pre-configured scope." +commands.deny = ["write_html"] diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/commands/write_image.toml b/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/commands/write_image.toml new file mode 100644 index 00000000..12e8e235 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/commands/write_image.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-write-image" +description = "Enables the write_image command without any pre-configured scope." +commands.allow = ["write_image"] + +[[permission]] +identifier = "deny-write-image" +description = "Denies the write_image command without any pre-configured scope." +commands.deny = ["write_image"] diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/commands/write_text.toml b/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/commands/write_text.toml new file mode 100644 index 00000000..ebff875a --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/commands/write_text.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-write-text" +description = "Enables the write_text command without any pre-configured scope." +commands.allow = ["write_text"] + +[[permission]] +identifier = "deny-write-text" +description = "Denies the write_text command without any pre-configured scope." +commands.deny = ["write_text"] diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/reference.md new file mode 100644 index 00000000..98a7fa96 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/permissions/autogenerated/reference.md @@ -0,0 +1,173 @@ +## Default Permission + +No features are enabled by default, as we believe +the clipboard can be inherently dangerous and it is +application specific if read and/or write access is needed. + +Clipboard interaction needs to be explicitly enabled. + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`clipboard-manager:allow-clear` + + + +Enables the clear command without any pre-configured scope. + +
+ +`clipboard-manager:deny-clear` + + + +Denies the clear command without any pre-configured scope. + +
+ +`clipboard-manager:allow-read-image` + + + +Enables the read_image command without any pre-configured scope. + +
+ +`clipboard-manager:deny-read-image` + + + +Denies the read_image command without any pre-configured scope. + +
+ +`clipboard-manager:allow-read-text` + + + +Enables the read_text command without any pre-configured scope. + +
+ +`clipboard-manager:deny-read-text` + + + +Denies the read_text command without any pre-configured scope. + +
+ +`clipboard-manager:allow-write-html` + + + +Enables the write_html command without any pre-configured scope. + +
+ +`clipboard-manager:deny-write-html` + + + +Denies the write_html command without any pre-configured scope. + +
+ +`clipboard-manager:allow-write-image` + + + +Enables the write_image command without any pre-configured scope. + +
+ +`clipboard-manager:deny-write-image` + + + +Denies the write_image command without any pre-configured scope. + +
+ +`clipboard-manager:allow-write-text` + + + +Enables the write_text command without any pre-configured scope. + +
+ +`clipboard-manager:deny-write-text` + + + +Denies the write_text command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/permissions/default.toml b/packages/kbot/gui/app/plugins/clipboard-manager/permissions/default.toml new file mode 100644 index 00000000..d6f65195 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/permissions/default.toml @@ -0,0 +1,11 @@ +"$schema" = "schemas/schema.json" +[default] +description = """ +No features are enabled by default, as we believe +the clipboard can be inherently dangerous and it is +application specific if read and/or write access is needed. + +Clipboard interaction needs to be explicitly enabled. +""" + +permissions = [] diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/clipboard-manager/permissions/schemas/schema.json new file mode 100644 index 00000000..891c6f0d --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/permissions/schemas/schema.json @@ -0,0 +1,378 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the clear command without any pre-configured scope.", + "type": "string", + "const": "allow-clear", + "markdownDescription": "Enables the clear command without any pre-configured scope." + }, + { + "description": "Denies the clear command without any pre-configured scope.", + "type": "string", + "const": "deny-clear", + "markdownDescription": "Denies the clear command without any pre-configured scope." + }, + { + "description": "Enables the read_image command without any pre-configured scope.", + "type": "string", + "const": "allow-read-image", + "markdownDescription": "Enables the read_image command without any pre-configured scope." + }, + { + "description": "Denies the read_image command without any pre-configured scope.", + "type": "string", + "const": "deny-read-image", + "markdownDescription": "Denies the read_image command without any pre-configured scope." + }, + { + "description": "Enables the read_text command without any pre-configured scope.", + "type": "string", + "const": "allow-read-text", + "markdownDescription": "Enables the read_text command without any pre-configured scope." + }, + { + "description": "Denies the read_text command without any pre-configured scope.", + "type": "string", + "const": "deny-read-text", + "markdownDescription": "Denies the read_text command without any pre-configured scope." + }, + { + "description": "Enables the write_html command without any pre-configured scope.", + "type": "string", + "const": "allow-write-html", + "markdownDescription": "Enables the write_html command without any pre-configured scope." + }, + { + "description": "Denies the write_html command without any pre-configured scope.", + "type": "string", + "const": "deny-write-html", + "markdownDescription": "Denies the write_html command without any pre-configured scope." + }, + { + "description": "Enables the write_image command without any pre-configured scope.", + "type": "string", + "const": "allow-write-image", + "markdownDescription": "Enables the write_image command without any pre-configured scope." + }, + { + "description": "Denies the write_image command without any pre-configured scope.", + "type": "string", + "const": "deny-write-image", + "markdownDescription": "Denies the write_image command without any pre-configured scope." + }, + { + "description": "Enables the write_text command without any pre-configured scope.", + "type": "string", + "const": "allow-write-text", + "markdownDescription": "Enables the write_text command without any pre-configured scope." + }, + { + "description": "Denies the write_text command without any pre-configured scope.", + "type": "string", + "const": "deny-write-text", + "markdownDescription": "Denies the write_text command without any pre-configured scope." + }, + { + "description": "No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n", + "type": "string", + "const": "default", + "markdownDescription": "No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/rollup.config.js b/packages/kbot/gui/app/plugins/clipboard-manager/rollup.config.js new file mode 100644 index 00000000..1f349ec8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/rollup.config.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' + +export default createConfig() diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/src/commands.rs b/packages/kbot/gui/app/plugins/clipboard-manager/src/commands.rs new file mode 100644 index 00000000..a8dd94ac --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/src/commands.rs @@ -0,0 +1,80 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tauri::{command, image::JsImage, AppHandle, Manager, ResourceId, Runtime, State, Webview}; + +use crate::{Clipboard, Result}; + +#[command] +#[cfg(desktop)] +pub(crate) async fn write_text( + _app: AppHandle, + clipboard: State<'_, Clipboard>, + text: &str, + #[allow(unused)] label: Option, +) -> Result<()> { + clipboard.write_text(text) +} + +#[command] +#[cfg(not(desktop))] +pub(crate) async fn write_text( + _app: AppHandle, + clipboard: State<'_, Clipboard>, + text: &str, + #[allow(unused)] label: Option<&str>, +) -> Result<()> { + match label { + Some(label) => clipboard.write_text_with_label(text, label), + None => clipboard.write_text(text), + } +} + +#[command] +pub(crate) async fn read_text( + _app: AppHandle, + clipboard: State<'_, Clipboard>, +) -> Result { + clipboard.read_text() +} + +#[command] +pub(crate) async fn write_image( + webview: Webview, + clipboard: State<'_, Clipboard>, + image: JsImage, +) -> Result<()> { + let resources_table = webview.resources_table(); + let image = image.into_img(&resources_table)?; + clipboard.write_image(&image) +} + +#[command] +pub(crate) async fn read_image( + webview: Webview, + clipboard: State<'_, Clipboard>, +) -> Result { + let image = clipboard.read_image()?.to_owned(); + let mut resources_table = webview.resources_table(); + let rid = resources_table.add(image); + Ok(rid) +} + +#[command] +pub(crate) async fn write_html( + _app: AppHandle, + clipboard: State<'_, Clipboard>, + html: &str, + alt_text: Option<&str>, +) -> Result<()> { + clipboard.write_html(html, alt_text) +} + +#[command] +pub(crate) async fn clear( + _app: AppHandle, + clipboard: State<'_, Clipboard>, +) -> Result<()> { + clipboard.clear() +} diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/src/desktop.rs b/packages/kbot/gui/app/plugins/clipboard-manager/src/desktop.rs new file mode 100644 index 00000000..f3570cc0 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/src/desktop.rs @@ -0,0 +1,123 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use arboard::ImageData; +use serde::de::DeserializeOwned; +use tauri::{image::Image, plugin::PluginApi, AppHandle, Runtime}; + +use std::{borrow::Cow, sync::Mutex}; + +pub fn init( + app: &AppHandle, + _api: PluginApi, +) -> crate::Result> { + Ok(Clipboard { + app: app.clone(), + clipboard: arboard::Clipboard::new().map(|c| Mutex::new(Some(c))), + }) +} + +/// Access to the clipboard APIs. +pub struct Clipboard { + #[allow(dead_code)] + app: AppHandle, + // According to arboard docs the clipboard must be dropped before exit. + // Since tauri doesn't call drop on exit we'll use an Option to take() on RunEvent::Exit. + clipboard: Result>, arboard::Error>, +} + +impl Clipboard { + pub fn write_text<'a, T: Into>>(&self, text: T) -> crate::Result<()> { + match &self.clipboard { + Ok(clipboard) => clipboard + .lock() + .unwrap() + .as_mut() + .unwrap() + .set_text(text) + .map_err(Into::into), + Err(e) => Err(crate::Error::Clipboard(e.to_string())), + } + } + + pub fn write_image(&self, image: &Image<'_>) -> crate::Result<()> { + match &self.clipboard { + Ok(clipboard) => clipboard + .lock() + .unwrap() + .as_mut() + .unwrap() + .set_image(ImageData { + bytes: Cow::Borrowed(image.rgba()), + width: image.width() as usize, + height: image.height() as usize, + }) + .map_err(Into::into), + Err(e) => Err(crate::Error::Clipboard(e.to_string())), + } + } + + /// Warning: This method should not be used on the main thread! Otherwise the underlying libraries may deadlock on Linux, freezing the whole app, when trying to copy data copied from this app, for example if the user copies text from the WebView. + pub fn read_text(&self) -> crate::Result { + match &self.clipboard { + Ok(clipboard) => { + let text = clipboard.lock().unwrap().as_mut().unwrap().get_text()?; + Ok(text) + } + Err(e) => Err(crate::Error::Clipboard(e.to_string())), + } + } + + pub fn write_html<'a, T: Into>>( + &self, + html: T, + alt_text: Option, + ) -> crate::Result<()> { + match &self.clipboard { + Ok(clipboard) => clipboard + .lock() + .unwrap() + .as_mut() + .unwrap() + .set_html(html, alt_text) + .map_err(Into::into), + Err(e) => Err(crate::Error::Clipboard(e.to_string())), + } + } + + pub fn clear(&self) -> crate::Result<()> { + match &self.clipboard { + Ok(clipboard) => clipboard + .lock() + .unwrap() + .as_mut() + .unwrap() + .clear() + .map_err(Into::into), + Err(e) => Err(crate::Error::Clipboard(e.to_string())), + } + } + + /// Warning: This method should not be used on the main thread! Otherwise the underlying libraries may deadlock on Linux, freezing the whole app, when trying to copy data copied from this app, for example if the user copies text from the WebView. + pub fn read_image(&self) -> crate::Result> { + match &self.clipboard { + Ok(clipboard) => { + let image = clipboard.lock().unwrap().as_mut().unwrap().get_image()?; + let image = Image::new_owned( + image.bytes.to_vec(), + image.width as u32, + image.height as u32, + ); + Ok(image) + } + Err(e) => Err(crate::Error::Clipboard(e.to_string())), + } + } + + pub(crate) fn cleanup(&self) { + if let Ok(clipboard) = &self.clipboard { + clipboard.lock().unwrap().take(); + } + } +} diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/src/error.rs b/packages/kbot/gui/app/plugins/clipboard-manager/src/error.rs new file mode 100644 index 00000000..1b8cf482 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/src/error.rs @@ -0,0 +1,34 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{ser::Serializer, Serialize}; + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), + #[error("{0}")] + Clipboard(String), + #[error(transparent)] + Tauri(#[from] tauri::Error), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} + +#[cfg(desktop)] +impl From for Error { + fn from(error: arboard::Error) -> Self { + Self::Clipboard(error.to_string()) + } +} diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/src/lib.rs b/packages/kbot/gui/app/plugins/clipboard-manager/src/lib.rs new file mode 100644 index 00000000..0cbb4e41 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/src/lib.rs @@ -0,0 +1,69 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Read and write to the system clipboard. + +#![doc( + html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", + html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" +)] + +use tauri::{ + plugin::{Builder, TauriPlugin}, + Manager, RunEvent, Runtime, +}; + +#[cfg(desktop)] +mod desktop; +#[cfg(mobile)] +mod mobile; + +mod commands; +mod error; + +pub use error::{Error, Result}; + +#[cfg(desktop)] +pub use desktop::Clipboard; +#[cfg(mobile)] +pub use mobile::Clipboard; + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the clipboard APIs. +pub trait ClipboardExt { + fn clipboard(&self) -> &Clipboard; +} + +impl> crate::ClipboardExt for T { + fn clipboard(&self) -> &Clipboard { + self.state::>().inner() + } +} + +/// Initializes the plugin. +pub fn init() -> TauriPlugin { + Builder::new("clipboard-manager") + .invoke_handler(tauri::generate_handler![ + commands::write_text, + commands::read_text, + commands::read_image, + commands::write_image, + commands::write_html, + commands::clear + ]) + .setup(|app, api| { + #[cfg(mobile)] + let clipboard = mobile::init(app, api)?; + #[cfg(desktop)] + let clipboard = desktop::init(app, api)?; + app.manage(clipboard); + Ok(()) + }) + .on_event(|_app, _event| { + #[cfg(desktop)] + if let RunEvent::Exit = _event { + _app.clipboard().cleanup(); + } + }) + .build() +} diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/src/mobile.rs b/packages/kbot/gui/app/plugins/clipboard-manager/src/mobile.rs new file mode 100644 index 00000000..72d5f6e0 --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/src/mobile.rs @@ -0,0 +1,109 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use tauri::{ + image::Image, + plugin::{PluginApi, PluginHandle}, + AppHandle, Runtime, +}; + +use std::borrow::Cow; + +#[cfg(target_os = "android")] +const PLUGIN_IDENTIFIER: &str = "app.tauri.clipboard"; + +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_clipboard); + +// initializes the Kotlin or Swift plugin classes +pub fn init( + _app: &AppHandle, + api: PluginApi, +) -> crate::Result> { + #[cfg(target_os = "android")] + let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "ClipboardPlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_clipboard)?; + Ok(Clipboard(handle)) +} + +/// Access to the clipboard APIs. +pub struct Clipboard(PluginHandle); + +impl Clipboard { + pub fn write_text<'a, T: Into>>(&self, text: T) -> crate::Result<()> { + let text = text.into().to_string(); + self.0 + .run_mobile_plugin("writeText", ClipKind::PlainText { text, label: None }) + .map_err(Into::into) + } + + pub fn write_text_with_label<'a, T: Into>>( + &self, + text: T, + label: T, + ) -> crate::Result<()> { + let text = text.into().to_string(); + let label = label.into().to_string(); + self.0 + .run_mobile_plugin( + "writeText", + ClipKind::PlainText { + text, + label: Some(label), + }, + ) + .map_err(Into::into) + } + + pub fn write_image(&self, _image: &Image<'_>) -> crate::Result<()> { + Err(crate::Error::Clipboard( + "Unsupported on this platform".to_string(), + )) + } + + pub fn read_text(&self) -> crate::Result { + self.0 + .run_mobile_plugin("readText", ()) + .map(|c| match c { + ClipboardContents::PlainText { text } => text, + }) + .map_err(Into::into) + } + + pub fn read_image(&self) -> crate::Result> { + Err(crate::Error::Clipboard( + "Unsupported on this platform".to_string(), + )) + } + + // Treat HTML as unsupported on mobile until tested + pub fn write_html<'a, T: Into>>( + &self, + _html: T, + _alt_text: Option, + ) -> crate::Result<()> { + Err(crate::Error::Clipboard( + "Unsupported on this platform".to_string(), + )) + } + + pub fn clear(&self) -> crate::Result<()> { + self.0.run_mobile_plugin("clear", ()).map_err(Into::into) + } +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +enum ClipKind { + PlainText { label: Option, text: String }, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +enum ClipboardContents { + PlainText { text: String }, +} diff --git a/packages/kbot/gui/app/plugins/clipboard-manager/tsconfig.json b/packages/kbot/gui/app/plugins/clipboard-manager/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/clipboard-manager/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/deep-link/.test-server/.well-known/apple-app-site-association b/packages/kbot/gui/app/plugins/deep-link/.test-server/.well-known/apple-app-site-association new file mode 100644 index 00000000..da5d0a77 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/.test-server/.well-known/apple-app-site-association @@ -0,0 +1,17 @@ +{ + "applinks": { + "details": [ + { + "appIDs": [ + "Q93MBH6S2F.com.tauri.deep-link-example" + ], + "components": [ + { + "/": "/open/*", + "comment": "Matches any URL whose path starts with /open/" + } + ] + } + ] + } +} diff --git a/packages/kbot/gui/app/plugins/deep-link/.test-server/server.js b/packages/kbot/gui/app/plugins/deep-link/.test-server/server.js new file mode 100644 index 00000000..0e2fec50 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/.test-server/server.js @@ -0,0 +1,27 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import http from 'http' +import fs from 'fs' + +const hostname = 'localhost' +const port = 8080 + +const server = http.createServer(function (req, res) { + console.log(req.url) + if (req.url == '/.well-known/apple-app-site-association') { + const association = fs.readFileSync( + '.well-known/apple-app-site-association' + ) + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.end(association) + } else { + res.writeHead(404) + res.end('404 NOT FOUND') + } +}) + +server.listen(port, hostname, () => { + console.log('Server started on port', port) +}) diff --git a/packages/kbot/gui/app/plugins/deep-link/CHANGELOG.md b/packages/kbot/gui/app/plugins/deep-link/CHANGELOG.md new file mode 100644 index 00000000..d9163a4f --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/CHANGELOG.md @@ -0,0 +1,172 @@ +# Changelog + +## \[2.4.3] + +- [`2522b71f`](https://github.com/tauri-apps/plugins-workspace/commit/2522b71f6bcae65c03b24415eb9295c9e7c84ffc) ([#2970](https://github.com/tauri-apps/plugins-workspace/pull/2970) by [@WSH032](https://github.com/tauri-apps/plugins-workspace/../../WSH032)) Revert the breaking change introduced by [#2928](https://github.com/tauri-apps/plugins-workspace/pull/2928). + +## \[2.4.2] + +- [`21d721a0`](https://github.com/tauri-apps/plugins-workspace/commit/21d721a0c2731fc201872f5b99ea8bbdc61b0b60) ([#2928](https://github.com/tauri-apps/plugins-workspace/pull/2928) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Linux, improved error messages when OS commands fail. + +## \[2.4.1] + +- [`d4f8299b`](https://github.com/tauri-apps/plugins-workspace/commit/d4f8299b12f107718c70692840a63768d65baaf9) ([#2844](https://github.com/tauri-apps/plugins-workspace/pull/2844) by [@yobson1](https://github.com/tauri-apps/plugins-workspace/../../yobson1)) Fix deep link protocol handler not set as default on linux + Fix duplicate protocols added to MimeType section in .desktop files on linux + +## \[2.4.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.3.0] + +- [`4d10acee`](https://github.com/tauri-apps/plugins-workspace/commit/4d10acee61bad8045705508121424ed5f2d381f6) ([#993](https://github.com/tauri-apps/plugins-workspace/pull/993) by [@m00nwtchr](https://github.com/tauri-apps/plugins-workspace/../../m00nwtchr)) Exposed Android's `path`, `pathPattern` and `pathSuffix` configurations. +- [`4d10acee`](https://github.com/tauri-apps/plugins-workspace/commit/4d10acee61bad8045705508121424ed5f2d381f6) ([#993](https://github.com/tauri-apps/plugins-workspace/pull/993) by [@m00nwtchr](https://github.com/tauri-apps/plugins-workspace/../../m00nwtchr)) Added a `scheme` configuration to set a scheme other than http/https. This is only supported on Android and will still default to http,https if not set. + +## \[2.2.1] + +### bug + +- [`38deef43`](https://github.com/tauri-apps/plugins-workspace/commit/38deef43dca9d5a09a38ed2da45b0f86c6afa1c5) ([#2483](https://github.com/tauri-apps/plugins-workspace/pull/2483)) Fix `is_registered` not being able to pickup deep link registered in `HKEY_LOCAL_MACHINE` on Windows +- [`38deef43`](https://github.com/tauri-apps/plugins-workspace/commit/38deef43dca9d5a09a38ed2da45b0f86c6afa1c5) ([#2483](https://github.com/tauri-apps/plugins-workspace/pull/2483)) Fix `unregister` not being able to remove deep link registered in `HKEY_LOCAL_MACHINE` on Windows + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.1] + +- [`b2aea045`](https://github.com/tauri-apps/plugins-workspace/commit/b2aea0456799775a7243706fdd7a5abf9a193992) ([#2008](https://github.com/tauri-apps/plugins-workspace/pull/2008) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) `onOpenUrl()` will now not call `getCurrent()` anymore, matching the documented behavior. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.7] + +- [`3168e176`](https://github.com/tauri-apps/plugins-workspace/commit/3168e176031a61215be542595ba90ca51f8f2d97) ([#1806](https://github.com/tauri-apps/plugins-workspace/pull/1806) by [@auggiebennett](https://github.com/tauri-apps/plugins-workspace/../../auggiebennett)) Fix fails to start when having spaces in the main binary path on Windows + +## \[2.0.0-rc.6] + +- [`6f3f6679`](https://github.com/tauri-apps/plugins-workspace/commit/6f3f66794a87ef9d1c16667c425d5ad7091a9c2f) ([#1780](https://github.com/tauri-apps/plugins-workspace/pull/1780)) Added `DeepLink::on_open_url` function to match the JavaScript API implementation, + which wraps the `deep-link://new-url` event and also send the current deep link if there's any. + +## \[2.0.0-rc.5] + +- [`984110a9`](https://github.com/tauri-apps/plugins-workspace/commit/984110a978774712bad4d746ed06134d54debcd0) ([#1770](https://github.com/tauri-apps/plugins-workspace/pull/1770) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Emit the `deep-link://new-url` event on Linux and Windows when the app is executed with a deep link CLI argument, + matching the iOS and macOS behavior. + +## \[2.0.0-rc.2] + +- [`64a6240f`](https://github.com/tauri-apps/plugins-workspace/commit/64a6240f79fcd52267c8d721b727ae695055d7ff) ([#1759](https://github.com/tauri-apps/plugins-workspace/pull/1759) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Implement `get_current` on Linux and Windows. + +## \[2.0.0-rc.3] + +- [`4654591d`](https://github.com/tauri-apps/plugins-workspace/commit/4654591d820403d6fa1a007fd55bb0d85947a6cc) ([#1732](https://github.com/tauri-apps/plugins-workspace/pull/1732) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Allow empty configuration values. + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.1] + +- [`2c00c029`](https://github.com/tauri-apps/plugins-workspace/commit/2c00c0292c9127b81567de46691e8c0f73557261) ([#1630](https://github.com/tauri-apps/plugins-workspace/pull/1630) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused multi-word IIFE names to not be formatted correctly. For example the `barcode-scanner` was defined as `window.__TAURI_PLUGIN_CLIPBOARDMANAGER__` instead of `window.__TAURI_PLUGIN_CLIPBOARD_MANAGER__`. +- [`5d170a54`](https://github.com/tauri-apps/plugins-workspace/commit/5d170a5444982dcc14135f6f1fc3e5da359f0eb0) ([#1671](https://github.com/tauri-apps/plugins-workspace/pull/1671) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.3. + +### changes + +- [`6b079cfd`](https://github.com/tauri-apps/plugins-workspace/commit/6b079cfdd107c94abc2c7300f6af00bac3ff4040) ([#1649](https://github.com/tauri-apps/plugins-workspace/pull/1649) by [@ahqsoftwares](https://github.com/tauri-apps/plugins-workspace/../../ahqsoftwares)) Remove targetSdk from build.kts files as it is deprecated and will be removed from DSL v9.0 + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +## \[2.0.0-beta.10] + +- [`99d6ac0f`](https://github.com/tauri-apps/plugins-workspace/commit/99d6ac0f9506a6a4a1aa59c728157190a7441af6) ([#1606](https://github.com/tauri-apps/plugins-workspace/pull/1606) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The JS packages now specify the *minimum* `@tauri-apps/api` version instead of a single exact version. +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.9] + +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.8] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.7] + +- [`0b008882`](https://github.com/tauri-apps/plugins-workspace/commit/0b0088821e50e33825f7d573b1c826cfeb38dda0) ([#1404](https://github.com/tauri-apps/plugins-workspace/pull/1404) by [@simonhyll](https://github.com/tauri-apps/plugins-workspace/../../simonhyll)) Fixed a typo in the `deep-link` js bindings causing `isRegistered` to not work. + +## \[2.0.0-beta.6] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.5] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.4] + +- [`021d23be`](https://github.com/tauri-apps/plugins-workspace/commit/021d23bef330de4ce001993e0ef2c7ab7815f044)([#916](https://github.com/tauri-apps/plugins-workspace/pull/916)) Added desktop support. + +## \[2.0.0-beta.3] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.3] + +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. + +## \[2.0.0-beta.2] + +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. + +## \[2.0.0-alpha.5] + +- [`8b1d821`](https://github.com/tauri-apps/plugins-workspace/commit/8b1d821a375d66a61e06c78b7148e255855cfe1b)([#844](https://github.com/tauri-apps/plugins-workspace/pull/844)) Fixes issue with tauri alpha.20. + +## \[2.0.0-alpha.4] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.13. + +## \[2.0.0-alpha.3] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.12. + +## \[2.0.0-alpha.2] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to @tauri-apps/api v2.0.0-alpha.11. + +## \[2.0.0-alpha.1] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update to @tauri-apps/api v2.0.0-alpha.9. + +## \[2.0.0-alpha.0] + +- [`eccd6f9`](https://github.com/tauri-apps/plugins-workspace/commit/eccd6f977af7629255b6f5a5205666c9079a86ed)([#504](https://github.com/tauri-apps/plugins-workspace/pull/504)) Initial release. + 0.0-alpha.0] + +- [`eccd6f9`](https://github.com/tauri-apps/plugins-workspace/commit/eccd6f977af7629255b6f5a5205666c9079a86ed)([#504](https://github.com/tauri-apps/plugins-workspace/pull/504)) Initial release. + +- [`eccd6f9`](https://github.com/tauri-apps/plugins-workspace/commit/eccd6f977af7629255b6f5a5205666c9079a86ed)([#504](https://github.com/tauri-apps/plugins-workspace/pull/504)) Initial release. + commit/eccd6f977af7629255b6f5a5205666c9079a86ed)([#504](https://github.com/tauri-apps/plugins-workspace/pull/504)) Initial release. + om/tauri-apps/plugins-workspace/commit/eccd6f977af7629255b6f5a5205666c9079a86ed)([#504](https://github.com/tauri-apps/plugins-workspace/pull/504)) Initial release. + +- [`eccd6f9`](https://github.com/tauri-apps/plugins-workspace/commit/eccd6f977af7629255b6f5a5205666c9079a86ed)([#504](https://github.com/tauri-apps/plugins-workspace/pull/504)) Initial release. + commit/eccd6f977af7629255b6f5a5205666c9079a86ed)([#504](https://github.com/tauri-apps/plugins-workspace/pull/504)) Initial release. + ithub.com/tauri-apps/plugins-workspace/pull/504)) Initial release. + ]\(https://github.com/tauri-apps/plugins-workspace/commit/eccd6f977af7629255b6f5a5205666c9079a86ed)([#504](https://github.com/tauri-apps/plugins-workspace/pull/504)) Initial release. + commit/eccd6f977af7629255b6f5a5205666c9079a86ed)([#504](https://github.com/tauri-apps/plugins-workspace/pull/504)) Initial release. + ithub.com/tauri-apps/plugins-workspace/pull/504)) Initial release. diff --git a/packages/kbot/gui/app/plugins/deep-link/Cargo.toml b/packages/kbot/gui/app/plugins/deep-link/Cargo.toml new file mode 100644 index 00000000..631e056b --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "tauri-plugin-deep-link" +version = "2.4.3" +description = "Set your Tauri application as the default handler for an URL" +authors = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-deep-link" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] +targets = ["x86_64-linux-android"] + +[package.metadata.platforms.support] +windows = { level = "full", notes = "" } +linux = { level = "full", notes = "" } +macos = { level = "partial", notes = "Deep links must be registered in config. Dynamic registration at runtime is not supported." } +android = { level = "partial", notes = "Deep links must be registered in config. Dynamic registration at runtime is not supported." } +ios = { level = "partial", notes = "Deep links must be registered in config. Dynamic registration at runtime is not supported." } + +[build-dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri-utils = { workspace = true } +tauri-plugin = { workspace = true, features = ["build"] } + +[target."cfg(target_os = \"macos\")".build-dependencies] +plist = "1" + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +tauri-utils = { workspace = true } +tracing = { workspace = true } +thiserror = { workspace = true } +url = { workspace = true } + +[target."cfg(windows)".dependencies] +dunce = "1" +windows-registry = "0.5" +windows-result = "0.3" + +[target."cfg(target_os = \"linux\")".dependencies] +rust-ini = "0.21" diff --git a/packages/kbot/gui/app/plugins/deep-link/LICENSE.spdx b/packages/kbot/gui/app/plugins/deep-link/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/deep-link/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/LICENSE_MIT b/packages/kbot/gui/app/plugins/deep-link/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/README.md b/packages/kbot/gui/app/plugins/deep-link/README.md new file mode 100644 index 00000000..384dffec --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/README.md @@ -0,0 +1,192 @@ +![plugin-deep-link](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/deep-link/banner.png) + +Set your Tauri application as the default handler for an URL. + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | ✓ | +| iOS | ✓ | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-deep-link = "2.0.0" +# alternatively with Git: +tauri-plugin-deep-link = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +```sh +pnpm add @tauri-apps/plugin-deep-link +# or +npm add @tauri-apps/plugin-deep-link +# or +yarn add @tauri-apps/plugin-deep-link +``` + +## Setting up + +### Android + +For [app links](https://developer.android.com/training/app-links#android-app-links), you need a server with a `.well-known/assetlinks.json` endpoint that must return a text response in the given format: + +``` +[ + { + "relation": ["delegate_permission/common.handle_all_urls"], + "target": { + "namespace": "android_app", + "package_name": "$APP_BUNDLE_ID", + "sha256_cert_fingerprints": [ + $CERT_FINGERPRINT + ] + } + } +] +``` + +Where `$APP_BUNDLE_ID` is the value defined on `tauri.conf.json > identifier` with `-` replaced with `_` and `$CERT_FINGERPRINT` is a list of SHA256 fingerprints of your app's signing certificates, see [verify android applinks](https://developer.android.com/training/app-links/verify-android-applinks#web-assoc) for more information. + +### iOS + +For [universal links](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content?language=objc), you need a server with a `.well-known/apple-app-site-association` endpoint that must return a text response in the given format: + +``` +{ + "applinks": { + "details": [ + { + "appIDs": [ "$DEVELOPMENT_TEAM_ID.$APP_BUNDLE_ID" ], + "components": [ + { + "/": "/open/*", + "comment": "Matches any URL whose path starts with /open/" + } + ] + } + ] + } +} +``` + +Where `$DEVELOPMENT_TEAM_ID` is the value defined on `tauri.conf.json > bundle > iOS > developmentTeam` or the `APPLE_DEVELOPMENT_TEAM` environment variable and `$APP_BUNDLE_ID` is the value defined on `tauri.conf.json > identifier`. See [applinks.details](https://developer.apple.com/documentation/bundleresources/applinks/details) for more information. + +To verify if your domain has been properly configured to expose the app associations, you can run the following command: + +```sh +curl -v https://app-site-association.cdn-apple.com/a/v1/ +``` + +**The apple-app-site-association file must be served over HTTPS and the response must include the `Content-Type: application/json` header.** + +To quickly open an app link on the iOS simulator you can execute `xcrun simctl openurl booted `. + +See [supporting associated domains](https://developer.apple.com/documentation/xcode/supporting-associated-domains?language=objc) for more information. + +## Configuration + +Under `tauri.conf.json > plugins > deep-link`, configure the domains (mobile) and schemes (desktop) you want to associate with your application: + +```json +{ + "plugins": { + "deep-link": { + "mobile": [ + { "host": "your.website.com", "pathPrefix": ["/open"] }, + { "host": "another.site.br" } + ], + "desktop": { + "schemes": ["something", "my-tauri-app"] + } + } + } +} +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_deep_link::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { onOpenUrl } from '@tauri-apps/plugin-deep-link' +await onOpenUrl((urls) => { + console.log('deep link:', urls) +}) +``` + +Note that the Plugin will only emit events on macOS, iOS and Android. On Windows and Linux the OS will spawn a new instance of your app with the URL as a CLI argument. If you want your app to behave on Windows & Linux similar to the other platforms you can use the [single-instance](../single-instance/) plugin with the `deep-link` feature enabled. + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Contributed By + + + + + + + + +
+ + CrabNebula + + + + Impierce + +
+ +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/deep-link/SECURITY.md b/packages/kbot/gui/app/plugins/deep-link/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/deep-link/android/.gitignore b/packages/kbot/gui/app/plugins/deep-link/android/.gitignore new file mode 100644 index 00000000..c0f21ec2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/android/.gitignore @@ -0,0 +1,2 @@ +/build +/.tauri diff --git a/packages/kbot/gui/app/plugins/deep-link/android/build.gradle.kts b/packages/kbot/gui/app/plugins/deep-link/android/build.gradle.kts new file mode 100644 index 00000000..671ee8b7 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/android/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "app.tauri.deep_link" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.0") + implementation("com.google.android.material:material:1.7.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + implementation(project(":tauri-android")) +} diff --git a/packages/kbot/gui/app/plugins/deep-link/android/proguard-rules.pro b/packages/kbot/gui/app/plugins/deep-link/android/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/android/settings.gradle b/packages/kbot/gui/app/plugins/deep-link/android/settings.gradle new file mode 100644 index 00000000..14a752e4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/android/settings.gradle @@ -0,0 +1,2 @@ +include ':tauri-android' +project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/packages/kbot/gui/app/plugins/deep-link/android/src/androidTest/java/ExampleInstrumentedTest.kt b/packages/kbot/gui/app/plugins/deep-link/android/src/androidTest/java/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..89a21d34 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/android/src/androidTest/java/ExampleInstrumentedTest.kt @@ -0,0 +1,28 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.deep_link + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("app.tauri.deep_link", appContext.packageName) + } +} diff --git a/packages/kbot/gui/app/plugins/deep-link/android/src/main/AndroidManifest.xml b/packages/kbot/gui/app/plugins/deep-link/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..9a40236b --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/packages/kbot/gui/app/plugins/deep-link/android/src/main/java/DeepLinkPlugin.kt b/packages/kbot/gui/app/plugins/deep-link/android/src/main/java/DeepLinkPlugin.kt new file mode 100644 index 00000000..db4e79af --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/android/src/main/java/DeepLinkPlugin.kt @@ -0,0 +1,77 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.deep_link + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.webkit.WebView +import app.tauri.Logger +import app.tauri.annotation.InvokeArg +import app.tauri.annotation.Command +import app.tauri.annotation.TauriPlugin +import app.tauri.plugin.Channel +import app.tauri.plugin.JSObject +import app.tauri.plugin.Plugin +import app.tauri.plugin.Invoke + +@InvokeArg +class SetEventHandlerArgs { + lateinit var handler: Channel +} + +@TauriPlugin +class DeepLinkPlugin(private val activity: Activity): Plugin(activity) { + //private val implementation = Example() + private var webView: WebView? = null + private var currentUrl: String? = null + private var channel: Channel? = null + + companion object { + var instance: DeepLinkPlugin? = null + } + + @Command + fun getCurrent(invoke: Invoke) { + val ret = JSObject() + ret.put("url", this.currentUrl) + invoke.resolve(ret) + } + + // This command should not be added to the `build.rs` and exposed as it is only + // used internally from the rust backend. + @Command + fun setEventHandler(invoke: Invoke) { + val args = invoke.parseArgs(SetEventHandlerArgs::class.java) + this.channel = args.handler + invoke.resolve() + } + + override fun load(webView: WebView) { + instance = this + + val intent = activity.intent + + if (intent.action == Intent.ACTION_VIEW) { + // TODO: check if it makes sense to split up init url and last url + this.currentUrl = intent.data.toString() + val event = JSObject() + event.put("url", this.currentUrl) + this.channel?.send(event) + } + + super.load(webView) + this.webView = webView + } + + override fun onNewIntent(intent: Intent) { + if (intent.action == Intent.ACTION_VIEW) { + this.currentUrl = intent.data.toString() + val event = JSObject() + event.put("url", this.currentUrl) + this.channel?.send(event) + } + } +} diff --git a/packages/kbot/gui/app/plugins/deep-link/android/src/test/java/ExampleUnitTest.kt b/packages/kbot/gui/app/plugins/deep-link/android/src/test/java/ExampleUnitTest.kt new file mode 100644 index 00000000..c10f171a --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/android/src/test/java/ExampleUnitTest.kt @@ -0,0 +1,21 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.deep_link + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/packages/kbot/gui/app/plugins/deep-link/api-iife.js b/packages/kbot/gui/app/plugins/deep-link/api-iife.js new file mode 100644 index 00000000..946ae7a2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_DEEP_LINK__=function(e){"use strict";function n(e,n=!1){return window.__TAURI_INTERNALS__.transformCallback(e,n)}async function r(e,n={},r){return window.__TAURI_INTERNALS__.invoke(e,n,r)}var t;async function i(e,t,i){const _={kind:"Any"};return r("plugin:event|listen",{event:e,target:_,handler:n(t)}).then((n=>async()=>async function(e,n){window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(e,n),await r("plugin:event|unlisten",{event:e,eventId:n})}(e,n)))}return"function"==typeof SuppressedError&&SuppressedError,function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG_ENTER="tauri://drag-enter",e.DRAG_OVER="tauri://drag-over",e.DRAG_DROP="tauri://drag-drop",e.DRAG_LEAVE="tauri://drag-leave"}(t||(t={})),e.getCurrent=async function(){return await r("plugin:deep-link|get_current")},e.isRegistered=async function(e){return await r("plugin:deep-link|is_registered",{protocol:e})},e.onOpenUrl=async function(e){return await i("deep-link://new-url",(n=>{e(n.payload)}))},e.register=async function(e){return await r("plugin:deep-link|register",{protocol:e})},e.unregister=async function(e){return await r("plugin:deep-link|unregister",{protocol:e})},e}({});Object.defineProperty(window.__TAURI__,"deepLink",{value:__TAURI_PLUGIN_DEEP_LINK__})} diff --git a/packages/kbot/gui/app/plugins/deep-link/banner.png b/packages/kbot/gui/app/plugins/deep-link/banner.png new file mode 100644 index 00000000..d4afc5a5 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/banner.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/build.rs b/packages/kbot/gui/app/plugins/deep-link/build.rs new file mode 100644 index 00000000..16d2a96e --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/build.rs @@ -0,0 +1,192 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#[path = "src/config.rs"] +mod config; +use config::{AssociatedDomain, Config}; + +const COMMANDS: &[&str] = &["get_current", "register", "unregister", "is_registered"]; + +// TODO: Consider using activity-alias in case users may have multiple activities in their app. +fn intent_filter(domain: &AssociatedDomain) -> String { + let host = domain + .host + .as_ref() + .map(|h| format!(r#""#)) + .unwrap_or_default(); + + let auto_verify = if domain.is_app_link() { + r#"android:autoVerify="true" "#.to_string() + } else { + String::new() + }; + + format!( + r#" + + + + {schemes} + {host} + {domains} + {path_patterns} + {path_prefixes} + {path_suffixes} +"#, + schemes = domain + .scheme + .iter() + .map(|scheme| format!(r#""#)) + .collect::>() + .join("\n "), + host = host, + domains = domain + .path + .iter() + .map(|path| format!(r#""#)) + .collect::>() + .join("\n "), + path_patterns = domain + .path_pattern + .iter() + .map(|pattern| format!(r#""#)) + .collect::>() + .join("\n "), + path_prefixes = domain + .path_prefix + .iter() + .map(|prefix| format!(r#""#)) + .collect::>() + .join("\n "), + path_suffixes = domain + .path_suffix + .iter() + .map(|suffix| format!(r#""#)) + .collect::>() + .join("\n "), + ) + .trim() + .to_string() +} + +fn main() { + let result = tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .android_path("android") + .try_build(); + + // when building documentation for Android the plugin build result is always Err() and is irrelevant to the crate documentation build + if !(cfg!(docsrs) && std::env::var("TARGET").unwrap().contains("android")) { + result.unwrap(); + } + + if let Some(config) = tauri_plugin::plugin_config::("deep-link") { + let errors: Vec = config + .mobile + .iter() + .filter_map(|d| d.validate().err()) + .collect(); + + if !errors.is_empty() { + panic!("Deep link config validation failed:\n{}", errors.join("\n")); + } + + tauri_plugin::mobile::update_android_manifest( + "DEEP LINK PLUGIN", + "activity", + config + .mobile + .iter() + .map(intent_filter) + .collect::>() + .join("\n"), + ) + .expect("failed to rewrite AndroidManifest.xml"); + + #[cfg(any(target_os = "macos", target_os = "ios"))] + { + // we need to ensure that the entitlements are only + // generated for explicit app links and not + // other deep links because then they + // are just going to complain and not be built or signed + let has_app_links = config.mobile.iter().any(|d| d.is_app_link()); + + if !has_app_links { + tauri_plugin::mobile::update_entitlements(|entitlements| { + entitlements.remove("com.apple.developer.associated-domains"); + }) + .expect("failed to update entitlements"); + } else { + tauri_plugin::mobile::update_entitlements(|entitlements| { + entitlements.insert( + "com.apple.developer.associated-domains".into(), + config + .mobile + .iter() + .filter(|d| d.is_app_link()) + .filter_map(|d| d.host.as_ref()) + .map(|host| format!("applinks:{}", host).into()) + .collect::>() + .into(), + ); + }) + .expect("failed to update entitlements"); + } + + let deep_link_domains = config + .mobile + .iter() + .filter_map(|domain| { + if domain.is_app_link() { + return None; + } + + Some(domain) + }) + .collect::>(); + + if deep_link_domains.is_empty() { + tauri_plugin::mobile::update_info_plist(|info_plist| { + info_plist.remove("CFBundleURLTypes"); + }) + .expect("failed to update Info.plist"); + } else { + tauri_plugin::mobile::update_info_plist(|info_plist| { + info_plist.insert( + "CFBundleURLTypes".into(), + deep_link_domains + .iter() + .map(|domain| { + let schemes = domain + .scheme + .iter() + .filter(|scheme| { + scheme.as_str() != "https" && scheme.as_str() != "http" + }) + .collect::>(); + + let mut dict = plist::Dictionary::new(); + dict.insert( + "CFBundleURLSchemes".into(), + schemes + .iter() + .map(|s| s.to_string().into()) + .collect::>() + .into(), + ); + dict.insert( + "CFBundleURLName".into(), + format!("{}", domain.scheme[0]).into(), + ); + plist::Value::Dictionary(dict) + }) + .collect::>() + .into(), + ); + }) + .expect("failed to update Info.plist"); + } + } + } +} diff --git a/packages/kbot/gui/app/plugins/deep-link/contributors/crabnebula.svg b/packages/kbot/gui/app/plugins/deep-link/contributors/crabnebula.svg new file mode 100644 index 00000000..a9bb4609 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/contributors/crabnebula.svg @@ -0,0 +1,31 @@ + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/contributors/impierce.svg b/packages/kbot/gui/app/plugins/deep-link/contributors/impierce.svg new file mode 100644 index 00000000..9d2a510b --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/contributors/impierce.svg @@ -0,0 +1,21 @@ + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/.gitignore b/packages/kbot/gui/app/plugins/deep-link/examples/app/.gitignore new file mode 100644 index 00000000..c9b61864 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/.gitignore @@ -0,0 +1,25 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +dist/ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/CHANGELOG.md b/packages/kbot/gui/app/plugins/deep-link/examples/app/CHANGELOG.md new file mode 100644 index 00000000..a9522551 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/CHANGELOG.md @@ -0,0 +1,171 @@ +# Changelog + +## \[2.2.6] + +### Dependencies + +- Upgraded to `deep-link-js@2.4.3` + +## \[2.2.5] + +### Dependencies + +- Upgraded to `deep-link-js@2.4.2` + +## \[2.2.4] + +### Dependencies + +- Upgraded to `deep-link-js@2.4.1` + +## \[2.2.3] + +### Dependencies + +- Upgraded to `deep-link-js@2.4.0` + +## \[2.2.2] + +### Dependencies + +- Upgraded to `deep-link-js@2.3.0` + +## \[2.2.1] + +### Dependencies + +- Upgraded to `deep-link-js@2.2.1` + +## \[2.2.0] + +### Dependencies + +- Upgraded to `deep-link-js@2.1.0` + +## \[2.0.1] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.1` + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0` + +## \[2.0.0-rc.1] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-rc.2` + +## \[2.0.0-rc.0] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-rc.1` + +## \[2.0.0-beta.11] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-rc.0` + +## \[2.0.0-beta.10] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-beta.10` + +## \[2.0.0-beta.9] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-beta.9` + +## \[2.0.0-beta.8] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-beta.8` + +## \[2.0.0-beta.7] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-beta.7` + +## \[2.0.0-beta.6] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-beta.6` + +## \[2.0.0-beta.5] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-beta.5` + +## \[2.0.0-beta.4] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-beta.4` + +## \[2.0.0-beta.3] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-beta.3` + +## \[2.0.0-beta.2] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-beta.2` + +## \[2.0.0-beta.1] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-beta.1` + +## \[2.0.0-beta.0] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-beta.0` + +## \[0.0.1-alpha.4] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-alpha.4` + +## \[0.0.1-alpha.3] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-alpha.3` + +## \[0.0.1-alpha.2] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-alpha.2` + +## \[0.0.1-alpha.1] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-alpha.1` + +## \[0.0.1-alpha.0] + +### Dependencies + +- Upgraded to `deep-link-js@2.0.0-alpha.0` diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/README.md b/packages/kbot/gui/app/plugins/deep-link/examples/app/README.md new file mode 100644 index 00000000..b381dcf5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/README.md @@ -0,0 +1,7 @@ +# Tauri + Vanilla TS + +This template should help get you started developing with Tauri in vanilla HTML, CSS and Typescript. + +## Recommended IDE Setup + +- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/index.html b/packages/kbot/gui/app/plugins/deep-link/examples/app/index.html new file mode 100644 index 00000000..b4f6c18b --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/index.html @@ -0,0 +1,65 @@ + + + + + + + Tauri App + + + + + +
+

Welcome to Tauri!

+ + + +

Click on the Tauri logo to learn more about the framework

+ +
+ +
+
+

Requested intent:

+

+
+ +
+

initial intent:

+

+
+ +
+

updated intent by event:

+

+
+
+ + diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/package.json b/packages/kbot/gui/app/plugins/deep-link/examples/app/package.json new file mode 100644 index 00000000..6276fa07 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/package.json @@ -0,0 +1,21 @@ +{ + "name": "deep-link-example", + "private": true, + "version": "2.2.6", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "tauri": "tauri" + }, + "dependencies": { + "@tauri-apps/api": "2.8.0", + "@tauri-apps/plugin-deep-link": "2.4.3" + }, + "devDependencies": { + "@tauri-apps/cli": "2.8.4", + "typescript": "^5.7.3", + "vite": "^7.0.4" + } +} diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/public/svelte.svg b/packages/kbot/gui/app/plugins/deep-link/examples/app/public/svelte.svg new file mode 100644 index 00000000..c5e08481 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/public/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/public/tauri.svg b/packages/kbot/gui/app/plugins/deep-link/examples/app/public/tauri.svg new file mode 100644 index 00000000..31b62c92 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/public/tauri.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/public/vite.svg b/packages/kbot/gui/app/plugins/deep-link/examples/app/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/.gitignore b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/.gitignore new file mode 100644 index 00000000..877b3f77 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +/gen/schemas + +.cargo \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/Cargo.toml b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/Cargo.toml new file mode 100644 index 00000000..130a7fef --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "deep-link-example" +version = "0.0.0" +description = "A Tauri App" +authors = ["you"] +license = "" +repository = "" +edition = "2021" +rust-version = "1.77.2" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { workspace = true } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true, features = ["wry", "common-controls-v6", "x11"] } +tauri-plugin-deep-link = { path = "../../../" } +tauri-plugin-log = { path = "../../../../log" } +tauri-plugin-single-instance = { path = "../../../../single-instance", features = [ + "deep-link", +] } +log = "0.4" + +[features] +# this feature is used for production builds or when `devUrl` points to the filesystem and the built-in dev server is disabled. +# If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes. +# DO NOT REMOVE!! +prod = ["tauri/custom-protocol"] diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/apple-app-site-association b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/apple-app-site-association new file mode 100644 index 00000000..f94da1be --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/apple-app-site-association @@ -0,0 +1,15 @@ +{ + "applinks": { + "details": [ + { + "appIDs": [ "DEVELOPMENT_TEAM_ID_HERE.com.tauri.deep-link-example" ], + "components": [ + { + "/": "/open/*", + "comment": "Matches any URL whose path starts with /open/" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/assetlinks.json.txt b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/assetlinks.json.txt new file mode 100644 index 00000000..0f09c9ad --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/assetlinks.json.txt @@ -0,0 +1 @@ +see https://fabianlars.de/.well-known/assetlinks.json \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/build.rs b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/build.rs new file mode 100644 index 00000000..5ebf8d2f --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/build.rs @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +fn main() { + tauri_build::build() +} diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/capabilities/app.json b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/capabilities/app.json new file mode 100644 index 00000000..a4bc7b4c --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/capabilities/app.json @@ -0,0 +1,11 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "run-app-base", + "description": "Base permissions to run the app", + "windows": ["main"], + "permissions": [ + "core:default", + "deep-link:allow-get-current", + "deep-link:default" + ] +} diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/.editorconfig b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/.editorconfig new file mode 100644 index 00000000..ebe51d3b --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = false \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/.gitignore b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/.gitignore new file mode 100644 index 00000000..b2482031 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/.gitignore @@ -0,0 +1,19 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +build +/captures +.externalNativeBuild +.cxx +local.properties +key.properties + +/.tauri +/tauri.settings.gradle \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/.gitignore b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/.gitignore new file mode 100644 index 00000000..1efb55bd --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/.gitignore @@ -0,0 +1,6 @@ +/src/main/java/com/tauri/deep_link_example/generated +/src/main/jniLibs/**/*.so +/src/main/assets/tauri.conf.json +/tauri.build.gradle.kts +/proguard-tauri.pro +/tauri.properties \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/build.gradle.kts b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/build.gradle.kts new file mode 100644 index 00000000..13ec1ffd --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/build.gradle.kts @@ -0,0 +1,70 @@ +import java.util.Properties + +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("rust") +} + +val tauriProperties = Properties().apply { + val propFile = file("tauri.properties") + if (propFile.exists()) { + propFile.inputStream().use { load(it) } + } +} + +android { + compileSdk = 36 + namespace = "com.tauri.deep_link_example" + defaultConfig { + manifestPlaceholders["usesCleartextTraffic"] = "false" + applicationId = "com.tauri.deep_link_example" + minSdk = 24 + targetSdk = 36 + versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt() + versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0") + } + buildTypes { + getByName("debug") { + manifestPlaceholders["usesCleartextTraffic"] = "true" + isDebuggable = true + isJniDebuggable = true + isMinifyEnabled = false + packaging { jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so") + jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so") + jniLibs.keepDebugSymbols.add("*/x86/*.so") + jniLibs.keepDebugSymbols.add("*/x86_64/*.so") + } + } + getByName("release") { + isMinifyEnabled = true + proguardFiles( + *fileTree(".") { include("**/*.pro") } + .plus(getDefaultProguardFile("proguard-android-optimize.txt")) + .toList().toTypedArray() + ) + } + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + buildConfig = true + } +} + +rust { + rootDirRel = "../../../" +} + +dependencies { + implementation("androidx.webkit:webkit:1.14.0") + implementation("androidx.appcompat:appcompat:1.7.1") + implementation("androidx.activity:activity-ktx:1.10.1") + implementation("com.google.android.material:material:1.12.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.4") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") +} + +apply(from = "tauri.build.gradle.kts") \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/proguard-rules.pro b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/AndroidManifest.xml b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..591f3c61 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/java/com/tauri/deep_link_example/MainActivity.kt b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/java/com/tauri/deep_link_example/MainActivity.kt new file mode 100644 index 00000000..313161c2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/java/com/tauri/deep_link_example/MainActivity.kt @@ -0,0 +1,11 @@ +package com.tauri.deep_link_example + +import android.os.Bundle +import androidx.activity.enableEdgeToEdge + +class MainActivity : TauriActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() + super.onCreate(savedInstanceState) + } +} diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..07d5da9c --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..4fc24441 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..28f1aa11 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..85d0c88a Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..28f1aa11 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..73e48dbf Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..13dd2147 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..73e48dbf Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..1d98044f Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..a888b336 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..1d98044f Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..08183246 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..a2a838e7 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..08183246 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..b18bceb6 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..3f8a57f3 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..b18bceb6 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/values-night/themes.xml b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/values-night/themes.xml new file mode 100644 index 00000000..3238df1f --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/values-night/themes.xml @@ -0,0 +1,6 @@ + + + + diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/values/colors.xml b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..f8c6127d --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/values/strings.xml b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..5c31c3f3 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + deep-link-example + deep-link-example + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/values/themes.xml b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/values/themes.xml new file mode 100644 index 00000000..3238df1f --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/values/themes.xml @@ -0,0 +1,6 @@ + + + + diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 00000000..782d63b9 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/build.gradle.kts b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/build.gradle.kts new file mode 100644 index 00000000..607240bc --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/build.gradle.kts @@ -0,0 +1,22 @@ +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.11.0") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25") + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +tasks.register("clean").configure { + delete("build") +} + diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/buildSrc/build.gradle.kts b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/buildSrc/build.gradle.kts new file mode 100644 index 00000000..5c55bba7 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/buildSrc/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + `kotlin-dsl` +} + +gradlePlugin { + plugins { + create("pluginsForCoolKids") { + id = "rust" + implementationClass = "RustPlugin" + } + } +} + +repositories { + google() + mavenCentral() +} + +dependencies { + compileOnly(gradleApi()) + implementation("com.android.tools.build:gradle:8.11.0") +} + diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/buildSrc/src/main/java/com/tauri/deep_link_example/kotlin/BuildTask.kt b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/buildSrc/src/main/java/com/tauri/deep_link_example/kotlin/BuildTask.kt new file mode 100644 index 00000000..f9874825 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/buildSrc/src/main/java/com/tauri/deep_link_example/kotlin/BuildTask.kt @@ -0,0 +1,52 @@ +import java.io.File +import org.apache.tools.ant.taskdefs.condition.Os +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.logging.LogLevel +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction + +open class BuildTask : DefaultTask() { + @Input + var rootDirRel: String? = null + @Input + var target: String? = null + @Input + var release: Boolean? = null + + @TaskAction + fun assemble() { + val executable = """pnpm"""; + try { + runTauriCli(executable) + } catch (e: Exception) { + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + runTauriCli("$executable.cmd") + } else { + throw e; + } + } + } + + fun runTauriCli(executable: String) { + val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null") + val target = target ?: throw GradleException("target cannot be null") + val release = release ?: throw GradleException("release cannot be null") + val args = listOf("tauri", "android", "android-studio-script"); + + project.exec { + workingDir(File(project.projectDir, rootDirRel)) + executable(executable) + args(args) + if (project.logger.isEnabled(LogLevel.DEBUG)) { + args("-vv") + } else if (project.logger.isEnabled(LogLevel.INFO)) { + args("-v") + } + if (release) { + args("--release") + } + args(listOf("--target", target)) + }.assertNormalExitValue() + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/buildSrc/src/main/java/com/tauri/deep_link_example/kotlin/RustPlugin.kt b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/buildSrc/src/main/java/com/tauri/deep_link_example/kotlin/RustPlugin.kt new file mode 100644 index 00000000..4aa7fcaf --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/buildSrc/src/main/java/com/tauri/deep_link_example/kotlin/RustPlugin.kt @@ -0,0 +1,85 @@ +import com.android.build.api.dsl.ApplicationExtension +import org.gradle.api.DefaultTask +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.get + +const val TASK_GROUP = "rust" + +open class Config { + lateinit var rootDirRel: String +} + +open class RustPlugin : Plugin { + private lateinit var config: Config + + override fun apply(project: Project) = with(project) { + config = extensions.create("rust", Config::class.java) + + val defaultAbiList = listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64"); + val abiList = (findProperty("abiList") as? String)?.split(',') ?: defaultAbiList + + val defaultArchList = listOf("arm64", "arm", "x86", "x86_64"); + val archList = (findProperty("archList") as? String)?.split(',') ?: defaultArchList + + val targetsList = (findProperty("targetList") as? String)?.split(',') ?: listOf("aarch64", "armv7", "i686", "x86_64") + + extensions.configure { + @Suppress("UnstableApiUsage") + flavorDimensions.add("abi") + productFlavors { + create("universal") { + dimension = "abi" + ndk { + abiFilters += abiList + } + } + defaultArchList.forEachIndexed { index, arch -> + create(arch) { + dimension = "abi" + ndk { + abiFilters.add(defaultAbiList[index]) + } + } + } + } + } + + afterEvaluate { + for (profile in listOf("debug", "release")) { + val profileCapitalized = profile.replaceFirstChar { it.uppercase() } + val buildTask = tasks.maybeCreate( + "rustBuildUniversal$profileCapitalized", + DefaultTask::class.java + ).apply { + group = TASK_GROUP + description = "Build dynamic library in $profile mode for all targets" + } + + tasks["mergeUniversal${profileCapitalized}JniLibFolders"].dependsOn(buildTask) + + for (targetPair in targetsList.withIndex()) { + val targetName = targetPair.value + val targetArch = archList[targetPair.index] + val targetArchCapitalized = targetArch.replaceFirstChar { it.uppercase() } + val targetBuildTask = project.tasks.maybeCreate( + "rustBuild$targetArchCapitalized$profileCapitalized", + BuildTask::class.java + ).apply { + group = TASK_GROUP + description = "Build dynamic library in $profile mode for $targetArch" + rootDirRel = config.rootDirRel + target = targetName + release = profile == "release" + } + + buildTask.dependsOn(targetBuildTask) + tasks["merge$targetArchCapitalized${profileCapitalized}JniLibFolders"].dependsOn( + targetBuildTask + ) + } + } + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/gradle.properties b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/gradle.properties new file mode 100644 index 00000000..2a7ec695 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/gradle.properties @@ -0,0 +1,24 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true +android.nonFinalResIds=false \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..e708b1c0 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..c5f9a53c --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue May 10 19:22:52 CST 2022 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/gradlew b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/gradlew new file mode 100644 index 00000000..4f906e0c --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/gradlew.bat b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/settings.gradle b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/settings.gradle new file mode 100644 index 00000000..39391166 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/android/settings.gradle @@ -0,0 +1,3 @@ +include ':app' + +apply from: 'tauri.settings.gradle' diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/.gitignore b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/.gitignore new file mode 100644 index 00000000..6726e2f8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/.gitignore @@ -0,0 +1,3 @@ +xcuserdata/ +build/ +Externals/ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png new file mode 100644 index 00000000..a6ac2a8c Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png new file mode 100644 index 00000000..2869541f Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png new file mode 100644 index 00000000..2869541f Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png new file mode 100644 index 00000000..cf265a45 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png new file mode 100644 index 00000000..29c9746c Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png new file mode 100644 index 00000000..a4e68c8d Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png new file mode 100644 index 00000000..a4e68c8d Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png new file mode 100644 index 00000000..e4adcbce Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png new file mode 100644 index 00000000..2869541f Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png new file mode 100644 index 00000000..a414e65b Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png new file mode 100644 index 00000000..a414e65b Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png new file mode 100644 index 00000000..a0807e5d Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png new file mode 100644 index 00000000..704c9291 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png new file mode 100644 index 00000000..a0807e5d Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png new file mode 100644 index 00000000..2a9fbc26 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png new file mode 100644 index 00000000..2cdf1848 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png new file mode 100644 index 00000000..4723e4b4 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png new file mode 100644 index 00000000..f26fee45 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..90eea7ec --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "AppIcon-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "AppIcon-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "AppIcon-29x29@2x-1.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "AppIcon-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "AppIcon-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "AppIcon-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "AppIcon-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "AppIcon-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "AppIcon-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "AppIcon-20x20@2x-1.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "AppIcon-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "AppIcon-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "AppIcon-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "AppIcon-40x40@2x-1.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "AppIcon-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "AppIcon-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "AppIcon-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "AppIcon-512@2x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/Contents.json b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/ExportOptions.plist b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/ExportOptions.plist new file mode 100644 index 00000000..0428a171 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/ExportOptions.plist @@ -0,0 +1,8 @@ + + + + + method + debugging + + diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/LaunchScreen.storyboard b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/LaunchScreen.storyboard new file mode 100644 index 00000000..81b5f90e --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/LaunchScreen.storyboard @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Podfile b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Podfile new file mode 100644 index 00000000..98045f14 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Podfile @@ -0,0 +1,21 @@ +# Uncomment the next line to define a global platform for your project + +target 'deep-link-example_iOS' do +platform :ios, '13.0' + # Pods for deep-link-example_iOS +end + +target 'deep-link-example_macOS' do +platform :osx, '11.0' + # Pods for deep-link-example_macOS +end + +# Delete the deployment target for iOS and macOS, causing it to be inherited from the Podfile +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' + config.build_settings.delete 'MACOSX_DEPLOYMENT_TARGET' + end + end +end diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Sources/deep-link-example/bindings/bindings.h b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Sources/deep-link-example/bindings/bindings.h new file mode 100644 index 00000000..51522007 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Sources/deep-link-example/bindings/bindings.h @@ -0,0 +1,8 @@ +#pragma once + +namespace ffi { + extern "C" { + void start_app(); + } +} + diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Sources/deep-link-example/main.mm b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Sources/deep-link-example/main.mm new file mode 100644 index 00000000..7793a9d5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/Sources/deep-link-example/main.mm @@ -0,0 +1,6 @@ +#include "bindings/bindings.h" + +int main(int argc, char * argv[]) { + ffi::start_app(); + return 0; +} diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/project.pbxproj b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/project.pbxproj new file mode 100644 index 00000000..4c01a958 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/project.pbxproj @@ -0,0 +1,479 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 63; + objects = { + +/* Begin PBXBuildFile section */ + 017AE826151E36372534A964 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED2B1BC06DFE0498ECDEEE51 /* Metal.framework */; }; + 65A8D948440EDAA7F62BA1F4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 486CAFD81CB14F9A2DF72FDF /* Assets.xcassets */; }; + 8267407118D9FF73C271D81B /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9435FC7E183EA6260CE76637 /* QuartzCore.framework */; }; + 8DD47883B792C4FC20927AB4 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BDECB1ED2EEEB5A6A8B8372 /* Security.framework */; }; + ABA8D5D86E66C92292DA3B3E /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F316D1CD78DD2E070DA5C17 /* UIKit.framework */; }; + BC36958BBBA7FE61066213D7 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C21C8B4A18EC7D0B5808C10 /* MetalKit.framework */; }; + C384FB77F116B05F8E642CA8 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = BF7ECB9AB55B71692A21D5F7 /* assets */; }; + D01EC573029B7BEC701F6012 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BDF5DBBA740DA7D86791DEC /* WebKit.framework */; }; + D4D232DBB85C5C1594FACC3D /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = D99665C1C3247732C6BF25F4 /* main.mm */; }; + D7A9EBD47413746EDE96BDF8 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B18865218362A4BE07527DBD /* CoreGraphics.framework */; }; + E26F7FA923DA1EABEE42B63A /* libapp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 403FB4BAE59F74EE98EF1EC6 /* libapp.a */; }; + E6992F2651B864B15ED14925 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1846ADCEDC2C208E1037ADC6 /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1846ADCEDC2C208E1037ADC6 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + 1C21C8B4A18EC7D0B5808C10 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; + 1CAAFA750FD735A285DC1238 /* deep-link-example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "deep-link-example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 2F316D1CD78DD2E070DA5C17 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 3DD32303BEC377C10162CF69 /* bindings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bindings.h; sourceTree = ""; }; + 403FB4BAE59F74EE98EF1EC6 /* libapp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libapp.a; sourceTree = ""; }; + 486CAFD81CB14F9A2DF72FDF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 4A33212233BFAA738F6A46FC /* lib.rs */ = {isa = PBXFileReference; lastKnownFileType = text; path = lib.rs; sourceTree = ""; }; + 4BDECB1ED2EEEB5A6A8B8372 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + 6BDF5DBBA740DA7D86791DEC /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; + 8AB0099573FE8BF1DC82CDBA /* deep-link-example_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "deep-link-example_iOS.entitlements"; sourceTree = ""; }; + 9435FC7E183EA6260CE76637 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + AEA78299D25FEC31E2988090 /* main.rs */ = {isa = PBXFileReference; lastKnownFileType = text; path = main.rs; sourceTree = ""; }; + B005488D1B56B657AB52E28C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + B18865218362A4BE07527DBD /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + BF7ECB9AB55B71692A21D5F7 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = SOURCE_ROOT; }; + D99665C1C3247732C6BF25F4 /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; }; + ED2B1BC06DFE0498ECDEEE51 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 5C340AB143FB1483D2013F8B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E26F7FA923DA1EABEE42B63A /* libapp.a in Frameworks */, + D7A9EBD47413746EDE96BDF8 /* CoreGraphics.framework in Frameworks */, + 017AE826151E36372534A964 /* Metal.framework in Frameworks */, + BC36958BBBA7FE61066213D7 /* MetalKit.framework in Frameworks */, + 8267407118D9FF73C271D81B /* QuartzCore.framework in Frameworks */, + 8DD47883B792C4FC20927AB4 /* Security.framework in Frameworks */, + ABA8D5D86E66C92292DA3B3E /* UIKit.framework in Frameworks */, + D01EC573029B7BEC701F6012 /* WebKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 146BAF1D709F8A0FE5B07709 /* Sources */ = { + isa = PBXGroup; + children = ( + 15AD0F4F98577F71B7752D85 /* deep-link-example */, + ); + path = Sources; + sourceTree = ""; + }; + 15AD0F4F98577F71B7752D85 /* deep-link-example */ = { + isa = PBXGroup; + children = ( + D99665C1C3247732C6BF25F4 /* main.mm */, + 9FE22F548D05F1B0C03527E4 /* bindings */, + ); + path = "deep-link-example"; + sourceTree = ""; + }; + 1DC58B1705AA3ECC6B887FE7 = { + isa = PBXGroup; + children = ( + BF7ECB9AB55B71692A21D5F7 /* assets */, + 486CAFD81CB14F9A2DF72FDF /* Assets.xcassets */, + 1846ADCEDC2C208E1037ADC6 /* LaunchScreen.storyboard */, + 7D12035C470ED9DAF55A709E /* deep-link-example_iOS */, + 84EADC52DA26583ACE816A6D /* Externals */, + 146BAF1D709F8A0FE5B07709 /* Sources */, + EEC07EE3E5E2B16228B36C78 /* src */, + 308F912BBFBABA6B939FA2B3 /* Frameworks */, + F9EEBB3248B74B1D6CDA4D84 /* Products */, + ); + sourceTree = ""; + }; + 308F912BBFBABA6B939FA2B3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + B18865218362A4BE07527DBD /* CoreGraphics.framework */, + 403FB4BAE59F74EE98EF1EC6 /* libapp.a */, + ED2B1BC06DFE0498ECDEEE51 /* Metal.framework */, + 1C21C8B4A18EC7D0B5808C10 /* MetalKit.framework */, + 9435FC7E183EA6260CE76637 /* QuartzCore.framework */, + 4BDECB1ED2EEEB5A6A8B8372 /* Security.framework */, + 2F316D1CD78DD2E070DA5C17 /* UIKit.framework */, + 6BDF5DBBA740DA7D86791DEC /* WebKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 7D12035C470ED9DAF55A709E /* deep-link-example_iOS */ = { + isa = PBXGroup; + children = ( + 8AB0099573FE8BF1DC82CDBA /* deep-link-example_iOS.entitlements */, + B005488D1B56B657AB52E28C /* Info.plist */, + ); + path = "deep-link-example_iOS"; + sourceTree = ""; + }; + 84EADC52DA26583ACE816A6D /* Externals */ = { + isa = PBXGroup; + children = ( + ); + path = Externals; + sourceTree = ""; + }; + 9FE22F548D05F1B0C03527E4 /* bindings */ = { + isa = PBXGroup; + children = ( + 3DD32303BEC377C10162CF69 /* bindings.h */, + ); + path = bindings; + sourceTree = ""; + }; + EEC07EE3E5E2B16228B36C78 /* src */ = { + isa = PBXGroup; + children = ( + 4A33212233BFAA738F6A46FC /* lib.rs */, + AEA78299D25FEC31E2988090 /* main.rs */, + ); + name = src; + path = ../../src; + sourceTree = ""; + }; + F9EEBB3248B74B1D6CDA4D84 /* Products */ = { + isa = PBXGroup; + children = ( + 1CAAFA750FD735A285DC1238 /* deep-link-example.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + A1C635908C823A89928264CD /* deep-link-example_iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = CC3B2B9068F702AB6ED6E36C /* Build configuration list for PBXNativeTarget "deep-link-example_iOS" */; + buildPhases = ( + E8BEC9005266B4780C27DC05 /* Build Rust Code */, + EAFF3E530DA24F7AB759ABB3 /* Sources */, + 8E775D86229F98E9F18AAAB7 /* Resources */, + 5C340AB143FB1483D2013F8B /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "deep-link-example_iOS"; + packageProductDependencies = ( + ); + productName = "deep-link-example_iOS"; + productReference = 1CAAFA750FD735A285DC1238 /* deep-link-example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + BCB4BA6E81088C5B470E3436 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + }; + buildConfigurationList = 8FCB58B8ADB9F9CB9ECE01FA /* Build configuration list for PBXProject "deep-link-example" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = 1DC58B1705AA3ECC6B887FE7; + minimizedProjectReferenceProxies = 1; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A1C635908C823A89928264CD /* deep-link-example_iOS */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8E775D86229F98E9F18AAAB7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 65A8D948440EDAA7F62BA1F4 /* Assets.xcassets in Resources */, + E6992F2651B864B15ED14925 /* LaunchScreen.storyboard in Resources */, + C384FB77F116B05F8E642CA8 /* assets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + E8BEC9005266B4780C27DC05 /* Build Rust Code */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Build Rust Code"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a", + "$(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "pnpm tauri ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --framework-search-paths \"${FRAMEWORK_SEARCH_PATHS:?}\" --header-search-paths \"${HEADER_SEARCH_PATHS:?}\" --gcc-preprocessor-definitions \"${GCC_PREPROCESSOR_DEFINITIONS:-}\" --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?}"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + EAFF3E530DA24F7AB759ABB3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D4D232DBB85C5C1594FACC3D /* main.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 21DF092E6F2020CEC95B687A /* debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DEBUG=1", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = debug; + }; + 3655D9D0A68600F8988F24EB /* debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = "deep-link-example_iOS/deep-link-example_iOS.entitlements"; + CODE_SIGN_IDENTITY = "iPhone Developer"; + DEVELOPMENT_TEAM = "Q93MBH6S2F"; + ENABLE_BITCODE = NO; + "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\".\"", + ); + INFOPLIST_FILE = "deep-link-example_iOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + "LIBRARY_SEARCH_PATHS[arch=arm64]" = ( + "$(inherited)", + "$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)", + "$(SDKROOT)/usr/lib/swift", + "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", + "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", + ); + "LIBRARY_SEARCH_PATHS[arch=x86_64]" = ( + "$(inherited)", + "$(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION)", + "$(SDKROOT)/usr/lib/swift", + "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", + "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.tauri.deep-link-example; + PRODUCT_NAME = "deep-link-example"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = arm64; + }; + name = debug; + }; + 7A19D7CB4CA2808477E73B8A /* release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + }; + name = release; + }; + 7C085E1672AF39D75FFD0081 /* release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = "deep-link-example_iOS/deep-link-example_iOS.entitlements"; + CODE_SIGN_IDENTITY = "iPhone Developer"; + DEVELOPMENT_TEAM = "Q93MBH6S2F"; + ENABLE_BITCODE = NO; + "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\".\"", + ); + INFOPLIST_FILE = "deep-link-example_iOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + "LIBRARY_SEARCH_PATHS[arch=arm64]" = ( + "$(inherited)", + "$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)", + "$(SDKROOT)/usr/lib/swift", + "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", + "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", + ); + "LIBRARY_SEARCH_PATHS[arch=x86_64]" = ( + "$(inherited)", + "$(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION)", + "$(SDKROOT)/usr/lib/swift", + "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)", + "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.tauri.deep-link-example; + PRODUCT_NAME = "deep-link-example"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = arm64; + }; + name = release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 8FCB58B8ADB9F9CB9ECE01FA /* Build configuration list for PBXProject "deep-link-example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 21DF092E6F2020CEC95B687A /* debug */, + 7A19D7CB4CA2808477E73B8A /* release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = debug; + }; + CC3B2B9068F702AB6ED6E36C /* Build configuration list for PBXNativeTarget "deep-link-example_iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3655D9D0A68600F8988F24EB /* debug */, + 7C085E1672AF39D75FFD0081 /* release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = BCB4BA6E81088C5B470E3436 /* Project object */; +} diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..ac90d5ac --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,10 @@ + + + + + BuildSystemType + Original + DisableBuildSystemDeprecationDiagnostic + + + diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/xcshareddata/xcschemes/deep-link-example_iOS.xcscheme b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/xcshareddata/xcschemes/deep-link-example_iOS.xcscheme new file mode 100644 index 00000000..a8fbc97e --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/xcshareddata/xcschemes/deep-link-example_iOS.xcscheme @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example_iOS/Info.plist b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example_iOS/Info.plist new file mode 100644 index 00000000..bc74b01b --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example_iOS/Info.plist @@ -0,0 +1,55 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.1.0 + CFBundleVersion + 0.1.0 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + arm64 + metal + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CFBundleURLTypes + + + CFBundleURLSchemes + + taurideeplink + + CFBundleURLName + taurideeplink + + + + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example_iOS/deep-link-example_iOS.entitlements b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example_iOS/deep-link-example_iOS.entitlements new file mode 100644 index 00000000..3216c743 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example_iOS/deep-link-example_iOS.entitlements @@ -0,0 +1,11 @@ + + + + + com.apple.developer.associated-domains + + applinks:fabianlars.de + applinks:tauri.app + + + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/project.yml b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/project.yml new file mode 100644 index 00000000..74d0ab49 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/gen/apple/project.yml @@ -0,0 +1,88 @@ +name: deep-link-example +options: + bundleIdPrefix: com.tauri.deep-link-example + deploymentTarget: + iOS: 13.0 +fileGroups: [../../src] +configs: + debug: debug + release: release +settingGroups: + app: + base: + PRODUCT_NAME: deep-link-example + PRODUCT_BUNDLE_IDENTIFIER: com.tauri.deep-link-example +targetTemplates: + app: + type: application + sources: + - path: Sources + scheme: + environmentVariables: + RUST_BACKTRACE: full + RUST_LOG: info + settings: + groups: [app] +targets: + deep-link-example_iOS: + type: application + platform: iOS + sources: + - path: Sources + - path: Assets.xcassets + - path: Externals + - path: deep-link-example_iOS + - path: assets + buildPhase: resources + type: folder + - path: LaunchScreen.storyboard + info: + path: deep-link-example_iOS/Info.plist + properties: + LSRequiresIPhoneOS: true + UILaunchStoryboardName: LaunchScreen + UIRequiredDeviceCapabilities: [arm64, metal] + UISupportedInterfaceOrientations: + - UIInterfaceOrientationPortrait + - UIInterfaceOrientationLandscapeLeft + - UIInterfaceOrientationLandscapeRight + UISupportedInterfaceOrientations~ipad: + - UIInterfaceOrientationPortrait + - UIInterfaceOrientationPortraitUpsideDown + - UIInterfaceOrientationLandscapeLeft + - UIInterfaceOrientationLandscapeRight + CFBundleShortVersionString: 0.1.0 + CFBundleVersion: '0.1.0' + entitlements: + path: deep-link-example_iOS/deep-link-example_iOS.entitlements + scheme: + environmentVariables: + RUST_BACKTRACE: full + RUST_LOG: info + settings: + base: + ENABLE_BITCODE: false + ARCHS: [arm64] + VALID_ARCHS: arm64 + LIBRARY_SEARCH_PATHS[arch=x86_64]: $(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME) + LIBRARY_SEARCH_PATHS[arch=arm64]: $(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME) + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES: true + EXCLUDED_ARCHS[sdk=iphoneos*]: x86_64 + groups: [app] + dependencies: + - framework: libapp.a + embed: false + - sdk: CoreGraphics.framework + - sdk: Metal.framework + - sdk: MetalKit.framework + - sdk: QuartzCore.framework + - sdk: Security.framework + - sdk: UIKit.framework + - sdk: WebKit.framework + preBuildScripts: + - script: pnpm tauri ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --framework-search-paths "${FRAMEWORK_SEARCH_PATHS:?}" --header-search-paths "${HEADER_SEARCH_PATHS:?}" --gcc-preprocessor-definitions "${GCC_PREPROCESSOR_DEFINITIONS:-}" --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?} + name: Build Rust Code + basedOnDependencyAnalysis: false + outputFiles: + - $(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a + - $(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/icons/128x128.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/icons/128x128.png new file mode 100644 index 00000000..77e7d233 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/icons/128x128.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/icons/128x128@2x.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/icons/128x128@2x.png new file mode 100644 index 00000000..0f7976f1 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/icons/128x128@2x.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/icons/32x32.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/icons/32x32.png new file mode 100644 index 00000000..98fda06f Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/icons/32x32.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/icons/icon.icns b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/icons/icon.icns new file mode 100644 index 00000000..29d6685a Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/icons/icon.icns differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/icons/icon.ico b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/icons/icon.ico new file mode 100644 index 00000000..06c23c82 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/icons/icon.ico differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/icons/icon.png b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/icons/icon.png new file mode 100644 index 00000000..d1756ce4 Binary files /dev/null and b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/icons/icon.png differ diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/server.js b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/server.js new file mode 100644 index 00000000..24a6ac6f --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/server.js @@ -0,0 +1,31 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import http from 'http' +import fs from 'fs' +import path from 'path' +import * as url from 'url' +const __dirname = url.fileURLToPath(new URL('.', import.meta.url)) + +const port = 8125 + +http + .createServer(function (request, response) { + if (request.url === '/.well-known/apple-app-site-association') { + // eslint-disable-next-line + fs.readFile( + path.resolve(__dirname, 'apple-app-site-association'), + function (_error, content) { + response.writeHead(200) + response.end(content, 'utf-8') + } + ) + } else { + response.writeHead(404) + response.end() + } + }) + .listen(port) + +console.log(`Server running at http://127.0.0.1:${port}/`) diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/src/lib.rs b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/src/lib.rs new file mode 100644 index 00000000..f85527d9 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/src/lib.rs @@ -0,0 +1,49 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tauri_plugin_deep_link::DeepLinkExt; + +// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command +#[tauri::command] +fn greet(name: &str) -> String { + format!("Hello, {name}! You've been greeted from Rust!") +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + #[allow(unused_mut)] + let mut builder = tauri::Builder::default(); + + #[cfg(desktop)] + { + builder = builder.plugin(tauri_plugin_single_instance::init(|_app, argv, _cwd| { + println!("single instance triggered: {argv:?}"); + })); + } + + builder + .plugin(tauri_plugin_deep_link::init()) + .plugin( + tauri_plugin_log::Builder::default() + .level(log::LevelFilter::Info) + .build(), + ) + .setup(|app| { + // ensure deep links are registered on the system + // this is useful because AppImages requires additional setup to be available in the system + // and calling register() makes the deep links immediately available - without any user input + // additionally, we manually register on Windows on debug builds for development + #[cfg(any(target_os = "linux", all(debug_assertions, windows)))] + app.deep_link().register_all()?; + + app.deep_link().on_open_url(|event| { + dbg!(event.urls()); + }); + + Ok(()) + }) + .invoke_handler(tauri::generate_handler![greet]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/src/main.rs b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/src/main.rs new file mode 100644 index 00000000..799fad7c --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/src/main.rs @@ -0,0 +1,11 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + #[cfg(desktop)] + deep_link_example::run(); +} diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/tauri.conf.json b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/tauri.conf.json new file mode 100644 index 00000000..61f7394d --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src-tauri/tauri.conf.json @@ -0,0 +1,59 @@ +{ + "productName": "deep-link-example", + "version": "0.1.0", + "identifier": "com.tauri.deep-link-example", + "build": { + "devUrl": "http://localhost:1420", + "frontendDist": "../dist", + "beforeDevCommand": "pnpm dev", + "beforeBuildCommand": "pnpm build" + }, + "app": { + "security": { + "csp": null + }, + "windows": [ + { + "fullscreen": false, + "height": 600, + "resizable": true, + "title": "tauri-app", + "width": 800 + } + ] + }, + "plugins": { + "whatever": "helloworld", + "sec": { + "hello": "world" + }, + "deep-link": { + "mobile": [ + { + "host": "fabianlars.de", + "pathPrefix": ["/intent"] + }, + { + "host": "tauri.app" + }, + { + "scheme": ["taurideeplink"] + } + ], + "desktop": { + "schemes": ["fabianlars", "my-tauri-app"] + } + } + }, + "bundle": { + "active": true, + "category": "DeveloperTool", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + } +} diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src/assets/tauri.svg b/packages/kbot/gui/app/plugins/deep-link/examples/app/src/assets/tauri.svg new file mode 100644 index 00000000..31b62c92 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src/assets/tauri.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src/assets/typescript.svg b/packages/kbot/gui/app/plugins/deep-link/examples/app/src/assets/typescript.svg new file mode 100644 index 00000000..30a5edd3 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src/assets/typescript.svg @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src/assets/vite.svg b/packages/kbot/gui/app/plugins/deep-link/examples/app/src/assets/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src/assets/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src/main.ts b/packages/kbot/gui/app/plugins/deep-link/examples/app/src/main.ts new file mode 100644 index 00000000..550e0aaa --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src/main.ts @@ -0,0 +1,38 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { + onOpenUrl, + getCurrent as getCurrentDeepLinkUrls +} from '@tauri-apps/plugin-deep-link' + +function handler(urls: string[]) { + console.log(urls) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const updateIntentEl = document.querySelector('#event-intent')! + updateIntentEl.textContent = JSON.stringify(urls) +} + +window.addEventListener('DOMContentLoaded', () => { + onOpenUrl(handler) + + document.querySelector('#intent-form')?.addEventListener('submit', (e) => { + e.preventDefault() + getCurrentDeepLinkUrls() + .then((res) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const updateIntentEl = document.querySelector('#update-intent')! + updateIntentEl.textContent = res ? JSON.stringify(res) : 'none' + }) + .catch(console.error) + }) + + getCurrentDeepLinkUrls() + .then((res) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const initialIntentEl = document.querySelector('#initial-intent')! + initialIntentEl.textContent = res ? JSON.stringify(res) : 'none' + }) + .catch(console.error) +}) diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/src/styles.css b/packages/kbot/gui/app/plugins/deep-link/examples/app/src/styles.css new file mode 100644 index 00000000..f7de85bf --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/src/styles.css @@ -0,0 +1,109 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color: #0f0f0f; + background-color: #f6f6f6; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +.container { + margin: 0; + padding-top: 10vh; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: 0.75s; +} + +.logo.tauri:hover { + filter: drop-shadow(0 0 2em #24c8db); +} + +.row { + display: flex; + justify-content: center; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +h1 { + text-align: center; +} + +input, +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + color: #0f0f0f; + background-color: #ffffff; + transition: border-color 0.25s; + box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); +} + +button { + cursor: pointer; +} + +button:hover { + border-color: #396cd8; +} +button:active { + border-color: #396cd8; + background-color: #e8e8e8; +} + +input, +button { + outline: none; +} + +#greet-input { + margin-right: 5px; +} + +@media (prefers-color-scheme: dark) { + :root { + color: #f6f6f6; + background-color: #2f2f2f; + } + + a:hover { + color: #24c8db; + } + + input, + button { + color: #ffffff; + background-color: #0f0f0f98; + } + button:active { + background-color: #0f0f0f69; + } +} diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/tsconfig.json b/packages/kbot/gui/app/plugins/deep-link/examples/app/tsconfig.json new file mode 100644 index 00000000..c0f337a2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "bundler", + "strict": true, + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "types": ["vite/client"], + "noEmit": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true + }, + "include": ["./src"] +} diff --git a/packages/kbot/gui/app/plugins/deep-link/examples/app/vite.config.ts b/packages/kbot/gui/app/plugins/deep-link/examples/app/vite.config.ts new file mode 100644 index 00000000..b54dc99a --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/examples/app/vite.config.ts @@ -0,0 +1,38 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { defineConfig } from 'vite' + +const host = process.env.TAURI_DEV_HOST + +// https://vitejs.dev/config/ +export default defineConfig({ + // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` + // prevent vite from obscuring rust errors + clearScreen: false, + // tauri expects a fixed port, fail if that port is not available + server: { + host: host || false, + port: 1420, + hmr: host + ? { + protocol: 'ws', + host, + port: 1421 + } + : undefined, + strictPort: true + }, + // to make use of `TAURI_DEBUG` and other env variables + // https://tauri.studio/v1/api/config#buildconfig.beforedevcommand + envPrefix: ['VITE_', 'TAURI_'], + build: { + // Tauri supports es2021 + target: process.env.TAURI_PLATFORM == 'windows' ? 'chrome105' : 'safari13', + // don't minify for debug builds + minify: !process.env.TAURI_DEBUG ? 'esbuild' : false, + // produce sourcemaps for debug builds + sourcemap: !!process.env.TAURI_DEBUG + } +}) diff --git a/packages/kbot/gui/app/plugins/deep-link/guest-js/index.ts b/packages/kbot/gui/app/plugins/deep-link/guest-js/index.ts new file mode 100644 index 00000000..461bec8a --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/guest-js/index.ts @@ -0,0 +1,105 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { invoke } from '@tauri-apps/api/core' +import { type UnlistenFn, listen } from '@tauri-apps/api/event' + +/** + * Get the current URLs that triggered the deep link. Use this on app load to check whether your app was started via a deep link. + * + * @example + * ```typescript + * import { getCurrent } from '@tauri-apps/plugin-deep-link'; + * const urls = await getCurrent(); + * ``` + * + * #### - **Windows / Linux**: This function reads the command line arguments and checks if there's only one value, which must be an URL with scheme matching one of the configured values. + * Note that you must manually check the arguments when registering deep link schemes dynamically with [`Self::register`]. + * Additionally, the deep link might have been provided as a CLI argument so you should check if its format matches what you expect.. + * + * @since 2.0.0 + */ +export async function getCurrent(): Promise { + return await invoke('plugin:deep-link|get_current') +} + +/** + * Register the app as the default handler for the specified protocol. + * + * @param protocol The name of the protocol without `://`. For example, if you want your app to handle `tauri://` links, call this method with `tauri` as the protocol. + * + * @example + * ```typescript + * import { register } from '@tauri-apps/plugin-deep-link'; + * await register("my-scheme"); + * ``` + * + * #### - **macOS / Android / iOS**: Unsupported. + * + * @since 2.0.0 + */ +export async function register(protocol: string): Promise { + return await invoke('plugin:deep-link|register', { protocol }) +} + +/** + * Unregister the app as the default handler for the specified protocol. + * + * @param protocol The name of the protocol without `://`. + * + * @example + * ```typescript + * import { unregister } from '@tauri-apps/plugin-deep-link'; + * await unregister("my-scheme"); + * ``` + * + * #### - **macOS / Linux / Android / iOS**: Unsupported. + * + * @since 2.0.0 + */ +export async function unregister(protocol: string): Promise { + return await invoke('plugin:deep-link|unregister', { protocol }) +} + +/** + * Check whether the app is the default handler for the specified protocol. + * + * @param protocol The name of the protocol without `://`. + * + * @example + * ```typescript + * import { isRegistered } from '@tauri-apps/plugin-deep-link'; + * await isRegistered("my-scheme"); + * ``` + * + * #### - **macOS / Android / iOS**: Unsupported. + * + * @since 2.0.0 + */ +export async function isRegistered(protocol: string): Promise { + return await invoke('plugin:deep-link|is_registered', { protocol }) +} + +/** + * Helper function for the `deep-link://new-url` event to run a function each time the protocol is triggered while the app is running. Use `getCurrent` on app load to check whether your app was started via a deep link. + * + * @param protocol The name of the protocol without `://`. + * + * @example + * ```typescript + * import { onOpenUrl } from '@tauri-apps/plugin-deep-link'; + * await onOpenUrl((urls) => { console.log(urls) }); + * ``` + * + * #### - **Windows / Linux**: Unsupported without the single-instance plugin. The OS will spawn a new app instance passing the URL as a CLI argument. + * + * @since 2.0.0 + */ +export async function onOpenUrl( + handler: (urls: string[]) => void +): Promise { + return await listen('deep-link://new-url', (event) => { + handler(event.payload) + }) +} diff --git a/packages/kbot/gui/app/plugins/deep-link/package.json b/packages/kbot/gui/app/plugins/deep-link/package.json new file mode 100644 index 00000000..7eebc79e --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/package.json @@ -0,0 +1,33 @@ +{ + "name": "@tauri-apps/plugin-deep-link", + "version": "2.4.3", + "description": "Set your Tauri application as the default handler for an URL", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + }, + "devDependencies": { + "@tauri-apps/cli": "2.8.4" + } +} diff --git a/packages/kbot/gui/app/plugins/deep-link/permissions/autogenerated/commands/get_current.toml b/packages/kbot/gui/app/plugins/deep-link/permissions/autogenerated/commands/get_current.toml new file mode 100644 index 00000000..e729de11 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/permissions/autogenerated/commands/get_current.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-get-current" +description = "Enables the get_current command without any pre-configured scope." +commands.allow = ["get_current"] + +[[permission]] +identifier = "deny-get-current" +description = "Denies the get_current command without any pre-configured scope." +commands.deny = ["get_current"] diff --git a/packages/kbot/gui/app/plugins/deep-link/permissions/autogenerated/commands/is_registered.toml b/packages/kbot/gui/app/plugins/deep-link/permissions/autogenerated/commands/is_registered.toml new file mode 100644 index 00000000..2dd73ace --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/permissions/autogenerated/commands/is_registered.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-is-registered" +description = "Enables the is_registered command without any pre-configured scope." +commands.allow = ["is_registered"] + +[[permission]] +identifier = "deny-is-registered" +description = "Denies the is_registered command without any pre-configured scope." +commands.deny = ["is_registered"] diff --git a/packages/kbot/gui/app/plugins/deep-link/permissions/autogenerated/commands/register.toml b/packages/kbot/gui/app/plugins/deep-link/permissions/autogenerated/commands/register.toml new file mode 100644 index 00000000..4eec17dc --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/permissions/autogenerated/commands/register.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-register" +description = "Enables the register command without any pre-configured scope." +commands.allow = ["register"] + +[[permission]] +identifier = "deny-register" +description = "Denies the register command without any pre-configured scope." +commands.deny = ["register"] diff --git a/packages/kbot/gui/app/plugins/deep-link/permissions/autogenerated/commands/unregister.toml b/packages/kbot/gui/app/plugins/deep-link/permissions/autogenerated/commands/unregister.toml new file mode 100644 index 00000000..5d33c97c --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/permissions/autogenerated/commands/unregister.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-unregister" +description = "Enables the unregister command without any pre-configured scope." +commands.allow = ["unregister"] + +[[permission]] +identifier = "deny-unregister" +description = "Denies the unregister command without any pre-configured scope." +commands.deny = ["unregister"] diff --git a/packages/kbot/gui/app/plugins/deep-link/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/deep-link/permissions/autogenerated/reference.md new file mode 100644 index 00000000..a8ef1874 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/permissions/autogenerated/reference.md @@ -0,0 +1,121 @@ +## Default Permission + +Allows reading the opened deep link via the get_current command + +#### This default permission set includes the following: + +- `allow-get-current` + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`deep-link:allow-get-current` + + + +Enables the get_current command without any pre-configured scope. + +
+ +`deep-link:deny-get-current` + + + +Denies the get_current command without any pre-configured scope. + +
+ +`deep-link:allow-is-registered` + + + +Enables the is_registered command without any pre-configured scope. + +
+ +`deep-link:deny-is-registered` + + + +Denies the is_registered command without any pre-configured scope. + +
+ +`deep-link:allow-register` + + + +Enables the register command without any pre-configured scope. + +
+ +`deep-link:deny-register` + + + +Denies the register command without any pre-configured scope. + +
+ +`deep-link:allow-unregister` + + + +Enables the unregister command without any pre-configured scope. + +
+ +`deep-link:deny-unregister` + + + +Denies the unregister command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/deep-link/permissions/default.toml b/packages/kbot/gui/app/plugins/deep-link/permissions/default.toml new file mode 100644 index 00000000..0c3cf2c0 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/permissions/default.toml @@ -0,0 +1,4 @@ +"$schema" = "schemas/schema.json" +[default] +description = "Allows reading the opened deep link via the get_current command" +permissions = ["allow-get-current"] diff --git a/packages/kbot/gui/app/plugins/deep-link/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/deep-link/permissions/schemas/schema.json new file mode 100644 index 00000000..c9fc2ceb --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/permissions/schemas/schema.json @@ -0,0 +1,354 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the get_current command without any pre-configured scope.", + "type": "string", + "const": "allow-get-current", + "markdownDescription": "Enables the get_current command without any pre-configured scope." + }, + { + "description": "Denies the get_current command without any pre-configured scope.", + "type": "string", + "const": "deny-get-current", + "markdownDescription": "Denies the get_current command without any pre-configured scope." + }, + { + "description": "Enables the is_registered command without any pre-configured scope.", + "type": "string", + "const": "allow-is-registered", + "markdownDescription": "Enables the is_registered command without any pre-configured scope." + }, + { + "description": "Denies the is_registered command without any pre-configured scope.", + "type": "string", + "const": "deny-is-registered", + "markdownDescription": "Denies the is_registered command without any pre-configured scope." + }, + { + "description": "Enables the register command without any pre-configured scope.", + "type": "string", + "const": "allow-register", + "markdownDescription": "Enables the register command without any pre-configured scope." + }, + { + "description": "Denies the register command without any pre-configured scope.", + "type": "string", + "const": "deny-register", + "markdownDescription": "Denies the register command without any pre-configured scope." + }, + { + "description": "Enables the unregister command without any pre-configured scope.", + "type": "string", + "const": "allow-unregister", + "markdownDescription": "Enables the unregister command without any pre-configured scope." + }, + { + "description": "Denies the unregister command without any pre-configured scope.", + "type": "string", + "const": "deny-unregister", + "markdownDescription": "Denies the unregister command without any pre-configured scope." + }, + { + "description": "Allows reading the opened deep link via the get_current command\n#### This default permission set includes:\n\n- `allow-get-current`", + "type": "string", + "const": "default", + "markdownDescription": "Allows reading the opened deep link via the get_current command\n#### This default permission set includes:\n\n- `allow-get-current`" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/rollup.config.js b/packages/kbot/gui/app/plugins/deep-link/rollup.config.js new file mode 100644 index 00000000..1f349ec8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/rollup.config.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' + +export default createConfig() diff --git a/packages/kbot/gui/app/plugins/deep-link/src/commands.rs b/packages/kbot/gui/app/plugins/deep-link/src/commands.rs new file mode 100644 index 00000000..078adfb1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/src/commands.rs @@ -0,0 +1,46 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tauri::{command, AppHandle, Runtime, State, Window}; + +use crate::{DeepLink, Result}; + +#[command] +pub(crate) async fn get_current( + _app: AppHandle, + _window: Window, + deep_link: State<'_, DeepLink>, +) -> Result>> { + deep_link.get_current() +} + +#[command] +pub(crate) async fn register( + _app: AppHandle, + _window: Window, + deep_link: State<'_, DeepLink>, + protocol: String, +) -> Result<()> { + deep_link.register(protocol) +} + +#[command] +pub(crate) async fn unregister( + _app: AppHandle, + _window: Window, + deep_link: State<'_, DeepLink>, + protocol: String, +) -> Result<()> { + deep_link.unregister(protocol) +} + +#[command] +pub(crate) async fn is_registered( + _app: AppHandle, + _window: Window, + deep_link: State<'_, DeepLink>, + protocol: String, +) -> Result { + deep_link.is_registered(protocol) +} diff --git a/packages/kbot/gui/app/plugins/deep-link/src/config.rs b/packages/kbot/gui/app/plugins/deep-link/src/config.rs new file mode 100644 index 00000000..49de68e3 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/src/config.rs @@ -0,0 +1,128 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +// This module is also imported in build.rs! + +use serde::{Deserialize, Deserializer}; +use tauri_utils::config::DeepLinkProtocol; + +#[derive(Deserialize, Clone)] +pub struct AssociatedDomain { + #[serde(default = "default_schemes")] + pub scheme: Vec, + #[serde(default, deserialize_with = "deserialize_associated_host")] + pub host: Option, // Optional custom uri schemes dont NEED a host (may have one still), but required for https/http schemes + #[serde(default)] + pub path: Vec, + #[serde(default, alias = "path-pattern", rename = "pathPattern")] + pub path_pattern: Vec, + #[serde(default, alias = "path-prefix", rename = "pathPrefix")] + pub path_prefix: Vec, + #[serde(default, alias = "path-suffix", rename = "pathSuffix")] + pub path_suffix: Vec, + #[serde(default, alias = "app-link", rename = "appLink")] + pub app_link: Option, +} + +impl AssociatedDomain { + /// Returns true if the domain uses http or https scheme. + pub fn is_web_link(&self) -> bool { + self.scheme.iter().any(|s| s == "https" || s == "http") + } + + /// Returns true if the domain uses http or https scheme and has proper host configuration. + pub fn is_app_link(&self) -> bool { + self.app_link + .unwrap_or_else(|| self.is_web_link() && self.host.is_some()) + } + + pub fn validate(&self) -> Result<(), String> { + // Rule 1: All web links require a host. + if self.is_web_link() && self.host.is_none() { + return Err("Web link requires a host".into()); + } + + // Rule 2: If it's an App Link, ensure http(s) and host. + if self.is_app_link() { + if !self.is_web_link() { + return Err("AppLink must be a valid web link (https/http + host)".into()); + } + if self.scheme.iter().any(|s| s == "http") && !self.scheme.iter().any(|s| s == "https") + { + eprintln!("Warning: AppLink uses only 'http' — allowed on Android but not secure for production."); + } + } + + Ok(()) + } +} + +// TODO: Consider removing this in v3 +fn default_schemes() -> Vec { + vec!["https".to_string(), "http".to_string()] +} + +fn deserialize_associated_host<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let opt = Option::::deserialize(deserializer)?; + if let Some(ref host) = opt { + if let Some((scheme, _)) = host.split_once("://") { + return Err(serde::de::Error::custom(format!( + "host `{host}` cannot start with a scheme, please remove the `{scheme}://` prefix" + ))); + } + } + Ok(opt) +} + +#[derive(Deserialize, Clone)] +pub struct Config { + /// Mobile requires `https://` urls. + #[serde(default)] + pub mobile: Vec, + /// Desktop requires urls starting with `://`. + /// These urls are also active in dev mode on Android. + #[allow(unused)] // Used in tauri-bundler + #[serde(default)] + pub desktop: DesktopProtocol, +} + +#[derive(Deserialize, Clone)] +#[serde(untagged)] +#[allow(unused)] // Used in tauri-bundler +pub enum DesktopProtocol { + One(DeepLinkProtocol), + List(Vec), +} + +impl Default for DesktopProtocol { + fn default() -> Self { + Self::List(Vec::new()) + } +} + +impl DesktopProtocol { + #[allow(dead_code)] + pub fn contains_scheme(&self, scheme: &String) -> bool { + match self { + Self::One(protocol) => protocol.schemes.contains(scheme), + Self::List(protocols) => protocols + .iter() + .any(|protocol| protocol.schemes.contains(scheme)), + } + } + + #[allow(dead_code)] + pub fn schemes(&self) -> Vec { + match self { + Self::One(protocol) => protocol.schemes.clone(), + Self::List(protocols) => protocols + .iter() + .flat_map(|protocol| protocol.schemes.clone()) + .collect(), + } + } +} diff --git a/packages/kbot/gui/app/plugins/deep-link/src/error.rs b/packages/kbot/gui/app/plugins/deep-link/src/error.rs new file mode 100644 index 00000000..41eb764f --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/src/error.rs @@ -0,0 +1,48 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{ser::Serializer, Serialize}; + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("unsupported platform")] + UnsupportedPlatform, + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + Tauri(#[from] tauri::Error), + #[cfg(target_os = "windows")] + #[error(transparent)] + Windows(#[from] windows_result::Error), + #[cfg(target_os = "linux")] + #[error(transparent)] + Ini(#[from] ini::Error), + #[cfg(target_os = "linux")] + #[error(transparent)] + ParseIni(#[from] ini::ParseError), + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), +} + +// TODO(v3): change this into an error in v3, +// see . +#[inline] +#[cfg(target_os = "linux")] +pub(crate) fn inspect_command_error<'a>(command: &'a str) -> impl Fn(&std::io::Error) + 'a { + move |e| { + tracing::error!("Failed to run OS command `{command}`: {e}"); + } +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/packages/kbot/gui/app/plugins/deep-link/src/lib.rs b/packages/kbot/gui/app/plugins/deep-link/src/lib.rs new file mode 100644 index 00000000..9db882c4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/src/lib.rs @@ -0,0 +1,539 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tauri::{ + plugin::{Builder, PluginApi, TauriPlugin}, + AppHandle, EventId, Listener, Manager, Runtime, +}; + +mod commands; +mod config; +mod error; + +pub use error::{Error, Result}; + +#[cfg(target_os = "android")] +const PLUGIN_IDENTIFIER: &str = "app.tauri.deep_link"; + +fn init_deep_link( + app: &AppHandle, + api: PluginApi>, +) -> crate::Result> { + #[cfg(target_os = "android")] + { + let _api = api; + + use tauri::{ + ipc::{Channel, InvokeResponseBody}, + Emitter, + }; + + let handle = _api.register_android_plugin(PLUGIN_IDENTIFIER, "DeepLinkPlugin")?; + + #[derive(serde::Deserialize)] + struct Event { + url: String, + } + + let app_handle = app.clone(); + handle.run_mobile_plugin::<()>( + "setEventHandler", + imp::EventHandler { + handler: Channel::new(move |event| { + let url = match event { + InvokeResponseBody::Json(payload) => { + serde_json::from_str::(&payload) + .ok() + .map(|payload| payload.url) + } + _ => None, + }; + + let _ = app_handle.emit("deep-link://new-url", vec![url]); + + Ok(()) + }), + }, + )?; + + return Ok(DeepLink { + app: app.clone(), + plugin_handle: handle, + }); + } + + #[cfg(target_os = "ios")] + return Ok(DeepLink { + app: app.clone(), + current: Default::default(), + config: api.config().clone(), + }); + + #[cfg(desktop)] + { + let args = std::env::args(); + let deep_link = DeepLink { + app: app.clone(), + current: Default::default(), + config: api.config().clone(), + }; + deep_link.handle_cli_arguments(args); + + Ok(deep_link) + } +} + +#[cfg(target_os = "android")] +mod imp { + use tauri::{ipc::Channel, plugin::PluginHandle, AppHandle, Runtime}; + + use serde::{Deserialize, Serialize}; + + #[derive(Serialize)] + #[serde(rename_all = "camelCase")] + pub struct EventHandler { + pub handler: Channel, + } + + #[derive(Debug, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct GetCurrentResponse { + pub url: Option, + } + + /// Access to the deep-link APIs. + pub struct DeepLink { + pub(crate) app: AppHandle, + pub(crate) plugin_handle: PluginHandle, + } + + impl DeepLink { + /// Get the current URLs that triggered the deep link. Use this on app load to check whether your app was started via a deep link. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux**: This function reads the command line arguments and checks if there's only one value, which must be an URL with scheme matching one of the configured values. + /// Note that you must manually check the arguments when registering deep link schemes dynamically with [`Self::register`]. + /// Additionally, the deep link might have been provided as a CLI argument so you should check if its format matches what you expect. + pub fn get_current(&self) -> crate::Result>> { + self.plugin_handle + .run_mobile_plugin::("getCurrent", ()) + .map(|v| v.url.map(|url| vec![url])) + .map_err(Into::into) + } + + /// Register the app as the default handler for the specified protocol. + /// + /// - `protocol`: The name of the protocol without `://`. For example, if you want your app to handle `tauri://` links, call this method with `tauri` as the protocol. + /// + /// ## Platform-specific: + /// + /// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`). + pub fn register>(&self, _protocol: S) -> crate::Result<()> { + Err(crate::Error::UnsupportedPlatform) + } + + /// Unregister the app as the default handler for the specified protocol. + /// + /// - `protocol`: The name of the protocol without `://`. + /// + /// ## Platform-specific: + /// + /// - **Linux**: Can only unregister the scheme if it was initially registered with [`register`](`Self::register`). May not work on older distros. + /// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`). + pub fn unregister>(&self, _protocol: S) -> crate::Result<()> { + Err(crate::Error::UnsupportedPlatform) + } + + /// Check whether the app is the default handler for the specified protocol. + /// + /// - `protocol`: The name of the protocol without `://`. + /// + /// ## Platform-specific: + /// + /// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`). + pub fn is_registered>(&self, _protocol: S) -> crate::Result { + Err(crate::Error::UnsupportedPlatform) + } + } +} + +#[cfg(not(target_os = "android"))] +mod imp { + use std::sync::Mutex; + #[cfg(target_os = "linux")] + use std::{ + fs::{create_dir_all, File}, + io::Write, + process::Command, + }; + #[cfg(target_os = "linux")] + use tauri::Manager; + use tauri::{AppHandle, Runtime}; + #[cfg(windows)] + use windows_registry::{CLASSES_ROOT, CURRENT_USER, LOCAL_MACHINE}; + + /// Access to the deep-link APIs. + pub struct DeepLink { + pub(crate) app: AppHandle, + pub(crate) current: Mutex>>, + pub(crate) config: Option, + } + + impl DeepLink { + /// Checks if the provided list of arguments (which should match [`std::env::args`]) + /// contains a deep link argument (for Linux and Windows). + /// + /// On Linux and Windows the deep links trigger a new app instance with the deep link URL as its only argument. + /// + /// This function does what it can to verify if the argument is actually a deep link, though it could also be a regular CLI argument. + /// To enhance its checks, we only match deep links against the schemes defined in the Tauri configuration + /// i.e. dynamic schemes WON'T be processed. + /// + /// This function updates the [`Self::get_current`] value and emits a `deep-link://new-url` event. + #[cfg(desktop)] + pub fn handle_cli_arguments, I: Iterator>(&self, mut args: I) { + use tauri::Emitter; + + let Some(config) = &self.config else { + return; + }; + + if cfg!(windows) || cfg!(target_os = "linux") { + args.next(); // bin name + let arg = args.next(); + + let maybe_deep_link = args.next().is_none(); // single argument + if !maybe_deep_link { + return; + } + + if let Some(url) = arg.and_then(|arg| arg.as_ref().parse::().ok()) { + if config.desktop.contains_scheme(&url.scheme().to_string()) { + let mut current = self.current.lock().unwrap(); + current.replace(vec![url.clone()]); + let _ = self.app.emit("deep-link://new-url", vec![url]); + } else if cfg!(debug_assertions) { + tracing::warn!("argument {url} does not match any configured deep link scheme; skipping it"); + } + } + } + } + + /// Get the current URLs that triggered the deep link. Use this on app load to check whether your app was started via a deep link. + /// + /// ## Platform-specific: + /// + /// - **Windows / Linux**: This function reads the command line arguments and checks if there's only one value, which must be an URL with scheme matching one of the configured values. + /// Note that you must manually check the arguments when registering deep link schemes dynamically with [`Self::register`]. + /// Additionally, the deep link might have been provided as a CLI argument so you should check if its format matches what you expect. + pub fn get_current(&self) -> crate::Result>> { + return Ok(self.current.lock().unwrap().clone()); + } + + /// Registers all schemes defined in the configuration file. + /// + /// This is useful to ensure the schemes are registered even if the user did not install the app properly + /// (e.g. an AppImage that was not properly registered with an AppImage launcher). + pub fn register_all(&self) -> crate::Result<()> { + let Some(config) = &self.config else { + return Ok(()); + }; + + for scheme in config.desktop.schemes() { + self.register(scheme)?; + } + + Ok(()) + } + + /// Register the app as the default handler for the specified protocol. + /// + /// - `protocol`: The name of the protocol without `://`. For example, if you want your app to handle `tauri://` links, call this method with `tauri` as the protocol. + /// + /// ## Platform-specific: + /// + /// - **Linux**: Needs the `xdg-mime` and `update-desktop-database` commands available on the system. + /// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`). + pub fn register>(&self, _protocol: S) -> crate::Result<()> { + #[cfg(windows)] + { + let protocol = _protocol.as_ref(); + let key_base = format!("Software\\Classes\\{protocol}"); + + let exe = dunce::simplified(&tauri::utils::platform::current_exe()?) + .display() + .to_string(); + + let key_reg = CURRENT_USER.create(&key_base)?; + key_reg.set_string("", format!("URL:{} protocol", self.app.config().identifier))?; + key_reg.set_string("URL Protocol", "")?; + + let icon_reg = CURRENT_USER.create(format!("{key_base}\\DefaultIcon"))?; + icon_reg.set_string("", format!("{exe},0"))?; + + let cmd_reg = CURRENT_USER.create(format!("{key_base}\\shell\\open\\command"))?; + + cmd_reg.set_string("", format!("\"{exe}\" \"%1\""))?; + + Ok(()) + } + + #[cfg(target_os = "linux")] + { + let bin = tauri::utils::platform::current_exe()?; + let file_name = format!( + "{}-handler.desktop", + bin.file_name().unwrap().to_string_lossy() + ); + let appimage = self.app.env().appimage; + let exec = appimage + .clone() + .unwrap_or_else(|| bin.into_os_string()) + .to_string_lossy() + .to_string(); + + let target = self.app.path().data_dir()?.join("applications"); + + create_dir_all(&target)?; + + let target_file = target.join(&file_name); + + let mime_type = format!("x-scheme-handler/{}", _protocol.as_ref()); + + if let Ok(mut desktop_file) = ini::Ini::load_from_file(&target_file) { + if let Some(section) = desktop_file.section_mut(Some("Desktop Entry")) { + // it's ok to remove it - we only write to the file if it's missing + // and in that case we include old_mimes + let old_mimes = section.remove("MimeType").unwrap_or_default(); + + if !old_mimes.split(';').any(|mime| mime == mime_type) { + section.append("MimeType", format!("{mime_type};{old_mimes}")); + desktop_file.write_to_file(&target_file)?; + } + } + } else { + let mut file = File::create(target_file)?; + file.write_all( + format!( + include_str!("template.desktop"), + name = self + .app + .config() + .product_name + .clone() + .unwrap_or_else(|| file_name.clone()), + exec = exec, + mime_type = mime_type + ) + .as_bytes(), + )?; + } + + Command::new("update-desktop-database") + .arg(target) + .status() + .inspect_err(crate::error::inspect_command_error( + "update-desktop-database", + ))?; + + Command::new("xdg-mime") + .args(["default", &file_name, mime_type.as_str()]) + .status() + .inspect_err(crate::error::inspect_command_error("xdg-mime"))?; + + Ok(()) + } + + #[cfg(not(any(windows, target_os = "linux")))] + Err(crate::Error::UnsupportedPlatform) + } + + /// Unregister the app as the default handler for the specified protocol. + /// + /// - `protocol`: The name of the protocol without `://`. + /// + /// ## Platform-specific: + /// + /// - **Windows**: Requires admin rights if the protocol is registered on local machine + /// (this can happen when registered from the NSIS installer when the install mode is set to both or per machine) + /// - **Linux**: Can only unregister the scheme if it was initially registered with [`register`](`Self::register`). May not work on older distros. + /// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`). + pub fn unregister>(&self, _protocol: S) -> crate::Result<()> { + #[cfg(windows)] + { + let protocol = _protocol.as_ref(); + let path = format!("Software\\Classes\\{protocol}"); + if LOCAL_MACHINE.open(&path).is_ok() { + LOCAL_MACHINE.remove_tree(&path)?; + } + if CURRENT_USER.open(&path).is_ok() { + CURRENT_USER.remove_tree(&path)?; + } + Ok(()) + } + + #[cfg(target_os = "linux")] + { + let mimeapps_path = self.app.path().config_dir()?.join("mimeapps.list"); + let mut mimeapps = ini::Ini::load_from_file(&mimeapps_path)?; + + let file_name = format!( + "{}-handler.desktop", + tauri::utils::platform::current_exe()? + .file_name() + .unwrap() + .to_string_lossy() + ); + + if let Some(section) = mimeapps.section_mut(Some("Default Applications")) { + let scheme = format!("x-scheme-handler/{}", _protocol.as_ref()); + + if section.get(&scheme).unwrap_or_default() == file_name { + section.remove(scheme); + } + } + + mimeapps.write_to_file(mimeapps_path)?; + + Ok(()) + } + + #[cfg(not(any(windows, target_os = "linux")))] + Err(crate::Error::UnsupportedPlatform) + } + + /// Check whether the app is the default handler for the specified protocol. + /// + /// - `protocol`: The name of the protocol without `://`. + /// + /// ## Platform-specific: + /// + /// - **Linux**: Needs the `xdg-mime` command available on the system. + /// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`). + pub fn is_registered>(&self, _protocol: S) -> crate::Result { + #[cfg(windows)] + { + let protocol = _protocol.as_ref(); + let Ok(cmd_reg) = CLASSES_ROOT.open(format!("{protocol}\\shell\\open\\command")) + else { + return Ok(false); + }; + + let registered_cmd = cmd_reg.get_string("")?; + + let exe = dunce::simplified(&tauri::utils::platform::current_exe()?) + .display() + .to_string(); + + Ok(registered_cmd == format!("\"{exe}\" \"%1\"")) + } + #[cfg(target_os = "linux")] + { + let file_name = format!( + "{}-handler.desktop", + tauri::utils::platform::current_exe()? + .file_name() + .unwrap() + .to_string_lossy() + ); + + let output = Command::new("xdg-mime") + .args([ + "query", + "default", + &format!("x-scheme-handler/{}", _protocol.as_ref()), + ]) + .output() + .inspect_err(crate::error::inspect_command_error("xdg-mime"))?; + + Ok(String::from_utf8_lossy(&output.stdout).contains(&file_name)) + } + + #[cfg(not(any(windows, target_os = "linux")))] + Err(crate::Error::UnsupportedPlatform) + } + } +} + +pub use imp::DeepLink; +use url::Url; + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the deep-link APIs. +pub trait DeepLinkExt { + fn deep_link(&self) -> &DeepLink; +} + +impl> crate::DeepLinkExt for T { + fn deep_link(&self) -> &DeepLink { + self.state::>().inner() + } +} + +/// Event that is triggered when the app was requested to open a new URL. +/// +/// Typed [`tauri::Event`]. +pub struct OpenUrlEvent { + id: EventId, + urls: Vec, +} + +impl OpenUrlEvent { + /// The event ID which can be used to stop listening to the event via [`tauri::Listener::unlisten`]. + pub fn id(&self) -> EventId { + self.id + } + + /// The event URLs. + pub fn urls(self) -> Vec { + self.urls + } +} + +impl DeepLink { + /// Helper function for the `deep-link://new-url` event to run a function each time the protocol is triggered while the app is running. + /// + /// Use `get_current` on app load to check whether your app was started via a deep link. + pub fn on_open_url(&self, f: F) -> EventId { + let event_id = self.app.listen("deep-link://new-url", move |event| { + if let Ok(urls) = serde_json::from_str(event.payload()) { + f(OpenUrlEvent { + id: event.id(), + urls, + }) + } + }); + + event_id + } +} + +/// Initializes the plugin. +pub fn init() -> TauriPlugin> { + Builder::new("deep-link") + .invoke_handler(tauri::generate_handler![ + commands::get_current, + commands::register, + commands::unregister, + commands::is_registered + ]) + .setup(|app, api| { + app.manage(init_deep_link(app, api)?); + Ok(()) + }) + .on_event(|_app, _event| { + #[cfg(any(target_os = "macos", target_os = "ios"))] + if let tauri::RunEvent::Opened { urls } = _event { + use tauri::Emitter; + + let _ = _app.emit("deep-link://new-url", urls); + _app.state::>() + .current + .lock() + .unwrap() + .replace(urls.clone()); + } + }) + .build() +} diff --git a/packages/kbot/gui/app/plugins/deep-link/src/template.desktop b/packages/kbot/gui/app/plugins/deep-link/src/template.desktop new file mode 100644 index 00000000..0fb89abb --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/src/template.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Type=Application +Name={name} +Exec={exec} %u +Terminal=false +MimeType={mime_type} +NoDisplay=true \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/deep-link/tsconfig.json b/packages/kbot/gui/app/plugins/deep-link/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/deep-link/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/dialog/CHANGELOG.md b/packages/kbot/gui/app/plugins/dialog/CHANGELOG.md new file mode 100644 index 00000000..fd5f8dd2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/CHANGELOG.md @@ -0,0 +1,280 @@ +# Changelog + +## \[2.4.0] + +- [`509eba8d`](https://github.com/tauri-apps/plugins-workspace/commit/509eba8d441c4f6ecf0af77b572cb2afd69a752d) ([#2641](https://github.com/tauri-apps/plugins-workspace/pull/2641) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Add support for showing a message dialog with 3 buttons. + +## \[2.3.3] + +### Dependencies + +- Upgraded to `fs-js@2.4.2` + +## \[2.3.2] + +- [`af08c66f`](https://github.com/tauri-apps/plugins-workspace/commit/af08c66faafe0dffc4b0a80aef030cd3f0f89a9c) ([#2871](https://github.com/tauri-apps/plugins-workspace/pull/2871) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused the file picker not to open on Android when extension filters were set. + +## \[2.3.1] + +### Dependencies + +- Upgraded to `fs-js@2.4.1` + +## \[2.3.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +### Dependencies + +- Upgraded to `fs-js@2.4.0` + +## \[2.2.2] + +### Dependencies + +- Upgraded to `fs-js@2.3.0` + +## \[2.2.1] + +### Dependencies + +- Upgraded to `fs-js@2.2.1` + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +### Dependencies + +- Upgraded to `fs-js@2.1.0` + +## \[2.0.2] + +### Dependencies + +- Upgraded to `fs-js@2.0.4` + +## \[2.0.4] + +- [`76f99ce9`](https://github.com/tauri-apps/plugins-workspace/commit/76f99ce999a2ff9e40235c1675e3eb6570b5e1e2) ([#2108](https://github.com/tauri-apps/plugins-workspace/pull/2108) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The `Dialog` struct is now correctly exported, primarily to fix the documentation on `docs.rs`. + +### Dependencies + +- Upgraded to `fs@2.1.0` + +## \[2.0.3] + +### Dependencies + +- Upgraded to `fs@2.0.3` + +## \[2.0.1] + +- [`2302c2db`](https://github.com/tauri-apps/plugins-workspace/commit/2302c2db1c49673e61dcbda8cdb01b2c57e9ba6f) ([#1910](https://github.com/tauri-apps/plugins-workspace/pull/1910) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Fix `ask` and `confirm` not using system button texts +- [`aee14ed4`](https://github.com/tauri-apps/plugins-workspace/commit/aee14ed4261cdedc4ed7cc2686f01f437859a5c7) ([#1892](https://github.com/tauri-apps/plugins-workspace/pull/1892) by [@nashaofu](https://github.com/tauri-apps/plugins-workspace/../../nashaofu)) Set `save` dialog mime type from the `filters` extensions on Android. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +### Dependencies + +- Upgraded to `fs@2.0.1` + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +### Dependencies + +- Upgraded to `fs@2.0.0` + +## \[2.0.0-rc.8] + +- [`6bf1bd8d`](https://github.com/tauri-apps/plugins-workspace/commit/6bf1bd8d44bb95618590aa066e638509b014e0f9) ([#1805](https://github.com/tauri-apps/plugins-workspace/pull/1805) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update rfd to 0.15 + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.6` + +### breaking + +- [`04459afb`](https://github.com/tauri-apps/plugins-workspace/commit/04459afbb67aafa5cd57e6a148c2beb0a8d3e04a) ([#1842](https://github.com/tauri-apps/plugins-workspace/pull/1842) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Changed `MessageDialogBuilder::ok_button_label` and `MessageDialogBuilder::cancel_button_label` to `MessageDialogBuilder::buttons` which takes an enum now + +## \[2.0.0-rc.7] + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.5` + +## \[2.0.0-rc.6] + +- [`2b898f07`](https://github.com/tauri-apps/plugins-workspace/commit/2b898f078688c57309ca17962bf02e665c406514) ([#1769](https://github.com/tauri-apps/plugins-workspace/pull/1769) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update Tauri scopes (asset protocol) when using the `open()` command to select directories. + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.4` + +## \[2.0.0-rc.5] + +- [`a2fe5551`](https://github.com/tauri-apps/plugins-workspace/commit/a2fe55512f908dd11c814ce021d164f01677572a) ([#1727](https://github.com/tauri-apps/plugins-workspace/pull/1727) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Add utility methods on `FilePath` and `SafeFilePath` enums which are: + + - `path` + - `simplified` + - `into_path` +- [`a2fe5551`](https://github.com/tauri-apps/plugins-workspace/commit/a2fe55512f908dd11c814ce021d164f01677572a) ([#1727](https://github.com/tauri-apps/plugins-workspace/pull/1727) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Implement `Serialize`, `Deserialize`, `From`, `TryFrom` and `FromStr` traits for `FilePath` and `SafeFilePath` enums. +- [`a2fe5551`](https://github.com/tauri-apps/plugins-workspace/commit/a2fe55512f908dd11c814ce021d164f01677572a) ([#1727](https://github.com/tauri-apps/plugins-workspace/pull/1727) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Mark `Error` enum as `#[non_exhuastive]`. +- [`a2fe5551`](https://github.com/tauri-apps/plugins-workspace/commit/a2fe55512f908dd11c814ce021d164f01677572a) ([#1727](https://github.com/tauri-apps/plugins-workspace/pull/1727) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Add `SafeFilePath` enum. + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.3` + +## \[2.0.0-rc.4] + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.2` + +### breaking + +- [`0cb99bda`](https://github.com/tauri-apps/plugins-workspace/commit/0cb99bdaf11b5a9bb66b80bdf40b085d87c3066d) ([#1706](https://github.com/tauri-apps/plugins-workspace/pull/1706) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) If no filters are specified, the file picker dialog now defaults to a file selection instead of photos. + +### feat + +- [`feb1e93f`](https://github.com/tauri-apps/plugins-workspace/commit/feb1e93fcb9a913c002daa29e3b709f24b97c664) ([#1707](https://github.com/tauri-apps/plugins-workspace/pull/1707) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Implement `save` API on iOS. + +## \[2.0.0-rc.1] + +- [`448846b8`](https://github.com/tauri-apps/plugins-workspace/commit/448846b834d23df6e7c5dc66c5dd9aa0cb01846d) ([#1658](https://github.com/tauri-apps/plugins-workspace/pull/1658) by [@mikoto2000](https://github.com/tauri-apps/plugins-workspace/../../mikoto2000)) The `open` function now returns a string representing either the file path or URI instead of an object. + To read the file data, use the `fs` APIs. +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.2] + +- [`b9147758`](https://github.com/tauri-apps/plugins-workspace/commit/b914775898c2bee7ceb20bd17ee595005cd17a64) ([#1679](https://github.com/tauri-apps/plugins-workspace/pull/1679) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Explicitly set a minimum macOS version for the Swift package. + +## \[2.0.0-rc.1] + +### feat + +- [`bc7eecf4`](https://github.com/tauri-apps/plugins-workspace/commit/bc7eecf4202e7d23b987440c1cbd2da0f68c8bef) ([#1657](https://github.com/tauri-apps/plugins-workspace/pull/1657) by [@mikoto2000](https://github.com/tauri-apps/plugins-workspace/../../mikoto2000)) Implement `save` API on Android. + +### changes + +- [`6b079cfd`](https://github.com/tauri-apps/plugins-workspace/commit/6b079cfdd107c94abc2c7300f6af00bac3ff4040) ([#1649](https://github.com/tauri-apps/plugins-workspace/pull/1649) by [@ahqsoftwares](https://github.com/tauri-apps/plugins-workspace/../../ahqsoftwares)) Remove targetSdk from build.kts files as it is deprecated and will be removed from DSL v9.0 + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +## \[2.0.0-beta.8] + +- [`99d6ac0f`](https://github.com/tauri-apps/plugins-workspace/commit/99d6ac0f9506a6a4a1aa59c728157190a7441af6) ([#1606](https://github.com/tauri-apps/plugins-workspace/pull/1606) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The JS packages now specify the *minimum* `@tauri-apps/api` version instead of a single exact version. +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.7] + +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.6] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.5] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.4] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.3] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.6] + +- [`326df688`](https://github.com/tauri-apps/plugins-workspace/commit/326df6883998d416fc0837583ed972854628bb52)([#1236](https://github.com/tauri-apps/plugins-workspace/pull/1236)) Fixes command argument parsing on iOS. + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.6` + +## \[2.0.0-beta.5] + +- [`bb51a41`](https://github.com/tauri-apps/plugins-workspace/commit/bb51a41d67ebf989e8aedf10c4b1a7f9514d1bdf)([#1168](https://github.com/tauri-apps/plugins-workspace/pull/1168)) **Breaking Change:** All apis that return paths to the frontend will now remove the `\\?\` UNC prefix on Windows. + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.5` + +## \[2.0.0-beta.4] + +- [`4cd8112`](https://github.com/tauri-apps/plugins-workspace/commit/4cd81126fdf25e1847546f8fdbd924aa4bfeabb5)([#1056](https://github.com/tauri-apps/plugins-workspace/pull/1056)) Fixed an issue where dialogs on android would return the Content URI instead of the file path + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.4` + +## \[2.0.0-beta.3] + +- [`35ea595`](https://github.com/tauri-apps/plugins-workspace/commit/35ea5956d060f0bdafd140f2541c607bb811805b)([#1073](https://github.com/tauri-apps/plugins-workspace/pull/1073)) Fixed an issue where the dialog apis panicked when they were called with no application windows open. +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.3` + +## \[2.0.0-beta.2] + +- [`aa25c91`](https://github.com/tauri-apps/plugins-workspace/commit/aa25c91bb01e957872fb2b606a3acaf2f2c4ef3a)([#978](https://github.com/tauri-apps/plugins-workspace/pull/978)) Allow configuring `canCreateDirectories` for open and save dialogs on macOS, if not configured, it will be set to `true` by default. +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. + +## \[2.0.0-alpha.7] + +### Dependencies + +- Upgraded to `fs@2.0.0-alpha.7` + +## \[2.0.0-alpha.5] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.13. + +## \[2.0.0-alpha.4] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.12. +- [`2e2c0a1`](https://github.com/tauri-apps/plugins-workspace/commit/2e2c0a1b958fcbc7de855ef1d305b11855f2eb8e)([#769](https://github.com/tauri-apps/plugins-workspace/pull/769)) Fix incorrect result for dialog messages. + +## \[2.0.0-alpha.3] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to @tauri-apps/api v2.0.0-alpha.11. + +## \[2.0.0-alpha.2] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update to @tauri-apps/api v2.0.0-alpha.9. + +## \[2.0.0-alpha.2] + +- [`4e2cef9`](https://github.com/tauri-apps/plugins-workspace/commit/4e2cef9b702bbbb9cf4ee17de50791cb21f1b2a4)([#593](https://github.com/tauri-apps/plugins-workspace/pull/593)) Update to alpha.12. + +### Dependencies + +- Upgraded to `fs@2.0.0-alpha.2` + +## \[2.0.0-alpha.1] + +- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11. + +## \[2.0.0-alpha.0] + +- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! + d6e80b)([#545](https://github.com/tauri-apps/plugins-workspace/pull/545)) Fixes docs.rs build by enabling the `tauri/dox` feature flag. diff --git a/packages/kbot/gui/app/plugins/dialog/Cargo.toml b/packages/kbot/gui/app/plugins/dialog/Cargo.toml new file mode 100644 index 00000000..7856e68d --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "tauri-plugin-dialog" +version = "2.4.0" +description = "Native system dialogs for opening and saving files along with message dialogs on your Tauri application." +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-dialog" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] +targets = ["x86_64-unknown-linux-gnu", "x86_64-linux-android"] + +[package.metadata.platforms.support] +windows = { level = "full", notes = "" } +linux = { level = "full", notes = "" } +macos = { level = "full", notes = "" } +android = { level = "partial", notes = "Does not support folder picker" } +ios = { level = "partial", notes = "Does not support folder picker" } + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } + +[dev-dependencies] +tauri = { workspace = true, features = ["wry"] } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } +url = { workspace = true } +tauri-plugin-fs = { path = "../fs", version = "2.4.2" } + +[target.'cfg(target_os = "ios")'.dependencies] +tauri = { workspace = true, features = ["wry"] } + +[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] +rfd = { version = "0.15", default-features = false, features = [ + "tokio", + "gtk3", + "common-controls-v6", +] } +raw-window-handle = "0.6" diff --git a/packages/kbot/gui/app/plugins/dialog/LICENSE.spdx b/packages/kbot/gui/app/plugins/dialog/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/dialog/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/dialog/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/dialog/LICENSE_MIT b/packages/kbot/gui/app/plugins/dialog/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/dialog/README.md b/packages/kbot/gui/app/plugins/dialog/README.md new file mode 100644 index 00000000..8c4f0c22 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/README.md @@ -0,0 +1,89 @@ +![plugin-dialog](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/dialog/banner.png) + +Native system dialogs for opening and saving files along with message dialogs. + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | ✓ | +| iOS | ✓ | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-dialog = "2.0.0" +# alternatively with Git: +tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +```sh +pnpm add @tauri-apps/plugin-dialog +# or +npm add @tauri-apps/plugin-dialog +# or +yarn add @tauri-apps/plugin-dialog +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_dialog::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript + +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/dialog/SECURITY.md b/packages/kbot/gui/app/plugins/dialog/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/dialog/android/.gitignore b/packages/kbot/gui/app/plugins/dialog/android/.gitignore new file mode 100644 index 00000000..c0f21ec2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/android/.gitignore @@ -0,0 +1,2 @@ +/build +/.tauri diff --git a/packages/kbot/gui/app/plugins/dialog/android/build.gradle.kts b/packages/kbot/gui/app/plugins/dialog/android/build.gradle.kts new file mode 100644 index 00000000..e824cff9 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/android/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "app.tauri.dialog" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.0") + implementation("com.google.android.material:material:1.7.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + implementation(project(":tauri-android")) +} diff --git a/packages/kbot/gui/app/plugins/dialog/android/proguard-rules.pro b/packages/kbot/gui/app/plugins/dialog/android/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/dialog/android/settings.gradle b/packages/kbot/gui/app/plugins/dialog/android/settings.gradle new file mode 100644 index 00000000..14a752e4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/android/settings.gradle @@ -0,0 +1,2 @@ +include ':tauri-android' +project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/packages/kbot/gui/app/plugins/dialog/android/src/androidTest/java/ExampleInstrumentedTest.kt b/packages/kbot/gui/app/plugins/dialog/android/src/androidTest/java/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..6c9d837e --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/android/src/androidTest/java/ExampleInstrumentedTest.kt @@ -0,0 +1,28 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.dialog + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("app.tauri.dialog", appContext.packageName) + } +} diff --git a/packages/kbot/gui/app/plugins/dialog/android/src/main/AndroidManifest.xml b/packages/kbot/gui/app/plugins/dialog/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..9a40236b --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/packages/kbot/gui/app/plugins/dialog/android/src/main/java/DialogPlugin.kt b/packages/kbot/gui/app/plugins/dialog/android/src/main/java/DialogPlugin.kt new file mode 100644 index 00000000..b9359635 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/android/src/main/java/DialogPlugin.kt @@ -0,0 +1,236 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.dialog + +import android.app.Activity +import android.app.AlertDialog +import android.content.Intent +import android.net.Uri +import android.os.Handler +import android.os.Looper +import android.webkit.MimeTypeMap +import androidx.activity.result.ActivityResult +import app.tauri.Logger +import app.tauri.annotation.ActivityCallback +import app.tauri.annotation.Command +import app.tauri.annotation.InvokeArg +import app.tauri.annotation.TauriPlugin +import app.tauri.plugin.Invoke +import app.tauri.plugin.JSArray +import app.tauri.plugin.JSObject +import app.tauri.plugin.Plugin + +@InvokeArg +class Filter { + lateinit var extensions: Array +} + +@InvokeArg +class FilePickerOptions { + lateinit var filters: Array + var multiple: Boolean? = null +} + +@InvokeArg +class MessageOptions { + var title: String? = null + lateinit var message: String + var okButtonLabel: String? = null + var noButtonLabel: String? = null + var cancelButtonLabel: String? = null +} + +@InvokeArg +class SaveFileDialogOptions { + var fileName: String? = null + lateinit var filters: Array +} + +@TauriPlugin +class DialogPlugin(private val activity: Activity): Plugin(activity) { + var filePickerOptions: FilePickerOptions? = null + + @Command + fun showFilePicker(invoke: Invoke) { + try { + val args = invoke.parseArgs(FilePickerOptions::class.java) + val parsedTypes = parseFiltersOption(args.filters) + + // TODO: ACTION_OPEN_DOCUMENT ?? + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.type = "*/*" + + if (parsedTypes.isNotEmpty()) { + intent.putExtra(Intent.EXTRA_MIME_TYPES, parsedTypes) + } + + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, args.multiple ?: false) + + startActivityForResult(invoke, intent, "filePickerResult") + } catch (ex: Exception) { + val message = ex.message ?: "Failed to pick file" + Logger.error(message) + invoke.reject(message) + } + } + + @ActivityCallback + fun filePickerResult(invoke: Invoke, result: ActivityResult) { + try { + when (result.resultCode) { + Activity.RESULT_OK -> { + val callResult = createPickFilesResult(result.data) + invoke.resolve(callResult) + } + Activity.RESULT_CANCELED -> invoke.reject("File picker cancelled") + else -> invoke.reject("Failed to pick files") + } + } catch (ex: java.lang.Exception) { + val message = ex.message ?: "Failed to read file pick result" + Logger.error(message) + invoke.reject(message) + } + } + + private fun createPickFilesResult(data: Intent?): JSObject { + val callResult = JSObject() + if (data == null) { + callResult.put("files", null) + return callResult + } + val uris: MutableList = ArrayList() + if (data.clipData == null) { + val uri: Uri? = data.data + uris.add(uri?.toString()) + } else { + for (i in 0 until data.clipData!!.itemCount) { + val uri: Uri = data.clipData!!.getItemAt(i).uri + uris.add(uri.toString()) + } + } + callResult.put("files", JSArray.from(uris.toTypedArray())) + return callResult + } + + private fun parseFiltersOption(filters: Array): Array { + val mimeTypes = mutableListOf() + for (filter in filters) { + for (ext in filter.extensions) { + if (ext.contains('/')) { + mimeTypes.add(if (ext == "text/csv") "text/comma-separated-values" else ext) + } else { + MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext)?.let { + mimeTypes.add(it) + } + } + } + } + return mimeTypes.toTypedArray() + } + + @Command + fun showMessageDialog(invoke: Invoke) { + val args = invoke.parseArgs(MessageOptions::class.java) + + if (activity.isFinishing) { + invoke.reject("App is finishing") + return + } + + val handler = { value: String -> + val ret = JSObject() + ret.put("value", value) + invoke.resolve(ret) + } + + Handler(Looper.getMainLooper()) + .post { + val builder = AlertDialog.Builder(activity) + + if (args.title != null) { + builder.setTitle(args.title) + } + + val okButtonLabel = args.okButtonLabel ?: "Ok" + + builder + .setMessage(args.message) + .setPositiveButton(okButtonLabel) { dialog, _ -> + dialog.dismiss() + handler(okButtonLabel) + } + .setOnCancelListener { dialog -> + dialog.dismiss() + handler(args.cancelButtonLabel ?: "Cancel") + } + + if (args.noButtonLabel != null) { + builder.setNeutralButton(args.noButtonLabel) { dialog, _ -> + dialog.dismiss() + handler(args.noButtonLabel!!) + } + } + + if (args.cancelButtonLabel != null) { + builder.setNegativeButton( args.cancelButtonLabel) { dialog, _ -> + dialog.dismiss() + handler(args.cancelButtonLabel!!) + } + } + + val dialog = builder.create() + dialog.show() + } + } + + @Command + fun saveFileDialog(invoke: Invoke) { + try { + val args = invoke.parseArgs(SaveFileDialogOptions::class.java) + val parsedTypes = parseFiltersOption(args.filters) + + val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.putExtra(Intent.EXTRA_TITLE, args.fileName ?: "") + intent.type = "*/*" + + if (parsedTypes.isNotEmpty()) { + intent.putExtra(Intent.EXTRA_MIME_TYPES, parsedTypes) + } + + startActivityForResult(invoke, intent, "saveFileDialogResult") + } catch (ex: Exception) { + val message = ex.message ?: "Failed to pick save file" + Logger.error(message) + invoke.reject(message) + } + } + + @ActivityCallback + fun saveFileDialogResult(invoke: Invoke, result: ActivityResult) { + try { + when (result.resultCode) { + Activity.RESULT_OK -> { + val callResult = JSObject() + val intent: Intent? = result.data + if (intent != null) { + val uri = intent.data + if (uri != null) { + callResult.put("file", uri.toString()) + } + } + invoke.resolve(callResult) + } + Activity.RESULT_CANCELED -> invoke.reject("File picker cancelled") + else -> invoke.reject("Failed to pick files") + } + } catch (ex: java.lang.Exception) { + val message = ex.message ?: "Failed to read file pick result" + Logger.error(message) + invoke.reject(message) + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/dialog/android/src/main/java/FilePickerUtils.kt b/packages/kbot/gui/app/plugins/dialog/android/src/main/java/FilePickerUtils.kt new file mode 100644 index 00000000..7029d4c6 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/android/src/main/java/FilePickerUtils.kt @@ -0,0 +1,230 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.dialog + + +import android.content.ContentUris +import android.database.Cursor +import android.provider.MediaStore +import android.content.Context +import android.graphics.BitmapFactory +import android.media.MediaMetadataRetriever +import android.net.Uri +import android.os.Environment +import android.provider.DocumentsContract +import android.provider.OpenableColumns +import android.util.Base64 +import app.tauri.Logger +import java.io.ByteArrayOutputStream +import java.io.FileNotFoundException +import java.io.IOException +import java.io.InputStream + +class FilePickerUtils { + class FileResolution(var height: Int, var width: Int) + + companion object { + fun getPathFromUri(context: Context, uri: Uri): String? { + if (DocumentsContract.isDocumentUri(context, uri)) { + if (isExternalStorageDocument(uri)) { + val docId = DocumentsContract.getDocumentId(uri) + val split = docId.split(":") + return if ("primary".equals(split[0], ignoreCase = true)) { + "${Environment.getExternalStorageDirectory()}/${split[1]}" + } else { + null + } + } else if (isDownloadsDocument(uri)) { + val id = DocumentsContract.getDocumentId(uri) + val contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id)) + return getDataColumn(context, contentUri, null, null) + } else if (isMediaDocument(uri)) { + val docId = DocumentsContract.getDocumentId(uri) + val split = docId.split(":") + val contentUri: Uri? = when (split[0]) { + "image" -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI + "video" -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI + "audio" -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + else -> null + } + val selection = "_id=?" + val selectionArgs = arrayOf(split[1]) + return getDataColumn(context, contentUri, selection, selectionArgs) + } + } else if ("content".equals(uri.scheme, ignoreCase = true)) { + return getDataColumn(context, uri, null, null) + } else if ("file".equals(uri.scheme, ignoreCase = true)) { + return uri.path + } + return null + } + + fun getNameFromUri(context: Context, uri: Uri): String? { + var displayName: String? = "" + val projection = arrayOf(OpenableColumns.DISPLAY_NAME) + val cursor = + context.contentResolver.query(uri, projection, null, null, null) + if (cursor != null) { + cursor.moveToFirst() + val columnIdx = cursor.getColumnIndex(projection[0]) + displayName = cursor.getString(columnIdx) + cursor.close() + } + if (displayName.isNullOrEmpty()) { + displayName = uri.lastPathSegment + } + return displayName + } + + fun getDataFromUri(context: Context, uri: Uri): String { + try { + val stream = context.contentResolver.openInputStream(uri) ?: return "" + val bytes = getBytesFromInputStream(stream) + return Base64.encodeToString(bytes, Base64.NO_WRAP) + } catch (e: FileNotFoundException) { + Logger.error("openInputStream failed.", e) + } catch (e: IOException) { + Logger.error("getBytesFromInputStream failed.", e) + } + return "" + } + + fun getMimeTypeFromUri(context: Context, uri: Uri): String? { + return context.contentResolver.getType(uri) + } + + fun getModifiedAtFromUri(context: Context, uri: Uri): Long? { + return try { + var modifiedAt: Long = 0 + val cursor = + context.contentResolver.query(uri, null, null, null, null) + if (cursor != null) { + cursor.moveToFirst() + val columnIdx = + cursor.getColumnIndex(DocumentsContract.Document.COLUMN_LAST_MODIFIED) + modifiedAt = cursor.getLong(columnIdx) + cursor.close() + } + modifiedAt + } catch (e: Exception) { + Logger.error("getModifiedAtFromUri failed.", e) + null + } + } + + fun getSizeFromUri(context: Context, uri: Uri): Long { + var size: Long = 0 + val projection = arrayOf(OpenableColumns.SIZE) + val cursor = + context.contentResolver.query(uri, projection, null, null, null) + if (cursor != null) { + cursor.moveToFirst() + val columnIdx = cursor.getColumnIndex(projection[0]) + size = cursor.getLong(columnIdx) + cursor.close() + } + return size + } + + fun getDurationFromUri(context: Context, uri: Uri): Long? { + if (isVideoUri(context, uri)) { + val retriever = MediaMetadataRetriever() + retriever.setDataSource(context, uri) + val time = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION) + val durationMs = time?.toLong() ?: 0 + try { + retriever.release() + } catch (e: Exception) { + Logger.error("MediaMetadataRetriever.release() failed.", e) + } + return durationMs / 1000L + } + return null + } + + fun getHeightAndWidthFromUri(context: Context, uri: Uri): FileResolution? { + if (isImageUri(context, uri)) { + val options = BitmapFactory.Options() + options.inJustDecodeBounds = true + return try { + BitmapFactory.decodeStream( + context.contentResolver.openInputStream(uri), + null, + options + ) + FileResolution(options.outHeight, options.outWidth) + } catch (exception: FileNotFoundException) { + exception.printStackTrace() + null + } + } else if (isVideoUri(context, uri)) { + val retriever = MediaMetadataRetriever() + retriever.setDataSource(context, uri) + val width = + Integer.valueOf(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) ?: "0") + val height = + Integer.valueOf(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT) ?: "0") + try { + retriever.release() + } catch (e: Exception) { + Logger.error("MediaMetadataRetriever.release() failed.", e) + } + return FileResolution(height, width) + } + return null + } + + private fun isImageUri(context: Context, uri: Uri): Boolean { + val mimeType = getMimeTypeFromUri(context, uri) ?: return false + return mimeType.startsWith("image") + } + + private fun isVideoUri(context: Context, uri: Uri): Boolean { + val mimeType = getMimeTypeFromUri(context, uri) ?: return false + return mimeType.startsWith("video") + } + + @Throws(IOException::class) + private fun getBytesFromInputStream(`is`: InputStream): ByteArray { + val os = ByteArrayOutputStream() + val buffer = ByteArray(0xFFFF) + var len = `is`.read(buffer) + while (len != -1) { + os.write(buffer, 0, len) + len = `is`.read(buffer) + } + return os.toByteArray() + } + } +} + +private fun getDataColumn(context: Context, uri: Uri?, selection: String?, selectionArgs: Array?): String? { + var cursor: Cursor? = null + val column = "_data" + val projection = arrayOf(column) + try { + cursor = context.contentResolver.query(uri!!, projection, selection, selectionArgs, null) + if (cursor != null && cursor.moveToFirst()) { + val columnIndex = cursor.getColumnIndexOrThrow(column) + return cursor.getString(columnIndex) + } + } finally { + cursor?.close() + } + return null +} + +private fun isExternalStorageDocument(uri: Uri): Boolean { + return "com.android.externalstorage.documents" == uri.authority +} + +private fun isDownloadsDocument(uri: Uri): Boolean { + return "com.android.providers.downloads.documents" == uri.authority +} + +private fun isMediaDocument(uri: Uri): Boolean { + return "com.android.providers.media.documents" == uri.authority +} diff --git a/packages/kbot/gui/app/plugins/dialog/android/src/test/java/ExampleUnitTest.kt b/packages/kbot/gui/app/plugins/dialog/android/src/test/java/ExampleUnitTest.kt new file mode 100644 index 00000000..00369a3f --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/android/src/test/java/ExampleUnitTest.kt @@ -0,0 +1,21 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.dialog + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/packages/kbot/gui/app/plugins/dialog/api-iife.js b/packages/kbot/gui/app/plugins/dialog/api-iife.js new file mode 100644 index 00000000..a357f2c0 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_DIALOG__=function(t){"use strict";async function n(t,n={},e){return window.__TAURI_INTERNALS__.invoke(t,n,e)}function e(t){if(void 0!==t)return"string"==typeof t?t:"ok"in t&&"cancel"in t?{OkCancelCustom:[t.ok,t.cancel]}:"yes"in t&&"no"in t&&"cancel"in t?{YesNoCancelCustom:[t.yes,t.no,t.cancel]}:"ok"in t?{OkCustom:t.ok}:void 0}return"function"==typeof SuppressedError&&SuppressedError,t.ask=async function(t,e){const o="string"==typeof e?{title:e}:e;return await n("plugin:dialog|ask",{message:t.toString(),title:o?.title?.toString(),kind:o?.kind,yesButtonLabel:o?.okLabel?.toString(),noButtonLabel:o?.cancelLabel?.toString()})},t.confirm=async function(t,e){const o="string"==typeof e?{title:e}:e;return await n("plugin:dialog|confirm",{message:t.toString(),title:o?.title?.toString(),kind:o?.kind,okButtonLabel:o?.okLabel?.toString(),cancelButtonLabel:o?.cancelLabel?.toString()})},t.message=async function(t,o){const i="string"==typeof o?{title:o}:o;return n("plugin:dialog|message",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,okButtonLabel:i?.okLabel?.toString(),buttons:e(i?.buttons)})},t.open=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|open",{options:t})},t.save=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|save",{options:t})},t}({});Object.defineProperty(window.__TAURI__,"dialog",{value:__TAURI_PLUGIN_DIALOG__})} diff --git a/packages/kbot/gui/app/plugins/dialog/banner.png b/packages/kbot/gui/app/plugins/dialog/banner.png new file mode 100644 index 00000000..88be34e4 Binary files /dev/null and b/packages/kbot/gui/app/plugins/dialog/banner.png differ diff --git a/packages/kbot/gui/app/plugins/dialog/build.rs b/packages/kbot/gui/app/plugins/dialog/build.rs new file mode 100644 index 00000000..4b3bb871 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/build.rs @@ -0,0 +1,18 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +const COMMANDS: &[&str] = &["open", "save", "message", "ask", "confirm"]; + +fn main() { + let result = tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .android_path("android") + .ios_path("ios") + .try_build(); + + // when building documentation for Android the plugin build result is always Err() and is irrelevant to the crate documentation build + if !(cfg!(docsrs) && std::env::var("TARGET").unwrap().contains("android")) { + result.unwrap(); + } +} diff --git a/packages/kbot/gui/app/plugins/dialog/guest-js/index.ts b/packages/kbot/gui/app/plugins/dialog/guest-js/index.ts new file mode 100644 index 00000000..a7785754 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/guest-js/index.ts @@ -0,0 +1,440 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { invoke } from '@tauri-apps/api/core' + +/** + * Extension filters for the file dialog. + * + * @since 2.0.0 + */ +interface DialogFilter { + /** Filter name. */ + name: string + /** + * Extensions to filter, without a `.` prefix. + * @example + * ```typescript + * extensions: ['svg', 'png'] + * ``` + */ + extensions: string[] +} + +/** + * Options for the open dialog. + * + * @since 2.0.0 + */ +interface OpenDialogOptions { + /** The title of the dialog window (desktop only). */ + title?: string + /** The filters of the dialog. */ + filters?: DialogFilter[] + /** + * Initial directory or file path. + * If it's a directory path, the dialog interface will change to that folder. + * If it's not an existing directory, the file name will be set to the dialog's file name input and the dialog will be set to the parent folder. + * + * On mobile the file name is always used on the dialog's file name input. + * If not provided, Android uses `(invalid).txt` as default file name. + */ + defaultPath?: string + /** Whether the dialog allows multiple selection or not. */ + multiple?: boolean + /** Whether the dialog is a directory selection or not. */ + directory?: boolean + /** + * If `directory` is true, indicates that it will be read recursively later. + * Defines whether subdirectories will be allowed on the scope or not. + */ + recursive?: boolean + /** Whether to allow creating directories in the dialog. Enabled by default. **macOS Only** */ + canCreateDirectories?: boolean +} + +/** + * Options for the save dialog. + * + * @since 2.0.0 + */ +interface SaveDialogOptions { + /** The title of the dialog window (desktop only). */ + title?: string + /** The filters of the dialog. */ + filters?: DialogFilter[] + /** + * Initial directory or file path. + * If it's a directory path, the dialog interface will change to that folder. + * If it's not an existing directory, the file name will be set to the dialog's file name input and the dialog will be set to the parent folder. + * + * On mobile the file name is always used on the dialog's file name input. + * If not provided, Android uses `(invalid).txt` as default file name. + */ + defaultPath?: string + /** Whether to allow creating directories in the dialog. Enabled by default. **macOS Only** */ + canCreateDirectories?: boolean +} + +/** + * Default buttons for a message dialog. + * + * @since 2.4.0 + */ +export type MessageDialogDefaultButtons = + | 'Ok' + | 'OkCancel' + | 'YesNo' + | 'YesNoCancel' + +/** All possible button keys. */ +type ButtonKey = 'ok' | 'cancel' | 'yes' | 'no' + +/** Ban everything except a set of keys. */ +type BanExcept = Partial< + Record, never> +> + +/** + * The Yes, No and Cancel buttons of a message dialog. + * + * @since 2.4.0 + */ +export type MessageDialogButtonsYesNoCancel = { + /** The Yes button. */ + yes: string + /** The No button. */ + no: string + /** The Cancel button. */ + cancel: string +} & BanExcept<'yes' | 'no' | 'cancel'> + +/** + * The Ok and Cancel buttons of a message dialog. + * + * @since 2.4.0 + */ +export type MessageDialogButtonsOkCancel = { + /** The Ok button. */ + ok: string + /** The Cancel button. */ + cancel: string +} & BanExcept<'ok' | 'cancel'> + +/** + * The Ok button of a message dialog. + * + * @since 2.4.0 + */ +export type MessageDialogButtonsOk = { + /** The Ok button. */ + ok: string +} & BanExcept<'ok'> + +/** + * Custom buttons for a message dialog. + * + * @since 2.4.0 + */ +export type MessageDialogCustomButtons = + | MessageDialogButtonsYesNoCancel + | MessageDialogButtonsOkCancel + | MessageDialogButtonsOk + +/** + * The buttons of a message dialog. + * + * @since 2.4.0 + */ +export type MessageDialogButtons = + | MessageDialogDefaultButtons + | MessageDialogCustomButtons + +/** + * @since 2.0.0 + */ +interface MessageDialogOptions { + /** The title of the dialog. Defaults to the app name. */ + title?: string + /** The kind of the dialog. Defaults to `info`. */ + kind?: 'info' | 'warning' | 'error' + /** + * The label of the Ok button. + * + * @deprecated Use {@linkcode MessageDialogOptions.buttons} instead. + */ + okLabel?: string + /** + * The buttons of the dialog. + * + * @example + * + * ```ts + * // Use system default buttons texts + * await message('Hello World!', { buttons: 'Ok' }) + * await message('Hello World!', { buttons: 'OkCancel' }) + * + * // Or with custom button texts + * await message('Hello World!', { buttons: { ok: 'Yes!' } }) + * await message('Take on the task?', { + * buttons: { ok: 'Accept', cancel: 'Cancel' } + * }) + * await message('Show the file content?', { + * buttons: { yes: 'Show content', no: 'Show in folder', cancel: 'Cancel' } + * }) + * ``` + * + * @since 2.4.0 + */ + buttons?: MessageDialogButtons +} + +/** + * Internal function to convert the buttons to the Rust type. + */ +function buttonsToRust(buttons: MessageDialogButtons | undefined) { + if (buttons === undefined) { + return undefined + } + + if (typeof buttons === 'string') { + return buttons + } else if ('ok' in buttons && 'cancel' in buttons) { + return { OkCancelCustom: [buttons.ok, buttons.cancel] } + } else if ('yes' in buttons && 'no' in buttons && 'cancel' in buttons) { + return { + YesNoCancelCustom: [buttons.yes, buttons.no, buttons.cancel] + } + } else if ('ok' in buttons) { + return { OkCustom: buttons.ok } + } + + return undefined +} + +interface ConfirmDialogOptions { + /** The title of the dialog. Defaults to the app name. */ + title?: string + /** The kind of the dialog. Defaults to `info`. */ + kind?: 'info' | 'warning' | 'error' + /** The label of the confirm button. */ + okLabel?: string + /** The label of the cancel button. */ + cancelLabel?: string +} + +type OpenDialogReturn = T['directory'] extends true + ? T['multiple'] extends true + ? string[] | null + : string | null + : T['multiple'] extends true + ? string[] | null + : string | null + +/** + * Open a file/directory selection dialog. + * + * The selected paths are added to the filesystem and asset protocol scopes. + * When security is more important than the easy of use of this API, + * prefer writing a dedicated command instead. + * + * Note that the scope change is not persisted, so the values are cleared when the application is restarted. + * You can save it to the filesystem using [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope). + * @example + * ```typescript + * import { open } from '@tauri-apps/plugin-dialog'; + * // Open a selection dialog for image files + * const selected = await open({ + * multiple: true, + * filters: [{ + * name: 'Image', + * extensions: ['png', 'jpeg'] + * }] + * }); + * if (Array.isArray(selected)) { + * // user selected multiple files + * } else if (selected === null) { + * // user cancelled the selection + * } else { + * // user selected a single file + * } + * ``` + * + * @example + * ```typescript + * import { open } from '@tauri-apps/plugin-dialog'; + * import { appDir } from '@tauri-apps/api/path'; + * // Open a selection dialog for directories + * const selected = await open({ + * directory: true, + * multiple: true, + * defaultPath: await appDir(), + * }); + * if (Array.isArray(selected)) { + * // user selected multiple directories + * } else if (selected === null) { + * // user cancelled the selection + * } else { + * // user selected a single directory + * } + * ``` + * + * @returns A promise resolving to the selected path(s) + * + * @since 2.0.0 + */ +async function open( + options: T = {} as T +): Promise> { + if (typeof options === 'object') { + Object.freeze(options) + } + + return await invoke('plugin:dialog|open', { options }) +} + +/** + * Open a file/directory save dialog. + * + * The selected path is added to the filesystem and asset protocol scopes. + * When security is more important than the easy of use of this API, + * prefer writing a dedicated command instead. + * + * Note that the scope change is not persisted, so the values are cleared when the application is restarted. + * You can save it to the filesystem using [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope). + * @example + * ```typescript + * import { save } from '@tauri-apps/plugin-dialog'; + * const filePath = await save({ + * filters: [{ + * name: 'Image', + * extensions: ['png', 'jpeg'] + * }] + * }); + * ``` + * + * @returns A promise resolving to the selected path. + * + * @since 2.0.0 + */ +async function save(options: SaveDialogOptions = {}): Promise { + if (typeof options === 'object') { + Object.freeze(options) + } + + return await invoke('plugin:dialog|save', { options }) +} + +/** + * The result of a message dialog. + * + * The result is a string if the dialog has custom buttons, + * otherwise it is one of the default buttons. + * + * @since 2.4.0 + */ +export type MessageDialogResult = 'Yes' | 'No' | 'Ok' | 'Cancel' | (string & {}) + +/** + * Shows a message dialog with an `Ok` button. + * @example + * ```typescript + * import { message } from '@tauri-apps/plugin-dialog'; + * await message('Tauri is awesome', 'Tauri'); + * await message('File not found', { title: 'Tauri', kind: 'error' }); + * ``` + * + * @param message The message to show. + * @param options The dialog's options. If a string, it represents the dialog title. + * + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.0.0 + * + */ +async function message( + message: string, + options?: string | MessageDialogOptions +): Promise { + const opts = typeof options === 'string' ? { title: options } : options + + return invoke('plugin:dialog|message', { + message: message.toString(), + title: opts?.title?.toString(), + kind: opts?.kind, + okButtonLabel: opts?.okLabel?.toString(), + buttons: buttonsToRust(opts?.buttons) + }) +} + +/** + * Shows a question dialog with `Yes` and `No` buttons. + * @example + * ```typescript + * import { ask } from '@tauri-apps/plugin-dialog'; + * const yes = await ask('Are you sure?', 'Tauri'); + * const yes2 = await ask('This action cannot be reverted. Are you sure?', { title: 'Tauri', kind: 'warning' }); + * ``` + * + * @param message The message to show. + * @param options The dialog's options. If a string, it represents the dialog title. + * + * @returns A promise resolving to a boolean indicating whether `Yes` was clicked or not. + * + * @since 2.0.0 + */ +async function ask( + message: string, + options?: string | ConfirmDialogOptions +): Promise { + const opts = typeof options === 'string' ? { title: options } : options + return await invoke('plugin:dialog|ask', { + message: message.toString(), + title: opts?.title?.toString(), + kind: opts?.kind, + yesButtonLabel: opts?.okLabel?.toString(), + noButtonLabel: opts?.cancelLabel?.toString() + }) +} + +/** + * Shows a question dialog with `Ok` and `Cancel` buttons. + * @example + * ```typescript + * import { confirm } from '@tauri-apps/plugin-dialog'; + * const confirmed = await confirm('Are you sure?', 'Tauri'); + * const confirmed2 = await confirm('This action cannot be reverted. Are you sure?', { title: 'Tauri', kind: 'warning' }); + * ``` + * + * @param message The message to show. + * @param options The dialog's options. If a string, it represents the dialog title. + * + * @returns A promise resolving to a boolean indicating whether `Ok` was clicked or not. + * + * @since 2.0.0 + */ +async function confirm( + message: string, + options?: string | ConfirmDialogOptions +): Promise { + const opts = typeof options === 'string' ? { title: options } : options + return await invoke('plugin:dialog|confirm', { + message: message.toString(), + title: opts?.title?.toString(), + kind: opts?.kind, + okButtonLabel: opts?.okLabel?.toString(), + cancelButtonLabel: opts?.cancelLabel?.toString() + }) +} + +export type { + DialogFilter, + OpenDialogOptions, + OpenDialogReturn, + SaveDialogOptions, + MessageDialogOptions, + ConfirmDialogOptions +} + +export { open, save, message, ask, confirm } diff --git a/packages/kbot/gui/app/plugins/dialog/guest-js/init.ts b/packages/kbot/gui/app/plugins/dialog/guest-js/init.ts new file mode 100644 index 00000000..520a469a --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/guest-js/init.ts @@ -0,0 +1,18 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { invoke } from '@tauri-apps/api/core' + +window.alert = function (message: string) { + void invoke('plugin:dialog|message', { + message: message.toString() + }) +} + +// @ts-expect-error tauri does not have sync IPC :( +window.confirm = async function (message: string) { + return await invoke('plugin:dialog|confirm', { + message: message.toString() + }) +} diff --git a/packages/kbot/gui/app/plugins/dialog/ios/.gitignore b/packages/kbot/gui/app/plugins/dialog/ios/.gitignore new file mode 100644 index 00000000..5922fdaa --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/ios/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Package.resolved diff --git a/packages/kbot/gui/app/plugins/dialog/ios/Package.swift b/packages/kbot/gui/app/plugins/dialog/ios/Package.swift new file mode 100644 index 00000000..f8983f14 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/ios/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version:5.3 +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import PackageDescription + +let package = Package( + name: "tauri-plugin-dialog", + platforms: [ + .macOS(.v10_13), + .iOS(.v13), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "tauri-plugin-dialog", + type: .static, + targets: ["tauri-plugin-dialog"]) + ], + dependencies: [ + .package(name: "Tauri", path: "../.tauri/tauri-api") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "tauri-plugin-dialog", + dependencies: [ + .byName(name: "Tauri") + ], + path: "Sources") + ] +) diff --git a/packages/kbot/gui/app/plugins/dialog/ios/README.md b/packages/kbot/gui/app/plugins/dialog/ios/README.md new file mode 100644 index 00000000..157cf032 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/ios/README.md @@ -0,0 +1,3 @@ +# Tauri Plugin Dialog + +A description of this package. diff --git a/packages/kbot/gui/app/plugins/dialog/ios/Sources/DialogPlugin.swift b/packages/kbot/gui/app/plugins/dialog/ios/Sources/DialogPlugin.swift new file mode 100644 index 00000000..710fd0bb --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/ios/Sources/DialogPlugin.swift @@ -0,0 +1,244 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import MobileCoreServices +import Photos +import PhotosUI +import SwiftRs +import Tauri +import UIKit +import WebKit + +enum FilePickerEvent { + case selected([URL]) + case cancelled + case error(String) +} + +struct MessageDialogOptions: Decodable { + var title: String? + let message: String + var okButtonLabel: String? + var noButtonLabel: String? + var cancelButtonLabel: String? +} + +struct Filter: Decodable { + var extensions: [String]? +} + +struct FilePickerOptions: Decodable { + var multiple: Bool? + var filters: [Filter]? + var defaultPath: String? +} + +struct SaveFileDialogOptions: Decodable { + var fileName: String? + var defaultPath: String? +} + +class DialogPlugin: Plugin { + + var filePickerController: FilePickerController! + var onFilePickerResult: ((FilePickerEvent) -> Void)? = nil + + override init() { + super.init() + filePickerController = FilePickerController(self) + } + + @objc public func showFilePicker(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(FilePickerOptions.self) + + let parsedTypes = parseFiltersOption(args.filters ?? []) + + var isMedia = !parsedTypes.isEmpty + var uniqueMimeType: Bool? = nil + var mimeKind: String? = nil + if !parsedTypes.isEmpty { + uniqueMimeType = true + for mime in parsedTypes { + let kind = mime.components(separatedBy: "/")[0] + if kind != "image" && kind != "video" { + isMedia = false + } + if mimeKind == nil { + mimeKind = kind + } else if mimeKind != kind { + uniqueMimeType = false + } + } + } + + onFilePickerResult = { (event: FilePickerEvent) -> Void in + switch event { + case .selected(let urls): + invoke.resolve(["files": urls]) + case .cancelled: + invoke.resolve(["files": nil]) + case .error(let error): + invoke.reject(error) + } + } + + if uniqueMimeType == true || isMedia { + DispatchQueue.main.async { + if #available(iOS 14, *) { + var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared()) + configuration.selectionLimit = (args.multiple ?? false) ? 0 : 1 + + if uniqueMimeType == true { + if mimeKind == "image" { + configuration.filter = .images + } else if mimeKind == "video" { + configuration.filter = .videos + } + } + + let picker = PHPickerViewController(configuration: configuration) + picker.delegate = self.filePickerController + picker.modalPresentationStyle = .fullScreen + self.presentViewController(picker) + } else { + let picker = UIImagePickerController() + picker.delegate = self.filePickerController + + if uniqueMimeType == true && mimeKind == "image" { + picker.sourceType = .photoLibrary + } + + picker.sourceType = .photoLibrary + picker.modalPresentationStyle = .fullScreen + self.presentViewController(picker) + } + } + } else { + let documentTypes = parsedTypes.isEmpty ? ["public.data"] : parsedTypes + DispatchQueue.main.async { + let picker = UIDocumentPickerViewController(documentTypes: documentTypes, in: .import) + if let defaultPath = args.defaultPath { + picker.directoryURL = URL(string: defaultPath) + } + picker.delegate = self.filePickerController + picker.allowsMultipleSelection = args.multiple ?? false + picker.modalPresentationStyle = .fullScreen + self.presentViewController(picker) + } + } + } + + @objc public func saveFileDialog(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(SaveFileDialogOptions.self) + + // The Tauri save dialog API prompts the user to select a path where a file must be saved + // This behavior maps to the operating system interfaces on all platforms except iOS, + // which only exposes a mechanism to "move file `srcPath` to a location defined by the user" + // + // so we have to work around it by creating an empty file matching the requested `args.fileName`, + // and using it as `srcPath` for the operation - returning the path the user selected + // so the app dev can write to it later - matching cross platform behavior as mentioned above + let fileManager = FileManager.default + let srcFolder = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! + let srcPath = srcFolder.appendingPathComponent(args.fileName ?? "file") + if !fileManager.fileExists(atPath: srcPath.path) { + // the file contents must be actually provided by the tauri dev after the path is resolved by the save API + try "".write(to: srcPath, atomically: true, encoding: .utf8) + } + + onFilePickerResult = { (event: FilePickerEvent) -> Void in + switch event { + case .selected(let urls): + invoke.resolve(["file": urls.first!]) + case .cancelled: + invoke.resolve(["file": nil]) + case .error(let error): + invoke.reject(error) + } + } + + DispatchQueue.main.async { + let picker = UIDocumentPickerViewController(url: srcPath, in: .exportToService) + if let defaultPath = args.defaultPath { + picker.directoryURL = URL(string: defaultPath) + } + picker.delegate = self.filePickerController + picker.modalPresentationStyle = .fullScreen + self.presentViewController(picker) + } + } + + private func presentViewController(_ viewControllerToPresent: UIViewController) { + self.manager.viewController?.present(viewControllerToPresent, animated: true, completion: nil) + } + + private func parseFiltersOption(_ filters: [Filter]) -> [String] { + var parsedTypes: [String] = [] + for filter in filters { + for ext in filter.extensions ?? [] { + guard + let utType: String = UTTypeCreatePreferredIdentifierForTag( + kUTTagClassMIMEType, ext as CFString, nil)?.takeRetainedValue() as String? + else { + continue + } + parsedTypes.append(utType) + } + } + return parsedTypes + } + + public func onFilePickerEvent(_ event: FilePickerEvent) { + self.onFilePickerResult?(event) + } + + @objc public func showMessageDialog(_ invoke: Invoke) throws { + let manager = self.manager + let args = try invoke.parseArgs(MessageDialogOptions.self) + + DispatchQueue.main.async { [] in + let alert = UIAlertController( + title: args.title, message: args.message, preferredStyle: UIAlertController.Style.alert) + + if let cancelButtonLabel = args.cancelButtonLabel { + alert.addAction( + UIAlertAction( + title: cancelButtonLabel, style: UIAlertAction.Style.default, + handler: { (_) -> Void in + invoke.resolve(["value": cancelButtonLabel]) + } + ) + ) + } + + if let noButtonLabel = args.noButtonLabel { + alert.addAction( + UIAlertAction( + title: noButtonLabel, style: UIAlertAction.Style.default, + handler: { (_) -> Void in + invoke.resolve(["value": noButtonLabel]) + } + ) + ) + } + + let okButtonLabel = args.okButtonLabel ?? "Ok" + alert.addAction( + UIAlertAction( + title: okButtonLabel, style: UIAlertAction.Style.default, + handler: { (_) -> Void in + invoke.resolve(["value": okButtonLabel]) + } + ) + ) + + manager.viewController?.present(alert, animated: true, completion: nil) + } + } +} + +@_cdecl("init_plugin_dialog") +func initPlugin() -> Plugin { + return DialogPlugin() +} diff --git a/packages/kbot/gui/app/plugins/dialog/ios/Sources/FilePickerController.swift b/packages/kbot/gui/app/plugins/dialog/ios/Sources/FilePickerController.swift new file mode 100644 index 00000000..b2752f0b --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/ios/Sources/FilePickerController.swift @@ -0,0 +1,233 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import UIKit +import MobileCoreServices +import PhotosUI +import Photos +import Tauri + +public class FilePickerController: NSObject { + var plugin: DialogPlugin + + init(_ dialogPlugin: DialogPlugin) { + plugin = dialogPlugin + } + + private func dismissViewController(_ viewControllerToPresent: UIViewController, completion: (() -> Void)? = nil) { + viewControllerToPresent.dismiss(animated: true, completion: completion) + } + + public func getModifiedAtFromUrl(_ url: URL) -> Int? { + do { + let attributes = try FileManager.default.attributesOfItem(atPath: url.path) + if let modifiedDateInSec = (attributes[.modificationDate] as? Date)?.timeIntervalSince1970 { + return Int(modifiedDateInSec * 1000.0) + } else { + return nil + } + } catch let error as NSError { + Logger.error("getModifiedAtFromUrl failed", error.localizedDescription) + return nil + } + } + + public func getMimeTypeFromUrl(_ url: URL) -> String { + let fileExtension = url.pathExtension as CFString + guard let extUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension, nil)?.takeUnretainedValue() else { + return "" + } + guard let mimeUTI = UTTypeCopyPreferredTagWithClass(extUTI, kUTTagClassMIMEType) else { + return "" + } + return mimeUTI.takeRetainedValue() as String + } + + public func getSizeFromUrl(_ url: URL) throws -> Int { + let values = try url.resourceValues(forKeys: [.fileSizeKey]) + return values.fileSize ?? 0 + } + + public func getVideoDuration(_ url: URL) -> Int { + let asset = AVAsset(url: url) + let duration = asset.duration + let durationTime = CMTimeGetSeconds(duration) + return Int(round(durationTime)) + } + + public func getImageDimensions(_ url: URL) -> (Int?, Int?) { + if let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil) { + if let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as Dictionary? { + return getHeightAndWidthFromImageProperties(imageProperties) + } + } + return (nil, nil) + } + + public func getVideoDimensions(_ url: URL) -> (Int?, Int?) { + guard let track = AVURLAsset(url: url).tracks(withMediaType: AVMediaType.video).first else { return (nil, nil) } + let size = track.naturalSize.applying(track.preferredTransform) + let height = abs(Int(size.height)) + let width = abs(Int(size.width)) + return (height, width) + } + + private func getHeightAndWidthFromImageProperties(_ properties: [NSObject: AnyObject]) -> (Int?, Int?) { + let width = properties[kCGImagePropertyPixelWidth] as? Int + let height = properties[kCGImagePropertyPixelHeight] as? Int + let orientation = properties[kCGImagePropertyOrientation] as? Int ?? UIImage.Orientation.up.rawValue + switch orientation { + case UIImage.Orientation.left.rawValue, UIImage.Orientation.right.rawValue, UIImage.Orientation.leftMirrored.rawValue, UIImage.Orientation.rightMirrored.rawValue: + return (width, height) + default: + return (height, width) + } + } + + private func getFileUrlByPath(_ path: String) -> URL? { + guard let url = URL.init(string: path) else { + return nil + } + if FileManager.default.fileExists(atPath: url.path) { + return url + } else { + return nil + } + } + + private func saveTemporaryFile(_ sourceUrl: URL) throws -> URL { + var directory = URL(fileURLWithPath: NSTemporaryDirectory()) + if let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first { + directory = cachesDirectory + } + let targetUrl = directory.appendingPathComponent(sourceUrl.lastPathComponent) + do { + try deleteFile(targetUrl) + } + try FileManager.default.copyItem(at: sourceUrl, to: targetUrl) + return targetUrl + } + + private func deleteFile(_ url: URL) throws { + if FileManager.default.fileExists(atPath: url.path) { + try FileManager.default.removeItem(atPath: url.path) + } + } +} + +extension FilePickerController: UIDocumentPickerDelegate { + public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + do { + let temporaryUrls = try urls.map { try saveTemporaryFile($0) } + self.plugin.onFilePickerEvent(.selected(temporaryUrls)) + } catch { + self.plugin.onFilePickerEvent(.error("Failed to create a temporary copy of the file")) + } + } + + public func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { + self.plugin.onFilePickerEvent(.cancelled) + } +} + +extension FilePickerController: UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIPopoverPresentationControllerDelegate { + public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + dismissViewController(picker) + self.plugin.onFilePickerEvent(.cancelled) + } + + public func popoverPresentationControllerDidDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) { + self.plugin.onFilePickerEvent(.cancelled) + } + + public func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + self.plugin.onFilePickerEvent(.cancelled) + } + + public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { + dismissViewController(picker) { + if let url = info[.mediaURL] as? URL { + do { + let temporaryUrl = try self.saveTemporaryFile(url) + self.plugin.onFilePickerEvent(.selected([temporaryUrl])) + } catch { + self.plugin.onFilePickerEvent(.error("Failed to create a temporary copy of the file")) + } + } else { + self.plugin.onFilePickerEvent(.cancelled) + } + } + } +} + +@available(iOS 14, *) +extension FilePickerController: PHPickerViewControllerDelegate { + public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { + dismissViewController(picker) + if results.first == nil { + self.plugin.onFilePickerEvent(.cancelled) + return + } + var temporaryUrls: [URL] = [] + var errorMessage: String? + let dispatchGroup = DispatchGroup() + for result in results { + if errorMessage != nil { + break + } + if result.itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) { + dispatchGroup.enter() + result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier, completionHandler: { url, error in + defer { + dispatchGroup.leave() + } + if let error = error { + errorMessage = error.localizedDescription + return + } + guard let url = url else { + errorMessage = "Unknown error" + return + } + do { + let temporaryUrl = try self.saveTemporaryFile(url) + temporaryUrls.append(temporaryUrl) + } catch { + errorMessage = "Failed to create a temporary copy of the file" + } + }) + } else if result.itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) { + dispatchGroup.enter() + result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier, completionHandler: { url, error in + defer { + dispatchGroup.leave() + } + if let error = error { + errorMessage = error.localizedDescription + return + } + guard let url = url else { + errorMessage = "Unknown error" + return + } + do { + let temporaryUrl = try self.saveTemporaryFile(url) + temporaryUrls.append(temporaryUrl) + } catch { + errorMessage = "Failed to create a temporary copy of the file" + } + }) + } else { + errorMessage = "Unsupported file type identifier" + } + } + dispatchGroup.notify(queue: .main) { + if let errorMessage = errorMessage { + self.plugin.onFilePickerEvent(.error(errorMessage)) + return + } + self.plugin.onFilePickerEvent(.selected(temporaryUrls)) + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/dialog/ios/Tests/PluginTests/PluginTests.swift b/packages/kbot/gui/app/plugins/dialog/ios/Tests/PluginTests/PluginTests.swift new file mode 100644 index 00000000..99992ce4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/ios/Tests/PluginTests/PluginTests.swift @@ -0,0 +1,12 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import XCTest +@testable import ExamplePlugin + +final class ExamplePluginTests: XCTestCase { + func testExample() throws { + let plugin = ExamplePlugin() + } +} diff --git a/packages/kbot/gui/app/plugins/dialog/package.json b/packages/kbot/gui/app/plugins/dialog/package.json new file mode 100644 index 00000000..ac363a66 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/package.json @@ -0,0 +1,29 @@ +{ + "name": "@tauri-apps/plugin-dialog", + "version": "2.4.0", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/dialog/permissions/autogenerated/commands/ask.toml b/packages/kbot/gui/app/plugins/dialog/permissions/autogenerated/commands/ask.toml new file mode 100644 index 00000000..4142c59f --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/permissions/autogenerated/commands/ask.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-ask" +description = "Enables the ask command without any pre-configured scope." +commands.allow = ["ask"] + +[[permission]] +identifier = "deny-ask" +description = "Denies the ask command without any pre-configured scope." +commands.deny = ["ask"] diff --git a/packages/kbot/gui/app/plugins/dialog/permissions/autogenerated/commands/confirm.toml b/packages/kbot/gui/app/plugins/dialog/permissions/autogenerated/commands/confirm.toml new file mode 100644 index 00000000..a297d075 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/permissions/autogenerated/commands/confirm.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-confirm" +description = "Enables the confirm command without any pre-configured scope." +commands.allow = ["confirm"] + +[[permission]] +identifier = "deny-confirm" +description = "Denies the confirm command without any pre-configured scope." +commands.deny = ["confirm"] diff --git a/packages/kbot/gui/app/plugins/dialog/permissions/autogenerated/commands/message.toml b/packages/kbot/gui/app/plugins/dialog/permissions/autogenerated/commands/message.toml new file mode 100644 index 00000000..d386d91e --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/permissions/autogenerated/commands/message.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-message" +description = "Enables the message command without any pre-configured scope." +commands.allow = ["message"] + +[[permission]] +identifier = "deny-message" +description = "Denies the message command without any pre-configured scope." +commands.deny = ["message"] diff --git a/packages/kbot/gui/app/plugins/dialog/permissions/autogenerated/commands/open.toml b/packages/kbot/gui/app/plugins/dialog/permissions/autogenerated/commands/open.toml new file mode 100644 index 00000000..4ea6dff1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/permissions/autogenerated/commands/open.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-open" +description = "Enables the open command without any pre-configured scope." +commands.allow = ["open"] + +[[permission]] +identifier = "deny-open" +description = "Denies the open command without any pre-configured scope." +commands.deny = ["open"] diff --git a/packages/kbot/gui/app/plugins/dialog/permissions/autogenerated/commands/save.toml b/packages/kbot/gui/app/plugins/dialog/permissions/autogenerated/commands/save.toml new file mode 100644 index 00000000..d3e84220 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/permissions/autogenerated/commands/save.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-save" +description = "Enables the save command without any pre-configured scope." +commands.allow = ["save"] + +[[permission]] +identifier = "deny-save" +description = "Denies the save command without any pre-configured scope." +commands.deny = ["save"] diff --git a/packages/kbot/gui/app/plugins/dialog/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/dialog/permissions/autogenerated/reference.md new file mode 100644 index 00000000..3bbd265b --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/permissions/autogenerated/reference.md @@ -0,0 +1,156 @@ +## Default Permission + +This permission set configures the types of dialogs +available from the dialog plugin. + +#### Granted Permissions + +All dialog types are enabled. + +#### This default permission set includes the following: + +- `allow-ask` +- `allow-confirm` +- `allow-message` +- `allow-save` +- `allow-open` + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`dialog:allow-ask` + + + +Enables the ask command without any pre-configured scope. + +
+ +`dialog:deny-ask` + + + +Denies the ask command without any pre-configured scope. + +
+ +`dialog:allow-confirm` + + + +Enables the confirm command without any pre-configured scope. + +
+ +`dialog:deny-confirm` + + + +Denies the confirm command without any pre-configured scope. + +
+ +`dialog:allow-message` + + + +Enables the message command without any pre-configured scope. + +
+ +`dialog:deny-message` + + + +Denies the message command without any pre-configured scope. + +
+ +`dialog:allow-open` + + + +Enables the open command without any pre-configured scope. + +
+ +`dialog:deny-open` + + + +Denies the open command without any pre-configured scope. + +
+ +`dialog:allow-save` + + + +Enables the save command without any pre-configured scope. + +
+ +`dialog:deny-save` + + + +Denies the save command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/dialog/permissions/default.toml b/packages/kbot/gui/app/plugins/dialog/permissions/default.toml new file mode 100644 index 00000000..cc936d90 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/permissions/default.toml @@ -0,0 +1,20 @@ +"$schema" = "schemas/schema.json" + +[default] +description = """ +This permission set configures the types of dialogs +available from the dialog plugin. + +#### Granted Permissions + +All dialog types are enabled. + + +""" +permissions = [ + "allow-ask", + "allow-confirm", + "allow-message", + "allow-save", + "allow-open", +] diff --git a/packages/kbot/gui/app/plugins/dialog/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/dialog/permissions/schemas/schema.json new file mode 100644 index 00000000..b47417ec --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/permissions/schemas/schema.json @@ -0,0 +1,366 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the ask command without any pre-configured scope.", + "type": "string", + "const": "allow-ask", + "markdownDescription": "Enables the ask command without any pre-configured scope." + }, + { + "description": "Denies the ask command without any pre-configured scope.", + "type": "string", + "const": "deny-ask", + "markdownDescription": "Denies the ask command without any pre-configured scope." + }, + { + "description": "Enables the confirm command without any pre-configured scope.", + "type": "string", + "const": "allow-confirm", + "markdownDescription": "Enables the confirm command without any pre-configured scope." + }, + { + "description": "Denies the confirm command without any pre-configured scope.", + "type": "string", + "const": "deny-confirm", + "markdownDescription": "Denies the confirm command without any pre-configured scope." + }, + { + "description": "Enables the message command without any pre-configured scope.", + "type": "string", + "const": "allow-message", + "markdownDescription": "Enables the message command without any pre-configured scope." + }, + { + "description": "Denies the message command without any pre-configured scope.", + "type": "string", + "const": "deny-message", + "markdownDescription": "Denies the message command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Enables the save command without any pre-configured scope.", + "type": "string", + "const": "allow-save", + "markdownDescription": "Enables the save command without any pre-configured scope." + }, + { + "description": "Denies the save command without any pre-configured scope.", + "type": "string", + "const": "deny-save", + "markdownDescription": "Denies the save command without any pre-configured scope." + }, + { + "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`", + "type": "string", + "const": "default", + "markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/dialog/rollup.config.js b/packages/kbot/gui/app/plugins/dialog/rollup.config.js new file mode 100644 index 00000000..a7dbd4f6 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/rollup.config.js @@ -0,0 +1,22 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' +import { nodeResolve } from '@rollup/plugin-node-resolve' +import typescript from '@rollup/plugin-typescript' +import terser from '@rollup/plugin-terser' + +export default createConfig({ + additionalConfigs: { + input: 'guest-js/init.ts', + output: { + file: 'src/init-iife.js', + format: 'iife' + }, + plugins: [typescript(), terser(), nodeResolve()], + onwarn: (warning) => { + throw Object.assign(new Error(), warning) + } + } +}) diff --git a/packages/kbot/gui/app/plugins/dialog/src/commands.rs b/packages/kbot/gui/app/plugins/dialog/src/commands.rs new file mode 100644 index 00000000..5298de9d --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/src/commands.rs @@ -0,0 +1,351 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; +use tauri::{command, Manager, Runtime, State, Window}; +use tauri_plugin_fs::FsExt; + +use crate::{ + Dialog, FileDialogBuilder, FilePath, MessageDialogBuilder, MessageDialogButtons, + MessageDialogKind, MessageDialogResult, Result, CANCEL, NO, OK, YES, +}; + +#[derive(Serialize)] +#[serde(untagged)] +pub enum OpenResponse { + #[cfg(desktop)] + Folders(Option>), + #[cfg(desktop)] + Folder(Option), + Files(Option>), + File(Option), +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DialogFilter { + name: String, + extensions: Vec, +} + +/// The options for the open dialog API. +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OpenDialogOptions { + /// The title of the dialog window. + title: Option, + /// The filters of the dialog. + #[serde(default)] + filters: Vec, + /// Whether the dialog allows multiple selection or not. + #[serde(default)] + multiple: bool, + /// Whether the dialog is a directory selection (`true` value) or file selection (`false` value). + #[serde(default)] + directory: bool, + /// The initial path of the dialog. + default_path: Option, + /// If [`Self::directory`] is true, indicates that it will be read recursively later. + /// Defines whether subdirectories will be allowed on the scope or not. + #[serde(default)] + #[cfg_attr(mobile, allow(dead_code))] + recursive: bool, + /// Whether to allow creating directories in the dialog **macOS Only** + can_create_directories: Option, +} + +/// The options for the save dialog API. +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +#[cfg_attr(mobile, allow(dead_code))] +pub struct SaveDialogOptions { + /// The title of the dialog window. + title: Option, + /// The filters of the dialog. + #[serde(default)] + filters: Vec, + /// The initial path of the dialog. + default_path: Option, + /// Whether to allow creating directories in the dialog **macOS Only** + can_create_directories: Option, +} + +#[cfg(mobile)] +fn set_default_path( + mut dialog_builder: FileDialogBuilder, + default_path: PathBuf, +) -> FileDialogBuilder { + if let Some(file_name) = default_path.file_name() { + dialog_builder = dialog_builder.set_file_name(file_name.to_string_lossy()); + } + dialog_builder +} + +#[cfg(desktop)] +fn set_default_path( + mut dialog_builder: FileDialogBuilder, + default_path: PathBuf, +) -> FileDialogBuilder { + // we need to adjust the separator on Windows: https://github.com/tauri-apps/tauri/issues/8074 + let default_path: PathBuf = default_path.components().collect(); + if default_path.is_file() || !default_path.exists() { + if let (Some(parent), Some(file_name)) = (default_path.parent(), default_path.file_name()) { + if parent.components().count() > 0 { + dialog_builder = dialog_builder.set_directory(parent); + } + dialog_builder = dialog_builder.set_file_name(file_name.to_string_lossy()); + } else { + dialog_builder = dialog_builder.set_directory(default_path); + } + dialog_builder + } else { + dialog_builder.set_directory(default_path) + } +} + +#[command] +pub(crate) async fn open( + window: Window, + dialog: State<'_, Dialog>, + options: OpenDialogOptions, +) -> Result { + let mut dialog_builder = dialog.file(); + #[cfg(any(windows, target_os = "macos"))] + { + dialog_builder = dialog_builder.set_parent(&window); + } + if let Some(title) = options.title { + dialog_builder = dialog_builder.set_title(title); + } + if let Some(default_path) = options.default_path { + dialog_builder = set_default_path(dialog_builder, default_path); + } + if let Some(can) = options.can_create_directories { + dialog_builder = dialog_builder.set_can_create_directories(can); + } + for filter in options.filters { + let extensions: Vec<&str> = filter.extensions.iter().map(|s| &**s).collect(); + dialog_builder = dialog_builder.add_filter(filter.name, &extensions); + } + + let res = if options.directory { + #[cfg(desktop)] + { + let tauri_scope = window.state::(); + + if options.multiple { + let folders = dialog_builder.blocking_pick_folders(); + if let Some(folders) = &folders { + for folder in folders { + if let Ok(path) = folder.clone().into_path() { + if let Some(s) = window.try_fs_scope() { + s.allow_directory(&path, options.recursive)?; + } + tauri_scope.allow_directory(&path, options.directory)?; + } + } + } + OpenResponse::Folders( + folders.map(|folders| folders.into_iter().map(|p| p.simplified()).collect()), + ) + } else { + let folder = dialog_builder.blocking_pick_folder(); + if let Some(folder) = &folder { + if let Ok(path) = folder.clone().into_path() { + if let Some(s) = window.try_fs_scope() { + s.allow_directory(&path, options.recursive)?; + } + tauri_scope.allow_directory(&path, options.directory)?; + } + } + OpenResponse::Folder(folder.map(|p| p.simplified())) + } + } + #[cfg(mobile)] + return Err(crate::Error::FolderPickerNotImplemented); + } else if options.multiple { + let tauri_scope = window.state::(); + + let files = dialog_builder.blocking_pick_files(); + if let Some(files) = &files { + for file in files { + if let Ok(path) = file.clone().into_path() { + if let Some(s) = window.try_fs_scope() { + s.allow_file(&path)?; + } + + tauri_scope.allow_file(&path)?; + } + } + } + OpenResponse::Files(files.map(|files| files.into_iter().map(|f| f.simplified()).collect())) + } else { + let tauri_scope = window.state::(); + let file = dialog_builder.blocking_pick_file(); + + if let Some(file) = &file { + if let Ok(path) = file.clone().into_path() { + if let Some(s) = window.try_fs_scope() { + s.allow_file(&path)?; + } + tauri_scope.allow_file(&path)?; + } + } + OpenResponse::File(file.map(|f| f.simplified())) + }; + Ok(res) +} + +#[allow(unused_variables)] +#[command] +pub(crate) async fn save( + window: Window, + dialog: State<'_, Dialog>, + options: SaveDialogOptions, +) -> Result> { + let mut dialog_builder = dialog.file(); + #[cfg(desktop)] + { + dialog_builder = dialog_builder.set_parent(&window); + } + if let Some(title) = options.title { + dialog_builder = dialog_builder.set_title(title); + } + if let Some(default_path) = options.default_path { + dialog_builder = set_default_path(dialog_builder, default_path); + } + if let Some(can) = options.can_create_directories { + dialog_builder = dialog_builder.set_can_create_directories(can); + } + for filter in options.filters { + let extensions: Vec<&str> = filter.extensions.iter().map(|s| &**s).collect(); + dialog_builder = dialog_builder.add_filter(filter.name, &extensions); + } + + let tauri_scope = window.state::(); + + let path = dialog_builder.blocking_save_file(); + if let Some(p) = &path { + if let Ok(path) = p.clone().into_path() { + if let Some(s) = window.try_fs_scope() { + s.allow_file(&path)?; + } + tauri_scope.allow_file(&path)?; + } + } + + Ok(path.map(|p| p.simplified())) +} + +fn message_dialog( + #[allow(unused_variables)] window: Window, + dialog: State<'_, Dialog>, + title: Option, + message: String, + kind: Option, + buttons: MessageDialogButtons, +) -> MessageDialogBuilder { + let mut builder = dialog.message(message); + + builder = builder.buttons(buttons); + + if let Some(title) = title { + builder = builder.title(title); + } + + #[cfg(desktop)] + { + builder = builder.parent(&window); + } + + if let Some(kind) = kind { + builder = builder.kind(kind); + } + + builder +} + +#[command] +pub(crate) async fn message( + window: Window, + dialog: State<'_, Dialog>, + title: Option, + message: String, + kind: Option, + ok_button_label: Option, + buttons: Option, +) -> Result { + let buttons = buttons.unwrap_or(if let Some(ok_button_label) = ok_button_label { + MessageDialogButtons::OkCustom(ok_button_label) + } else { + MessageDialogButtons::Ok + }); + + Ok(message_dialog(window, dialog, title, message, kind, buttons).blocking_show_with_result()) +} + +#[command] +pub(crate) async fn ask( + window: Window, + dialog: State<'_, Dialog>, + title: Option, + message: String, + kind: Option, + yes_button_label: Option, + no_button_label: Option, +) -> Result { + let dialog = message_dialog( + window, + dialog, + title, + message, + kind, + if let Some(yes_button_label) = yes_button_label { + MessageDialogButtons::OkCancelCustom( + yes_button_label, + no_button_label.unwrap_or(NO.to_string()), + ) + } else if let Some(no_button_label) = no_button_label { + MessageDialogButtons::OkCancelCustom(YES.to_string(), no_button_label) + } else { + MessageDialogButtons::YesNo + }, + ); + + Ok(dialog.blocking_show()) +} + +#[command] +pub(crate) async fn confirm( + window: Window, + dialog: State<'_, Dialog>, + title: Option, + message: String, + kind: Option, + ok_button_label: Option, + cancel_button_label: Option, +) -> Result { + let dialog = message_dialog( + window, + dialog, + title, + message, + kind, + if let Some(ok_button_label) = ok_button_label { + MessageDialogButtons::OkCancelCustom( + ok_button_label, + cancel_button_label.unwrap_or(CANCEL.to_string()), + ) + } else if let Some(cancel_button_label) = cancel_button_label { + MessageDialogButtons::OkCancelCustom(OK.to_string(), cancel_button_label) + } else { + MessageDialogButtons::OkCancel + }, + ); + + Ok(dialog.blocking_show()) +} diff --git a/packages/kbot/gui/app/plugins/dialog/src/desktop.rs b/packages/kbot/gui/app/plugins/dialog/src/desktop.rs new file mode 100644 index 00000000..8d3b08f9 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/src/desktop.rs @@ -0,0 +1,257 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Use native message and file open/save dialogs. +//! +//! This module exposes non-blocking APIs on its root, relying on callback closures +//! to give results back. This is particularly useful when running dialogs from the main thread. +//! When using on asynchronous contexts such as async commands, the [`blocking`] APIs are recommended. + +use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle}; +use rfd::{AsyncFileDialog, AsyncMessageDialog}; +use serde::de::DeserializeOwned; +use tauri::{plugin::PluginApi, AppHandle, Runtime}; + +use crate::{models::*, FileDialogBuilder, FilePath, MessageDialogBuilder}; + +pub fn init( + app: &AppHandle, + _api: PluginApi, +) -> crate::Result> { + Ok(Dialog(app.clone())) +} + +/// Access to the dialog APIs. +#[derive(Debug)] +pub struct Dialog(AppHandle); + +impl Clone for Dialog { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Dialog { + pub(crate) fn app_handle(&self) -> &AppHandle { + &self.0 + } +} + +impl From for rfd::MessageLevel { + fn from(kind: MessageDialogKind) -> Self { + match kind { + MessageDialogKind::Info => Self::Info, + MessageDialogKind::Warning => Self::Warning, + MessageDialogKind::Error => Self::Error, + } + } +} + +#[derive(Debug)] +pub(crate) struct WindowHandle { + window_handle: RawWindowHandle, + display_handle: RawDisplayHandle, +} + +impl WindowHandle { + pub(crate) fn new(window_handle: RawWindowHandle, display_handle: RawDisplayHandle) -> Self { + Self { + window_handle, + display_handle, + } + } +} + +impl HasWindowHandle for WindowHandle { + fn window_handle( + &self, + ) -> Result, raw_window_handle::HandleError> { + Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(self.window_handle) }) + } +} + +impl HasDisplayHandle for WindowHandle { + fn display_handle( + &self, + ) -> Result, raw_window_handle::HandleError> { + Ok(unsafe { raw_window_handle::DisplayHandle::borrow_raw(self.display_handle) }) + } +} + +impl From> for AsyncFileDialog { + fn from(d: FileDialogBuilder) -> Self { + let mut builder = AsyncFileDialog::new(); + + if let Some(title) = d.title { + builder = builder.set_title(title); + } + if let Some(starting_directory) = d.starting_directory { + builder = builder.set_directory(starting_directory); + } + if let Some(file_name) = d.file_name { + builder = builder.set_file_name(file_name); + } + for filter in d.filters { + let v: Vec<&str> = filter.extensions.iter().map(|x| &**x).collect(); + builder = builder.add_filter(&filter.name, &v); + } + #[cfg(desktop)] + if let Some(parent) = d.parent { + builder = builder.set_parent(&parent); + } + + builder = builder.set_can_create_directories(d.can_create_directories.unwrap_or(true)); + + builder + } +} + +impl From for rfd::MessageButtons { + fn from(value: MessageDialogButtons) -> Self { + match value { + MessageDialogButtons::Ok => Self::Ok, + MessageDialogButtons::OkCancel => Self::OkCancel, + MessageDialogButtons::YesNo => Self::YesNo, + MessageDialogButtons::OkCustom(ok) => Self::OkCustom(ok), + MessageDialogButtons::OkCancelCustom(ok, cancel) => Self::OkCancelCustom(ok, cancel), + MessageDialogButtons::YesNoCancel => Self::YesNoCancel, + MessageDialogButtons::YesNoCancelCustom(yes, no, cancel) => { + Self::YesNoCancelCustom(yes, no, cancel) + } + } + } +} + +impl From> for AsyncMessageDialog { + fn from(d: MessageDialogBuilder) -> Self { + let mut dialog = AsyncMessageDialog::new() + .set_title(&d.title) + .set_description(&d.message) + .set_level(d.kind.into()) + .set_buttons(d.buttons.into()); + + if let Some(parent) = d.parent { + dialog = dialog.set_parent(&parent); + } + + dialog + } +} + +pub fn pick_file) + Send + 'static>( + dialog: FileDialogBuilder, + f: F, +) { + let f = |path: Option| f(path.map(|p| p.path().to_path_buf().into())); + let handle = dialog.dialog.app_handle().to_owned(); + let _ = handle.run_on_main_thread(move || { + let dialog = AsyncFileDialog::from(dialog).pick_file(); + std::thread::spawn(move || f(tauri::async_runtime::block_on(dialog))); + }); +} + +pub fn pick_files>) + Send + 'static>( + dialog: FileDialogBuilder, + f: F, +) { + let f = |paths: Option>| { + f(paths.map(|list| { + list.into_iter() + .map(|p| p.path().to_path_buf().into()) + .collect() + })) + }; + let handle = dialog.dialog.app_handle().to_owned(); + let _ = handle.run_on_main_thread(move || { + let dialog = AsyncFileDialog::from(dialog).pick_files(); + std::thread::spawn(move || f(tauri::async_runtime::block_on(dialog))); + }); +} + +pub fn pick_folder) + Send + 'static>( + dialog: FileDialogBuilder, + f: F, +) { + let f = |path: Option| f(path.map(|p| p.path().to_path_buf().into())); + let handle = dialog.dialog.app_handle().to_owned(); + let _ = handle.run_on_main_thread(move || { + let dialog = AsyncFileDialog::from(dialog).pick_folder(); + std::thread::spawn(move || f(tauri::async_runtime::block_on(dialog))); + }); +} + +pub fn pick_folders>) + Send + 'static>( + dialog: FileDialogBuilder, + f: F, +) { + let f = |paths: Option>| { + f(paths.map(|list| { + list.into_iter() + .map(|p| p.path().to_path_buf().into()) + .collect() + })) + }; + let handle = dialog.dialog.app_handle().to_owned(); + let _ = handle.run_on_main_thread(move || { + let dialog = AsyncFileDialog::from(dialog).pick_folders(); + std::thread::spawn(move || f(tauri::async_runtime::block_on(dialog))); + }); +} + +pub fn save_file) + Send + 'static>( + dialog: FileDialogBuilder, + f: F, +) { + let f = |path: Option| f(path.map(|p| p.path().to_path_buf().into())); + let handle = dialog.dialog.app_handle().to_owned(); + let _ = handle.run_on_main_thread(move || { + let dialog = AsyncFileDialog::from(dialog).save_file(); + std::thread::spawn(move || f(tauri::async_runtime::block_on(dialog))); + }); +} + +/// Shows a message dialog +pub fn show_message_dialog( + dialog: MessageDialogBuilder, + callback: F, +) { + let f = move |res: rfd::MessageDialogResult| callback(res.into()); + + let handle = dialog.dialog.app_handle().to_owned(); + let _ = handle.run_on_main_thread(move || { + let buttons = dialog.buttons.clone(); + let dialog = AsyncMessageDialog::from(dialog).show(); + std::thread::spawn(move || { + let result = tauri::async_runtime::block_on(dialog); + // on Linux rfd does not return rfd::MessageDialogResult::Custom, so we must map manually + let result = match (result, buttons) { + (rfd::MessageDialogResult::Ok, MessageDialogButtons::OkCustom(s)) => { + rfd::MessageDialogResult::Custom(s) + } + ( + rfd::MessageDialogResult::Ok, + MessageDialogButtons::OkCancelCustom(ok, _cancel), + ) => rfd::MessageDialogResult::Custom(ok), + ( + rfd::MessageDialogResult::Cancel, + MessageDialogButtons::OkCancelCustom(_ok, cancel), + ) => rfd::MessageDialogResult::Custom(cancel), + ( + rfd::MessageDialogResult::Yes, + MessageDialogButtons::YesNoCancelCustom(yes, _no, _cancel), + ) => rfd::MessageDialogResult::Custom(yes), + ( + rfd::MessageDialogResult::No, + MessageDialogButtons::YesNoCancelCustom(_yes, no, _cancel), + ) => rfd::MessageDialogResult::Custom(no), + ( + rfd::MessageDialogResult::Cancel, + MessageDialogButtons::YesNoCancelCustom(_yes, _no, cancel), + ) => rfd::MessageDialogResult::Custom(cancel), + (result, _) => result, + }; + f(result); + }); + }); +} diff --git a/packages/kbot/gui/app/plugins/dialog/src/error.rs b/packages/kbot/gui/app/plugins/dialog/src/error.rs new file mode 100644 index 00000000..0c3ed5b8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/src/error.rs @@ -0,0 +1,33 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{ser::Serializer, Serialize}; + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum Error { + #[error(transparent)] + Tauri(#[from] tauri::Error), + #[error(transparent)] + Io(#[from] std::io::Error), + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), + #[cfg(mobile)] + #[error("Folder picker is not implemented on mobile")] + FolderPickerNotImplemented, + #[error(transparent)] + Fs(#[from] tauri_plugin_fs::Error), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/packages/kbot/gui/app/plugins/dialog/src/init-iife.js b/packages/kbot/gui/app/plugins/dialog/src/init-iife.js new file mode 100644 index 00000000..bcdf3f56 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/src/init-iife.js @@ -0,0 +1 @@ +!function(){"use strict";async function n(n,i={},o){return window.__TAURI_INTERNALS__.invoke(n,i,o)}"function"==typeof SuppressedError&&SuppressedError,window.alert=function(i){n("plugin:dialog|message",{message:i.toString()})},window.confirm=async function(i){return await n("plugin:dialog|confirm",{message:i.toString()})}}(); diff --git a/packages/kbot/gui/app/plugins/dialog/src/lib.rs b/packages/kbot/gui/app/plugins/dialog/src/lib.rs new file mode 100644 index 00000000..17d9a829 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/src/lib.rs @@ -0,0 +1,702 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Native system dialogs for opening and saving files along with message dialogs. + +#![doc( + html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", + html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" +)] + +use serde::Serialize; +use tauri::{ + plugin::{Builder, TauriPlugin}, + Manager, Runtime, +}; + +use std::{ + path::{Path, PathBuf}, + sync::mpsc::sync_channel, +}; + +pub use models::*; + +pub use tauri_plugin_fs::FilePath; +#[cfg(desktop)] +mod desktop; +#[cfg(mobile)] +mod mobile; + +mod commands; +mod error; +mod models; + +pub use error::{Error, Result}; + +#[cfg(desktop)] +use desktop::*; +#[cfg(mobile)] +use mobile::*; + +#[cfg(desktop)] +pub use desktop::Dialog; +#[cfg(mobile)] +pub use mobile::Dialog; + +pub(crate) const OK: &str = "Ok"; +pub(crate) const CANCEL: &str = "Cancel"; +pub(crate) const YES: &str = "Yes"; +pub(crate) const NO: &str = "No"; + +macro_rules! blocking_fn { + ($self:ident, $fn:ident) => {{ + let (tx, rx) = sync_channel(0); + let cb = move |response| { + tx.send(response).unwrap(); + }; + $self.$fn(cb); + rx.recv().unwrap() + }}; +} + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the dialog APIs. +pub trait DialogExt { + fn dialog(&self) -> &Dialog; +} + +impl> crate::DialogExt for T { + fn dialog(&self) -> &Dialog { + self.state::>().inner() + } +} + +impl Dialog { + /// Create a new messaging dialog builder. + /// The dialog can optionally ask the user for confirmation or include an OK button. + /// + /// # Examples + /// + /// - Message dialog: + /// + /// ``` + /// use tauri_plugin_dialog::DialogExt; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// app + /// .dialog() + /// .message("Tauri is Awesome!") + /// .show(|_| { + /// println!("dialog closed"); + /// }); + /// Ok(()) + /// }); + /// ``` + /// + /// - Ask dialog: + /// + /// ``` + /// use tauri_plugin_dialog::{DialogExt, MessageDialogButtons}; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// app.dialog() + /// .message("Are you sure?") + /// .buttons(MessageDialogButtons::OkCancelCustom("Yes", "No")) + /// .show(|yes| { + /// println!("user said {}", if yes { "yes" } else { "no" }); + /// }); + /// Ok(()) + /// }); + /// ``` + /// + /// - Message dialog with OK button: + /// + /// ``` + /// use tauri_plugin_dialog::{DialogExt, MessageDialogButtons}; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// app.dialog() + /// .message("Job completed successfully") + /// .buttons(MessageDialogButtons::Ok) + /// .show(|_| { + /// println!("dialog closed"); + /// }); + /// Ok(()) + /// }); + /// ``` + /// + /// # `show` vs `blocking_show` + /// + /// The dialog builder includes two separate APIs for rendering the dialog: `show` and `blocking_show`. + /// The `show` function is asynchronous and takes a closure to be executed when the dialog is closed. + /// To block the current thread until the user acted on the dialog, you can use `blocking_show`, + /// but note that it cannot be executed on the main thread as it will freeze your application. + /// + /// ``` + /// use tauri_plugin_dialog::{DialogExt, MessageDialogButtons}; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// let handle = app.handle().clone(); + /// std::thread::spawn(move || { + /// let yes = handle.dialog() + /// .message("Are you sure?") + /// .buttons(MessageDialogButtons::OkCancelCustom("Yes", "No")) + /// .blocking_show(); + /// }); + /// + /// Ok(()) + /// }); + /// ``` + pub fn message(&self, message: impl Into) -> MessageDialogBuilder { + MessageDialogBuilder::new( + self.clone(), + self.app_handle().package_info().name.clone(), + message, + ) + } + + /// Creates a new builder for dialogs that lets the user select file(s) or folder(s). + pub fn file(&self) -> FileDialogBuilder { + FileDialogBuilder::new(self.clone()) + } +} + +/// Initializes the plugin. +pub fn init() -> TauriPlugin { + #[allow(unused_mut)] + let mut builder = Builder::new("dialog"); + + // Dialogs are implemented natively on Android + #[cfg(not(target_os = "android"))] + { + builder = builder.js_init_script(include_str!("init-iife.js").to_string()); + } + + builder + .invoke_handler(tauri::generate_handler![ + commands::open, + commands::save, + commands::message, + commands::ask, + commands::confirm + ]) + .setup(|app, api| { + #[cfg(mobile)] + let dialog = mobile::init(app, api)?; + #[cfg(desktop)] + let dialog = desktop::init(app, api)?; + app.manage(dialog); + Ok(()) + }) + .build() +} + +/// A builder for message dialogs. +pub struct MessageDialogBuilder { + #[allow(dead_code)] + pub(crate) dialog: Dialog, + pub(crate) title: String, + pub(crate) message: String, + pub(crate) kind: MessageDialogKind, + pub(crate) buttons: MessageDialogButtons, + #[cfg(desktop)] + pub(crate) parent: Option, +} + +/// Payload for the message dialog mobile API. +#[cfg(mobile)] +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct MessageDialogPayload<'a> { + title: &'a String, + message: &'a String, + kind: &'a MessageDialogKind, + ok_button_label: Option<&'a str>, + no_button_label: Option<&'a str>, + cancel_button_label: Option<&'a str>, +} + +// raw window handle :( +unsafe impl Send for MessageDialogBuilder {} + +impl MessageDialogBuilder { + /// Creates a new message dialog builder. + pub fn new(dialog: Dialog, title: impl Into, message: impl Into) -> Self { + Self { + dialog, + title: title.into(), + message: message.into(), + kind: Default::default(), + buttons: Default::default(), + #[cfg(desktop)] + parent: None, + } + } + + #[cfg(mobile)] + pub(crate) fn payload(&self) -> MessageDialogPayload<'_> { + let (ok_button_label, no_button_label, cancel_button_label) = match &self.buttons { + MessageDialogButtons::Ok => (Some(OK), None, None), + MessageDialogButtons::OkCancel => (Some(OK), None, Some(CANCEL)), + MessageDialogButtons::YesNo => (Some(YES), Some(NO), None), + MessageDialogButtons::YesNoCancel => (Some(YES), Some(NO), Some(CANCEL)), + MessageDialogButtons::OkCustom(ok) => (Some(ok.as_str()), None, None), + MessageDialogButtons::OkCancelCustom(ok, cancel) => { + (Some(ok.as_str()), None, Some(cancel.as_str())) + } + MessageDialogButtons::YesNoCancelCustom(yes, no, cancel) => { + (Some(yes.as_str()), Some(no.as_str()), Some(cancel.as_str())) + } + }; + MessageDialogPayload { + title: &self.title, + message: &self.message, + kind: &self.kind, + ok_button_label, + no_button_label, + cancel_button_label, + } + } + + /// Sets the dialog title. + pub fn title(mut self, title: impl Into) -> Self { + self.title = title.into(); + self + } + + /// Set parent windows explicitly (optional) + #[cfg(desktop)] + pub fn parent( + mut self, + parent: &W, + ) -> Self { + if let (Ok(window_handle), Ok(display_handle)) = + (parent.window_handle(), parent.display_handle()) + { + self.parent.replace(crate::desktop::WindowHandle::new( + window_handle.as_raw(), + display_handle.as_raw(), + )); + } + self + } + + /// Sets the dialog buttons. + pub fn buttons(mut self, buttons: MessageDialogButtons) -> Self { + self.buttons = buttons; + self + } + + /// Set type of a dialog. + /// + /// Depending on the system it can result in type specific icon to show up, + /// the will inform user it message is a error, warning or just information. + pub fn kind(mut self, kind: MessageDialogKind) -> Self { + self.kind = kind; + self + } + + /// Shows a message dialog + /// + /// Returns `true` if the user pressed the OK/Yes button, + pub fn show(self, f: F) { + let ok_label = match &self.buttons { + MessageDialogButtons::OkCustom(ok) => Some(ok.clone()), + MessageDialogButtons::OkCancelCustom(ok, _) => Some(ok.clone()), + MessageDialogButtons::YesNoCancelCustom(yes, _, _) => Some(yes.clone()), + _ => None, + }; + + show_message_dialog(self, move |res| { + let sucess = match res { + MessageDialogResult::Ok | MessageDialogResult::Yes => true, + MessageDialogResult::Custom(s) => { + ok_label.map_or(s == OK, |ok_label| ok_label == s) + } + _ => false, + }; + + f(sucess) + }) + } + + /// Shows a message dialog and returns the button that was pressed. + /// + /// Returns a [`MessageDialogResult`] enum that indicates which button was pressed. + pub fn show_with_result(self, f: F) { + show_message_dialog(self, f) + } + + /// Shows a message dialog. + /// + /// Returns `true` if the user pressed the OK/Yes button, + /// + /// This is a blocking operation, + /// and should *NOT* be used when running on the main thread context. + pub fn blocking_show(self) -> bool { + blocking_fn!(self, show) + } + + /// Shows a message dialog and returns the button that was pressed. + /// + /// Returns a [`MessageDialogResult`] enum that indicates which button was pressed. + /// + /// This is a blocking operation, + /// and should *NOT* be used when running on the main thread context. + pub fn blocking_show_with_result(self) -> MessageDialogResult { + blocking_fn!(self, show_with_result) + } +} +#[derive(Debug, Serialize)] +pub(crate) struct Filter { + pub name: String, + pub extensions: Vec, +} + +/// The file dialog builder. +/// +/// Constructs file picker dialogs that can select single/multiple files or directories. +#[derive(Debug)] +pub struct FileDialogBuilder { + #[allow(dead_code)] + pub(crate) dialog: Dialog, + pub(crate) filters: Vec, + pub(crate) starting_directory: Option, + pub(crate) file_name: Option, + pub(crate) title: Option, + pub(crate) can_create_directories: Option, + #[cfg(desktop)] + pub(crate) parent: Option, +} + +#[cfg(mobile)] +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct FileDialogPayload<'a> { + file_name: &'a Option, + filters: &'a Vec, + multiple: bool, +} + +// raw window handle :( +unsafe impl Send for FileDialogBuilder {} + +impl FileDialogBuilder { + /// Gets the default file dialog builder. + pub fn new(dialog: Dialog) -> Self { + Self { + dialog, + filters: Vec::new(), + starting_directory: None, + file_name: None, + title: None, + can_create_directories: None, + #[cfg(desktop)] + parent: None, + } + } + + #[cfg(mobile)] + pub(crate) fn payload(&self, multiple: bool) -> FileDialogPayload<'_> { + FileDialogPayload { + file_name: &self.file_name, + filters: &self.filters, + multiple, + } + } + + /// Add file extension filter. Takes in the name of the filter, and list of extensions + #[must_use] + pub fn add_filter(mut self, name: impl Into, extensions: &[&str]) -> Self { + self.filters.push(Filter { + name: name.into(), + extensions: extensions.iter().map(|e| e.to_string()).collect(), + }); + self + } + + /// Set starting directory of the dialog. + #[must_use] + pub fn set_directory>(mut self, directory: P) -> Self { + self.starting_directory.replace(directory.as_ref().into()); + self + } + + /// Set starting file name of the dialog. + #[must_use] + pub fn set_file_name(mut self, file_name: impl Into) -> Self { + self.file_name.replace(file_name.into()); + self + } + + /// Sets the parent window of the dialog. + #[cfg(desktop)] + #[must_use] + pub fn set_parent< + W: raw_window_handle::HasWindowHandle + raw_window_handle::HasDisplayHandle, + >( + mut self, + parent: &W, + ) -> Self { + if let (Ok(window_handle), Ok(display_handle)) = + (parent.window_handle(), parent.display_handle()) + { + self.parent.replace(crate::desktop::WindowHandle::new( + window_handle.as_raw(), + display_handle.as_raw(), + )); + } + self + } + + /// Set the title of the dialog. + #[must_use] + pub fn set_title(mut self, title: impl Into) -> Self { + self.title.replace(title.into()); + self + } + + /// Set whether it should be possible to create new directories in the dialog. Enabled by default. **macOS only**. + pub fn set_can_create_directories(mut self, can: bool) -> Self { + self.can_create_directories.replace(can); + self + } + + /// Shows the dialog to select a single file. + /// This is not a blocking operation, + /// and should be used when running on the main thread to avoid deadlocks with the event loop. + /// + /// For usage in other contexts such as commands, prefer [`Self::pick_file`]. + /// + /// # Examples + /// + /// ``` + /// use tauri_plugin_dialog::DialogExt; + /// tauri::Builder::default() + /// .setup(|app| { + /// app.dialog().file().pick_file(|file_path| { + /// // do something with the optional file path here + /// // the file path is `None` if the user closed the dialog + /// }); + /// Ok(()) + /// }); + /// ``` + pub fn pick_file) + Send + 'static>(self, f: F) { + pick_file(self, f) + } + + /// Shows the dialog to select multiple files. + /// This is not a blocking operation, + /// and should be used when running on the main thread to avoid deadlocks with the event loop. + /// + /// # Reading the files + /// + /// The file paths cannot be read directly on Android as they are behind a content URI. + /// The recommended way to read the files is using the [`fs`](https://v2.tauri.app/plugin/file-system/) plugin: + /// + /// ``` + /// use tauri_plugin_dialog::DialogExt; + /// use tauri_plugin_fs::FsExt; + /// tauri::Builder::default() + /// .setup(|app| { + /// let handle = app.handle().clone(); + /// app.dialog().file().pick_file(move |file_path| { + /// let Some(path) = file_path else { return }; + /// let Ok(contents) = handle.fs().read_to_string(path) else { + /// eprintln!("failed to read file, "); + /// return; + /// }; + /// }); + /// Ok(()) + /// }); + /// ``` + /// + /// See for more information. + /// + /// # Examples + /// + /// ``` + /// use tauri_plugin_dialog::DialogExt; + /// tauri::Builder::default() + /// .setup(|app| { + /// app.dialog().file().pick_files(|file_paths| { + /// // do something with the optional file paths here + /// // the file paths value is `None` if the user closed the dialog + /// }); + /// Ok(()) + /// }); + /// ``` + pub fn pick_files>) + Send + 'static>(self, f: F) { + pick_files(self, f) + } + + /// Shows the dialog to select a single folder. + /// This is not a blocking operation, + /// and should be used when running on the main thread to avoid deadlocks with the event loop. + /// + /// # Examples + /// + /// ``` + /// use tauri_plugin_dialog::DialogExt; + /// tauri::Builder::default() + /// .setup(|app| { + /// app.dialog().file().pick_folder(|folder_path| { + /// // do something with the optional folder path here + /// // the folder path is `None` if the user closed the dialog + /// }); + /// Ok(()) + /// }); + /// ``` + #[cfg(desktop)] + pub fn pick_folder) + Send + 'static>(self, f: F) { + pick_folder(self, f) + } + + /// Shows the dialog to select multiple folders. + /// This is not a blocking operation, + /// and should be used when running on the main thread to avoid deadlocks with the event loop. + /// + /// # Examples + /// + /// ``` + /// use tauri_plugin_dialog::DialogExt; + /// tauri::Builder::default() + /// .setup(|app| { + /// app.dialog().file().pick_folders(|file_paths| { + /// // do something with the optional folder paths here + /// // the folder paths value is `None` if the user closed the dialog + /// }); + /// Ok(()) + /// }); + /// ``` + #[cfg(desktop)] + pub fn pick_folders>) + Send + 'static>(self, f: F) { + pick_folders(self, f) + } + + /// Shows the dialog to save a file. + /// + /// This is not a blocking operation, + /// and should be used when running on the main thread to avoid deadlocks with the event loop. + /// + /// # Examples + /// + /// ``` + /// use tauri_plugin_dialog::DialogExt; + /// tauri::Builder::default() + /// .setup(|app| { + /// app.dialog().file().save_file(|file_path| { + /// // do something with the optional file path here + /// // the file path is `None` if the user closed the dialog + /// }); + /// Ok(()) + /// }); + /// ``` + pub fn save_file) + Send + 'static>(self, f: F) { + save_file(self, f) + } +} + +/// Blocking APIs. +impl FileDialogBuilder { + /// Shows the dialog to select a single file. + /// This is a blocking operation, + /// and should *NOT* be used when running on the main thread context. + /// + /// # Examples + /// + /// ``` + /// use tauri_plugin_dialog::DialogExt; + /// #[tauri::command] + /// async fn my_command(app: tauri::AppHandle) { + /// let file_path = app.dialog().file().blocking_pick_file(); + /// // do something with the optional file path here + /// // the file path is `None` if the user closed the dialog + /// } + /// ``` + pub fn blocking_pick_file(self) -> Option { + blocking_fn!(self, pick_file) + } + + /// Shows the dialog to select multiple files. + /// This is a blocking operation, + /// and should *NOT* be used when running on the main thread context. + /// + /// # Examples + /// + /// ``` + /// use tauri_plugin_dialog::DialogExt; + /// #[tauri::command] + /// async fn my_command(app: tauri::AppHandle) { + /// let file_path = app.dialog().file().blocking_pick_files(); + /// // do something with the optional file paths here + /// // the file paths value is `None` if the user closed the dialog + /// } + /// ``` + pub fn blocking_pick_files(self) -> Option> { + blocking_fn!(self, pick_files) + } + + /// Shows the dialog to select a single folder. + /// This is a blocking operation, + /// and should *NOT* be used when running on the main thread context. + /// + /// # Examples + /// + /// ``` + /// use tauri_plugin_dialog::DialogExt; + /// #[tauri::command] + /// async fn my_command(app: tauri::AppHandle) { + /// let folder_path = app.dialog().file().blocking_pick_folder(); + /// // do something with the optional folder path here + /// // the folder path is `None` if the user closed the dialog + /// } + /// ``` + #[cfg(desktop)] + pub fn blocking_pick_folder(self) -> Option { + blocking_fn!(self, pick_folder) + } + + /// Shows the dialog to select multiple folders. + /// This is a blocking operation, + /// and should *NOT* be used when running on the main thread context. + /// + /// # Examples + /// + /// ``` + /// use tauri_plugin_dialog::DialogExt; + /// #[tauri::command] + /// async fn my_command(app: tauri::AppHandle) { + /// let folder_paths = app.dialog().file().blocking_pick_folders(); + /// // do something with the optional folder paths here + /// // the folder paths value is `None` if the user closed the dialog + /// } + /// ``` + #[cfg(desktop)] + pub fn blocking_pick_folders(self) -> Option> { + blocking_fn!(self, pick_folders) + } + + /// Shows the dialog to save a file. + /// This is a blocking operation, + /// and should *NOT* be used when running on the main thread context. + /// + /// # Examples + /// + /// ``` + /// use tauri_plugin_dialog::DialogExt; + /// #[tauri::command] + /// async fn my_command(app: tauri::AppHandle) { + /// let file_path = app.dialog().file().blocking_save_file(); + /// // do something with the optional file path here + /// // the file path is `None` if the user closed the dialog + /// } + /// ``` + pub fn blocking_save_file(self) -> Option { + blocking_fn!(self, save_file) + } +} diff --git a/packages/kbot/gui/app/plugins/dialog/src/mobile.rs b/packages/kbot/gui/app/plugins/dialog/src/mobile.rs new file mode 100644 index 00000000..46ea3a27 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/src/mobile.rs @@ -0,0 +1,127 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{de::DeserializeOwned, Deserialize}; +use tauri::{ + plugin::{PluginApi, PluginHandle}, + AppHandle, Runtime, +}; + +use crate::{FileDialogBuilder, FilePath, MessageDialogBuilder, MessageDialogResult}; + +#[cfg(target_os = "android")] +const PLUGIN_IDENTIFIER: &str = "app.tauri.dialog"; + +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_dialog); + +// initializes the Kotlin or Swift plugin classes +pub fn init( + _app: &AppHandle, + api: PluginApi, +) -> crate::Result> { + #[cfg(target_os = "android")] + let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "DialogPlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_dialog)?; + Ok(Dialog(handle)) +} + +/// Access to the dialog APIs. +#[derive(Debug)] +pub struct Dialog(PluginHandle); + +impl Clone for Dialog { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Dialog { + pub(crate) fn app_handle(&self) -> &AppHandle { + self.0.app() + } +} + +#[derive(Debug, Deserialize)] +struct FilePickerResponse { + files: Vec, +} + +#[derive(Debug, Deserialize)] +struct SaveFileResponse { + file: FilePath, +} + +pub fn pick_file) + Send + 'static>( + dialog: FileDialogBuilder, + f: F, +) { + std::thread::spawn(move || { + let res = dialog + .dialog + .0 + .run_mobile_plugin::("showFilePicker", dialog.payload(false)); + if let Ok(response) = res { + f(Some(response.files.into_iter().next().unwrap())) + } else { + f(None) + } + }); +} + +pub fn pick_files>) + Send + 'static>( + dialog: FileDialogBuilder, + f: F, +) { + std::thread::spawn(move || { + let res = dialog + .dialog + .0 + .run_mobile_plugin::("showFilePicker", dialog.payload(true)); + if let Ok(response) = res { + f(Some(response.files)) + } else { + f(None) + } + }); +} + +pub fn save_file) + Send + 'static>( + dialog: FileDialogBuilder, + f: F, +) { + std::thread::spawn(move || { + let res = dialog + .dialog + .0 + .run_mobile_plugin::("saveFileDialog", dialog.payload(false)); + if let Ok(response) = res { + f(Some(response.file)) + } else { + f(None) + } + }); +} + +#[derive(Debug, Deserialize)] +struct ShowMessageDialogResponse { + value: String, +} + +/// Shows a message dialog +pub fn show_message_dialog( + dialog: MessageDialogBuilder, + f: F, +) { + std::thread::spawn(move || { + let res = dialog + .dialog + .0 + .run_mobile_plugin::("showMessageDialog", dialog.payload()); + + let res = res.map(|res| res.value.into()); + f(res.unwrap_or_default()) + }); +} diff --git a/packages/kbot/gui/app/plugins/dialog/src/models.rs b/packages/kbot/gui/app/plugins/dialog/src/models.rs new file mode 100644 index 00000000..0b2de2c9 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/src/models.rs @@ -0,0 +1,109 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +/// Types of message, ask and confirm dialogs. +#[non_exhaustive] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum MessageDialogKind { + /// Information dialog. + Info, + /// Warning dialog. + Warning, + /// Error dialog. + Error, +} + +impl Default for MessageDialogKind { + fn default() -> Self { + Self::Info + } +} + +impl<'de> Deserialize<'de> for MessageDialogKind { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Ok(match s.to_lowercase().as_str() { + "info" => MessageDialogKind::Info, + "warning" => MessageDialogKind::Warning, + "error" => MessageDialogKind::Error, + _ => MessageDialogKind::Info, + }) + } +} + +impl Serialize for MessageDialogKind { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + match self { + Self::Info => serializer.serialize_str("info"), + Self::Warning => serializer.serialize_str("warning"), + Self::Error => serializer.serialize_str("error"), + } + } +} + +/// Set of button that will be displayed on the dialog +#[non_exhaustive] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub enum MessageDialogButtons { + #[default] + /// A single `Ok` button with OS default dialog text + Ok, + /// 2 buttons `Ok` and `Cancel` with OS default dialog texts + OkCancel, + /// 2 buttons `Yes` and `No` with OS default dialog texts + YesNo, + /// 3 buttons `Yes`, `No` and `Cancel` with OS default dialog texts + YesNoCancel, + /// A single `Ok` button with custom text + OkCustom(String), + /// 2 buttons `Ok` and `Cancel` with custom texts + OkCancelCustom(String, String), + /// 3 buttons `Yes`, `No` and `Cancel` with custom texts + YesNoCancelCustom(String, String, String), +} + +/// Result of a message dialog +#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub enum MessageDialogResult { + Yes, + No, + Ok, + #[default] + Cancel, + #[serde(untagged)] + Custom(String), +} + +#[cfg(desktop)] +impl From for MessageDialogResult { + fn from(result: rfd::MessageDialogResult) -> Self { + match result { + rfd::MessageDialogResult::Yes => Self::Yes, + rfd::MessageDialogResult::No => Self::No, + rfd::MessageDialogResult::Ok => Self::Ok, + rfd::MessageDialogResult::Cancel => Self::Cancel, + rfd::MessageDialogResult::Custom(s) => Self::Custom(s), + } + } +} + +impl From for MessageDialogResult { + fn from(value: String) -> Self { + match value.as_str() { + "Yes" => Self::Yes, + "No" => Self::No, + "Ok" => Self::Ok, + "Cancel" => Self::Cancel, + _ => Self::Custom(value), + } + } +} diff --git a/packages/kbot/gui/app/plugins/dialog/test/tauri.conf.json b/packages/kbot/gui/app/plugins/dialog/test/tauri.conf.json new file mode 100644 index 00000000..4d0ae021 --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/test/tauri.conf.json @@ -0,0 +1,21 @@ +{ + "identifier": "app.tauri.example", + "build": { + "frontendDist": ".", + "devUrl": "http://localhost:4000" + }, + "app": { + "windows": [ + { + "title": "Tauri App" + } + ], + "security": { + "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: http://tauri.localhost 'unsafe-eval' 'unsafe-inline' 'self'" + } + }, + "bundle": { + "active": true, + "icon": ["../../../examples/api/src-tauri/icons/icon.png"] + } +} diff --git a/packages/kbot/gui/app/plugins/dialog/tsconfig.json b/packages/kbot/gui/app/plugins/dialog/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/dialog/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/fs/CHANGELOG.md b/packages/kbot/gui/app/plugins/fs/CHANGELOG.md new file mode 100644 index 00000000..e1e1ad40 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/CHANGELOG.md @@ -0,0 +1,200 @@ +# Changelog + +## \[2.4.2] + +- [`4eb36b0f`](https://github.com/tauri-apps/plugins-workspace/commit/4eb36b0ff57acb0bb1b911c583efa3bf2f56aa32) ([#2907](https://github.com/tauri-apps/plugins-workspace/pull/2907) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Fixed calling `writeFile` with `data: ReadableStream` throws `Invalid argument` +- [`515182a1`](https://github.com/tauri-apps/plugins-workspace/commit/515182a179d4439079b2b7f6927555ba5ab0b035) ([#2915](https://github.com/tauri-apps/plugins-workspace/pull/2915) by [@samhinshaw](https://github.com/tauri-apps/plugins-workspace/../../samhinshaw)) `readFile` now returns a more specific type `Promise>` instead of the default `Promise` + +## \[2.4.1] + +- [`44a1f659`](https://github.com/tauri-apps/plugins-workspace/commit/44a1f659125a341191420e650608b0b6ff316a0e) ([#2846](https://github.com/tauri-apps/plugins-workspace/pull/2846) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Fix `writeFile` doesn't create a new file by default when the data is a `ReadableStream` + +## \[2.4.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.3.0] + +- [`dac4d537`](https://github.com/tauri-apps/plugins-workspace/commit/dac4d53724bb3430a00a3f0119857cba32a031e8) ([#2613](https://github.com/tauri-apps/plugins-workspace/pull/2613) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Reduce the overhead of `watch` and `unwatch` + +## \[2.2.1] + +### bug + +- [`831c35ff`](https://github.com/tauri-apps/plugins-workspace/commit/831c35ff3940e841fe4418bb4cb104038b03304b) ([#2550](https://github.com/tauri-apps/plugins-workspace/pull/2550)) Fix `writeFile` ReadableStream handling due to missing async iterator support on macOS platform + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.4] + +- [`77b85507`](https://github.com/tauri-apps/plugins-workspace/commit/77b855074aad612f2b28e6a3b5881fac767a05ae) ([#2171](https://github.com/tauri-apps/plugins-workspace/pull/2171) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed docs.rs build. + +## \[2.0.3] + +- [`ed981027`](https://github.com/tauri-apps/plugins-workspace/commit/ed981027dd4fba7d0e2f836eb5db34d344388d73) ([#1962](https://github.com/tauri-apps/plugins-workspace/pull/1962) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Improve performance of `readTextFile` and `readTextFileLines` APIs +- [`3e78173d`](https://github.com/tauri-apps/plugins-workspace/commit/3e78173df9ce90aa3b19e1f36d1f8712c5020fb6) ([#2018](https://github.com/tauri-apps/plugins-workspace/pull/2018) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Fix `readDir` function failing to read directories that contain broken symlinks. +- [`5092ea5e`](https://github.com/tauri-apps/plugins-workspace/commit/5092ea5e89817c0550d09b0a4ad17bf1253b23df) ([#1964](https://github.com/tauri-apps/plugins-workspace/pull/1964) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Add support for using `ReadableStream` with `writeFile` API. + +## \[2.0.2] + +- [`77149dc4`](https://github.com/tauri-apps/plugins-workspace/commit/77149dc4320d26b413e4a6bbe82c654367c51b32) ([#1965](https://github.com/tauri-apps/plugins-workspace/pull/1965) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Fix `writeTextFile` converting UTF-8 characters (for example `äöü`) in the given path into replacement character (`�`) + +## \[2.0.3] + +- [`14cee64c`](https://github.com/tauri-apps/plugins-workspace/commit/14cee64c82a72655ae6a4ac0892736a2959dbda5) ([#1958](https://github.com/tauri-apps/plugins-workspace/pull/1958) by [@bWanShiTong](https://github.com/tauri-apps/plugins-workspace/../../bWanShiTong)) Fix compilation on targets with pointer width of `16` or `32` + +## \[2.0.1] + +- [`ae802456`](https://github.com/tauri-apps/plugins-workspace/commit/ae8024565f074f313084777c8b10d1b5e3bbe220) ([#1950](https://github.com/tauri-apps/plugins-workspace/pull/1950) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Improve performance of the `FileHandle.read` and `writeTextFile` APIs. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.6] + +- [`fc9b189e`](https://github.com/tauri-apps/plugins-workspace/commit/fc9b189e83a29bd750714ec6336133c6eabdfa20) ([#1837](https://github.com/tauri-apps/plugins-workspace/pull/1837) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fix failing to deserialize capability file when using an OS specific path in the scope that is not available on the current OS. + +## \[2.0.0-rc.5] + +- [`cc03ccf5`](https://github.com/tauri-apps/plugins-workspace/commit/cc03ccf5e0e4be8bbf50bbdebe957c84be7f779b) ([#1774](https://github.com/tauri-apps/plugins-workspace/pull/1774)) Fix `scope-app`, `scope-app-recursive` and `scope-index` not properly enabling the application paths. + +## \[2.0.0-rc.4] + +- [`9291e4d2`](https://github.com/tauri-apps/plugins-workspace/commit/9291e4d2caa31c883c71e55f2193bd8754d72f03) ([#1640](https://github.com/tauri-apps/plugins-workspace/pull/1640) by [@SRutile](https://github.com/tauri-apps/plugins-workspace/../../SRutile)) Support any UTF-8 character in the writeFile API. + +## \[2.0.0-rc.3] + +- [`a2fe5551`](https://github.com/tauri-apps/plugins-workspace/commit/a2fe55512f908dd11c814ce021d164f01677572a) ([#1727](https://github.com/tauri-apps/plugins-workspace/pull/1727) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Add utility methods on `FilePath` and `SafeFilePath` enums which are: + + - `path` + - `simplified` + - `into_path` +- [`a2fe5551`](https://github.com/tauri-apps/plugins-workspace/commit/a2fe55512f908dd11c814ce021d164f01677572a) ([#1727](https://github.com/tauri-apps/plugins-workspace/pull/1727) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Implement `Serialize`, `Deserialize`, `From`, `TryFrom` and `FromStr` traits for `FilePath` and `SafeFilePath` enums. +- [`a2fe5551`](https://github.com/tauri-apps/plugins-workspace/commit/a2fe55512f908dd11c814ce021d164f01677572a) ([#1727](https://github.com/tauri-apps/plugins-workspace/pull/1727) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Mark `Error` enum as `#[non_exhuastive]`. +- [`a2fe5551`](https://github.com/tauri-apps/plugins-workspace/commit/a2fe55512f908dd11c814ce021d164f01677572a) ([#1727](https://github.com/tauri-apps/plugins-workspace/pull/1727) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Add `SafeFilePath` enum. + +## \[2.0.0-rc.2] + +- [`f7280c88`](https://github.com/tauri-apps/plugins-workspace/commit/f7280c88309cdf1f2330574fec31e26e01e9cdbd) ([#1710](https://github.com/tauri-apps/plugins-workspace/pull/1710) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Fix can't use Windows paths like `C:/Users/UserName/file.txt` + +### bug + +- [`51819c60`](https://github.com/tauri-apps/plugins-workspace/commit/51819c601f863cbfbd11809a1c4cf9df5a20b1e0) ([#1708](https://github.com/tauri-apps/plugins-workspace/pull/1708) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Fixes `writeFile` command implementation on Android. + +## \[2.0.0-rc.2] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.1] + +- [`5f689902`](https://github.com/tauri-apps/plugins-workspace/commit/5f68990297f2cefac4220649a469adb7fa94fe1b) ([#1645](https://github.com/tauri-apps/plugins-workspace/pull/1645) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update documentation. + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +## \[2.0.0-beta.8] + +- [`99d6ac0f`](https://github.com/tauri-apps/plugins-workspace/commit/99d6ac0f9506a6a4a1aa59c728157190a7441af6) ([#1606](https://github.com/tauri-apps/plugins-workspace/pull/1606) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The JS packages now specify the *minimum* `@tauri-apps/api` version instead of a single exact version. +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.7] + +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.6] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.5] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.4] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.3] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.6] + +- [`b115fd22`](https://github.com/tauri-apps/plugins-workspace/commit/b115fd22e0da073f5d758c13474ec2106cf78163)([#1221](https://github.com/tauri-apps/plugins-workspace/pull/1221)) Fixes an issue that caused the app to freeze when the `dialog`, `fs`, and `persisted-scope` plugins were used together. + +## \[2.0.0-beta.5] + +- [`bb51a41`](https://github.com/tauri-apps/plugins-workspace/commit/bb51a41d67ebf989e8aedf10c4b1a7f9514d1bdf)([#1168](https://github.com/tauri-apps/plugins-workspace/pull/1168)) **Breaking Change:** All apis that return paths to the frontend will now remove the `\\?\` UNC prefix on Windows. +- [`e3d41f4`](https://github.com/tauri-apps/plugins-workspace/commit/e3d41f4011bd3ea3ce281bb38bbe31d3709f8e0f)([#1191](https://github.com/tauri-apps/plugins-workspace/pull/1191)) Internally use the webview scoped resources table instead of the app one, so other webviews can't access other webviews resources. +- [`e3d41f4`](https://github.com/tauri-apps/plugins-workspace/commit/e3d41f4011bd3ea3ce281bb38bbe31d3709f8e0f)([#1191](https://github.com/tauri-apps/plugins-workspace/pull/1191)) Update for tauri 2.0.0-beta.15. + +## \[2.0.0-beta.4] + +- [`9c2fb93`](https://github.com/tauri-apps/plugins-workspace/commit/9c2fb9306ecd3936a2aef56b3c012899036db098) Enhance the scope type to also allow a plain string representing the path to allow or deny. +- [`772f2bc`](https://github.com/tauri-apps/plugins-workspace/commit/772f2bc3495a4f83f1c3e538cbac6d29cbd7d5ef)([#1136](https://github.com/tauri-apps/plugins-workspace/pull/1136)) Update for tauri 2.0.0-beta.14. + +## \[2.0.0-beta.3] + +- [`cb96aa0`](https://github.com/tauri-apps/plugins-workspace/commit/cb96aa06277f7b864952827ec9fb1e74c8a1f761)([#1082](https://github.com/tauri-apps/plugins-workspace/pull/1082)) Fixes `watch` and `watchImmediate` which previously ignored the `baseDir` parameter. +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. + +## \[2.0.0-beta.2] + +- [`7358102`](https://github.com/tauri-apps/plugins-workspace/commit/735810237e21504a027a65a7b3c25fd7e594288a)([#1029](https://github.com/tauri-apps/plugins-workspace/pull/1029)) Fix infinite loop on cargo build operations +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. +- [`ea8eadc`](https://github.com/tauri-apps/plugins-workspace/commit/ea8eadce85b2e3e8eb7eb1a779fc3aa6c1201fa3)([#865](https://github.com/tauri-apps/plugins-workspace/pull/865)) Fix incorrect `create` option default value for `writeFile` and `writeTextFile` which didn't match documentation. +- [`61edbbe`](https://github.com/tauri-apps/plugins-workspace/commit/61edbbec0acda4213ed8684f75a973e8be123a52)([#885](https://github.com/tauri-apps/plugins-workspace/pull/885)) Replace `notify-debouncer-mini` with `notify-debouncer-full`. [(plugins-workspace#885)](https://github.com/tauri-apps/plugins-workspace/pull/885) + +## \[2.0.0-alpha.6] + +- [`85f8419`](https://github.com/tauri-apps/plugins-workspace/commit/85f841968200316958d707db0c39bb115f762471)([#848](https://github.com/tauri-apps/plugins-workspace/pull/848)) Fix `DebouncedEvent` type to correctly represent the actual type. +- [`c601230`](https://github.com/tauri-apps/plugins-workspace/commit/c60123093ddf725af7228494182fed697ff8b021)([#847](https://github.com/tauri-apps/plugins-workspace/pull/847)) Add `createNew` option for `writeFile` and `writeTextFile` to create the file if doesn't exist and fail if it does. +- [`c601230`](https://github.com/tauri-apps/plugins-workspace/commit/c60123093ddf725af7228494182fed697ff8b021)([#847](https://github.com/tauri-apps/plugins-workspace/pull/847)) Truncate files when using `writeFile` and `writeTextFile` with `append: false`. +- [`2e2fc8d`](https://github.com/tauri-apps/plugins-workspace/commit/2e2fc8de69dd8d282b66ec81561d57d8af802dc5)([#857](https://github.com/tauri-apps/plugins-workspace/pull/857)) Fix `invalid args id for command unwatch` error when trying to unwatch a previously watched file or directory. +- [`c601230`](https://github.com/tauri-apps/plugins-workspace/commit/c60123093ddf725af7228494182fed697ff8b021)([#847](https://github.com/tauri-apps/plugins-workspace/pull/847)) Fix panic when using `writeFile` or `writeTextFile` without passing an option object. + +## \[2.0.0-alpha.5] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.13. +- [`69a1fa0`](https://github.com/tauri-apps/plugins-workspace/commit/69a1fa099c3143b6e426492f1c9d9cfbe56d2209)([#751](https://github.com/tauri-apps/plugins-workspace/pull/751)) The `fs` plugin received a major overhaul to add new APIs and changed existing APIs to be closer to Node.js and Deno APIs. + +## \[2.0.0-alpha.4] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.12. +- [`88d260d`](https://github.com/tauri-apps/plugins-workspace/commit/88d260d90130f9df4b9ce00c1ad1bf1e4b30b1c0)([#744](https://github.com/tauri-apps/plugins-workspace/pull/744)) Add second argument to `exists` function to specify base directory. + +## \[2.0.0-alpha.3] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to @tauri-apps/api v2.0.0-alpha.11. + +## \[2.0.0-alpha.2] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update to @tauri-apps/api v2.0.0-alpha.9. + +## \[2.0.0-alpha.2] + +- [`4e2cef9`](https://github.com/tauri-apps/plugins-workspace/commit/4e2cef9b702bbbb9cf4ee17de50791cb21f1b2a4)([#593](https://github.com/tauri-apps/plugins-workspace/pull/593)) Update to alpha.12. + +## \[2.0.0-alpha.1] + +- [`0bba693`](https://github.com/tauri-apps/plugins-workspace/commit/0bba6932c09da5267a9dbf75ba52252e39458420)([#454](https://github.com/tauri-apps/plugins-workspace/pull/454)) Fix `writeBinaryFile` crashing with `command 'write_binary_file' not found` +- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11. + +## \[2.0.0-alpha.0] + +- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! diff --git a/packages/kbot/gui/app/plugins/fs/Cargo.toml b/packages/kbot/gui/app/plugins/fs/Cargo.toml new file mode 100644 index 00000000..bff3baea --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "tauri-plugin-fs" +version = "2.4.2" +description = "Access the file system." +authors = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-fs" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.platforms.support] +windows = { level = "full", notes = "Apps installed via MSI or NSIS in `perMachine` and `both` mode require admin permissions for write access in `$RESOURCES` folder" } +linux = { level = "full", notes = "No write access to `$RESOURCES` folder" } +macos = { level = "full", notes = "No write access to `$RESOURCES` folder" } +android = { level = "partial", notes = "Access is restricted to Application folder by default" } +ios = { level = "partial", notes = "Access is restricted to Application folder by default" } + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } +schemars = { workspace = true } +serde = { workspace = true } +toml = "0.9" +tauri-utils = { workspace = true, features = ["build"] } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +serde_repr = "0.1" +tauri = { workspace = true } +thiserror = { workspace = true } +url = { workspace = true } +anyhow = "1" +glob = { workspace = true } +# TODO: Remove `serialization-compat-6` in v3 +notify = { version = "8", optional = true, features = [ + "serde", + "serialization-compat-6", +] } +notify-debouncer-full = { version = "0.6", optional = true } +dunce = { workspace = true } +percent-encoding = "2" + +[features] +watch = ["notify", "notify-debouncer-full"] diff --git a/packages/kbot/gui/app/plugins/fs/LICENSE.spdx b/packages/kbot/gui/app/plugins/fs/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/fs/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/LICENSE_MIT b/packages/kbot/gui/app/plugins/fs/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/README.md b/packages/kbot/gui/app/plugins/fs/README.md new file mode 100644 index 00000000..d02c71d5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/README.md @@ -0,0 +1,91 @@ +![plugin-fs](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/fs/banner.png) + +Access the file system. + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | ✓ | +| iOS | ✓ | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-fs = "2.0.0" +# alternatively with Git: +tauri-plugin-fs = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +```sh +pnpm add @tauri-apps/plugin-fs +# or +npm add @tauri-apps/plugin-fs +# or +yarn add @tauri-apps/plugin-fs +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_fs::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { stat } from '@tauri-apps/plugin-fs' + +await stat('/path/to/file') +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/fs/SECURITY.md b/packages/kbot/gui/app/plugins/fs/SECURITY.md new file mode 100644 index 00000000..838ed670 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/SECURITY.md @@ -0,0 +1,49 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). + +## Threat Model + +This plugin possibly allows access to the full filesystem available to the application process. +Depending on the operating system the access is already confined (android/ios) to only certain locations. +In other operating systems like Linux/MacOS/Windows it depends on the installation and packaging method but in most cases full +access is granted. + +To prevent exposure of sensitive locations and data this plugin can be scoped to only allow certain base directories +or only access to specific files or subdirectories. +This scoping effectively affects only calls made from the webviews/frontend code and calls made from rust can always circumvent +the restrictions imposed by the scope. + +The scope is defined at compile time in the used permissions but the user or application developer can grant or revoke access to specific files or folders at runtime by modifying the scope state through the runtime authority, if configured during plugin initialization. + +### Security Assumptions + +- The filesystem access is limited by user permissions +- The operating system filesystem access confinment works as documented +- The scoping mechanism of the Tauri `fs` commands work as intended and has no bypasses +- The user or application developer can grant or revoke access to specific files at runtime by modifying the scope + +#### Out Of Scope + +- Exploits in underlying filesystems +- Exploits in the underlying rust `std::fs` library diff --git a/packages/kbot/gui/app/plugins/fs/android/.gitignore b/packages/kbot/gui/app/plugins/fs/android/.gitignore new file mode 100644 index 00000000..c0f21ec2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/android/.gitignore @@ -0,0 +1,2 @@ +/build +/.tauri diff --git a/packages/kbot/gui/app/plugins/fs/android/build.gradle.kts b/packages/kbot/gui/app/plugins/fs/android/build.gradle.kts new file mode 100644 index 00000000..575040aa --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/android/build.gradle.kts @@ -0,0 +1,45 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "com.plugin.fs" + compileSdk = 34 + + defaultConfig { + minSdk = 21 + targetSdk = 34 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.0") + implementation("com.google.android.material:material:1.7.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + implementation(project(":tauri-android")) +} diff --git a/packages/kbot/gui/app/plugins/fs/android/proguard-rules.pro b/packages/kbot/gui/app/plugins/fs/android/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/android/settings.gradle b/packages/kbot/gui/app/plugins/fs/android/settings.gradle new file mode 100644 index 00000000..d7782a40 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/android/settings.gradle @@ -0,0 +1,31 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + google() + } + resolutionStrategy { + eachPlugin { + switch (requested.id.id) { + case "com.android.library": + useVersion("8.0.2") + break + case "org.jetbrains.kotlin.android": + useVersion("1.8.20") + break + } + } + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + mavenCentral() + google() + + } +} + +include ':tauri-android' +project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/packages/kbot/gui/app/plugins/fs/android/src/androidTest/java/ExampleInstrumentedTest.kt b/packages/kbot/gui/app/plugins/fs/android/src/androidTest/java/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..c3b473f7 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/android/src/androidTest/java/ExampleInstrumentedTest.kt @@ -0,0 +1,28 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package com.plugin.fs + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.plugin.fs", appContext.packageName) + } +} diff --git a/packages/kbot/gui/app/plugins/fs/android/src/main/AndroidManifest.xml b/packages/kbot/gui/app/plugins/fs/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..9a40236b --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/packages/kbot/gui/app/plugins/fs/android/src/main/java/FsPlugin.kt b/packages/kbot/gui/app/plugins/fs/android/src/main/java/FsPlugin.kt new file mode 100644 index 00000000..877fbf4a --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/android/src/main/java/FsPlugin.kt @@ -0,0 +1,93 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package com.plugin.fs + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.res.AssetManager.ACCESS_BUFFER +import android.net.Uri +import android.os.ParcelFileDescriptor +import app.tauri.annotation.Command +import app.tauri.annotation.InvokeArg +import app.tauri.annotation.TauriPlugin +import app.tauri.plugin.Invoke +import app.tauri.plugin.JSObject +import app.tauri.plugin.Plugin +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream + +@InvokeArg +class WriteTextFileArgs { + val uri: String = "" + val content: String = "" +} + +@InvokeArg +class GetFileDescriptorArgs { + lateinit var uri: String + lateinit var mode: String +} + +@TauriPlugin +class FsPlugin(private val activity: Activity): Plugin(activity) { + @SuppressLint("Recycle") + @Command + fun getFileDescriptor(invoke: Invoke) { + val args = invoke.parseArgs(GetFileDescriptorArgs::class.java) + + val res = JSObject() + + if (args.uri.startsWith(app.tauri.TAURI_ASSETS_DIRECTORY_URI)) { + val path = args.uri.substring(app.tauri.TAURI_ASSETS_DIRECTORY_URI.length) + try { + val fd = activity.assets.openFd(path).parcelFileDescriptor?.detachFd() + res.put("fd", fd) + } catch (e: IOException) { + // if the asset is compressed, we cannot open a file descriptor directly + // so we copy it to the cache and get a fd from there + // this is a lot faster than serializing the file and sending it as invoke response + // because on the Rust side we can leverage the custom protocol IPC and read the file directly + val cacheFile = File(activity.cacheDir, "_assets/$path") + cacheFile.parentFile?.mkdirs() + copyAsset(path, cacheFile) + + val fd = ParcelFileDescriptor.open(cacheFile, ParcelFileDescriptor.parseMode(args.mode)).detachFd() + res.put("fd", fd) + } + } else { + val fd = activity.contentResolver.openAssetFileDescriptor( + Uri.parse(args.uri), + args.mode + )?.parcelFileDescriptor?.detachFd() + res.put("fd", fd) + } + + invoke.resolve(res) + } + + @Throws(IOException::class) + private fun copy(input: InputStream, output: OutputStream) { + val buf = ByteArray(1024) + var len: Int + while ((input.read(buf).also { len = it }) > 0) { + output.write(buf, 0, len) + } + } + + @Throws(IOException::class) + private fun copyAsset(assetPath: String, cacheFile: File) { + val input = activity.assets.open(assetPath, ACCESS_BUFFER) + input.use { i -> + val output = FileOutputStream(cacheFile, false) + output.use { o -> + copy(i, o) + } + } + } +} + diff --git a/packages/kbot/gui/app/plugins/fs/android/src/test/java/ExampleUnitTest.kt b/packages/kbot/gui/app/plugins/fs/android/src/test/java/ExampleUnitTest.kt new file mode 100644 index 00000000..340839a5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/android/src/test/java/ExampleUnitTest.kt @@ -0,0 +1,21 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package com.plugin.fs + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/packages/kbot/gui/app/plugins/fs/api-iife.js b/packages/kbot/gui/app/plugins/fs/api-iife.js new file mode 100644 index 00000000..5fdd186d --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_FS__=function(t){"use strict";function e(t,e,n,i){if("function"==typeof e?t!==e||!i:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?i:"a"===n?i.call(t):i?i.value:e.get(t)}function n(t,e,n,i,o){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,n),n}var i,o,r,a,s;"function"==typeof SuppressedError&&SuppressedError;const c="__TAURI_TO_IPC_KEY__";class f{constructor(t){i.set(this,void 0),o.set(this,0),r.set(this,[]),a.set(this,void 0),n(this,i,t||(()=>{})),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((t=>{const s=t.index;if("end"in t)return void(s==e(this,o,"f")?this.cleanupCallback():n(this,a,s));const c=t.message;if(s==e(this,o,"f")){for(e(this,i,"f").call(this,c),n(this,o,e(this,o,"f")+1);e(this,o,"f")in e(this,r,"f");){const t=e(this,r,"f")[e(this,o,"f")];e(this,i,"f").call(this,t),delete e(this,r,"f")[e(this,o,"f")],n(this,o,e(this,o,"f")+1)}e(this,o,"f")===e(this,a,"f")&&this.cleanupCallback()}else e(this,r,"f")[s]=c}))}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(t){n(this,i,t)}get onmessage(){return e(this,i,"f")}[(i=new WeakMap,o=new WeakMap,r=new WeakMap,a=new WeakMap,c)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[c]()}}async function l(t,e={},n){return window.__TAURI_INTERNALS__.invoke(t,e,n)}class u{get rid(){return e(this,s,"f")}constructor(t){s.set(this,void 0),n(this,s,t)}async close(){return l("plugin:resources|close",{rid:this.rid})}}var p,w;function d(t){return{isFile:t.isFile,isDirectory:t.isDirectory,isSymlink:t.isSymlink,size:t.size,mtime:null!==t.mtime?new Date(t.mtime):null,atime:null!==t.atime?new Date(t.atime):null,birthtime:null!==t.birthtime?new Date(t.birthtime):null,readonly:t.readonly,fileAttributes:t.fileAttributes,dev:t.dev,ino:t.ino,mode:t.mode,nlink:t.nlink,uid:t.uid,gid:t.gid,rdev:t.rdev,blksize:t.blksize,blocks:t.blocks}}s=new WeakMap,t.BaseDirectory=void 0,(p=t.BaseDirectory||(t.BaseDirectory={}))[p.Audio=1]="Audio",p[p.Cache=2]="Cache",p[p.Config=3]="Config",p[p.Data=4]="Data",p[p.LocalData=5]="LocalData",p[p.Document=6]="Document",p[p.Download=7]="Download",p[p.Picture=8]="Picture",p[p.Public=9]="Public",p[p.Video=10]="Video",p[p.Resource=11]="Resource",p[p.Temp=12]="Temp",p[p.AppConfig=13]="AppConfig",p[p.AppData=14]="AppData",p[p.AppLocalData=15]="AppLocalData",p[p.AppCache=16]="AppCache",p[p.AppLog=17]="AppLog",p[p.Desktop=18]="Desktop",p[p.Executable=19]="Executable",p[p.Font=20]="Font",p[p.Home=21]="Home",p[p.Runtime=22]="Runtime",p[p.Template=23]="Template",t.SeekMode=void 0,(w=t.SeekMode||(t.SeekMode={}))[w.Start=0]="Start",w[w.Current=1]="Current",w[w.End=2]="End";class h extends u{async read(t){if(0===t.byteLength)return 0;const e=await l("plugin:fs|read",{rid:this.rid,len:t.byteLength}),n=function(t){const e=new Uint8ClampedArray(t),n=e.byteLength;let i=0;for(let t=0;tt instanceof URL?t.toString():t)),options:n,onEvent:o}),a=new L(r);return()=>{a.close()}}return t.FileHandle=h,t.copyFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol||e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|copy_file",{fromPath:t instanceof URL?t.toString():t,toPath:e instanceof URL?e.toString():e,options:n})},t.create=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await l("plugin:fs|create",{path:t instanceof URL?t.toString():t,options:e});return new h(n)},t.exists=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");return await l("plugin:fs|exists",{path:t instanceof URL?t.toString():t,options:e})},t.lstat=async function(t,e){return d(await l("plugin:fs|lstat",{path:t instanceof URL?t.toString():t,options:e}))},t.mkdir=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|mkdir",{path:t instanceof URL?t.toString():t,options:e})},t.open=y,t.readDir=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");return await l("plugin:fs|read_dir",{path:t instanceof URL?t.toString():t,options:e})},t.readFile=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await l("plugin:fs|read_file",{path:t instanceof URL?t.toString():t,options:e});return n instanceof ArrayBuffer?new Uint8Array(n):Uint8Array.from(n)},t.readTextFile=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await l("plugin:fs|read_text_file",{path:t instanceof URL?t.toString():t,options:e}),i=n instanceof ArrayBuffer?n:Uint8Array.from(n);return(new TextDecoder).decode(i)},t.readTextFileLines=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=t instanceof URL?t.toString():t;return await Promise.resolve({path:n,rid:null,async next(){null===this.rid&&(this.rid=await l("plugin:fs|read_text_file_lines",{path:n,options:e}));const t=await l("plugin:fs|read_text_file_lines_next",{rid:this.rid}),i=t instanceof ArrayBuffer?new Uint8Array(t):Uint8Array.from(t),o=1===i[i.byteLength-1];if(o)return this.rid=null,{value:null,done:o};return{value:(new TextDecoder).decode(i.slice(0,i.byteLength)),done:o}},[Symbol.asyncIterator](){return this}})},t.remove=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|remove",{path:t instanceof URL?t.toString():t,options:e})},t.rename=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol||e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|rename",{oldPath:t instanceof URL?t.toString():t,newPath:e instanceof URL?e.toString():e,options:n})},t.size=async function(t){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");return await l("plugin:fs|size",{path:t instanceof URL?t.toString():t})},t.stat=async function(t,e){return d(await l("plugin:fs|stat",{path:t instanceof URL?t.toString():t,options:e}))},t.truncate=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|truncate",{path:t instanceof URL?t.toString():t,len:e,options:n})},t.watch=async function(t,e,n){return await R(t,e,{delayMs:2e3,...n})},t.watchImmediate=async function(t,e,n){return await R(t,e,{...n,delayMs:void 0})},t.writeFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");if(e instanceof ReadableStream){const i=await y(t,{read:!1,create:!0,write:!0,...n}),o=e.getReader();try{for(;;){const{done:t,value:e}=await o.read();if(t)break;await i.write(e)}}finally{o.releaseLock(),await i.close()}}else await l("plugin:fs|write_file",e,{headers:{path:encodeURIComponent(t instanceof URL?t.toString():t),options:JSON.stringify(n)}})},t.writeTextFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const i=new TextEncoder;await l("plugin:fs|write_text_file",i.encode(e),{headers:{path:encodeURIComponent(t instanceof URL?t.toString():t),options:JSON.stringify(n)}})},t}({});Object.defineProperty(window.__TAURI__,"fs",{value:__TAURI_PLUGIN_FS__})} diff --git a/packages/kbot/gui/app/plugins/fs/banner.png b/packages/kbot/gui/app/plugins/fs/banner.png new file mode 100644 index 00000000..767a1fe2 Binary files /dev/null and b/packages/kbot/gui/app/plugins/fs/banner.png differ diff --git a/packages/kbot/gui/app/plugins/fs/build.rs b/packages/kbot/gui/app/plugins/fs/build.rs new file mode 100644 index 00000000..47e27003 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/build.rs @@ -0,0 +1,267 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{ + fs::create_dir_all, + path::{Path, PathBuf}, +}; + +use tauri_utils::acl::manifest::PermissionFile; + +#[path = "src/scope.rs"] +#[allow(dead_code)] +mod scope; + +/// FS scope entry. +#[derive(schemars::JsonSchema)] +#[serde(untagged)] +#[allow(unused)] +enum FsScopeEntry { + /// A path that can be accessed by the webview when using the fs APIs. + /// FS scope path pattern. + /// + /// The pattern can start with a variable that resolves to a system base directory. + /// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, + /// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, + /// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, + /// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`. + Value(PathBuf), + Object { + /// A path that can be accessed by the webview when using the fs APIs. + /// + /// The pattern can start with a variable that resolves to a system base directory. + /// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, + /// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, + /// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, + /// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`. + path: PathBuf, + }, +} + +// Ensure `FsScopeEntry` and `scope::EntryRaw` is kept in sync +fn _f() { + match scope::EntryRaw::Value(PathBuf::new()) { + scope::EntryRaw::Value(path) => FsScopeEntry::Value(path), + scope::EntryRaw::Object { path } => FsScopeEntry::Object { path }, + }; + match FsScopeEntry::Value(PathBuf::new()) { + FsScopeEntry::Value(path) => scope::EntryRaw::Value(path), + FsScopeEntry::Object { path } => scope::EntryRaw::Object { path }, + }; +} + +const BASE_DIR_VARS: &[&str] = &[ + "AUDIO", + "CACHE", + "CONFIG", + "DATA", + "LOCALDATA", + "DESKTOP", + "DOCUMENT", + "DOWNLOAD", + "EXE", + "FONT", + "HOME", + "PICTURE", + "PUBLIC", + "RUNTIME", + "TEMPLATE", + "VIDEO", + "RESOURCE", + "LOG", + "TEMP", + "APPCONFIG", + "APPDATA", + "APPLOCALDATA", + "APPCACHE", + "APPLOG", +]; +const COMMANDS: &[(&str, &[&str])] = &[ + ("mkdir", &[]), + ("create", &[]), + ("copy_file", &[]), + ("remove", &[]), + ("rename", &[]), + ("truncate", &[]), + ("ftruncate", &[]), + ("write", &[]), + ("write_file", &["open", "write"]), + ("write_text_file", &[]), + ("read_dir", &[]), + ("read_file", &[]), + ("read", &[]), + ("open", &[]), + ("read_text_file", &[]), + ("read_text_file_lines", &["read_text_file_lines_next"]), + ("read_text_file_lines_next", &[]), + ("seek", &[]), + ("stat", &[]), + ("lstat", &[]), + ("fstat", &[]), + ("exists", &[]), + ("watch", &[]), + // TODO: Remove this in v3 + ("unwatch", &[]), + ("size", &[]), +]; + +fn main() { + let autogenerated = Path::new("permissions/autogenerated/"); + let base_dirs = &autogenerated.join("base-directories"); + + if !base_dirs.exists() { + create_dir_all(base_dirs).expect("unable to create autogenerated base directories dir"); + } + + for base_dir in BASE_DIR_VARS { + let upper = base_dir; + let lower = base_dir.to_lowercase(); + let toml = format!( + r###"# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-{lower}-recursive" +description = "This scope permits recursive access to the complete `${upper}` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "${upper}" +[[permission.scope.allow]] +path = "${upper}/**" + +[[permission]] +identifier = "scope-{lower}" +description = "This scope permits access to all files and list content of top level directories in the `${upper}` folder." + +[[permission.scope.allow]] +path = "${upper}" +[[permission.scope.allow]] +path = "${upper}/*" + +[[permission]] +identifier = "scope-{lower}-index" +description = "This scope permits to list all files and folders in the `${upper}`folder." + +[[permission.scope.allow]] +path = "${upper}" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-{lower}-read-recursive" +description = "This allows full recursive read access to the complete `${upper}` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-{lower}-recursive" +] + +[[set]] +identifier = "allow-{lower}-write-recursive" +description = "This allows full recursive write access to the complete `${upper}` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-{lower}-recursive" +] + +[[set]] +identifier = "allow-{lower}-read" +description = "This allows non-recursive read access to the `${upper}` folder." +permissions = [ + "read-all", + "scope-{lower}" +] + +[[set]] +identifier = "allow-{lower}-write" +description = "This allows non-recursive write access to the `${upper}` folder." +permissions = [ + "write-all", + "scope-{lower}" +] + +[[set]] +identifier = "allow-{lower}-meta-recursive" +description = "This allows full recursive read access to metadata of the `${upper}` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-{lower}-recursive" +] + +[[set]] +identifier = "allow-{lower}-meta" +description = "This allows non-recursive read access to metadata of the `${upper}` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-{lower}-index" +]"### + ); + + let permission_path = base_dirs.join(format!("{lower}.toml")); + if toml != std::fs::read_to_string(&permission_path).unwrap_or_default() { + std::fs::write(permission_path, toml) + .unwrap_or_else(|e| panic!("unable to autogenerate ${lower}: {e}")); + } + } + + tauri_plugin::Builder::new( + &COMMANDS + .iter() + // FIXME: https://docs.rs/crate/tauri-plugin-fs/2.1.0/builds/1571296 + .filter(|c| c.1.is_empty()) + .map(|c| c.0) + .collect::>(), + ) + .global_api_script_path("./api-iife.js") + .global_scope_schema(schemars::schema_for!(FsScopeEntry)) + .android_path("android") + .build(); + + // workaround to include nested permissions as `tauri_plugin` doesn't support it + let permissions_dir = autogenerated.join("commands"); + for (command, nested_commands) in COMMANDS { + if nested_commands.is_empty() { + continue; + } + + let permission_path = permissions_dir.join(format!("{command}.toml")); + + let content = std::fs::read_to_string(&permission_path) + .unwrap_or_else(|_| panic!("failed to read {command}.toml")); + + let mut permission_file = toml::from_str::(&content) + .unwrap_or_else(|_| panic!("failed to deserialize {command}.toml")); + + for p in permission_file + .permission + .iter_mut() + .filter(|p| p.identifier.starts_with("allow")) + { + for c in nested_commands.iter().map(|s| s.to_string()) { + if !p.commands.allow.contains(&c) { + p.commands.allow.push(c); + } + } + } + + let out = toml::to_string_pretty(&permission_file) + .unwrap_or_else(|_| panic!("failed to serialize {command}.toml")); + let out = format!( + r#"# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +{out}"# + ); + + if content != out { + std::fs::write(permission_path, out) + .unwrap_or_else(|_| panic!("failed to write {command}.toml")); + } + } +} diff --git a/packages/kbot/gui/app/plugins/fs/guest-js/index.ts b/packages/kbot/gui/app/plugins/fs/guest-js/index.ts new file mode 100644 index 00000000..572cc1e8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/guest-js/index.ts @@ -0,0 +1,1398 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/** + * Access the file system. + * + * ## Security + * + * This module prevents path traversal, not allowing parent directory accessors to be used + * (i.e. "/usr/path/to/../file" or "../path/to/file" paths are not allowed). + * Paths accessed with this API must be either relative to one of the {@link BaseDirectory | base directories} + * or created with the {@link https://v2.tauri.app/reference/javascript/api/namespacepath/ | path API}. + * + * The API has a scope configuration that forces you to restrict the paths that can be accessed using glob patterns. + * + * The scope configuration is an array of glob patterns describing file/directory paths that are allowed. + * For instance, this scope configuration allows **all** enabled `fs` APIs to (only) access files in the + * *databases* directory of the {@link https://v2.tauri.app/reference/javascript/api/namespacepath/#appdatadir | `$APPDATA` directory}: + * ```json + * { + * "permissions": [ + * { + * "identifier": "fs:scope", + * "allow": [{ "path": "$APPDATA/databases/*" }] + * } + * ] + * } + * ``` + * + * Scopes can also be applied to specific `fs` APIs by using the API's identifier instead of `fs:scope`: + * ```json + * { + * "permissions": [ + * { + * "identifier": "fs:allow-exists", + * "allow": [{ "path": "$APPDATA/databases/*" }] + * } + * ] + * } + * ``` + * + * Notice the use of the `$APPDATA` variable. The value is injected at runtime, resolving to the {@link https://v2.tauri.app/reference/javascript/api/namespacepath/#appdatadir | app data directory}. + * + * The available variables are: + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#appconfigdir | $APPCONFIG}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#appdatadir | $APPDATA}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#applocaldatadir | $APPLOCALDATA}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#appcachedir | $APPCACHE}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#applogdir | $APPLOG}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#audiodir | $AUDIO}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#cachedir | $CACHE}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#configdir | $CONFIG}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#datadir | $DATA}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#localdatadir | $LOCALDATA}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#desktopdir | $DESKTOP}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#documentdir | $DOCUMENT}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#downloaddir | $DOWNLOAD}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#executabledir | $EXE}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#fontdir | $FONT}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#homedir | $HOME}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#picturedir | $PICTURE}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#publicdir | $PUBLIC}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#runtimedir | $RUNTIME}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#templatedir | $TEMPLATE}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#videodir | $VIDEO}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#resourcedir | $RESOURCE}, + * {@linkcode https://v2.tauri.app/reference/javascript/api/namespacepath/#tempdir | $TEMP}. + * + * Trying to execute any API with a URL not configured on the scope results in a promise rejection due to denied access. + * + * @module + */ + +import { BaseDirectory } from '@tauri-apps/api/path' +import { Channel, invoke, Resource } from '@tauri-apps/api/core' + +enum SeekMode { + Start = 0, + Current = 1, + End = 2 +} + +/** + * A FileInfo describes a file and is returned by `stat`, `lstat` or `fstat`. + * + * @since 2.0.0 + */ +interface FileInfo { + /** + * True if this is info for a regular file. Mutually exclusive to + * `FileInfo.isDirectory` and `FileInfo.isSymlink`. + */ + isFile: boolean + /** + * True if this is info for a regular directory. Mutually exclusive to + * `FileInfo.isFile` and `FileInfo.isSymlink`. + */ + isDirectory: boolean + /** + * True if this is info for a symlink. Mutually exclusive to + * `FileInfo.isFile` and `FileInfo.isDirectory`. + */ + isSymlink: boolean + /** + * The size of the file, in bytes. + */ + size: number + /** + * The last modification time of the file. This corresponds to the `mtime` + * field from `stat` on Linux/Mac OS and `ftLastWriteTime` on Windows. This + * may not be available on all platforms. + */ + mtime: Date | null + /** + * The last access time of the file. This corresponds to the `atime` + * field from `stat` on Unix and `ftLastAccessTime` on Windows. This may not + * be available on all platforms. + */ + atime: Date | null + /** + * The creation time of the file. This corresponds to the `birthtime` + * field from `stat` on Mac/BSD and `ftCreationTime` on Windows. This may + * not be available on all platforms. + */ + birthtime: Date | null + /** Whether this is a readonly (unwritable) file. */ + readonly: boolean + /** + * This field contains the file system attribute information for a file + * or directory. For possible values and their descriptions, see + * {@link https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants | File Attribute Constants} in the Windows Dev Center + * + * #### Platform-specific + * + * - **macOS / Linux / Android / iOS:** Unsupported. + */ + fileAttributes: number | null + /** + * ID of the device containing the file. + * + * #### Platform-specific + * + * - **Windows:** Unsupported. + */ + dev: number | null + /** + * Inode number. + * + * #### Platform-specific + * + * - **Windows:** Unsupported. + */ + ino: number | null + /** + * The underlying raw `st_mode` bits that contain the standard Unix + * permissions for this file/directory. + * + * #### Platform-specific + * + * - **Windows:** Unsupported. + */ + mode: number | null + /** + * Number of hard links pointing to this file. + * + * #### Platform-specific + * + * - **Windows:** Unsupported. + */ + nlink: number | null + /** + * User ID of the owner of this file. + * + * #### Platform-specific + * + * - **Windows:** Unsupported. + */ + uid: number | null + /** + * Group ID of the owner of this file. + * + * #### Platform-specific + * + * - **Windows:** Unsupported. + */ + gid: number | null + /** + * Device ID of this file. + * + * #### Platform-specific + * + * - **Windows:** Unsupported. + */ + rdev: number | null + /** + * Blocksize for filesystem I/O. + * + * #### Platform-specific + * + * - **Windows:** Unsupported. + */ + blksize: number | null + /** + * Number of blocks allocated to the file, in 512-byte units. + * + * #### Platform-specific + * + * - **Windows:** Unsupported. + */ + blocks: number | null +} + +interface UnparsedFileInfo { + isFile: boolean + isDirectory: boolean + isSymlink: boolean + size: number + mtime: number | null + atime: number | null + birthtime: number | null + readonly: boolean + fileAttributes: number + dev: number | null + ino: number | null + mode: number | null + nlink: number | null + uid: number | null + gid: number | null + rdev: number | null + blksize: number | null + blocks: number | null +} +function parseFileInfo(r: UnparsedFileInfo): FileInfo { + return { + isFile: r.isFile, + isDirectory: r.isDirectory, + isSymlink: r.isSymlink, + size: r.size, + mtime: r.mtime !== null ? new Date(r.mtime) : null, + atime: r.atime !== null ? new Date(r.atime) : null, + birthtime: r.birthtime !== null ? new Date(r.birthtime) : null, + readonly: r.readonly, + fileAttributes: r.fileAttributes, + dev: r.dev, + ino: r.ino, + mode: r.mode, + nlink: r.nlink, + uid: r.uid, + gid: r.gid, + rdev: r.rdev, + blksize: r.blksize, + blocks: r.blocks + } +} + +// https://mstn.github.io/2018/06/08/fixed-size-arrays-in-typescript/ +type FixedSizeArray = ReadonlyArray & { + length: N +} + +// https://gist.github.com/zapthedingbat/38ebfbedd98396624e5b5f2ff462611d +/** Converts a big-endian eight byte array to number */ +function fromBytes(buffer: FixedSizeArray): number { + const bytes = new Uint8ClampedArray(buffer) + const size = bytes.byteLength + let x = 0 + for (let i = 0; i < size; i++) { + // eslint-disable-next-line security/detect-object-injection + const byte = bytes[i] + x *= 0x100 + x += byte + } + return x +} + +/** + * The Tauri abstraction for reading and writing files. + * + * @since 2.0.0 + */ +class FileHandle extends Resource { + /** + * Reads up to `p.byteLength` bytes into `p`. It resolves to the number of + * bytes read (`0` < `n` <= `p.byteLength`) and rejects if any error + * encountered. Even if `read()` resolves to `n` < `p.byteLength`, it may + * use all of `p` as scratch space during the call. If some data is + * available but not `p.byteLength` bytes, `read()` conventionally resolves + * to what is available instead of waiting for more. + * + * When `read()` encounters end-of-file condition, it resolves to EOF + * (`null`). + * + * When `read()` encounters an error, it rejects with an error. + * + * Callers should always process the `n` > `0` bytes returned before + * considering the EOF (`null`). Doing so correctly handles I/O errors that + * happen after reading some bytes and also both of the allowed EOF + * behaviors. + * + * @example + * ```typescript + * import { open, BaseDirectory } from "@tauri-apps/plugin-fs" + * // if "$APPCONFIG/foo/bar.txt" contains the text "hello world": + * const file = await open("foo/bar.txt", { baseDir: BaseDirectory.AppConfig }); + * const buf = new Uint8Array(100); + * const numberOfBytesRead = await file.read(buf); // 11 bytes + * const text = new TextDecoder().decode(buf); // "hello world" + * await file.close(); + * ``` + * + * @since 2.0.0 + */ + async read(buffer: Uint8Array): Promise { + if (buffer.byteLength === 0) { + return 0 + } + + const data = await invoke('plugin:fs|read', { + rid: this.rid, + len: buffer.byteLength + }) + + // Rust side will never return an empty array for this command and + // ensure there is at least 8 elements there. + // + // This is an optimization to include the number of read bytes (as bigendian bytes) + // at the end of returned array to avoid serialization overhead of separate values. + const nread = fromBytes(data.slice(-8) as FixedSizeArray) + + const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data + buffer.set(bytes.slice(0, bytes.length - 8)) + + return nread === 0 ? null : nread + } + + /** + * Seek sets the offset for the next `read()` or `write()` to offset, + * interpreted according to `whence`: `Start` means relative to the + * start of the file, `Current` means relative to the current offset, + * and `End` means relative to the end. Seek resolves to the new offset + * relative to the start of the file. + * + * Seeking to an offset before the start of the file is an error. Seeking to + * any positive offset is legal, but the behavior of subsequent I/O + * operations on the underlying object is implementation-dependent. + * It returns the number of cursor position. + * + * @example + * ```typescript + * import { open, SeekMode, BaseDirectory } from '@tauri-apps/plugin-fs'; + * + * // Given hello.txt pointing to file with "Hello world", which is 11 bytes long: + * const file = await open('hello.txt', { read: true, write: true, truncate: true, create: true, baseDir: BaseDirectory.AppLocalData }); + * await file.write(new TextEncoder().encode("Hello world")); + * + * // Seek 6 bytes from the start of the file + * console.log(await file.seek(6, SeekMode.Start)); // "6" + * // Seek 2 more bytes from the current position + * console.log(await file.seek(2, SeekMode.Current)); // "8" + * // Seek backwards 2 bytes from the end of the file + * console.log(await file.seek(-2, SeekMode.End)); // "9" (e.g. 11-2) + * + * await file.close(); + * ``` + * + * @since 2.0.0 + */ + async seek(offset: number, whence: SeekMode): Promise { + return await invoke('plugin:fs|seek', { + rid: this.rid, + offset, + whence + }) + } + + /** + * Returns a {@linkcode FileInfo } for this file. + * + * @example + * ```typescript + * import { open, BaseDirectory } from '@tauri-apps/plugin-fs'; + * const file = await open("file.txt", { read: true, baseDir: BaseDirectory.AppLocalData }); + * const fileInfo = await file.stat(); + * console.log(fileInfo.isFile); // true + * await file.close(); + * ``` + * + * @since 2.0.0 + */ + async stat(): Promise { + const res = await invoke('plugin:fs|fstat', { + rid: this.rid + }) + + return parseFileInfo(res) + } + + /** + * Truncates or extends this file, to reach the specified `len`. + * If `len` is not specified then the entire file contents are truncated. + * + * @example + * ```typescript + * import { open, BaseDirectory } from '@tauri-apps/plugin-fs'; + * + * // truncate the entire file + * const file = await open("my_file.txt", { read: true, write: true, create: true, baseDir: BaseDirectory.AppLocalData }); + * await file.truncate(); + * + * // truncate part of the file + * const file = await open("my_file.txt", { read: true, write: true, create: true, baseDir: BaseDirectory.AppLocalData }); + * await file.write(new TextEncoder().encode("Hello World")); + * await file.truncate(7); + * const data = new Uint8Array(32); + * await file.read(data); + * console.log(new TextDecoder().decode(data)); // Hello W + * await file.close(); + * ``` + * + * @since 2.0.0 + */ + async truncate(len?: number): Promise { + await invoke('plugin:fs|ftruncate', { + rid: this.rid, + len + }) + } + + /** + * Writes `data.byteLength` bytes from `data` to the underlying data stream. It + * resolves to the number of bytes written from `data` (`0` <= `n` <= + * `data.byteLength`) or reject with the error encountered that caused the + * write to stop early. `write()` must reject with a non-null error if + * would resolve to `n` < `data.byteLength`. `write()` must not modify the + * slice data, even temporarily. + * + * @example + * ```typescript + * import { open, write, BaseDirectory } from '@tauri-apps/plugin-fs'; + * const encoder = new TextEncoder(); + * const data = encoder.encode("Hello world"); + * const file = await open("bar.txt", { write: true, baseDir: BaseDirectory.AppLocalData }); + * const bytesWritten = await file.write(data); // 11 + * await file.close(); + * ``` + * + * @since 2.0.0 + */ + async write(data: Uint8Array): Promise { + return await invoke('plugin:fs|write', { + rid: this.rid, + data + }) + } +} + +/** + * @since 2.0.0 + */ +interface CreateOptions { + /** Base directory for `path` */ + baseDir?: BaseDirectory +} + +/** + * Creates a file if none exists or truncates an existing file and resolves to + * an instance of {@linkcode FileHandle }. + * + * @example + * ```typescript + * import { create, BaseDirectory } from "@tauri-apps/plugin-fs" + * const file = await create("foo/bar.txt", { baseDir: BaseDirectory.AppConfig }); + * await file.write(new TextEncoder().encode("Hello world")); + * await file.close(); + * ``` + * + * @since 2.0.0 + */ +async function create( + path: string | URL, + options?: CreateOptions +): Promise { + if (path instanceof URL && path.protocol !== 'file:') { + throw new TypeError('Must be a file URL.') + } + + const rid = await invoke('plugin:fs|create', { + path: path instanceof URL ? path.toString() : path, + options + }) + + return new FileHandle(rid) +} + +/** + * @since 2.0.0 + */ +interface OpenOptions { + /** + * Sets the option for read access. This option, when `true`, means that the + * file should be read-able if opened. + */ + read?: boolean + /** + * Sets the option for write access. This option, when `true`, means that + * the file should be write-able if opened. If the file already exists, + * any write calls on it will overwrite its contents, by default without + * truncating it. + */ + write?: boolean + /** + * Sets the option for the append mode. This option, when `true`, means that + * writes will append to a file instead of overwriting previous contents. + * Note that setting `{ write: true, append: true }` has the same effect as + * setting only `{ append: true }`. + */ + append?: boolean + /** + * Sets the option for truncating a previous file. If a file is + * successfully opened with this option set it will truncate the file to `0` + * size if it already exists. The file must be opened with write access + * for truncate to work. + */ + truncate?: boolean + /** + * Sets the option to allow creating a new file, if one doesn't already + * exist at the specified path. Requires write or append access to be + * used. + */ + create?: boolean + /** + * Defaults to `false`. If set to `true`, no file, directory, or symlink is + * allowed to exist at the target location. Requires write or append + * access to be used. When createNew is set to `true`, create and truncate + * are ignored. + */ + createNew?: boolean + /** + * Permissions to use if creating the file (defaults to `0o666`, before + * the process's umask). + * Ignored on Windows. + */ + mode?: number + /** Base directory for `path` */ + baseDir?: BaseDirectory +} + +/** + * Open a file and resolve to an instance of {@linkcode FileHandle}. The + * file does not need to previously exist if using the `create` or `createNew` + * open options. It is the callers responsibility to close the file when finished + * with it. + * + * @example + * ```typescript + * import { open, BaseDirectory } from "@tauri-apps/plugin-fs" + * const file = await open("foo/bar.txt", { read: true, write: true, baseDir: BaseDirectory.AppLocalData }); + * // Do work with file + * await file.close(); + * ``` + * + * @since 2.0.0 + */ +async function open( + path: string | URL, + options?: OpenOptions +): Promise { + if (path instanceof URL && path.protocol !== 'file:') { + throw new TypeError('Must be a file URL.') + } + + const rid = await invoke('plugin:fs|open', { + path: path instanceof URL ? path.toString() : path, + options + }) + + return new FileHandle(rid) +} + +/** + * @since 2.0.0 + */ +interface CopyFileOptions { + /** Base directory for `fromPath`. */ + fromPathBaseDir?: BaseDirectory + /** Base directory for `toPath`. */ + toPathBaseDir?: BaseDirectory +} + +/** + * Copies the contents and permissions of one file to another specified path, by default creating a new file if needed, else overwriting. + * @example + * ```typescript + * import { copyFile, BaseDirectory } from '@tauri-apps/plugin-fs'; + * await copyFile('app.conf', 'app.conf.bk', { fromPathBaseDir: BaseDirectory.AppConfig, toPathBaseDir: BaseDirectory.AppConfig }); + * ``` + * + * @since 2.0.0 + */ +async function copyFile( + fromPath: string | URL, + toPath: string | URL, + options?: CopyFileOptions +): Promise { + if ( + (fromPath instanceof URL && fromPath.protocol !== 'file:') + || (toPath instanceof URL && toPath.protocol !== 'file:') + ) { + throw new TypeError('Must be a file URL.') + } + + await invoke('plugin:fs|copy_file', { + fromPath: fromPath instanceof URL ? fromPath.toString() : fromPath, + toPath: toPath instanceof URL ? toPath.toString() : toPath, + options + }) +} + +/** + * @since 2.0.0 + */ +interface MkdirOptions { + /** Permissions to use when creating the directory (defaults to `0o777`, before the process's umask). Ignored on Windows. */ + mode?: number + /** + * Defaults to `false`. If set to `true`, means that any intermediate directories will also be created (as with the shell command `mkdir -p`). + * */ + recursive?: boolean + /** Base directory for `path` */ + baseDir?: BaseDirectory +} + +/** + * Creates a new directory with the specified path. + * @example + * ```typescript + * import { mkdir, BaseDirectory } from '@tauri-apps/plugin-fs'; + * await mkdir('users', { baseDir: BaseDirectory.AppLocalData }); + * ``` + * + * @since 2.0.0 + */ +async function mkdir( + path: string | URL, + options?: MkdirOptions +): Promise { + if (path instanceof URL && path.protocol !== 'file:') { + throw new TypeError('Must be a file URL.') + } + + await invoke('plugin:fs|mkdir', { + path: path instanceof URL ? path.toString() : path, + options + }) +} + +/** + * @since 2.0.0 + */ +interface ReadDirOptions { + /** Base directory for `path` */ + baseDir?: BaseDirectory +} + +/** + * A disk entry which is either a file, a directory or a symlink. + * + * This is the result of the {@linkcode readDir}. + * + * @since 2.0.0 + */ +interface DirEntry { + /** The name of the entry (file name with extension or directory name). */ + name: string + /** Specifies whether this entry is a directory or not. */ + isDirectory: boolean + /** Specifies whether this entry is a file or not. */ + isFile: boolean + /** Specifies whether this entry is a symlink or not. */ + isSymlink: boolean +} + +/** + * Reads the directory given by path and returns an array of `DirEntry`. + * @example + * ```typescript + * import { readDir, BaseDirectory } from '@tauri-apps/plugin-fs'; + * import { join } from '@tauri-apps/api/path'; + * const dir = "users" + * const entries = await readDir('users', { baseDir: BaseDirectory.AppLocalData }); + * processEntriesRecursively(dir, entries); + * async function processEntriesRecursively(parent, entries) { + * for (const entry of entries) { + * console.log(`Entry: ${entry.name}`); + * if (entry.isDirectory) { + * const dir = await join(parent, entry.name); + * processEntriesRecursively(dir, await readDir(dir, { baseDir: BaseDirectory.AppLocalData })) + * } + * } + * } + * ``` + * + * @since 2.0.0 + */ +async function readDir( + path: string | URL, + options?: ReadDirOptions +): Promise { + if (path instanceof URL && path.protocol !== 'file:') { + throw new TypeError('Must be a file URL.') + } + + return await invoke('plugin:fs|read_dir', { + path: path instanceof URL ? path.toString() : path, + options + }) +} + +/** + * @since 2.0.0 + */ +interface ReadFileOptions { + /** Base directory for `path` */ + baseDir?: BaseDirectory +} + +/** + * Reads and resolves to the entire contents of a file as an array of bytes. + * TextDecoder can be used to transform the bytes to string if required. + * @example + * ```typescript + * import { readFile, BaseDirectory } from '@tauri-apps/plugin-fs'; + * const contents = await readFile('avatar.png', { baseDir: BaseDirectory.Resource }); + * ``` + * + * @since 2.0.0 + */ +async function readFile( + path: string | URL, + options?: ReadFileOptions +): Promise> { + if (path instanceof URL && path.protocol !== 'file:') { + throw new TypeError('Must be a file URL.') + } + + const arr = await invoke('plugin:fs|read_file', { + path: path instanceof URL ? path.toString() : path, + options + }) + + return arr instanceof ArrayBuffer ? new Uint8Array(arr) : Uint8Array.from(arr) +} + +/** + * Reads and returns the entire contents of a file as UTF-8 string. + * @example + * ```typescript + * import { readTextFile, BaseDirectory } from '@tauri-apps/plugin-fs'; + * const contents = await readTextFile('app.conf', { baseDir: BaseDirectory.AppConfig }); + * ``` + * + * @since 2.0.0 + */ +async function readTextFile( + path: string | URL, + options?: ReadFileOptions +): Promise { + if (path instanceof URL && path.protocol !== 'file:') { + throw new TypeError('Must be a file URL.') + } + + const arr = await invoke('plugin:fs|read_text_file', { + path: path instanceof URL ? path.toString() : path, + options + }) + + const bytes = arr instanceof ArrayBuffer ? arr : Uint8Array.from(arr) + + return new TextDecoder().decode(bytes) +} + +/** + * Returns an async {@linkcode AsyncIterableIterator} over the lines of a file as UTF-8 string. + * @example + * ```typescript + * import { readTextFileLines, BaseDirectory } from '@tauri-apps/plugin-fs'; + * const lines = await readTextFileLines('app.conf', { baseDir: BaseDirectory.AppConfig }); + * for await (const line of lines) { + * console.log(line); + * } + * ``` + * You could also call {@linkcode AsyncIterableIterator.next} to advance the + * iterator so you can lazily read the next line whenever you want. + * + * @since 2.0.0 + */ +async function readTextFileLines( + path: string | URL, + options?: ReadFileOptions +): Promise> { + if (path instanceof URL && path.protocol !== 'file:') { + throw new TypeError('Must be a file URL.') + } + + const pathStr = path instanceof URL ? path.toString() : path + + return await Promise.resolve({ + path: pathStr, + rid: null as number | null, + + async next(): Promise> { + if (this.rid === null) { + this.rid = await invoke('plugin:fs|read_text_file_lines', { + path: pathStr, + options + }) + } + + const arr = await invoke( + 'plugin:fs|read_text_file_lines_next', + { rid: this.rid } + ) + + const bytes = + arr instanceof ArrayBuffer ? new Uint8Array(arr) : Uint8Array.from(arr) + + // Rust side will never return an empty array for this command and + // ensure there is at least one elements there. + // + // This is an optimization to include whether we finished iteration or not (1 or 0) + // at the end of returned array to avoid serialization overhead of separate values. + const done = bytes[bytes.byteLength - 1] === 1 + + if (done) { + // a full iteration is over, reset rid for next iteration + this.rid = null + return { value: null, done } + } + + const line = new TextDecoder().decode(bytes.slice(0, bytes.byteLength)) + + return { + value: line, + done + } + }, + + [Symbol.asyncIterator](): AsyncIterableIterator { + return this + } + }) +} + +/** + * @since 2.0.0 + */ +interface RemoveOptions { + /** Defaults to `false`. If set to `true`, path will be removed even if it's a non-empty directory. */ + recursive?: boolean + /** Base directory for `path` */ + baseDir?: BaseDirectory +} + +/** + * Removes the named file or directory. + * If the directory is not empty and the `recursive` option isn't set to true, the promise will be rejected. + * @example + * ```typescript + * import { remove, BaseDirectory } from '@tauri-apps/plugin-fs'; + * await remove('users/file.txt', { baseDir: BaseDirectory.AppLocalData }); + * await remove('users', { baseDir: BaseDirectory.AppLocalData }); + * ``` + * + * @since 2.0.0 + */ +async function remove( + path: string | URL, + options?: RemoveOptions +): Promise { + if (path instanceof URL && path.protocol !== 'file:') { + throw new TypeError('Must be a file URL.') + } + + await invoke('plugin:fs|remove', { + path: path instanceof URL ? path.toString() : path, + options + }) +} + +/** + * @since 2.0.0 + */ +interface RenameOptions { + /** Base directory for `oldPath`. */ + oldPathBaseDir?: BaseDirectory + /** Base directory for `newPath`. */ + newPathBaseDir?: BaseDirectory +} + +/** + * Renames (moves) oldpath to newpath. Paths may be files or directories. + * If newpath already exists and is not a directory, rename() replaces it. + * OS-specific restrictions may apply when oldpath and newpath are in different directories. + * + * On Unix, this operation does not follow symlinks at either path. + * + * @example + * ```typescript + * import { rename, BaseDirectory } from '@tauri-apps/plugin-fs'; + * await rename('avatar.png', 'deleted.png', { oldPathBaseDir: BaseDirectory.App, newPathBaseDir: BaseDirectory.AppLocalData }); + * ``` + * + * @since 2.0.0 + */ +async function rename( + oldPath: string | URL, + newPath: string | URL, + options?: RenameOptions +): Promise { + if ( + (oldPath instanceof URL && oldPath.protocol !== 'file:') + || (newPath instanceof URL && newPath.protocol !== 'file:') + ) { + throw new TypeError('Must be a file URL.') + } + + await invoke('plugin:fs|rename', { + oldPath: oldPath instanceof URL ? oldPath.toString() : oldPath, + newPath: newPath instanceof URL ? newPath.toString() : newPath, + options + }) +} + +/** + * @since 2.0.0 + */ +interface StatOptions { + /** Base directory for `path`. */ + baseDir?: BaseDirectory +} + +/** + * Resolves to a {@linkcode FileInfo} for the specified `path`. Will always + * follow symlinks but will reject if the symlink points to a path outside of the scope. + * + * @example + * ```typescript + * import { stat, BaseDirectory } from '@tauri-apps/plugin-fs'; + * const fileInfo = await stat("hello.txt", { baseDir: BaseDirectory.AppLocalData }); + * console.log(fileInfo.isFile); // true + * ``` + * + * @since 2.0.0 + */ +async function stat( + path: string | URL, + options?: StatOptions +): Promise { + const res = await invoke('plugin:fs|stat', { + path: path instanceof URL ? path.toString() : path, + options + }) + + return parseFileInfo(res) +} + +/** + * Resolves to a {@linkcode FileInfo} for the specified `path`. If `path` is a + * symlink, information for the symlink will be returned instead of what it + * points to. + * + * @example + * ```typescript + * import { lstat, BaseDirectory } from '@tauri-apps/plugin-fs'; + * const fileInfo = await lstat("hello.txt", { baseDir: BaseDirectory.AppLocalData }); + * console.log(fileInfo.isFile); // true + * ``` + * + * @since 2.0.0 + */ +async function lstat( + path: string | URL, + options?: StatOptions +): Promise { + const res = await invoke('plugin:fs|lstat', { + path: path instanceof URL ? path.toString() : path, + options + }) + + return parseFileInfo(res) +} + +/** + * @since 2.0.0 + */ +interface TruncateOptions { + /** Base directory for `path`. */ + baseDir?: BaseDirectory +} + +/** + * Truncates or extends the specified file, to reach the specified `len`. + * If `len` is `0` or not specified, then the entire file contents are truncated. + * + * @example + * ```typescript + * import { truncate, readTextFile, writeTextFile, BaseDirectory } from '@tauri-apps/plugin-fs'; + * // truncate the entire file + * await truncate("my_file.txt", 0, { baseDir: BaseDirectory.AppLocalData }); + * + * // truncate part of the file + * const filePath = "file.txt"; + * await writeTextFile(filePath, "Hello World", { baseDir: BaseDirectory.AppLocalData }); + * await truncate(filePath, 7, { baseDir: BaseDirectory.AppLocalData }); + * const data = await readTextFile(filePath, { baseDir: BaseDirectory.AppLocalData }); + * console.log(data); // "Hello W" + * ``` + * + * @since 2.0.0 + */ +async function truncate( + path: string | URL, + len?: number, + options?: TruncateOptions +): Promise { + if (path instanceof URL && path.protocol !== 'file:') { + throw new TypeError('Must be a file URL.') + } + + await invoke('plugin:fs|truncate', { + path: path instanceof URL ? path.toString() : path, + len, + options + }) +} + +/** + * @since 2.0.0 + */ +interface WriteFileOptions { + /** Defaults to `false`. If set to `true`, will append to a file instead of overwriting previous contents. */ + append?: boolean + /** Sets the option to allow creating a new file, if one doesn't already exist at the specified path (defaults to `true`). */ + create?: boolean + /** Sets the option to create a new file, failing if it already exists. */ + createNew?: boolean + /** File permissions. Ignored on Windows. */ + mode?: number + /** Base directory for `path` */ + baseDir?: BaseDirectory +} + +/** + * Write `data` to the given `path`, by default creating a new file if needed, else overwriting. + * @example + * ```typescript + * import { writeFile, BaseDirectory } from '@tauri-apps/plugin-fs'; + * + * let encoder = new TextEncoder(); + * let data = encoder.encode("Hello World"); + * await writeFile('file.txt', data, { baseDir: BaseDirectory.AppLocalData }); + * ``` + * + * @since 2.0.0 + */ +async function writeFile( + path: string | URL, + data: Uint8Array | ReadableStream, + options?: WriteFileOptions +): Promise { + if (path instanceof URL && path.protocol !== 'file:') { + throw new TypeError('Must be a file URL.') + } + + if (data instanceof ReadableStream) { + const file = await open(path, { + read: false, + create: true, + write: true, + ...options + }) + const reader = data.getReader() + + try { + while (true) { + const { done, value } = await reader.read() + if (done) break + await file.write(value) + } + } finally { + reader.releaseLock() + await file.close() + } + } else { + await invoke('plugin:fs|write_file', data, { + headers: { + path: encodeURIComponent(path instanceof URL ? path.toString() : path), + options: JSON.stringify(options) + } + }) + } +} + +/** + * Writes UTF-8 string `data` to the given `path`, by default creating a new file if needed, else overwriting. + @example + * ```typescript + * import { writeTextFile, BaseDirectory } from '@tauri-apps/plugin-fs'; + * + * await writeTextFile('file.txt', "Hello world", { baseDir: BaseDirectory.AppLocalData }); + * ``` + * + * @since 2.0.0 + */ +async function writeTextFile( + path: string | URL, + data: string, + options?: WriteFileOptions +): Promise { + if (path instanceof URL && path.protocol !== 'file:') { + throw new TypeError('Must be a file URL.') + } + + const encoder = new TextEncoder() + + await invoke('plugin:fs|write_text_file', encoder.encode(data), { + headers: { + path: encodeURIComponent(path instanceof URL ? path.toString() : path), + options: JSON.stringify(options) + } + }) +} + +/** + * @since 2.0.0 + */ +interface ExistsOptions { + /** Base directory for `path`. */ + baseDir?: BaseDirectory +} + +/** + * Check if a path exists. + * @example + * ```typescript + * import { exists, BaseDirectory } from '@tauri-apps/plugin-fs'; + * // Check if the `$APPDATA/avatar.png` file exists + * await exists('avatar.png', { baseDir: BaseDirectory.AppData }); + * ``` + * + * @since 2.0.0 + */ +async function exists( + path: string | URL, + options?: ExistsOptions +): Promise { + if (path instanceof URL && path.protocol !== 'file:') { + throw new TypeError('Must be a file URL.') + } + + return await invoke('plugin:fs|exists', { + path: path instanceof URL ? path.toString() : path, + options + }) +} + +/** + * @since 2.0.0 + */ +interface WatchOptions { + /** Watch a directory recursively */ + recursive?: boolean + /** Base directory for `path` */ + baseDir?: BaseDirectory +} + +/** + * @since 2.0.0 + */ +interface DebouncedWatchOptions extends WatchOptions { + /** Debounce delay */ + delayMs?: number +} + +/** + * @since 2.0.0 + */ +interface WatchEvent { + type: WatchEventKind + paths: string[] + attrs: unknown +} + +/** + * @since 2.0.0 + */ +type WatchEventKind = + | 'any' + | { access: WatchEventKindAccess } + | { create: WatchEventKindCreate } + | { modify: WatchEventKindModify } + | { remove: WatchEventKindRemove } + | 'other' + +/** + * @since 2.0.0 + */ +type WatchEventKindAccess = + | { kind: 'any' } + | { kind: 'close'; mode: 'any' | 'execute' | 'read' | 'write' | 'other' } + | { kind: 'open'; mode: 'any' | 'execute' | 'read' | 'write' | 'other' } + | { kind: 'other' } + +/** + * @since 2.0.0 + */ +type WatchEventKindCreate = + | { kind: 'any' } + | { kind: 'file' } + | { kind: 'folder' } + | { kind: 'other' } + +/** + * @since 2.0.0 + */ +type WatchEventKindModify = + | { kind: 'any' } + | { kind: 'data'; mode: 'any' | 'size' | 'content' | 'other' } + | { + kind: 'metadata' + mode: + | 'any' + | 'access-time' + | 'write-time' + | 'permissions' + | 'ownership' + | 'extended' + | 'other' + } + | { kind: 'rename'; mode: 'any' | 'to' | 'from' | 'both' | 'other' } + | { kind: 'other' } + +/** + * @since 2.0.0 + */ +type WatchEventKindRemove = + | { kind: 'any' } + | { kind: 'file' } + | { kind: 'folder' } + | { kind: 'other' } + +// TODO: Remove this in v3, return `Watcher` instead +/** + * @since 2.0.0 + */ +type UnwatchFn = () => void + +class Watcher extends Resource {} + +async function watchInternal( + paths: string | string[] | URL | URL[], + cb: (event: WatchEvent) => void, + options: DebouncedWatchOptions +): Promise { + const watchPaths = Array.isArray(paths) ? paths : [paths] + + for (const path of watchPaths) { + if (path instanceof URL && path.protocol !== 'file:') { + throw new TypeError('Must be a file URL.') + } + } + + const onEvent = new Channel() + onEvent.onmessage = cb + + const rid: number = await invoke('plugin:fs|watch', { + paths: watchPaths.map((p) => (p instanceof URL ? p.toString() : p)), + options, + onEvent + }) + + const watcher = new Watcher(rid) + + return () => { + void watcher.close() + } +} + +// TODO: Return `Watcher` instead in v3 +/** + * Watch changes (after a delay) on files or directories. + * + * @since 2.0.0 + */ +async function watch( + paths: string | string[] | URL | URL[], + cb: (event: WatchEvent) => void, + options?: DebouncedWatchOptions +): Promise { + return await watchInternal(paths, cb, { + delayMs: 2000, + ...options + }) +} + +// TODO: Return `Watcher` instead in v3 +/** + * Watch changes on files or directories. + * + * @since 2.0.0 + */ +async function watchImmediate( + paths: string | string[] | URL | URL[], + cb: (event: WatchEvent) => void, + options?: WatchOptions +): Promise { + return await watchInternal(paths, cb, { + ...options, + delayMs: undefined + }) +} + +/** + * Get the size of a file or directory. For files, the `stat` functions can be used as well. + * + * If `path` is a directory, this function will recursively iterate over every file and every directory inside of `path` and therefore will be very time consuming if used on larger directories. + * + * @example + * ```typescript + * import { size, BaseDirectory } from '@tauri-apps/plugin-fs'; + * // Get the size of the `$APPDATA/tauri` directory. + * const dirSize = await size('tauri', { baseDir: BaseDirectory.AppData }); + * console.log(dirSize); // 1024 + * ``` + * + * @since 2.1.0 + */ +async function size(path: string | URL): Promise { + if (path instanceof URL && path.protocol !== 'file:') { + throw new TypeError('Must be a file URL.') + } + + return await invoke('plugin:fs|size', { + path: path instanceof URL ? path.toString() : path + }) +} + +export type { + CreateOptions, + OpenOptions, + CopyFileOptions, + MkdirOptions, + DirEntry, + ReadDirOptions, + ReadFileOptions, + RemoveOptions, + RenameOptions, + StatOptions, + TruncateOptions, + WriteFileOptions, + ExistsOptions, + FileInfo, + WatchOptions, + DebouncedWatchOptions, + WatchEvent, + WatchEventKind, + WatchEventKindAccess, + WatchEventKindCreate, + WatchEventKindModify, + WatchEventKindRemove, + UnwatchFn +} + +export { + BaseDirectory, + FileHandle, + create, + open, + copyFile, + mkdir, + readDir, + readFile, + readTextFile, + readTextFileLines, + remove, + rename, + SeekMode, + stat, + lstat, + truncate, + writeFile, + writeTextFile, + exists, + watch, + watchImmediate, + size +} diff --git a/packages/kbot/gui/app/plugins/fs/package.json b/packages/kbot/gui/app/plugins/fs/package.json new file mode 100644 index 00000000..203fb2ad --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/package.json @@ -0,0 +1,30 @@ +{ + "name": "@tauri-apps/plugin-fs", + "version": "2.4.2", + "description": "Access the file system.", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/fs/permissions/app.toml b/packages/kbot/gui/app/plugins/fs/permissions/app.toml new file mode 100644 index 00000000..aeb0521b --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/app.toml @@ -0,0 +1,114 @@ +"$schema" = "schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-app-recursive" +description = "This scope permits recursive access to the complete application folders, including sub directories and files." + +[[permission.scope.allow]] +path = "$APPCONFIG" +[[permission.scope.allow]] +path = "$APPCONFIG/**" + +[[permission.scope.allow]] +path = "$APPDATA" +[[permission.scope.allow]] +path = "$APPDATA/**" + +[[permission.scope.allow]] +path = "$APPLOCALDATA" +[[permission.scope.allow]] +path = "$APPLOCALDATA/**" + +[[permission.scope.allow]] +path = "$APPCACHE" +[[permission.scope.allow]] +path = "$APPCACHE/**" + +[[permission.scope.allow]] +path = "$APPLOG" +[[permission.scope.allow]] +path = "$APPLOG/**" + +[[permission]] +identifier = "scope-app" +description = "This scope permits access to all files and list content of top level directories in the application folders." + +[[permission.scope.allow]] +path = "$APPCONFIG" +[[permission.scope.allow]] +path = "$APPCONFIG/*" + +[[permission.scope.allow]] +path = "$APPDATA" +[[permission.scope.allow]] +path = "$APPDATA/*" + +[[permission.scope.allow]] +path = "$APPLOCALDATA" +[[permission.scope.allow]] +path = "$APPLOCALDATA/*" + +[[permission.scope.allow]] +path = "$APPCACHE" +[[permission.scope.allow]] +path = "$APPCACHE/*" + +[[permission.scope.allow]] +path = "$APPLOG" +[[permission.scope.allow]] +path = "$APPLOG/*" + +[[permission]] +identifier = "scope-app-index" +description = "This scope permits to list all files and folders in the application directories." + +[[permission.scope.allow]] +path = "$APPCONFIG" + +[[permission.scope.allow]] +path = "$APPDATA" + +[[permission.scope.allow]] +path = "$APPLOCALDATA" + +[[permission.scope.allow]] +path = "$APPCACHE" + +[[permission.scope.allow]] +path = "$APPLOG" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-app-read-recursive" +description = "This allows full recursive read access to the complete application folders, files and subdirectories." +permissions = ["read-all", "scope-app-recursive"] + +[[set]] +identifier = "allow-app-write-recursive" +description = "This allows full recursive write access to the complete application folders, files and subdirectories." +permissions = ["write-all", "scope-app-recursive"] + +[[set]] +identifier = "allow-app-read" +description = "This allows non-recursive read access to the application folders." +permissions = ["read-all", "scope-app"] + +[[set]] +identifier = "allow-app-write" +description = "This allows non-recursive write access to the application folders." +permissions = ["write-all", "scope-app"] + +[[set]] +identifier = "allow-app-meta-recursive" +description = "This allows full recursive read access to metadata of the application folders, including file listing and statistics." +permissions = ["read-meta", "scope-app-recursive"] + +[[set]] +identifier = "allow-app-meta" +description = "This allows non-recursive read access to metadata of the application folders, including file listing and statistics." +permissions = ["read-meta", "scope-app-index"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/appcache.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/appcache.toml new file mode 100644 index 00000000..50e19efc --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/appcache.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-appcache-recursive" +description = "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$APPCACHE" +[[permission.scope.allow]] +path = "$APPCACHE/**" + +[[permission]] +identifier = "scope-appcache" +description = "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder." + +[[permission.scope.allow]] +path = "$APPCACHE" +[[permission.scope.allow]] +path = "$APPCACHE/*" + +[[permission]] +identifier = "scope-appcache-index" +description = "This scope permits to list all files and folders in the `$APPCACHE`folder." + +[[permission.scope.allow]] +path = "$APPCACHE" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-appcache-read-recursive" +description = "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-appcache-recursive" +] + +[[set]] +identifier = "allow-appcache-write-recursive" +description = "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-appcache-recursive" +] + +[[set]] +identifier = "allow-appcache-read" +description = "This allows non-recursive read access to the `$APPCACHE` folder." +permissions = [ + "read-all", + "scope-appcache" +] + +[[set]] +identifier = "allow-appcache-write" +description = "This allows non-recursive write access to the `$APPCACHE` folder." +permissions = [ + "write-all", + "scope-appcache" +] + +[[set]] +identifier = "allow-appcache-meta-recursive" +description = "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-appcache-recursive" +] + +[[set]] +identifier = "allow-appcache-meta" +description = "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-appcache-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/appconfig.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/appconfig.toml new file mode 100644 index 00000000..ab136956 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/appconfig.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-appconfig-recursive" +description = "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$APPCONFIG" +[[permission.scope.allow]] +path = "$APPCONFIG/**" + +[[permission]] +identifier = "scope-appconfig" +description = "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder." + +[[permission.scope.allow]] +path = "$APPCONFIG" +[[permission.scope.allow]] +path = "$APPCONFIG/*" + +[[permission]] +identifier = "scope-appconfig-index" +description = "This scope permits to list all files and folders in the `$APPCONFIG`folder." + +[[permission.scope.allow]] +path = "$APPCONFIG" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-appconfig-read-recursive" +description = "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-appconfig-recursive" +] + +[[set]] +identifier = "allow-appconfig-write-recursive" +description = "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-appconfig-recursive" +] + +[[set]] +identifier = "allow-appconfig-read" +description = "This allows non-recursive read access to the `$APPCONFIG` folder." +permissions = [ + "read-all", + "scope-appconfig" +] + +[[set]] +identifier = "allow-appconfig-write" +description = "This allows non-recursive write access to the `$APPCONFIG` folder." +permissions = [ + "write-all", + "scope-appconfig" +] + +[[set]] +identifier = "allow-appconfig-meta-recursive" +description = "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-appconfig-recursive" +] + +[[set]] +identifier = "allow-appconfig-meta" +description = "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-appconfig-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/appdata.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/appdata.toml new file mode 100644 index 00000000..1b0931e2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/appdata.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-appdata-recursive" +description = "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$APPDATA" +[[permission.scope.allow]] +path = "$APPDATA/**" + +[[permission]] +identifier = "scope-appdata" +description = "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder." + +[[permission.scope.allow]] +path = "$APPDATA" +[[permission.scope.allow]] +path = "$APPDATA/*" + +[[permission]] +identifier = "scope-appdata-index" +description = "This scope permits to list all files and folders in the `$APPDATA`folder." + +[[permission.scope.allow]] +path = "$APPDATA" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-appdata-read-recursive" +description = "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-appdata-recursive" +] + +[[set]] +identifier = "allow-appdata-write-recursive" +description = "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-appdata-recursive" +] + +[[set]] +identifier = "allow-appdata-read" +description = "This allows non-recursive read access to the `$APPDATA` folder." +permissions = [ + "read-all", + "scope-appdata" +] + +[[set]] +identifier = "allow-appdata-write" +description = "This allows non-recursive write access to the `$APPDATA` folder." +permissions = [ + "write-all", + "scope-appdata" +] + +[[set]] +identifier = "allow-appdata-meta-recursive" +description = "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-appdata-recursive" +] + +[[set]] +identifier = "allow-appdata-meta" +description = "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-appdata-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/applocaldata.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/applocaldata.toml new file mode 100644 index 00000000..a6e38a31 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/applocaldata.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-applocaldata-recursive" +description = "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$APPLOCALDATA" +[[permission.scope.allow]] +path = "$APPLOCALDATA/**" + +[[permission]] +identifier = "scope-applocaldata" +description = "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder." + +[[permission.scope.allow]] +path = "$APPLOCALDATA" +[[permission.scope.allow]] +path = "$APPLOCALDATA/*" + +[[permission]] +identifier = "scope-applocaldata-index" +description = "This scope permits to list all files and folders in the `$APPLOCALDATA`folder." + +[[permission.scope.allow]] +path = "$APPLOCALDATA" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-applocaldata-read-recursive" +description = "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-applocaldata-recursive" +] + +[[set]] +identifier = "allow-applocaldata-write-recursive" +description = "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-applocaldata-recursive" +] + +[[set]] +identifier = "allow-applocaldata-read" +description = "This allows non-recursive read access to the `$APPLOCALDATA` folder." +permissions = [ + "read-all", + "scope-applocaldata" +] + +[[set]] +identifier = "allow-applocaldata-write" +description = "This allows non-recursive write access to the `$APPLOCALDATA` folder." +permissions = [ + "write-all", + "scope-applocaldata" +] + +[[set]] +identifier = "allow-applocaldata-meta-recursive" +description = "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-applocaldata-recursive" +] + +[[set]] +identifier = "allow-applocaldata-meta" +description = "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-applocaldata-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/applog.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/applog.toml new file mode 100644 index 00000000..a979ce76 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/applog.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-applog-recursive" +description = "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$APPLOG" +[[permission.scope.allow]] +path = "$APPLOG/**" + +[[permission]] +identifier = "scope-applog" +description = "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder." + +[[permission.scope.allow]] +path = "$APPLOG" +[[permission.scope.allow]] +path = "$APPLOG/*" + +[[permission]] +identifier = "scope-applog-index" +description = "This scope permits to list all files and folders in the `$APPLOG`folder." + +[[permission.scope.allow]] +path = "$APPLOG" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-applog-read-recursive" +description = "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-applog-recursive" +] + +[[set]] +identifier = "allow-applog-write-recursive" +description = "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-applog-recursive" +] + +[[set]] +identifier = "allow-applog-read" +description = "This allows non-recursive read access to the `$APPLOG` folder." +permissions = [ + "read-all", + "scope-applog" +] + +[[set]] +identifier = "allow-applog-write" +description = "This allows non-recursive write access to the `$APPLOG` folder." +permissions = [ + "write-all", + "scope-applog" +] + +[[set]] +identifier = "allow-applog-meta-recursive" +description = "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-applog-recursive" +] + +[[set]] +identifier = "allow-applog-meta" +description = "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-applog-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/audio.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/audio.toml new file mode 100644 index 00000000..d66d68a2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/audio.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-audio-recursive" +description = "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$AUDIO" +[[permission.scope.allow]] +path = "$AUDIO/**" + +[[permission]] +identifier = "scope-audio" +description = "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder." + +[[permission.scope.allow]] +path = "$AUDIO" +[[permission.scope.allow]] +path = "$AUDIO/*" + +[[permission]] +identifier = "scope-audio-index" +description = "This scope permits to list all files and folders in the `$AUDIO`folder." + +[[permission.scope.allow]] +path = "$AUDIO" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-audio-read-recursive" +description = "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-audio-recursive" +] + +[[set]] +identifier = "allow-audio-write-recursive" +description = "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-audio-recursive" +] + +[[set]] +identifier = "allow-audio-read" +description = "This allows non-recursive read access to the `$AUDIO` folder." +permissions = [ + "read-all", + "scope-audio" +] + +[[set]] +identifier = "allow-audio-write" +description = "This allows non-recursive write access to the `$AUDIO` folder." +permissions = [ + "write-all", + "scope-audio" +] + +[[set]] +identifier = "allow-audio-meta-recursive" +description = "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-audio-recursive" +] + +[[set]] +identifier = "allow-audio-meta" +description = "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-audio-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/cache.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/cache.toml new file mode 100644 index 00000000..814319eb --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/cache.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-cache-recursive" +description = "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$CACHE" +[[permission.scope.allow]] +path = "$CACHE/**" + +[[permission]] +identifier = "scope-cache" +description = "This scope permits access to all files and list content of top level directories in the `$CACHE` folder." + +[[permission.scope.allow]] +path = "$CACHE" +[[permission.scope.allow]] +path = "$CACHE/*" + +[[permission]] +identifier = "scope-cache-index" +description = "This scope permits to list all files and folders in the `$CACHE`folder." + +[[permission.scope.allow]] +path = "$CACHE" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-cache-read-recursive" +description = "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-cache-recursive" +] + +[[set]] +identifier = "allow-cache-write-recursive" +description = "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-cache-recursive" +] + +[[set]] +identifier = "allow-cache-read" +description = "This allows non-recursive read access to the `$CACHE` folder." +permissions = [ + "read-all", + "scope-cache" +] + +[[set]] +identifier = "allow-cache-write" +description = "This allows non-recursive write access to the `$CACHE` folder." +permissions = [ + "write-all", + "scope-cache" +] + +[[set]] +identifier = "allow-cache-meta-recursive" +description = "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-cache-recursive" +] + +[[set]] +identifier = "allow-cache-meta" +description = "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-cache-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/config.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/config.toml new file mode 100644 index 00000000..59221045 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/config.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-config-recursive" +description = "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$CONFIG" +[[permission.scope.allow]] +path = "$CONFIG/**" + +[[permission]] +identifier = "scope-config" +description = "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder." + +[[permission.scope.allow]] +path = "$CONFIG" +[[permission.scope.allow]] +path = "$CONFIG/*" + +[[permission]] +identifier = "scope-config-index" +description = "This scope permits to list all files and folders in the `$CONFIG`folder." + +[[permission.scope.allow]] +path = "$CONFIG" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-config-read-recursive" +description = "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-config-recursive" +] + +[[set]] +identifier = "allow-config-write-recursive" +description = "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-config-recursive" +] + +[[set]] +identifier = "allow-config-read" +description = "This allows non-recursive read access to the `$CONFIG` folder." +permissions = [ + "read-all", + "scope-config" +] + +[[set]] +identifier = "allow-config-write" +description = "This allows non-recursive write access to the `$CONFIG` folder." +permissions = [ + "write-all", + "scope-config" +] + +[[set]] +identifier = "allow-config-meta-recursive" +description = "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-config-recursive" +] + +[[set]] +identifier = "allow-config-meta" +description = "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-config-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/data.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/data.toml new file mode 100644 index 00000000..a8428ca1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/data.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-data-recursive" +description = "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$DATA" +[[permission.scope.allow]] +path = "$DATA/**" + +[[permission]] +identifier = "scope-data" +description = "This scope permits access to all files and list content of top level directories in the `$DATA` folder." + +[[permission.scope.allow]] +path = "$DATA" +[[permission.scope.allow]] +path = "$DATA/*" + +[[permission]] +identifier = "scope-data-index" +description = "This scope permits to list all files and folders in the `$DATA`folder." + +[[permission.scope.allow]] +path = "$DATA" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-data-read-recursive" +description = "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-data-recursive" +] + +[[set]] +identifier = "allow-data-write-recursive" +description = "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-data-recursive" +] + +[[set]] +identifier = "allow-data-read" +description = "This allows non-recursive read access to the `$DATA` folder." +permissions = [ + "read-all", + "scope-data" +] + +[[set]] +identifier = "allow-data-write" +description = "This allows non-recursive write access to the `$DATA` folder." +permissions = [ + "write-all", + "scope-data" +] + +[[set]] +identifier = "allow-data-meta-recursive" +description = "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-data-recursive" +] + +[[set]] +identifier = "allow-data-meta" +description = "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-data-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/desktop.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/desktop.toml new file mode 100644 index 00000000..da369fa0 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/desktop.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-desktop-recursive" +description = "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$DESKTOP" +[[permission.scope.allow]] +path = "$DESKTOP/**" + +[[permission]] +identifier = "scope-desktop" +description = "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder." + +[[permission.scope.allow]] +path = "$DESKTOP" +[[permission.scope.allow]] +path = "$DESKTOP/*" + +[[permission]] +identifier = "scope-desktop-index" +description = "This scope permits to list all files and folders in the `$DESKTOP`folder." + +[[permission.scope.allow]] +path = "$DESKTOP" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-desktop-read-recursive" +description = "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-desktop-recursive" +] + +[[set]] +identifier = "allow-desktop-write-recursive" +description = "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-desktop-recursive" +] + +[[set]] +identifier = "allow-desktop-read" +description = "This allows non-recursive read access to the `$DESKTOP` folder." +permissions = [ + "read-all", + "scope-desktop" +] + +[[set]] +identifier = "allow-desktop-write" +description = "This allows non-recursive write access to the `$DESKTOP` folder." +permissions = [ + "write-all", + "scope-desktop" +] + +[[set]] +identifier = "allow-desktop-meta-recursive" +description = "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-desktop-recursive" +] + +[[set]] +identifier = "allow-desktop-meta" +description = "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-desktop-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/document.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/document.toml new file mode 100644 index 00000000..9feb4d0d --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/document.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-document-recursive" +description = "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$DOCUMENT" +[[permission.scope.allow]] +path = "$DOCUMENT/**" + +[[permission]] +identifier = "scope-document" +description = "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder." + +[[permission.scope.allow]] +path = "$DOCUMENT" +[[permission.scope.allow]] +path = "$DOCUMENT/*" + +[[permission]] +identifier = "scope-document-index" +description = "This scope permits to list all files and folders in the `$DOCUMENT`folder." + +[[permission.scope.allow]] +path = "$DOCUMENT" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-document-read-recursive" +description = "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-document-recursive" +] + +[[set]] +identifier = "allow-document-write-recursive" +description = "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-document-recursive" +] + +[[set]] +identifier = "allow-document-read" +description = "This allows non-recursive read access to the `$DOCUMENT` folder." +permissions = [ + "read-all", + "scope-document" +] + +[[set]] +identifier = "allow-document-write" +description = "This allows non-recursive write access to the `$DOCUMENT` folder." +permissions = [ + "write-all", + "scope-document" +] + +[[set]] +identifier = "allow-document-meta-recursive" +description = "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-document-recursive" +] + +[[set]] +identifier = "allow-document-meta" +description = "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-document-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/download.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/download.toml new file mode 100644 index 00000000..8659e3ac --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/download.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-download-recursive" +description = "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$DOWNLOAD" +[[permission.scope.allow]] +path = "$DOWNLOAD/**" + +[[permission]] +identifier = "scope-download" +description = "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder." + +[[permission.scope.allow]] +path = "$DOWNLOAD" +[[permission.scope.allow]] +path = "$DOWNLOAD/*" + +[[permission]] +identifier = "scope-download-index" +description = "This scope permits to list all files and folders in the `$DOWNLOAD`folder." + +[[permission.scope.allow]] +path = "$DOWNLOAD" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-download-read-recursive" +description = "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-download-recursive" +] + +[[set]] +identifier = "allow-download-write-recursive" +description = "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-download-recursive" +] + +[[set]] +identifier = "allow-download-read" +description = "This allows non-recursive read access to the `$DOWNLOAD` folder." +permissions = [ + "read-all", + "scope-download" +] + +[[set]] +identifier = "allow-download-write" +description = "This allows non-recursive write access to the `$DOWNLOAD` folder." +permissions = [ + "write-all", + "scope-download" +] + +[[set]] +identifier = "allow-download-meta-recursive" +description = "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-download-recursive" +] + +[[set]] +identifier = "allow-download-meta" +description = "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-download-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/exe.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/exe.toml new file mode 100644 index 00000000..94950e84 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/exe.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-exe-recursive" +description = "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$EXE" +[[permission.scope.allow]] +path = "$EXE/**" + +[[permission]] +identifier = "scope-exe" +description = "This scope permits access to all files and list content of top level directories in the `$EXE` folder." + +[[permission.scope.allow]] +path = "$EXE" +[[permission.scope.allow]] +path = "$EXE/*" + +[[permission]] +identifier = "scope-exe-index" +description = "This scope permits to list all files and folders in the `$EXE`folder." + +[[permission.scope.allow]] +path = "$EXE" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-exe-read-recursive" +description = "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-exe-recursive" +] + +[[set]] +identifier = "allow-exe-write-recursive" +description = "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-exe-recursive" +] + +[[set]] +identifier = "allow-exe-read" +description = "This allows non-recursive read access to the `$EXE` folder." +permissions = [ + "read-all", + "scope-exe" +] + +[[set]] +identifier = "allow-exe-write" +description = "This allows non-recursive write access to the `$EXE` folder." +permissions = [ + "write-all", + "scope-exe" +] + +[[set]] +identifier = "allow-exe-meta-recursive" +description = "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-exe-recursive" +] + +[[set]] +identifier = "allow-exe-meta" +description = "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-exe-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/font.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/font.toml new file mode 100644 index 00000000..21840046 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/font.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-font-recursive" +description = "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$FONT" +[[permission.scope.allow]] +path = "$FONT/**" + +[[permission]] +identifier = "scope-font" +description = "This scope permits access to all files and list content of top level directories in the `$FONT` folder." + +[[permission.scope.allow]] +path = "$FONT" +[[permission.scope.allow]] +path = "$FONT/*" + +[[permission]] +identifier = "scope-font-index" +description = "This scope permits to list all files and folders in the `$FONT`folder." + +[[permission.scope.allow]] +path = "$FONT" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-font-read-recursive" +description = "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-font-recursive" +] + +[[set]] +identifier = "allow-font-write-recursive" +description = "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-font-recursive" +] + +[[set]] +identifier = "allow-font-read" +description = "This allows non-recursive read access to the `$FONT` folder." +permissions = [ + "read-all", + "scope-font" +] + +[[set]] +identifier = "allow-font-write" +description = "This allows non-recursive write access to the `$FONT` folder." +permissions = [ + "write-all", + "scope-font" +] + +[[set]] +identifier = "allow-font-meta-recursive" +description = "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-font-recursive" +] + +[[set]] +identifier = "allow-font-meta" +description = "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-font-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/home.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/home.toml new file mode 100644 index 00000000..cbf48e2f --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/home.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-home-recursive" +description = "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$HOME" +[[permission.scope.allow]] +path = "$HOME/**" + +[[permission]] +identifier = "scope-home" +description = "This scope permits access to all files and list content of top level directories in the `$HOME` folder." + +[[permission.scope.allow]] +path = "$HOME" +[[permission.scope.allow]] +path = "$HOME/*" + +[[permission]] +identifier = "scope-home-index" +description = "This scope permits to list all files and folders in the `$HOME`folder." + +[[permission.scope.allow]] +path = "$HOME" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-home-read-recursive" +description = "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-home-recursive" +] + +[[set]] +identifier = "allow-home-write-recursive" +description = "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-home-recursive" +] + +[[set]] +identifier = "allow-home-read" +description = "This allows non-recursive read access to the `$HOME` folder." +permissions = [ + "read-all", + "scope-home" +] + +[[set]] +identifier = "allow-home-write" +description = "This allows non-recursive write access to the `$HOME` folder." +permissions = [ + "write-all", + "scope-home" +] + +[[set]] +identifier = "allow-home-meta-recursive" +description = "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-home-recursive" +] + +[[set]] +identifier = "allow-home-meta" +description = "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-home-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/localdata.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/localdata.toml new file mode 100644 index 00000000..90a8f48b --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/localdata.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-localdata-recursive" +description = "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$LOCALDATA" +[[permission.scope.allow]] +path = "$LOCALDATA/**" + +[[permission]] +identifier = "scope-localdata" +description = "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder." + +[[permission.scope.allow]] +path = "$LOCALDATA" +[[permission.scope.allow]] +path = "$LOCALDATA/*" + +[[permission]] +identifier = "scope-localdata-index" +description = "This scope permits to list all files and folders in the `$LOCALDATA`folder." + +[[permission.scope.allow]] +path = "$LOCALDATA" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-localdata-read-recursive" +description = "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-localdata-recursive" +] + +[[set]] +identifier = "allow-localdata-write-recursive" +description = "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-localdata-recursive" +] + +[[set]] +identifier = "allow-localdata-read" +description = "This allows non-recursive read access to the `$LOCALDATA` folder." +permissions = [ + "read-all", + "scope-localdata" +] + +[[set]] +identifier = "allow-localdata-write" +description = "This allows non-recursive write access to the `$LOCALDATA` folder." +permissions = [ + "write-all", + "scope-localdata" +] + +[[set]] +identifier = "allow-localdata-meta-recursive" +description = "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-localdata-recursive" +] + +[[set]] +identifier = "allow-localdata-meta" +description = "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-localdata-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/log.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/log.toml new file mode 100644 index 00000000..d505a3ce --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/log.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-log-recursive" +description = "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$LOG" +[[permission.scope.allow]] +path = "$LOG/**" + +[[permission]] +identifier = "scope-log" +description = "This scope permits access to all files and list content of top level directories in the `$LOG` folder." + +[[permission.scope.allow]] +path = "$LOG" +[[permission.scope.allow]] +path = "$LOG/*" + +[[permission]] +identifier = "scope-log-index" +description = "This scope permits to list all files and folders in the `$LOG`folder." + +[[permission.scope.allow]] +path = "$LOG" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-log-read-recursive" +description = "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-log-recursive" +] + +[[set]] +identifier = "allow-log-write-recursive" +description = "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-log-recursive" +] + +[[set]] +identifier = "allow-log-read" +description = "This allows non-recursive read access to the `$LOG` folder." +permissions = [ + "read-all", + "scope-log" +] + +[[set]] +identifier = "allow-log-write" +description = "This allows non-recursive write access to the `$LOG` folder." +permissions = [ + "write-all", + "scope-log" +] + +[[set]] +identifier = "allow-log-meta-recursive" +description = "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-log-recursive" +] + +[[set]] +identifier = "allow-log-meta" +description = "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-log-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/picture.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/picture.toml new file mode 100644 index 00000000..6a760909 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/picture.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-picture-recursive" +description = "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$PICTURE" +[[permission.scope.allow]] +path = "$PICTURE/**" + +[[permission]] +identifier = "scope-picture" +description = "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder." + +[[permission.scope.allow]] +path = "$PICTURE" +[[permission.scope.allow]] +path = "$PICTURE/*" + +[[permission]] +identifier = "scope-picture-index" +description = "This scope permits to list all files and folders in the `$PICTURE`folder." + +[[permission.scope.allow]] +path = "$PICTURE" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-picture-read-recursive" +description = "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-picture-recursive" +] + +[[set]] +identifier = "allow-picture-write-recursive" +description = "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-picture-recursive" +] + +[[set]] +identifier = "allow-picture-read" +description = "This allows non-recursive read access to the `$PICTURE` folder." +permissions = [ + "read-all", + "scope-picture" +] + +[[set]] +identifier = "allow-picture-write" +description = "This allows non-recursive write access to the `$PICTURE` folder." +permissions = [ + "write-all", + "scope-picture" +] + +[[set]] +identifier = "allow-picture-meta-recursive" +description = "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-picture-recursive" +] + +[[set]] +identifier = "allow-picture-meta" +description = "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-picture-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/public.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/public.toml new file mode 100644 index 00000000..2e39abb4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/public.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-public-recursive" +description = "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$PUBLIC" +[[permission.scope.allow]] +path = "$PUBLIC/**" + +[[permission]] +identifier = "scope-public" +description = "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder." + +[[permission.scope.allow]] +path = "$PUBLIC" +[[permission.scope.allow]] +path = "$PUBLIC/*" + +[[permission]] +identifier = "scope-public-index" +description = "This scope permits to list all files and folders in the `$PUBLIC`folder." + +[[permission.scope.allow]] +path = "$PUBLIC" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-public-read-recursive" +description = "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-public-recursive" +] + +[[set]] +identifier = "allow-public-write-recursive" +description = "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-public-recursive" +] + +[[set]] +identifier = "allow-public-read" +description = "This allows non-recursive read access to the `$PUBLIC` folder." +permissions = [ + "read-all", + "scope-public" +] + +[[set]] +identifier = "allow-public-write" +description = "This allows non-recursive write access to the `$PUBLIC` folder." +permissions = [ + "write-all", + "scope-public" +] + +[[set]] +identifier = "allow-public-meta-recursive" +description = "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-public-recursive" +] + +[[set]] +identifier = "allow-public-meta" +description = "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-public-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/resource.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/resource.toml new file mode 100644 index 00000000..53dfeb07 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/resource.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-resource-recursive" +description = "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$RESOURCE" +[[permission.scope.allow]] +path = "$RESOURCE/**" + +[[permission]] +identifier = "scope-resource" +description = "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder." + +[[permission.scope.allow]] +path = "$RESOURCE" +[[permission.scope.allow]] +path = "$RESOURCE/*" + +[[permission]] +identifier = "scope-resource-index" +description = "This scope permits to list all files and folders in the `$RESOURCE`folder." + +[[permission.scope.allow]] +path = "$RESOURCE" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-resource-read-recursive" +description = "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-resource-recursive" +] + +[[set]] +identifier = "allow-resource-write-recursive" +description = "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-resource-recursive" +] + +[[set]] +identifier = "allow-resource-read" +description = "This allows non-recursive read access to the `$RESOURCE` folder." +permissions = [ + "read-all", + "scope-resource" +] + +[[set]] +identifier = "allow-resource-write" +description = "This allows non-recursive write access to the `$RESOURCE` folder." +permissions = [ + "write-all", + "scope-resource" +] + +[[set]] +identifier = "allow-resource-meta-recursive" +description = "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-resource-recursive" +] + +[[set]] +identifier = "allow-resource-meta" +description = "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-resource-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/runtime.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/runtime.toml new file mode 100644 index 00000000..8dcc2a03 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/runtime.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-runtime-recursive" +description = "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$RUNTIME" +[[permission.scope.allow]] +path = "$RUNTIME/**" + +[[permission]] +identifier = "scope-runtime" +description = "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder." + +[[permission.scope.allow]] +path = "$RUNTIME" +[[permission.scope.allow]] +path = "$RUNTIME/*" + +[[permission]] +identifier = "scope-runtime-index" +description = "This scope permits to list all files and folders in the `$RUNTIME`folder." + +[[permission.scope.allow]] +path = "$RUNTIME" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-runtime-read-recursive" +description = "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-runtime-recursive" +] + +[[set]] +identifier = "allow-runtime-write-recursive" +description = "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-runtime-recursive" +] + +[[set]] +identifier = "allow-runtime-read" +description = "This allows non-recursive read access to the `$RUNTIME` folder." +permissions = [ + "read-all", + "scope-runtime" +] + +[[set]] +identifier = "allow-runtime-write" +description = "This allows non-recursive write access to the `$RUNTIME` folder." +permissions = [ + "write-all", + "scope-runtime" +] + +[[set]] +identifier = "allow-runtime-meta-recursive" +description = "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-runtime-recursive" +] + +[[set]] +identifier = "allow-runtime-meta" +description = "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-runtime-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/temp.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/temp.toml new file mode 100644 index 00000000..c08e1da2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/temp.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-temp-recursive" +description = "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$TEMP" +[[permission.scope.allow]] +path = "$TEMP/**" + +[[permission]] +identifier = "scope-temp" +description = "This scope permits access to all files and list content of top level directories in the `$TEMP` folder." + +[[permission.scope.allow]] +path = "$TEMP" +[[permission.scope.allow]] +path = "$TEMP/*" + +[[permission]] +identifier = "scope-temp-index" +description = "This scope permits to list all files and folders in the `$TEMP`folder." + +[[permission.scope.allow]] +path = "$TEMP" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-temp-read-recursive" +description = "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-temp-recursive" +] + +[[set]] +identifier = "allow-temp-write-recursive" +description = "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-temp-recursive" +] + +[[set]] +identifier = "allow-temp-read" +description = "This allows non-recursive read access to the `$TEMP` folder." +permissions = [ + "read-all", + "scope-temp" +] + +[[set]] +identifier = "allow-temp-write" +description = "This allows non-recursive write access to the `$TEMP` folder." +permissions = [ + "write-all", + "scope-temp" +] + +[[set]] +identifier = "allow-temp-meta-recursive" +description = "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-temp-recursive" +] + +[[set]] +identifier = "allow-temp-meta" +description = "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-temp-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/template.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/template.toml new file mode 100644 index 00000000..ce39f773 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/template.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-template-recursive" +description = "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$TEMPLATE" +[[permission.scope.allow]] +path = "$TEMPLATE/**" + +[[permission]] +identifier = "scope-template" +description = "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder." + +[[permission.scope.allow]] +path = "$TEMPLATE" +[[permission.scope.allow]] +path = "$TEMPLATE/*" + +[[permission]] +identifier = "scope-template-index" +description = "This scope permits to list all files and folders in the `$TEMPLATE`folder." + +[[permission.scope.allow]] +path = "$TEMPLATE" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-template-read-recursive" +description = "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-template-recursive" +] + +[[set]] +identifier = "allow-template-write-recursive" +description = "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-template-recursive" +] + +[[set]] +identifier = "allow-template-read" +description = "This allows non-recursive read access to the `$TEMPLATE` folder." +permissions = [ + "read-all", + "scope-template" +] + +[[set]] +identifier = "allow-template-write" +description = "This allows non-recursive write access to the `$TEMPLATE` folder." +permissions = [ + "write-all", + "scope-template" +] + +[[set]] +identifier = "allow-template-meta-recursive" +description = "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-template-recursive" +] + +[[set]] +identifier = "allow-template-meta" +description = "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-template-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/video.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/video.toml new file mode 100644 index 00000000..df41abdc --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/base-directories/video.toml @@ -0,0 +1,82 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +# Scopes Section +# This section contains scopes, which define file level access + +[[permission]] +identifier = "scope-video-recursive" +description = "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files." + +[[permission.scope.allow]] +path = "$VIDEO" +[[permission.scope.allow]] +path = "$VIDEO/**" + +[[permission]] +identifier = "scope-video" +description = "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder." + +[[permission.scope.allow]] +path = "$VIDEO" +[[permission.scope.allow]] +path = "$VIDEO/*" + +[[permission]] +identifier = "scope-video-index" +description = "This scope permits to list all files and folders in the `$VIDEO`folder." + +[[permission.scope.allow]] +path = "$VIDEO" + +# Sets Section +# This section combines the scope elements with enablement of commands + +[[set]] +identifier = "allow-video-read-recursive" +description = "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories." +permissions = [ + "read-all", + "scope-video-recursive" +] + +[[set]] +identifier = "allow-video-write-recursive" +description = "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories." +permissions = [ + "write-all", + "scope-video-recursive" +] + +[[set]] +identifier = "allow-video-read" +description = "This allows non-recursive read access to the `$VIDEO` folder." +permissions = [ + "read-all", + "scope-video" +] + +[[set]] +identifier = "allow-video-write" +description = "This allows non-recursive write access to the `$VIDEO` folder." +permissions = [ + "write-all", + "scope-video" +] + +[[set]] +identifier = "allow-video-meta-recursive" +description = "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-video-recursive" +] + +[[set]] +identifier = "allow-video-meta" +description = "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics." +permissions = [ + "read-meta", + "scope-video-index" +] \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/copy_file.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/copy_file.toml new file mode 100644 index 00000000..61bedf9e --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/copy_file.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-copy-file" +description = "Enables the copy_file command without any pre-configured scope." +commands.allow = ["copy_file"] + +[[permission]] +identifier = "deny-copy-file" +description = "Denies the copy_file command without any pre-configured scope." +commands.deny = ["copy_file"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/create.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/create.toml new file mode 100644 index 00000000..6646cc6c --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/create.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-create" +description = "Enables the create command without any pre-configured scope." +commands.allow = ["create"] + +[[permission]] +identifier = "deny-create" +description = "Denies the create command without any pre-configured scope." +commands.deny = ["create"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/exists.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/exists.toml new file mode 100644 index 00000000..0eed148f --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/exists.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-exists" +description = "Enables the exists command without any pre-configured scope." +commands.allow = ["exists"] + +[[permission]] +identifier = "deny-exists" +description = "Denies the exists command without any pre-configured scope." +commands.deny = ["exists"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/fstat.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/fstat.toml new file mode 100644 index 00000000..30d28112 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/fstat.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-fstat" +description = "Enables the fstat command without any pre-configured scope." +commands.allow = ["fstat"] + +[[permission]] +identifier = "deny-fstat" +description = "Denies the fstat command without any pre-configured scope." +commands.deny = ["fstat"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/ftruncate.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/ftruncate.toml new file mode 100644 index 00000000..6b54ffe0 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/ftruncate.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-ftruncate" +description = "Enables the ftruncate command without any pre-configured scope." +commands.allow = ["ftruncate"] + +[[permission]] +identifier = "deny-ftruncate" +description = "Denies the ftruncate command without any pre-configured scope." +commands.deny = ["ftruncate"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/lstat.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/lstat.toml new file mode 100644 index 00000000..b224635b --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/lstat.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-lstat" +description = "Enables the lstat command without any pre-configured scope." +commands.allow = ["lstat"] + +[[permission]] +identifier = "deny-lstat" +description = "Denies the lstat command without any pre-configured scope." +commands.deny = ["lstat"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/mkdir.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/mkdir.toml new file mode 100644 index 00000000..58cdbbc7 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/mkdir.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-mkdir" +description = "Enables the mkdir command without any pre-configured scope." +commands.allow = ["mkdir"] + +[[permission]] +identifier = "deny-mkdir" +description = "Denies the mkdir command without any pre-configured scope." +commands.deny = ["mkdir"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/open.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/open.toml new file mode 100644 index 00000000..4ea6dff1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/open.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-open" +description = "Enables the open command without any pre-configured scope." +commands.allow = ["open"] + +[[permission]] +identifier = "deny-open" +description = "Denies the open command without any pre-configured scope." +commands.deny = ["open"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/read.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/read.toml new file mode 100644 index 00000000..20fa10c6 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/read.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-read" +description = "Enables the read command without any pre-configured scope." +commands.allow = ["read"] + +[[permission]] +identifier = "deny-read" +description = "Denies the read command without any pre-configured scope." +commands.deny = ["read"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/read_dir.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/read_dir.toml new file mode 100644 index 00000000..eef68eba --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/read_dir.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-read-dir" +description = "Enables the read_dir command without any pre-configured scope." +commands.allow = ["read_dir"] + +[[permission]] +identifier = "deny-read-dir" +description = "Denies the read_dir command without any pre-configured scope." +commands.deny = ["read_dir"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/read_file.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/read_file.toml new file mode 100644 index 00000000..b932b71a --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/read_file.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-read-file" +description = "Enables the read_file command without any pre-configured scope." +commands.allow = ["read_file"] + +[[permission]] +identifier = "deny-read-file" +description = "Denies the read_file command without any pre-configured scope." +commands.deny = ["read_file"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/read_text_file.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/read_text_file.toml new file mode 100644 index 00000000..7a25115d --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/read_text_file.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-read-text-file" +description = "Enables the read_text_file command without any pre-configured scope." +commands.allow = ["read_text_file"] + +[[permission]] +identifier = "deny-read-text-file" +description = "Denies the read_text_file command without any pre-configured scope." +commands.deny = ["read_text_file"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/read_text_file_lines.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/read_text_file_lines.toml new file mode 100644 index 00000000..84b4ebb2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/read_text_file_lines.toml @@ -0,0 +1,22 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-read-text-file-lines" +description = "Enables the read_text_file_lines command without any pre-configured scope." + +[permission.commands] +allow = [ + "read_text_file_lines", + "read_text_file_lines_next", +] +deny = [] + +[[permission]] +identifier = "deny-read-text-file-lines" +description = "Denies the read_text_file_lines command without any pre-configured scope." + +[permission.commands] +allow = [] +deny = ["read_text_file_lines"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/read_text_file_lines_next.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/read_text_file_lines_next.toml new file mode 100644 index 00000000..021ea37c --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/read_text_file_lines_next.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-read-text-file-lines-next" +description = "Enables the read_text_file_lines_next command without any pre-configured scope." +commands.allow = ["read_text_file_lines_next"] + +[[permission]] +identifier = "deny-read-text-file-lines-next" +description = "Denies the read_text_file_lines_next command without any pre-configured scope." +commands.deny = ["read_text_file_lines_next"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/remove.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/remove.toml new file mode 100644 index 00000000..9c9791eb --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/remove.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-remove" +description = "Enables the remove command without any pre-configured scope." +commands.allow = ["remove"] + +[[permission]] +identifier = "deny-remove" +description = "Denies the remove command without any pre-configured scope." +commands.deny = ["remove"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/rename.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/rename.toml new file mode 100644 index 00000000..91def18f --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/rename.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-rename" +description = "Enables the rename command without any pre-configured scope." +commands.allow = ["rename"] + +[[permission]] +identifier = "deny-rename" +description = "Denies the rename command without any pre-configured scope." +commands.deny = ["rename"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/seek.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/seek.toml new file mode 100644 index 00000000..cb21bdc3 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/seek.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-seek" +description = "Enables the seek command without any pre-configured scope." +commands.allow = ["seek"] + +[[permission]] +identifier = "deny-seek" +description = "Denies the seek command without any pre-configured scope." +commands.deny = ["seek"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/size.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/size.toml new file mode 100644 index 00000000..8a0ea55c --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/size.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-size" +description = "Enables the size command without any pre-configured scope." +commands.allow = ["size"] + +[[permission]] +identifier = "deny-size" +description = "Denies the size command without any pre-configured scope." +commands.deny = ["size"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/stat.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/stat.toml new file mode 100644 index 00000000..56f751bc --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/stat.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-stat" +description = "Enables the stat command without any pre-configured scope." +commands.allow = ["stat"] + +[[permission]] +identifier = "deny-stat" +description = "Denies the stat command without any pre-configured scope." +commands.deny = ["stat"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/truncate.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/truncate.toml new file mode 100644 index 00000000..62fbb144 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/truncate.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-truncate" +description = "Enables the truncate command without any pre-configured scope." +commands.allow = ["truncate"] + +[[permission]] +identifier = "deny-truncate" +description = "Denies the truncate command without any pre-configured scope." +commands.deny = ["truncate"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/unwatch.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/unwatch.toml new file mode 100644 index 00000000..3259e9a6 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/unwatch.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-unwatch" +description = "Enables the unwatch command without any pre-configured scope." +commands.allow = ["unwatch"] + +[[permission]] +identifier = "deny-unwatch" +description = "Denies the unwatch command without any pre-configured scope." +commands.deny = ["unwatch"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/watch.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/watch.toml new file mode 100644 index 00000000..8dd1b577 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/watch.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-watch" +description = "Enables the watch command without any pre-configured scope." +commands.allow = ["watch"] + +[[permission]] +identifier = "deny-watch" +description = "Denies the watch command without any pre-configured scope." +commands.deny = ["watch"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/write.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/write.toml new file mode 100644 index 00000000..73d1d387 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/write.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-write" +description = "Enables the write command without any pre-configured scope." +commands.allow = ["write"] + +[[permission]] +identifier = "deny-write" +description = "Denies the write command without any pre-configured scope." +commands.deny = ["write"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/write_file.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/write_file.toml new file mode 100644 index 00000000..ea7d5136 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/write_file.toml @@ -0,0 +1,23 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-write-file" +description = "Enables the write_file command without any pre-configured scope." + +[permission.commands] +allow = [ + "write_file", + "open", + "write", +] +deny = [] + +[[permission]] +identifier = "deny-write-file" +description = "Denies the write_file command without any pre-configured scope." + +[permission.commands] +allow = [] +deny = ["write_file"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/write_text_file.toml b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/write_text_file.toml new file mode 100644 index 00000000..6b497a70 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/commands/write_text_file.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-write-text-file" +description = "Enables the write_text_file command without any pre-configured scope." +commands.allow = ["write_text_file"] + +[[permission]] +identifier = "deny-write-text-file" +description = "Denies the write_text_file command without any pre-configured scope." +commands.deny = ["write_text_file"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/reference.md new file mode 100644 index 00000000..c1a60319 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/autogenerated/reference.md @@ -0,0 +1,3779 @@ +## Default Permission + +This set of permissions describes the what kind of +file system access the `fs` plugin has enabled or denied by default. + +#### Granted Permissions + +This default permission set enables read access to the +application specific directories (AppConfig, AppData, AppLocalData, AppCache, +AppLog) and all files and sub directories created in it. +The location of these directories depends on the operating system, +where the application is run. + +In general these directories need to be manually created +by the application at runtime, before accessing files or folders +in it is possible. + +Therefore, it is also allowed to create all of these folders via +the `mkdir` command. + +#### Denied Permissions + +This default permission set prevents access to critical components +of the Tauri application by default. +On Windows the webview data folder access is denied. + +#### This default permission set includes the following: + +- `create-app-specific-dirs` +- `read-app-specific-dirs-recursive` +- `deny-default` + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`fs:allow-app-read-recursive` + + + +This allows full recursive read access to the complete application folders, files and subdirectories. + +
+ +`fs:allow-app-write-recursive` + + + +This allows full recursive write access to the complete application folders, files and subdirectories. + +
+ +`fs:allow-app-read` + + + +This allows non-recursive read access to the application folders. + +
+ +`fs:allow-app-write` + + + +This allows non-recursive write access to the application folders. + +
+ +`fs:allow-app-meta-recursive` + + + +This allows full recursive read access to metadata of the application folders, including file listing and statistics. + +
+ +`fs:allow-app-meta` + + + +This allows non-recursive read access to metadata of the application folders, including file listing and statistics. + +
+ +`fs:scope-app-recursive` + + + +This scope permits recursive access to the complete application folders, including sub directories and files. + +
+ +`fs:scope-app` + + + +This scope permits access to all files and list content of top level directories in the application folders. + +
+ +`fs:scope-app-index` + + + +This scope permits to list all files and folders in the application directories. + +
+ +`fs:allow-appcache-read-recursive` + + + +This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories. + +
+ +`fs:allow-appcache-write-recursive` + + + +This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories. + +
+ +`fs:allow-appcache-read` + + + +This allows non-recursive read access to the `$APPCACHE` folder. + +
+ +`fs:allow-appcache-write` + + + +This allows non-recursive write access to the `$APPCACHE` folder. + +
+ +`fs:allow-appcache-meta-recursive` + + + +This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics. + +
+ +`fs:allow-appcache-meta` + + + +This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics. + +
+ +`fs:scope-appcache-recursive` + + + +This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files. + +
+ +`fs:scope-appcache` + + + +This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder. + +
+ +`fs:scope-appcache-index` + + + +This scope permits to list all files and folders in the `$APPCACHE`folder. + +
+ +`fs:allow-appconfig-read-recursive` + + + +This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories. + +
+ +`fs:allow-appconfig-write-recursive` + + + +This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories. + +
+ +`fs:allow-appconfig-read` + + + +This allows non-recursive read access to the `$APPCONFIG` folder. + +
+ +`fs:allow-appconfig-write` + + + +This allows non-recursive write access to the `$APPCONFIG` folder. + +
+ +`fs:allow-appconfig-meta-recursive` + + + +This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics. + +
+ +`fs:allow-appconfig-meta` + + + +This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics. + +
+ +`fs:scope-appconfig-recursive` + + + +This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files. + +
+ +`fs:scope-appconfig` + + + +This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder. + +
+ +`fs:scope-appconfig-index` + + + +This scope permits to list all files and folders in the `$APPCONFIG`folder. + +
+ +`fs:allow-appdata-read-recursive` + + + +This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories. + +
+ +`fs:allow-appdata-write-recursive` + + + +This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories. + +
+ +`fs:allow-appdata-read` + + + +This allows non-recursive read access to the `$APPDATA` folder. + +
+ +`fs:allow-appdata-write` + + + +This allows non-recursive write access to the `$APPDATA` folder. + +
+ +`fs:allow-appdata-meta-recursive` + + + +This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics. + +
+ +`fs:allow-appdata-meta` + + + +This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics. + +
+ +`fs:scope-appdata-recursive` + + + +This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files. + +
+ +`fs:scope-appdata` + + + +This scope permits access to all files and list content of top level directories in the `$APPDATA` folder. + +
+ +`fs:scope-appdata-index` + + + +This scope permits to list all files and folders in the `$APPDATA`folder. + +
+ +`fs:allow-applocaldata-read-recursive` + + + +This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories. + +
+ +`fs:allow-applocaldata-write-recursive` + + + +This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories. + +
+ +`fs:allow-applocaldata-read` + + + +This allows non-recursive read access to the `$APPLOCALDATA` folder. + +
+ +`fs:allow-applocaldata-write` + + + +This allows non-recursive write access to the `$APPLOCALDATA` folder. + +
+ +`fs:allow-applocaldata-meta-recursive` + + + +This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics. + +
+ +`fs:allow-applocaldata-meta` + + + +This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics. + +
+ +`fs:scope-applocaldata-recursive` + + + +This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files. + +
+ +`fs:scope-applocaldata` + + + +This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder. + +
+ +`fs:scope-applocaldata-index` + + + +This scope permits to list all files and folders in the `$APPLOCALDATA`folder. + +
+ +`fs:allow-applog-read-recursive` + + + +This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories. + +
+ +`fs:allow-applog-write-recursive` + + + +This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories. + +
+ +`fs:allow-applog-read` + + + +This allows non-recursive read access to the `$APPLOG` folder. + +
+ +`fs:allow-applog-write` + + + +This allows non-recursive write access to the `$APPLOG` folder. + +
+ +`fs:allow-applog-meta-recursive` + + + +This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics. + +
+ +`fs:allow-applog-meta` + + + +This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics. + +
+ +`fs:scope-applog-recursive` + + + +This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files. + +
+ +`fs:scope-applog` + + + +This scope permits access to all files and list content of top level directories in the `$APPLOG` folder. + +
+ +`fs:scope-applog-index` + + + +This scope permits to list all files and folders in the `$APPLOG`folder. + +
+ +`fs:allow-audio-read-recursive` + + + +This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories. + +
+ +`fs:allow-audio-write-recursive` + + + +This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories. + +
+ +`fs:allow-audio-read` + + + +This allows non-recursive read access to the `$AUDIO` folder. + +
+ +`fs:allow-audio-write` + + + +This allows non-recursive write access to the `$AUDIO` folder. + +
+ +`fs:allow-audio-meta-recursive` + + + +This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics. + +
+ +`fs:allow-audio-meta` + + + +This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics. + +
+ +`fs:scope-audio-recursive` + + + +This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files. + +
+ +`fs:scope-audio` + + + +This scope permits access to all files and list content of top level directories in the `$AUDIO` folder. + +
+ +`fs:scope-audio-index` + + + +This scope permits to list all files and folders in the `$AUDIO`folder. + +
+ +`fs:allow-cache-read-recursive` + + + +This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories. + +
+ +`fs:allow-cache-write-recursive` + + + +This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories. + +
+ +`fs:allow-cache-read` + + + +This allows non-recursive read access to the `$CACHE` folder. + +
+ +`fs:allow-cache-write` + + + +This allows non-recursive write access to the `$CACHE` folder. + +
+ +`fs:allow-cache-meta-recursive` + + + +This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics. + +
+ +`fs:allow-cache-meta` + + + +This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics. + +
+ +`fs:scope-cache-recursive` + + + +This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files. + +
+ +`fs:scope-cache` + + + +This scope permits access to all files and list content of top level directories in the `$CACHE` folder. + +
+ +`fs:scope-cache-index` + + + +This scope permits to list all files and folders in the `$CACHE`folder. + +
+ +`fs:allow-config-read-recursive` + + + +This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories. + +
+ +`fs:allow-config-write-recursive` + + + +This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories. + +
+ +`fs:allow-config-read` + + + +This allows non-recursive read access to the `$CONFIG` folder. + +
+ +`fs:allow-config-write` + + + +This allows non-recursive write access to the `$CONFIG` folder. + +
+ +`fs:allow-config-meta-recursive` + + + +This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics. + +
+ +`fs:allow-config-meta` + + + +This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics. + +
+ +`fs:scope-config-recursive` + + + +This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files. + +
+ +`fs:scope-config` + + + +This scope permits access to all files and list content of top level directories in the `$CONFIG` folder. + +
+ +`fs:scope-config-index` + + + +This scope permits to list all files and folders in the `$CONFIG`folder. + +
+ +`fs:allow-data-read-recursive` + + + +This allows full recursive read access to the complete `$DATA` folder, files and subdirectories. + +
+ +`fs:allow-data-write-recursive` + + + +This allows full recursive write access to the complete `$DATA` folder, files and subdirectories. + +
+ +`fs:allow-data-read` + + + +This allows non-recursive read access to the `$DATA` folder. + +
+ +`fs:allow-data-write` + + + +This allows non-recursive write access to the `$DATA` folder. + +
+ +`fs:allow-data-meta-recursive` + + + +This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics. + +
+ +`fs:allow-data-meta` + + + +This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics. + +
+ +`fs:scope-data-recursive` + + + +This scope permits recursive access to the complete `$DATA` folder, including sub directories and files. + +
+ +`fs:scope-data` + + + +This scope permits access to all files and list content of top level directories in the `$DATA` folder. + +
+ +`fs:scope-data-index` + + + +This scope permits to list all files and folders in the `$DATA`folder. + +
+ +`fs:allow-desktop-read-recursive` + + + +This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories. + +
+ +`fs:allow-desktop-write-recursive` + + + +This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories. + +
+ +`fs:allow-desktop-read` + + + +This allows non-recursive read access to the `$DESKTOP` folder. + +
+ +`fs:allow-desktop-write` + + + +This allows non-recursive write access to the `$DESKTOP` folder. + +
+ +`fs:allow-desktop-meta-recursive` + + + +This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics. + +
+ +`fs:allow-desktop-meta` + + + +This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics. + +
+ +`fs:scope-desktop-recursive` + + + +This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files. + +
+ +`fs:scope-desktop` + + + +This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder. + +
+ +`fs:scope-desktop-index` + + + +This scope permits to list all files and folders in the `$DESKTOP`folder. + +
+ +`fs:allow-document-read-recursive` + + + +This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories. + +
+ +`fs:allow-document-write-recursive` + + + +This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories. + +
+ +`fs:allow-document-read` + + + +This allows non-recursive read access to the `$DOCUMENT` folder. + +
+ +`fs:allow-document-write` + + + +This allows non-recursive write access to the `$DOCUMENT` folder. + +
+ +`fs:allow-document-meta-recursive` + + + +This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics. + +
+ +`fs:allow-document-meta` + + + +This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics. + +
+ +`fs:scope-document-recursive` + + + +This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files. + +
+ +`fs:scope-document` + + + +This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder. + +
+ +`fs:scope-document-index` + + + +This scope permits to list all files and folders in the `$DOCUMENT`folder. + +
+ +`fs:allow-download-read-recursive` + + + +This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories. + +
+ +`fs:allow-download-write-recursive` + + + +This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories. + +
+ +`fs:allow-download-read` + + + +This allows non-recursive read access to the `$DOWNLOAD` folder. + +
+ +`fs:allow-download-write` + + + +This allows non-recursive write access to the `$DOWNLOAD` folder. + +
+ +`fs:allow-download-meta-recursive` + + + +This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics. + +
+ +`fs:allow-download-meta` + + + +This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics. + +
+ +`fs:scope-download-recursive` + + + +This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files. + +
+ +`fs:scope-download` + + + +This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder. + +
+ +`fs:scope-download-index` + + + +This scope permits to list all files and folders in the `$DOWNLOAD`folder. + +
+ +`fs:allow-exe-read-recursive` + + + +This allows full recursive read access to the complete `$EXE` folder, files and subdirectories. + +
+ +`fs:allow-exe-write-recursive` + + + +This allows full recursive write access to the complete `$EXE` folder, files and subdirectories. + +
+ +`fs:allow-exe-read` + + + +This allows non-recursive read access to the `$EXE` folder. + +
+ +`fs:allow-exe-write` + + + +This allows non-recursive write access to the `$EXE` folder. + +
+ +`fs:allow-exe-meta-recursive` + + + +This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics. + +
+ +`fs:allow-exe-meta` + + + +This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics. + +
+ +`fs:scope-exe-recursive` + + + +This scope permits recursive access to the complete `$EXE` folder, including sub directories and files. + +
+ +`fs:scope-exe` + + + +This scope permits access to all files and list content of top level directories in the `$EXE` folder. + +
+ +`fs:scope-exe-index` + + + +This scope permits to list all files and folders in the `$EXE`folder. + +
+ +`fs:allow-font-read-recursive` + + + +This allows full recursive read access to the complete `$FONT` folder, files and subdirectories. + +
+ +`fs:allow-font-write-recursive` + + + +This allows full recursive write access to the complete `$FONT` folder, files and subdirectories. + +
+ +`fs:allow-font-read` + + + +This allows non-recursive read access to the `$FONT` folder. + +
+ +`fs:allow-font-write` + + + +This allows non-recursive write access to the `$FONT` folder. + +
+ +`fs:allow-font-meta-recursive` + + + +This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics. + +
+ +`fs:allow-font-meta` + + + +This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics. + +
+ +`fs:scope-font-recursive` + + + +This scope permits recursive access to the complete `$FONT` folder, including sub directories and files. + +
+ +`fs:scope-font` + + + +This scope permits access to all files and list content of top level directories in the `$FONT` folder. + +
+ +`fs:scope-font-index` + + + +This scope permits to list all files and folders in the `$FONT`folder. + +
+ +`fs:allow-home-read-recursive` + + + +This allows full recursive read access to the complete `$HOME` folder, files and subdirectories. + +
+ +`fs:allow-home-write-recursive` + + + +This allows full recursive write access to the complete `$HOME` folder, files and subdirectories. + +
+ +`fs:allow-home-read` + + + +This allows non-recursive read access to the `$HOME` folder. + +
+ +`fs:allow-home-write` + + + +This allows non-recursive write access to the `$HOME` folder. + +
+ +`fs:allow-home-meta-recursive` + + + +This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics. + +
+ +`fs:allow-home-meta` + + + +This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics. + +
+ +`fs:scope-home-recursive` + + + +This scope permits recursive access to the complete `$HOME` folder, including sub directories and files. + +
+ +`fs:scope-home` + + + +This scope permits access to all files and list content of top level directories in the `$HOME` folder. + +
+ +`fs:scope-home-index` + + + +This scope permits to list all files and folders in the `$HOME`folder. + +
+ +`fs:allow-localdata-read-recursive` + + + +This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories. + +
+ +`fs:allow-localdata-write-recursive` + + + +This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories. + +
+ +`fs:allow-localdata-read` + + + +This allows non-recursive read access to the `$LOCALDATA` folder. + +
+ +`fs:allow-localdata-write` + + + +This allows non-recursive write access to the `$LOCALDATA` folder. + +
+ +`fs:allow-localdata-meta-recursive` + + + +This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics. + +
+ +`fs:allow-localdata-meta` + + + +This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics. + +
+ +`fs:scope-localdata-recursive` + + + +This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files. + +
+ +`fs:scope-localdata` + + + +This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder. + +
+ +`fs:scope-localdata-index` + + + +This scope permits to list all files and folders in the `$LOCALDATA`folder. + +
+ +`fs:allow-log-read-recursive` + + + +This allows full recursive read access to the complete `$LOG` folder, files and subdirectories. + +
+ +`fs:allow-log-write-recursive` + + + +This allows full recursive write access to the complete `$LOG` folder, files and subdirectories. + +
+ +`fs:allow-log-read` + + + +This allows non-recursive read access to the `$LOG` folder. + +
+ +`fs:allow-log-write` + + + +This allows non-recursive write access to the `$LOG` folder. + +
+ +`fs:allow-log-meta-recursive` + + + +This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics. + +
+ +`fs:allow-log-meta` + + + +This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics. + +
+ +`fs:scope-log-recursive` + + + +This scope permits recursive access to the complete `$LOG` folder, including sub directories and files. + +
+ +`fs:scope-log` + + + +This scope permits access to all files and list content of top level directories in the `$LOG` folder. + +
+ +`fs:scope-log-index` + + + +This scope permits to list all files and folders in the `$LOG`folder. + +
+ +`fs:allow-picture-read-recursive` + + + +This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories. + +
+ +`fs:allow-picture-write-recursive` + + + +This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories. + +
+ +`fs:allow-picture-read` + + + +This allows non-recursive read access to the `$PICTURE` folder. + +
+ +`fs:allow-picture-write` + + + +This allows non-recursive write access to the `$PICTURE` folder. + +
+ +`fs:allow-picture-meta-recursive` + + + +This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics. + +
+ +`fs:allow-picture-meta` + + + +This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics. + +
+ +`fs:scope-picture-recursive` + + + +This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files. + +
+ +`fs:scope-picture` + + + +This scope permits access to all files and list content of top level directories in the `$PICTURE` folder. + +
+ +`fs:scope-picture-index` + + + +This scope permits to list all files and folders in the `$PICTURE`folder. + +
+ +`fs:allow-public-read-recursive` + + + +This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories. + +
+ +`fs:allow-public-write-recursive` + + + +This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories. + +
+ +`fs:allow-public-read` + + + +This allows non-recursive read access to the `$PUBLIC` folder. + +
+ +`fs:allow-public-write` + + + +This allows non-recursive write access to the `$PUBLIC` folder. + +
+ +`fs:allow-public-meta-recursive` + + + +This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics. + +
+ +`fs:allow-public-meta` + + + +This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics. + +
+ +`fs:scope-public-recursive` + + + +This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files. + +
+ +`fs:scope-public` + + + +This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder. + +
+ +`fs:scope-public-index` + + + +This scope permits to list all files and folders in the `$PUBLIC`folder. + +
+ +`fs:allow-resource-read-recursive` + + + +This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories. + +
+ +`fs:allow-resource-write-recursive` + + + +This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories. + +
+ +`fs:allow-resource-read` + + + +This allows non-recursive read access to the `$RESOURCE` folder. + +
+ +`fs:allow-resource-write` + + + +This allows non-recursive write access to the `$RESOURCE` folder. + +
+ +`fs:allow-resource-meta-recursive` + + + +This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics. + +
+ +`fs:allow-resource-meta` + + + +This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics. + +
+ +`fs:scope-resource-recursive` + + + +This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files. + +
+ +`fs:scope-resource` + + + +This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder. + +
+ +`fs:scope-resource-index` + + + +This scope permits to list all files and folders in the `$RESOURCE`folder. + +
+ +`fs:allow-runtime-read-recursive` + + + +This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories. + +
+ +`fs:allow-runtime-write-recursive` + + + +This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories. + +
+ +`fs:allow-runtime-read` + + + +This allows non-recursive read access to the `$RUNTIME` folder. + +
+ +`fs:allow-runtime-write` + + + +This allows non-recursive write access to the `$RUNTIME` folder. + +
+ +`fs:allow-runtime-meta-recursive` + + + +This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics. + +
+ +`fs:allow-runtime-meta` + + + +This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics. + +
+ +`fs:scope-runtime-recursive` + + + +This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files. + +
+ +`fs:scope-runtime` + + + +This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder. + +
+ +`fs:scope-runtime-index` + + + +This scope permits to list all files and folders in the `$RUNTIME`folder. + +
+ +`fs:allow-temp-read-recursive` + + + +This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories. + +
+ +`fs:allow-temp-write-recursive` + + + +This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories. + +
+ +`fs:allow-temp-read` + + + +This allows non-recursive read access to the `$TEMP` folder. + +
+ +`fs:allow-temp-write` + + + +This allows non-recursive write access to the `$TEMP` folder. + +
+ +`fs:allow-temp-meta-recursive` + + + +This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics. + +
+ +`fs:allow-temp-meta` + + + +This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics. + +
+ +`fs:scope-temp-recursive` + + + +This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files. + +
+ +`fs:scope-temp` + + + +This scope permits access to all files and list content of top level directories in the `$TEMP` folder. + +
+ +`fs:scope-temp-index` + + + +This scope permits to list all files and folders in the `$TEMP`folder. + +
+ +`fs:allow-template-read-recursive` + + + +This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories. + +
+ +`fs:allow-template-write-recursive` + + + +This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories. + +
+ +`fs:allow-template-read` + + + +This allows non-recursive read access to the `$TEMPLATE` folder. + +
+ +`fs:allow-template-write` + + + +This allows non-recursive write access to the `$TEMPLATE` folder. + +
+ +`fs:allow-template-meta-recursive` + + + +This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics. + +
+ +`fs:allow-template-meta` + + + +This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics. + +
+ +`fs:scope-template-recursive` + + + +This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files. + +
+ +`fs:scope-template` + + + +This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder. + +
+ +`fs:scope-template-index` + + + +This scope permits to list all files and folders in the `$TEMPLATE`folder. + +
+ +`fs:allow-video-read-recursive` + + + +This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories. + +
+ +`fs:allow-video-write-recursive` + + + +This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories. + +
+ +`fs:allow-video-read` + + + +This allows non-recursive read access to the `$VIDEO` folder. + +
+ +`fs:allow-video-write` + + + +This allows non-recursive write access to the `$VIDEO` folder. + +
+ +`fs:allow-video-meta-recursive` + + + +This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics. + +
+ +`fs:allow-video-meta` + + + +This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics. + +
+ +`fs:scope-video-recursive` + + + +This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files. + +
+ +`fs:scope-video` + + + +This scope permits access to all files and list content of top level directories in the `$VIDEO` folder. + +
+ +`fs:scope-video-index` + + + +This scope permits to list all files and folders in the `$VIDEO`folder. + +
+ +`fs:allow-copy-file` + + + +Enables the copy_file command without any pre-configured scope. + +
+ +`fs:deny-copy-file` + + + +Denies the copy_file command without any pre-configured scope. + +
+ +`fs:allow-create` + + + +Enables the create command without any pre-configured scope. + +
+ +`fs:deny-create` + + + +Denies the create command without any pre-configured scope. + +
+ +`fs:allow-exists` + + + +Enables the exists command without any pre-configured scope. + +
+ +`fs:deny-exists` + + + +Denies the exists command without any pre-configured scope. + +
+ +`fs:allow-fstat` + + + +Enables the fstat command without any pre-configured scope. + +
+ +`fs:deny-fstat` + + + +Denies the fstat command without any pre-configured scope. + +
+ +`fs:allow-ftruncate` + + + +Enables the ftruncate command without any pre-configured scope. + +
+ +`fs:deny-ftruncate` + + + +Denies the ftruncate command without any pre-configured scope. + +
+ +`fs:allow-lstat` + + + +Enables the lstat command without any pre-configured scope. + +
+ +`fs:deny-lstat` + + + +Denies the lstat command without any pre-configured scope. + +
+ +`fs:allow-mkdir` + + + +Enables the mkdir command without any pre-configured scope. + +
+ +`fs:deny-mkdir` + + + +Denies the mkdir command without any pre-configured scope. + +
+ +`fs:allow-open` + + + +Enables the open command without any pre-configured scope. + +
+ +`fs:deny-open` + + + +Denies the open command without any pre-configured scope. + +
+ +`fs:allow-read` + + + +Enables the read command without any pre-configured scope. + +
+ +`fs:deny-read` + + + +Denies the read command without any pre-configured scope. + +
+ +`fs:allow-read-dir` + + + +Enables the read_dir command without any pre-configured scope. + +
+ +`fs:deny-read-dir` + + + +Denies the read_dir command without any pre-configured scope. + +
+ +`fs:allow-read-file` + + + +Enables the read_file command without any pre-configured scope. + +
+ +`fs:deny-read-file` + + + +Denies the read_file command without any pre-configured scope. + +
+ +`fs:allow-read-text-file` + + + +Enables the read_text_file command without any pre-configured scope. + +
+ +`fs:deny-read-text-file` + + + +Denies the read_text_file command without any pre-configured scope. + +
+ +`fs:allow-read-text-file-lines` + + + +Enables the read_text_file_lines command without any pre-configured scope. + +
+ +`fs:deny-read-text-file-lines` + + + +Denies the read_text_file_lines command without any pre-configured scope. + +
+ +`fs:allow-read-text-file-lines-next` + + + +Enables the read_text_file_lines_next command without any pre-configured scope. + +
+ +`fs:deny-read-text-file-lines-next` + + + +Denies the read_text_file_lines_next command without any pre-configured scope. + +
+ +`fs:allow-remove` + + + +Enables the remove command without any pre-configured scope. + +
+ +`fs:deny-remove` + + + +Denies the remove command without any pre-configured scope. + +
+ +`fs:allow-rename` + + + +Enables the rename command without any pre-configured scope. + +
+ +`fs:deny-rename` + + + +Denies the rename command without any pre-configured scope. + +
+ +`fs:allow-seek` + + + +Enables the seek command without any pre-configured scope. + +
+ +`fs:deny-seek` + + + +Denies the seek command without any pre-configured scope. + +
+ +`fs:allow-size` + + + +Enables the size command without any pre-configured scope. + +
+ +`fs:deny-size` + + + +Denies the size command without any pre-configured scope. + +
+ +`fs:allow-stat` + + + +Enables the stat command without any pre-configured scope. + +
+ +`fs:deny-stat` + + + +Denies the stat command without any pre-configured scope. + +
+ +`fs:allow-truncate` + + + +Enables the truncate command without any pre-configured scope. + +
+ +`fs:deny-truncate` + + + +Denies the truncate command without any pre-configured scope. + +
+ +`fs:allow-unwatch` + + + +Enables the unwatch command without any pre-configured scope. + +
+ +`fs:deny-unwatch` + + + +Denies the unwatch command without any pre-configured scope. + +
+ +`fs:allow-watch` + + + +Enables the watch command without any pre-configured scope. + +
+ +`fs:deny-watch` + + + +Denies the watch command without any pre-configured scope. + +
+ +`fs:allow-write` + + + +Enables the write command without any pre-configured scope. + +
+ +`fs:deny-write` + + + +Denies the write command without any pre-configured scope. + +
+ +`fs:allow-write-file` + + + +Enables the write_file command without any pre-configured scope. + +
+ +`fs:deny-write-file` + + + +Denies the write_file command without any pre-configured scope. + +
+ +`fs:allow-write-text-file` + + + +Enables the write_text_file command without any pre-configured scope. + +
+ +`fs:deny-write-text-file` + + + +Denies the write_text_file command without any pre-configured scope. + +
+ +`fs:create-app-specific-dirs` + + + +This permissions allows to create the application specific directories. + + +
+ +`fs:deny-default` + + + +This denies access to dangerous Tauri relevant files and folders by default. + +
+ +`fs:deny-webview-data-linux` + + + +This denies read access to the +`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here. +Allowing access can lead to sensitive information disclosure and should be well considered. + +
+ +`fs:deny-webview-data-windows` + + + +This denies read access to the +`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here. +Allowing access can lead to sensitive information disclosure and should be well considered. + +
+ +`fs:read-all` + + + +This enables all read related commands without any pre-configured accessible paths. + +
+ +`fs:read-app-specific-dirs-recursive` + + + +This permission allows recursive read functionality on the application +specific base directories. + + +
+ +`fs:read-dirs` + + + +This enables directory read and file metadata related commands without any pre-configured accessible paths. + +
+ +`fs:read-files` + + + +This enables file read related commands without any pre-configured accessible paths. + +
+ +`fs:read-meta` + + + +This enables all index or metadata related commands without any pre-configured accessible paths. + +
+ +`fs:scope` + + + +An empty permission you can use to modify the global scope. + +
+ +`fs:write-all` + + + +This enables all write related commands without any pre-configured accessible paths. + +
+ +`fs:write-files` + + + +This enables all file write related commands without any pre-configured accessible paths. + +
diff --git a/packages/kbot/gui/app/plugins/fs/permissions/create-app-specific-dirs.toml b/packages/kbot/gui/app/plugins/fs/permissions/create-app-specific-dirs.toml new file mode 100644 index 00000000..7785cb13 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/create-app-specific-dirs.toml @@ -0,0 +1,8 @@ +"$schema" = "schemas/schema.json" + +[[permission]] +identifier = "create-app-specific-dirs" +description = """ +This permissions allows to create the application specific directories. +""" +commands.allow = ["mkdir", "scope-app-index"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/default.toml b/packages/kbot/gui/app/plugins/fs/permissions/default.toml new file mode 100644 index 00000000..78836df7 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/default.toml @@ -0,0 +1,33 @@ +"$schema" = "schemas/schema.json" + +[default] +description = """ +This set of permissions describes the what kind of +file system access the `fs` plugin has enabled or denied by default. + +#### Granted Permissions + +This default permission set enables read access to the +application specific directories (AppConfig, AppData, AppLocalData, AppCache, +AppLog) and all files and sub directories created in it. +The location of these directories depends on the operating system, +where the application is run. + +In general these directories need to be manually created +by the application at runtime, before accessing files or folders +in it is possible. + +Therefore, it is also allowed to create all of these folders via +the `mkdir` command. + +#### Denied Permissions + +This default permission set prevents access to critical components +of the Tauri application by default. +On Windows the webview data folder access is denied. +""" +permissions = [ + "create-app-specific-dirs", + "read-app-specific-dirs-recursive", + "deny-default", +] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/deny-default.toml b/packages/kbot/gui/app/plugins/fs/permissions/deny-default.toml new file mode 100644 index 00000000..22d8186f --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/deny-default.toml @@ -0,0 +1,6 @@ +"$schema" = "schemas/schema.json" + +[[set]] +identifier = "deny-default" +description = "This denies access to dangerous Tauri relevant files and folders by default." +permissions = ["deny-webview-data-linux", "deny-webview-data-windows"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/deny-webview-data.toml b/packages/kbot/gui/app/plugins/fs/permissions/deny-webview-data.toml new file mode 100644 index 00000000..73712d95 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/deny-webview-data.toml @@ -0,0 +1,19 @@ +"$schema" = "schemas/schema.json" + +[[permission]] +identifier = "deny-webview-data-linux" +description = """This denies read access to the +`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here. +Allowing access can lead to sensitive information disclosure and should be well considered.""" + +[[scope.deny]] +path = "$APPLOCALDATA/**" + +[[permission]] +identifier = "deny-webview-data-windows" +description = """This denies read access to the +`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here. +Allowing access can lead to sensitive information disclosure and should be well considered.""" + +[[scope.deny]] +path = "$APPLOCALDATA/EBWebView/**" diff --git a/packages/kbot/gui/app/plugins/fs/permissions/read-all.toml b/packages/kbot/gui/app/plugins/fs/permissions/read-all.toml new file mode 100644 index 00000000..d43af5e0 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/read-all.toml @@ -0,0 +1,21 @@ +"$schema" = "schemas/schema.json" + +[[permission]] +identifier = "read-all" +description = "This enables all read related commands without any pre-configured accessible paths." +commands.allow = [ + "read_dir", + "read_file", + "read", + "open", + "read_text_file", + "read_text_file_lines", + "read_text_file_lines_next", + "seek", + "stat", + "lstat", + "fstat", + "exists", + "watch", + "unwatch", +] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/read-app-specific-dirs-recursive.toml b/packages/kbot/gui/app/plugins/fs/permissions/read-app-specific-dirs-recursive.toml new file mode 100644 index 00000000..5342dbc0 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/read-app-specific-dirs-recursive.toml @@ -0,0 +1,17 @@ +"$schema" = "schemas/schema.json" + +[[permission]] +identifier = "read-app-specific-dirs-recursive" +description = """ +This permission allows recursive read functionality on the application +specific base directories. +""" +commands.allow = [ + "read_dir", + "read_file", + "read_text_file", + "read_text_file_lines", + "read_text_file_lines_next", + "exists", + "scope-app-recursive", +] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/read-dirs.toml b/packages/kbot/gui/app/plugins/fs/permissions/read-dirs.toml new file mode 100644 index 00000000..eb383632 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/read-dirs.toml @@ -0,0 +1,6 @@ +"$schema" = "schemas/schema.json" + +[[permission]] +identifier = "read-dirs" +description = "This enables directory read and file metadata related commands without any pre-configured accessible paths." +commands.allow = ["read_dir", "stat", "lstat", "fstat", "exists"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/read-files.toml b/packages/kbot/gui/app/plugins/fs/permissions/read-files.toml new file mode 100644 index 00000000..f2685108 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/read-files.toml @@ -0,0 +1,19 @@ +"$schema" = "schemas/schema.json" + +[[permission]] +identifier = "read-files" +description = "This enables file read related commands without any pre-configured accessible paths." +commands.allow = [ + "read_file", + "read", + "open", + "read_text_file", + "read_text_file_lines", + "read_text_file_lines_next", + "seek", + "stat", + "lstat", + "fstat", + "exists", + +] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/read-meta.toml b/packages/kbot/gui/app/plugins/fs/permissions/read-meta.toml new file mode 100644 index 00000000..83024b0c --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/read-meta.toml @@ -0,0 +1,6 @@ +"$schema" = "schemas/schema.json" + +[[permission]] +identifier = "read-meta" +description = "This enables all index or metadata related commands without any pre-configured accessible paths." +commands.allow = ["read_dir", "stat", "lstat", "fstat", "exists", "size"] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/fs/permissions/schemas/schema.json new file mode 100644 index 00000000..54c6798b --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/schemas/schema.json @@ -0,0 +1,2028 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`", + "type": "string", + "const": "allow-app-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`", + "type": "string", + "const": "allow-app-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete application folders, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`", + "type": "string", + "const": "allow-app-read", + "markdownDescription": "This allows non-recursive read access to the application folders.\n#### This permission set includes:\n\n- `read-all`\n- `scope-app`" + }, + { + "description": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`", + "type": "string", + "const": "allow-app-write", + "markdownDescription": "This allows non-recursive write access to the application folders.\n#### This permission set includes:\n\n- `write-all`\n- `scope-app`" + }, + { + "description": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`", + "type": "string", + "const": "allow-app-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`", + "type": "string", + "const": "allow-app-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the application folders, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-app-index`" + }, + { + "description": "This scope permits recursive access to the complete application folders, including sub directories and files.", + "type": "string", + "const": "scope-app-recursive", + "markdownDescription": "This scope permits recursive access to the complete application folders, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the application folders.", + "type": "string", + "const": "scope-app", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the application folders." + }, + { + "description": "This scope permits to list all files and folders in the application directories.", + "type": "string", + "const": "scope-app-index", + "markdownDescription": "This scope permits to list all files and folders in the application directories." + }, + { + "description": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "allow-appcache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`", + "type": "string", + "const": "allow-appcache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`", + "type": "string", + "const": "allow-appcache-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appcache`" + }, + { + "description": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`", + "type": "string", + "const": "allow-appcache-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appcache`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`", + "type": "string", + "const": "allow-appcache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`", + "type": "string", + "const": "allow-appcache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appcache-index`" + }, + { + "description": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files.", + "type": "string", + "const": "scope-appcache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder.", + "type": "string", + "const": "scope-appcache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCACHE`folder.", + "type": "string", + "const": "scope-appcache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCACHE`folder." + }, + { + "description": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "allow-appconfig-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "allow-appconfig-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPCONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`", + "type": "string", + "const": "allow-appconfig-read", + "markdownDescription": "This allows non-recursive read access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appconfig`" + }, + { + "description": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`", + "type": "string", + "const": "allow-appconfig-write", + "markdownDescription": "This allows non-recursive write access to the `$APPCONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appconfig`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`", + "type": "string", + "const": "allow-appconfig-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`", + "type": "string", + "const": "allow-appconfig-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPCONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appconfig-index`" + }, + { + "description": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files.", + "type": "string", + "const": "scope-appconfig-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPCONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder.", + "type": "string", + "const": "scope-appconfig", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPCONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPCONFIG`folder.", + "type": "string", + "const": "scope-appconfig-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPCONFIG`folder." + }, + { + "description": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "allow-appdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`", + "type": "string", + "const": "allow-appdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`", + "type": "string", + "const": "allow-appdata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-appdata`" + }, + { + "description": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`", + "type": "string", + "const": "allow-appdata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-appdata`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`", + "type": "string", + "const": "allow-appdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`", + "type": "string", + "const": "allow-appdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-appdata-index`" + }, + { + "description": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files.", + "type": "string", + "const": "scope-appdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder.", + "type": "string", + "const": "scope-appdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPDATA`folder.", + "type": "string", + "const": "scope-appdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPDATA`folder." + }, + { + "description": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "allow-applocaldata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "allow-applocaldata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`", + "type": "string", + "const": "allow-applocaldata-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`", + "type": "string", + "const": "allow-applocaldata-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applocaldata`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`", + "type": "string", + "const": "allow-applocaldata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`", + "type": "string", + "const": "allow-applocaldata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applocaldata-index`" + }, + { + "description": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "scope-applocaldata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder.", + "type": "string", + "const": "scope-applocaldata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder.", + "type": "string", + "const": "scope-applocaldata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOCALDATA`folder." + }, + { + "description": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "allow-applog-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`", + "type": "string", + "const": "allow-applog-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$APPLOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`", + "type": "string", + "const": "allow-applog-read", + "markdownDescription": "This allows non-recursive read access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-applog`" + }, + { + "description": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`", + "type": "string", + "const": "allow-applog-write", + "markdownDescription": "This allows non-recursive write access to the `$APPLOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-applog`" + }, + { + "description": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`", + "type": "string", + "const": "allow-applog-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`", + "type": "string", + "const": "allow-applog-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$APPLOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-applog-index`" + }, + { + "description": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files.", + "type": "string", + "const": "scope-applog-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$APPLOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder.", + "type": "string", + "const": "scope-applog", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$APPLOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$APPLOG`folder.", + "type": "string", + "const": "scope-applog-index", + "markdownDescription": "This scope permits to list all files and folders in the `$APPLOG`folder." + }, + { + "description": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "allow-audio-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`", + "type": "string", + "const": "allow-audio-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$AUDIO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`", + "type": "string", + "const": "allow-audio-read", + "markdownDescription": "This allows non-recursive read access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-audio`" + }, + { + "description": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`", + "type": "string", + "const": "allow-audio-write", + "markdownDescription": "This allows non-recursive write access to the `$AUDIO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-audio`" + }, + { + "description": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`", + "type": "string", + "const": "allow-audio-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`", + "type": "string", + "const": "allow-audio-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$AUDIO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-audio-index`" + }, + { + "description": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files.", + "type": "string", + "const": "scope-audio-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$AUDIO` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder.", + "type": "string", + "const": "scope-audio", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$AUDIO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$AUDIO`folder.", + "type": "string", + "const": "scope-audio-index", + "markdownDescription": "This scope permits to list all files and folders in the `$AUDIO`folder." + }, + { + "description": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "allow-cache-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`", + "type": "string", + "const": "allow-cache-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CACHE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`", + "type": "string", + "const": "allow-cache-read", + "markdownDescription": "This allows non-recursive read access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-cache`" + }, + { + "description": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`", + "type": "string", + "const": "allow-cache-write", + "markdownDescription": "This allows non-recursive write access to the `$CACHE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-cache`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`", + "type": "string", + "const": "allow-cache-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`", + "type": "string", + "const": "allow-cache-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CACHE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-cache-index`" + }, + { + "description": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files.", + "type": "string", + "const": "scope-cache-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CACHE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder.", + "type": "string", + "const": "scope-cache", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CACHE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CACHE`folder.", + "type": "string", + "const": "scope-cache-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CACHE`folder." + }, + { + "description": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`", + "type": "string", + "const": "allow-config-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`", + "type": "string", + "const": "allow-config-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$CONFIG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`", + "type": "string", + "const": "allow-config-read", + "markdownDescription": "This allows non-recursive read access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-config`" + }, + { + "description": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`", + "type": "string", + "const": "allow-config-write", + "markdownDescription": "This allows non-recursive write access to the `$CONFIG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-config`" + }, + { + "description": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`", + "type": "string", + "const": "allow-config-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`", + "type": "string", + "const": "allow-config-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$CONFIG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-config-index`" + }, + { + "description": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files.", + "type": "string", + "const": "scope-config-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$CONFIG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder.", + "type": "string", + "const": "scope-config", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$CONFIG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$CONFIG`folder.", + "type": "string", + "const": "scope-config-index", + "markdownDescription": "This scope permits to list all files and folders in the `$CONFIG`folder." + }, + { + "description": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`", + "type": "string", + "const": "allow-data-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`", + "type": "string", + "const": "allow-data-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`", + "type": "string", + "const": "allow-data-read", + "markdownDescription": "This allows non-recursive read access to the `$DATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-data`" + }, + { + "description": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`", + "type": "string", + "const": "allow-data-write", + "markdownDescription": "This allows non-recursive write access to the `$DATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-data`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`", + "type": "string", + "const": "allow-data-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`", + "type": "string", + "const": "allow-data-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-data-index`" + }, + { + "description": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files.", + "type": "string", + "const": "scope-data-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DATA` folder.", + "type": "string", + "const": "scope-data", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DATA`folder.", + "type": "string", + "const": "scope-data-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DATA`folder." + }, + { + "description": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "allow-desktop-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`", + "type": "string", + "const": "allow-desktop-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DESKTOP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`", + "type": "string", + "const": "allow-desktop-read", + "markdownDescription": "This allows non-recursive read access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-desktop`" + }, + { + "description": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`", + "type": "string", + "const": "allow-desktop-write", + "markdownDescription": "This allows non-recursive write access to the `$DESKTOP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-desktop`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`", + "type": "string", + "const": "allow-desktop-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`", + "type": "string", + "const": "allow-desktop-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DESKTOP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-desktop-index`" + }, + { + "description": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files.", + "type": "string", + "const": "scope-desktop-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DESKTOP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder.", + "type": "string", + "const": "scope-desktop", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DESKTOP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DESKTOP`folder.", + "type": "string", + "const": "scope-desktop-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DESKTOP`folder." + }, + { + "description": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`", + "type": "string", + "const": "allow-document-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`", + "type": "string", + "const": "allow-document-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOCUMENT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`", + "type": "string", + "const": "allow-document-read", + "markdownDescription": "This allows non-recursive read access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-document`" + }, + { + "description": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`", + "type": "string", + "const": "allow-document-write", + "markdownDescription": "This allows non-recursive write access to the `$DOCUMENT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-document`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`", + "type": "string", + "const": "allow-document-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`", + "type": "string", + "const": "allow-document-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOCUMENT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-document-index`" + }, + { + "description": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files.", + "type": "string", + "const": "scope-document-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOCUMENT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder.", + "type": "string", + "const": "scope-document", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOCUMENT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOCUMENT`folder.", + "type": "string", + "const": "scope-document-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOCUMENT`folder." + }, + { + "description": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`", + "type": "string", + "const": "allow-download-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`", + "type": "string", + "const": "allow-download-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$DOWNLOAD` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`", + "type": "string", + "const": "allow-download-read", + "markdownDescription": "This allows non-recursive read access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-download`" + }, + { + "description": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`", + "type": "string", + "const": "allow-download-write", + "markdownDescription": "This allows non-recursive write access to the `$DOWNLOAD` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-download`" + }, + { + "description": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`", + "type": "string", + "const": "allow-download-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`", + "type": "string", + "const": "allow-download-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$DOWNLOAD` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-download-index`" + }, + { + "description": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files.", + "type": "string", + "const": "scope-download-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$DOWNLOAD` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder.", + "type": "string", + "const": "scope-download", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$DOWNLOAD` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$DOWNLOAD`folder.", + "type": "string", + "const": "scope-download-index", + "markdownDescription": "This scope permits to list all files and folders in the `$DOWNLOAD`folder." + }, + { + "description": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "allow-exe-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`", + "type": "string", + "const": "allow-exe-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$EXE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`", + "type": "string", + "const": "allow-exe-read", + "markdownDescription": "This allows non-recursive read access to the `$EXE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-exe`" + }, + { + "description": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`", + "type": "string", + "const": "allow-exe-write", + "markdownDescription": "This allows non-recursive write access to the `$EXE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-exe`" + }, + { + "description": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`", + "type": "string", + "const": "allow-exe-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`", + "type": "string", + "const": "allow-exe-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$EXE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-exe-index`" + }, + { + "description": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files.", + "type": "string", + "const": "scope-exe-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$EXE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$EXE` folder.", + "type": "string", + "const": "scope-exe", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$EXE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$EXE`folder.", + "type": "string", + "const": "scope-exe-index", + "markdownDescription": "This scope permits to list all files and folders in the `$EXE`folder." + }, + { + "description": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`", + "type": "string", + "const": "allow-font-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`", + "type": "string", + "const": "allow-font-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$FONT` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`", + "type": "string", + "const": "allow-font-read", + "markdownDescription": "This allows non-recursive read access to the `$FONT` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-font`" + }, + { + "description": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`", + "type": "string", + "const": "allow-font-write", + "markdownDescription": "This allows non-recursive write access to the `$FONT` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-font`" + }, + { + "description": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`", + "type": "string", + "const": "allow-font-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`", + "type": "string", + "const": "allow-font-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$FONT` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-font-index`" + }, + { + "description": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files.", + "type": "string", + "const": "scope-font-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$FONT` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$FONT` folder.", + "type": "string", + "const": "scope-font", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$FONT` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$FONT`folder.", + "type": "string", + "const": "scope-font-index", + "markdownDescription": "This scope permits to list all files and folders in the `$FONT`folder." + }, + { + "description": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`", + "type": "string", + "const": "allow-home-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`", + "type": "string", + "const": "allow-home-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$HOME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`", + "type": "string", + "const": "allow-home-read", + "markdownDescription": "This allows non-recursive read access to the `$HOME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-home`" + }, + { + "description": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`", + "type": "string", + "const": "allow-home-write", + "markdownDescription": "This allows non-recursive write access to the `$HOME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-home`" + }, + { + "description": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`", + "type": "string", + "const": "allow-home-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`", + "type": "string", + "const": "allow-home-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$HOME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-home-index`" + }, + { + "description": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files.", + "type": "string", + "const": "scope-home-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$HOME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$HOME` folder.", + "type": "string", + "const": "scope-home", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$HOME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$HOME`folder.", + "type": "string", + "const": "scope-home-index", + "markdownDescription": "This scope permits to list all files and folders in the `$HOME`folder." + }, + { + "description": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "allow-localdata-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`", + "type": "string", + "const": "allow-localdata-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOCALDATA` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`", + "type": "string", + "const": "allow-localdata-read", + "markdownDescription": "This allows non-recursive read access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-localdata`" + }, + { + "description": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`", + "type": "string", + "const": "allow-localdata-write", + "markdownDescription": "This allows non-recursive write access to the `$LOCALDATA` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-localdata`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`", + "type": "string", + "const": "allow-localdata-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`", + "type": "string", + "const": "allow-localdata-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOCALDATA` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-localdata-index`" + }, + { + "description": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files.", + "type": "string", + "const": "scope-localdata-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOCALDATA` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder.", + "type": "string", + "const": "scope-localdata", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOCALDATA` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOCALDATA`folder.", + "type": "string", + "const": "scope-localdata-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOCALDATA`folder." + }, + { + "description": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`", + "type": "string", + "const": "allow-log-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`", + "type": "string", + "const": "allow-log-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$LOG` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`", + "type": "string", + "const": "allow-log-read", + "markdownDescription": "This allows non-recursive read access to the `$LOG` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-log`" + }, + { + "description": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`", + "type": "string", + "const": "allow-log-write", + "markdownDescription": "This allows non-recursive write access to the `$LOG` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-log`" + }, + { + "description": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`", + "type": "string", + "const": "allow-log-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`", + "type": "string", + "const": "allow-log-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$LOG` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-log-index`" + }, + { + "description": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files.", + "type": "string", + "const": "scope-log-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$LOG` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$LOG` folder.", + "type": "string", + "const": "scope-log", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$LOG` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$LOG`folder.", + "type": "string", + "const": "scope-log-index", + "markdownDescription": "This scope permits to list all files and folders in the `$LOG`folder." + }, + { + "description": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "allow-picture-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`", + "type": "string", + "const": "allow-picture-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PICTURE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`", + "type": "string", + "const": "allow-picture-read", + "markdownDescription": "This allows non-recursive read access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-picture`" + }, + { + "description": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`", + "type": "string", + "const": "allow-picture-write", + "markdownDescription": "This allows non-recursive write access to the `$PICTURE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-picture`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`", + "type": "string", + "const": "allow-picture-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`", + "type": "string", + "const": "allow-picture-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PICTURE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-picture-index`" + }, + { + "description": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files.", + "type": "string", + "const": "scope-picture-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PICTURE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder.", + "type": "string", + "const": "scope-picture", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PICTURE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PICTURE`folder.", + "type": "string", + "const": "scope-picture-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PICTURE`folder." + }, + { + "description": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`", + "type": "string", + "const": "allow-public-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`", + "type": "string", + "const": "allow-public-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$PUBLIC` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`", + "type": "string", + "const": "allow-public-read", + "markdownDescription": "This allows non-recursive read access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-public`" + }, + { + "description": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`", + "type": "string", + "const": "allow-public-write", + "markdownDescription": "This allows non-recursive write access to the `$PUBLIC` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-public`" + }, + { + "description": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`", + "type": "string", + "const": "allow-public-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`", + "type": "string", + "const": "allow-public-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$PUBLIC` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-public-index`" + }, + { + "description": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files.", + "type": "string", + "const": "scope-public-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$PUBLIC` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder.", + "type": "string", + "const": "scope-public", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$PUBLIC` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$PUBLIC`folder.", + "type": "string", + "const": "scope-public-index", + "markdownDescription": "This scope permits to list all files and folders in the `$PUBLIC`folder." + }, + { + "description": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "allow-resource-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`", + "type": "string", + "const": "allow-resource-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RESOURCE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`", + "type": "string", + "const": "allow-resource-read", + "markdownDescription": "This allows non-recursive read access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-resource`" + }, + { + "description": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`", + "type": "string", + "const": "allow-resource-write", + "markdownDescription": "This allows non-recursive write access to the `$RESOURCE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-resource`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`", + "type": "string", + "const": "allow-resource-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`", + "type": "string", + "const": "allow-resource-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RESOURCE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-resource-index`" + }, + { + "description": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files.", + "type": "string", + "const": "scope-resource-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RESOURCE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder.", + "type": "string", + "const": "scope-resource", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RESOURCE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RESOURCE`folder.", + "type": "string", + "const": "scope-resource-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RESOURCE`folder." + }, + { + "description": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "allow-runtime-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`", + "type": "string", + "const": "allow-runtime-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$RUNTIME` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`", + "type": "string", + "const": "allow-runtime-read", + "markdownDescription": "This allows non-recursive read access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-runtime`" + }, + { + "description": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`", + "type": "string", + "const": "allow-runtime-write", + "markdownDescription": "This allows non-recursive write access to the `$RUNTIME` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-runtime`" + }, + { + "description": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`", + "type": "string", + "const": "allow-runtime-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`", + "type": "string", + "const": "allow-runtime-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$RUNTIME` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-runtime-index`" + }, + { + "description": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files.", + "type": "string", + "const": "scope-runtime-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$RUNTIME` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder.", + "type": "string", + "const": "scope-runtime", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$RUNTIME` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$RUNTIME`folder.", + "type": "string", + "const": "scope-runtime-index", + "markdownDescription": "This scope permits to list all files and folders in the `$RUNTIME`folder." + }, + { + "description": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "allow-temp-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`", + "type": "string", + "const": "allow-temp-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMP` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`", + "type": "string", + "const": "allow-temp-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-temp`" + }, + { + "description": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`", + "type": "string", + "const": "allow-temp-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMP` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-temp`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`", + "type": "string", + "const": "allow-temp-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`", + "type": "string", + "const": "allow-temp-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMP` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-temp-index`" + }, + { + "description": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files.", + "type": "string", + "const": "scope-temp-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMP` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder.", + "type": "string", + "const": "scope-temp", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMP` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMP`folder.", + "type": "string", + "const": "scope-temp-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMP`folder." + }, + { + "description": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`", + "type": "string", + "const": "allow-template-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`", + "type": "string", + "const": "allow-template-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$TEMPLATE` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`", + "type": "string", + "const": "allow-template-read", + "markdownDescription": "This allows non-recursive read access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-template`" + }, + { + "description": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`", + "type": "string", + "const": "allow-template-write", + "markdownDescription": "This allows non-recursive write access to the `$TEMPLATE` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-template`" + }, + { + "description": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`", + "type": "string", + "const": "allow-template-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`", + "type": "string", + "const": "allow-template-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$TEMPLATE` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-template-index`" + }, + { + "description": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files.", + "type": "string", + "const": "scope-template-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$TEMPLATE` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder.", + "type": "string", + "const": "scope-template", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$TEMPLATE` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$TEMPLATE`folder.", + "type": "string", + "const": "scope-template-index", + "markdownDescription": "This scope permits to list all files and folders in the `$TEMPLATE`folder." + }, + { + "description": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`", + "type": "string", + "const": "allow-video-read-recursive", + "markdownDescription": "This allows full recursive read access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video-recursive`" + }, + { + "description": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`", + "type": "string", + "const": "allow-video-write-recursive", + "markdownDescription": "This allows full recursive write access to the complete `$VIDEO` folder, files and subdirectories.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`", + "type": "string", + "const": "allow-video-read", + "markdownDescription": "This allows non-recursive read access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `read-all`\n- `scope-video`" + }, + { + "description": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`", + "type": "string", + "const": "allow-video-write", + "markdownDescription": "This allows non-recursive write access to the `$VIDEO` folder.\n#### This permission set includes:\n\n- `write-all`\n- `scope-video`" + }, + { + "description": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`", + "type": "string", + "const": "allow-video-meta-recursive", + "markdownDescription": "This allows full recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-recursive`" + }, + { + "description": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`", + "type": "string", + "const": "allow-video-meta", + "markdownDescription": "This allows non-recursive read access to metadata of the `$VIDEO` folder, including file listing and statistics.\n#### This permission set includes:\n\n- `read-meta`\n- `scope-video-index`" + }, + { + "description": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files.", + "type": "string", + "const": "scope-video-recursive", + "markdownDescription": "This scope permits recursive access to the complete `$VIDEO` folder, including sub directories and files." + }, + { + "description": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder.", + "type": "string", + "const": "scope-video", + "markdownDescription": "This scope permits access to all files and list content of top level directories in the `$VIDEO` folder." + }, + { + "description": "This scope permits to list all files and folders in the `$VIDEO`folder.", + "type": "string", + "const": "scope-video-index", + "markdownDescription": "This scope permits to list all files and folders in the `$VIDEO`folder." + }, + { + "description": "Enables the copy_file command without any pre-configured scope.", + "type": "string", + "const": "allow-copy-file", + "markdownDescription": "Enables the copy_file command without any pre-configured scope." + }, + { + "description": "Denies the copy_file command without any pre-configured scope.", + "type": "string", + "const": "deny-copy-file", + "markdownDescription": "Denies the copy_file command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Enables the exists command without any pre-configured scope.", + "type": "string", + "const": "allow-exists", + "markdownDescription": "Enables the exists command without any pre-configured scope." + }, + { + "description": "Denies the exists command without any pre-configured scope.", + "type": "string", + "const": "deny-exists", + "markdownDescription": "Denies the exists command without any pre-configured scope." + }, + { + "description": "Enables the fstat command without any pre-configured scope.", + "type": "string", + "const": "allow-fstat", + "markdownDescription": "Enables the fstat command without any pre-configured scope." + }, + { + "description": "Denies the fstat command without any pre-configured scope.", + "type": "string", + "const": "deny-fstat", + "markdownDescription": "Denies the fstat command without any pre-configured scope." + }, + { + "description": "Enables the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "allow-ftruncate", + "markdownDescription": "Enables the ftruncate command without any pre-configured scope." + }, + { + "description": "Denies the ftruncate command without any pre-configured scope.", + "type": "string", + "const": "deny-ftruncate", + "markdownDescription": "Denies the ftruncate command without any pre-configured scope." + }, + { + "description": "Enables the lstat command without any pre-configured scope.", + "type": "string", + "const": "allow-lstat", + "markdownDescription": "Enables the lstat command without any pre-configured scope." + }, + { + "description": "Denies the lstat command without any pre-configured scope.", + "type": "string", + "const": "deny-lstat", + "markdownDescription": "Denies the lstat command without any pre-configured scope." + }, + { + "description": "Enables the mkdir command without any pre-configured scope.", + "type": "string", + "const": "allow-mkdir", + "markdownDescription": "Enables the mkdir command without any pre-configured scope." + }, + { + "description": "Denies the mkdir command without any pre-configured scope.", + "type": "string", + "const": "deny-mkdir", + "markdownDescription": "Denies the mkdir command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Enables the read command without any pre-configured scope.", + "type": "string", + "const": "allow-read", + "markdownDescription": "Enables the read command without any pre-configured scope." + }, + { + "description": "Denies the read command without any pre-configured scope.", + "type": "string", + "const": "deny-read", + "markdownDescription": "Denies the read command without any pre-configured scope." + }, + { + "description": "Enables the read_dir command without any pre-configured scope.", + "type": "string", + "const": "allow-read-dir", + "markdownDescription": "Enables the read_dir command without any pre-configured scope." + }, + { + "description": "Denies the read_dir command without any pre-configured scope.", + "type": "string", + "const": "deny-read-dir", + "markdownDescription": "Denies the read_dir command without any pre-configured scope." + }, + { + "description": "Enables the read_file command without any pre-configured scope.", + "type": "string", + "const": "allow-read-file", + "markdownDescription": "Enables the read_file command without any pre-configured scope." + }, + { + "description": "Denies the read_file command without any pre-configured scope.", + "type": "string", + "const": "deny-read-file", + "markdownDescription": "Denies the read_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "allow-read-text-file", + "markdownDescription": "Enables the read_text_file command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file command without any pre-configured scope.", + "type": "string", + "const": "deny-read-text-file", + "markdownDescription": "Denies the read_text_file command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "allow-read-text-file-lines", + "markdownDescription": "Enables the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines command without any pre-configured scope.", + "type": "string", + "const": "deny-read-text-file-lines", + "markdownDescription": "Denies the read_text_file_lines command without any pre-configured scope." + }, + { + "description": "Enables the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "allow-read-text-file-lines-next", + "markdownDescription": "Enables the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Denies the read_text_file_lines_next command without any pre-configured scope.", + "type": "string", + "const": "deny-read-text-file-lines-next", + "markdownDescription": "Denies the read_text_file_lines_next command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Enables the rename command without any pre-configured scope.", + "type": "string", + "const": "allow-rename", + "markdownDescription": "Enables the rename command without any pre-configured scope." + }, + { + "description": "Denies the rename command without any pre-configured scope.", + "type": "string", + "const": "deny-rename", + "markdownDescription": "Denies the rename command without any pre-configured scope." + }, + { + "description": "Enables the seek command without any pre-configured scope.", + "type": "string", + "const": "allow-seek", + "markdownDescription": "Enables the seek command without any pre-configured scope." + }, + { + "description": "Denies the seek command without any pre-configured scope.", + "type": "string", + "const": "deny-seek", + "markdownDescription": "Denies the seek command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Enables the stat command without any pre-configured scope.", + "type": "string", + "const": "allow-stat", + "markdownDescription": "Enables the stat command without any pre-configured scope." + }, + { + "description": "Denies the stat command without any pre-configured scope.", + "type": "string", + "const": "deny-stat", + "markdownDescription": "Denies the stat command without any pre-configured scope." + }, + { + "description": "Enables the truncate command without any pre-configured scope.", + "type": "string", + "const": "allow-truncate", + "markdownDescription": "Enables the truncate command without any pre-configured scope." + }, + { + "description": "Denies the truncate command without any pre-configured scope.", + "type": "string", + "const": "deny-truncate", + "markdownDescription": "Denies the truncate command without any pre-configured scope." + }, + { + "description": "Enables the unwatch command without any pre-configured scope.", + "type": "string", + "const": "allow-unwatch", + "markdownDescription": "Enables the unwatch command without any pre-configured scope." + }, + { + "description": "Denies the unwatch command without any pre-configured scope.", + "type": "string", + "const": "deny-unwatch", + "markdownDescription": "Denies the unwatch command without any pre-configured scope." + }, + { + "description": "Enables the watch command without any pre-configured scope.", + "type": "string", + "const": "allow-watch", + "markdownDescription": "Enables the watch command without any pre-configured scope." + }, + { + "description": "Denies the watch command without any pre-configured scope.", + "type": "string", + "const": "deny-watch", + "markdownDescription": "Denies the watch command without any pre-configured scope." + }, + { + "description": "Enables the write command without any pre-configured scope.", + "type": "string", + "const": "allow-write", + "markdownDescription": "Enables the write command without any pre-configured scope." + }, + { + "description": "Denies the write command without any pre-configured scope.", + "type": "string", + "const": "deny-write", + "markdownDescription": "Denies the write command without any pre-configured scope." + }, + { + "description": "Enables the write_file command without any pre-configured scope.", + "type": "string", + "const": "allow-write-file", + "markdownDescription": "Enables the write_file command without any pre-configured scope." + }, + { + "description": "Denies the write_file command without any pre-configured scope.", + "type": "string", + "const": "deny-write-file", + "markdownDescription": "Denies the write_file command without any pre-configured scope." + }, + { + "description": "Enables the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "allow-write-text-file", + "markdownDescription": "Enables the write_text_file command without any pre-configured scope." + }, + { + "description": "Denies the write_text_file command without any pre-configured scope.", + "type": "string", + "const": "deny-write-text-file", + "markdownDescription": "Denies the write_text_file command without any pre-configured scope." + }, + { + "description": "This permissions allows to create the application specific directories.\n", + "type": "string", + "const": "create-app-specific-dirs", + "markdownDescription": "This permissions allows to create the application specific directories.\n" + }, + { + "description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`", + "type": "string", + "const": "default", + "markdownDescription": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### This default permission set includes:\n\n- `create-app-specific-dirs`\n- `read-app-specific-dirs-recursive`\n- `deny-default`" + }, + { + "description": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`", + "type": "string", + "const": "deny-default", + "markdownDescription": "This denies access to dangerous Tauri relevant files and folders by default.\n#### This permission set includes:\n\n- `deny-webview-data-linux`\n- `deny-webview-data-windows`" + }, + { + "description": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", + "type": "string", + "const": "deny-webview-data-linux", + "markdownDescription": "This denies read access to the\n`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered." + }, + { + "description": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered.", + "type": "string", + "const": "deny-webview-data-windows", + "markdownDescription": "This denies read access to the\n`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.\nAllowing access can lead to sensitive information disclosure and should be well considered." + }, + { + "description": "This enables all read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "read-all", + "markdownDescription": "This enables all read related commands without any pre-configured accessible paths." + }, + { + "description": "This permission allows recursive read functionality on the application\nspecific base directories. \n", + "type": "string", + "const": "read-app-specific-dirs-recursive", + "markdownDescription": "This permission allows recursive read functionality on the application\nspecific base directories. \n" + }, + { + "description": "This enables directory read and file metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "read-dirs", + "markdownDescription": "This enables directory read and file metadata related commands without any pre-configured accessible paths." + }, + { + "description": "This enables file read related commands without any pre-configured accessible paths.", + "type": "string", + "const": "read-files", + "markdownDescription": "This enables file read related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all index or metadata related commands without any pre-configured accessible paths.", + "type": "string", + "const": "read-meta", + "markdownDescription": "This enables all index or metadata related commands without any pre-configured accessible paths." + }, + { + "description": "An empty permission you can use to modify the global scope.", + "type": "string", + "const": "scope", + "markdownDescription": "An empty permission you can use to modify the global scope." + }, + { + "description": "This enables all write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "write-all", + "markdownDescription": "This enables all write related commands without any pre-configured accessible paths." + }, + { + "description": "This enables all file write related commands without any pre-configured accessible paths.", + "type": "string", + "const": "write-files", + "markdownDescription": "This enables all file write related commands without any pre-configured accessible paths." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/fs/permissions/scope.toml b/packages/kbot/gui/app/plugins/fs/permissions/scope.toml new file mode 100644 index 00000000..7e945aa8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/scope.toml @@ -0,0 +1,5 @@ +"$schema" = "schemas/schema.json" + +[[permission]] +identifier = "scope" +description = "An empty permission you can use to modify the global scope." diff --git a/packages/kbot/gui/app/plugins/fs/permissions/write-all.toml b/packages/kbot/gui/app/plugins/fs/permissions/write-all.toml new file mode 100644 index 00000000..c1802782 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/write-all.toml @@ -0,0 +1,17 @@ +"$schema" = "schemas/schema.json" + +[[permission]] +identifier = "write-all" +description = "This enables all write related commands without any pre-configured accessible paths." +commands.allow = [ + "mkdir", + "create", + "copy_file", + "remove", + "rename", + "truncate", + "ftruncate", + "write", + "write_file", + "write_text_file", +] diff --git a/packages/kbot/gui/app/plugins/fs/permissions/write-files.toml b/packages/kbot/gui/app/plugins/fs/permissions/write-files.toml new file mode 100644 index 00000000..2d6aeffb --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/permissions/write-files.toml @@ -0,0 +1,16 @@ +"$schema" = "schemas/schema.json" + +[[permission]] +identifier = "write-files" +description = "This enables all file write related commands without any pre-configured accessible paths." +commands.allow = [ + "create", + "copy_file", + "remove", + "rename", + "truncate", + "ftruncate", + "write", + "write_file", + "write_text_file", +] diff --git a/packages/kbot/gui/app/plugins/fs/rollup.config.js b/packages/kbot/gui/app/plugins/fs/rollup.config.js new file mode 100644 index 00000000..1f349ec8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/rollup.config.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' + +export default createConfig() diff --git a/packages/kbot/gui/app/plugins/fs/src/commands.rs b/packages/kbot/gui/app/plugins/fs/src/commands.rs new file mode 100644 index 00000000..bd1400ea --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/src/commands.rs @@ -0,0 +1,1282 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// Copyright 2018-2023 the Deno authors. +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{Deserialize, Serialize, Serializer}; +use serde_repr::{Deserialize_repr, Serialize_repr}; +use tauri::{ + ipc::{CommandScope, GlobalScope}, + path::BaseDirectory, + utils::config::FsScope, + Manager, Resource, ResourceId, Runtime, Webview, +}; + +use std::{ + borrow::Cow, + fs::File, + io::{BufRead, BufReader, Read, Write}, + path::{Path, PathBuf}, + str::FromStr, + sync::Mutex, + time::{SystemTime, UNIX_EPOCH}, +}; + +use crate::{scope::Entry, Error, SafeFilePath}; + +#[derive(Debug, thiserror::Error)] +pub enum CommandError { + #[error(transparent)] + Anyhow(#[from] anyhow::Error), + #[error(transparent)] + Plugin(#[from] Error), + #[error(transparent)] + Tauri(#[from] tauri::Error), + #[error(transparent)] + Json(#[from] serde_json::Error), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + UrlParseError(#[from] url::ParseError), + #[cfg(feature = "watch")] + #[error(transparent)] + Watcher(#[from] notify::Error), +} + +impl From for CommandError { + fn from(value: String) -> Self { + Self::Anyhow(anyhow::anyhow!(value)) + } +} + +impl From<&str> for CommandError { + fn from(value: &str) -> Self { + Self::Anyhow(anyhow::anyhow!(value.to_string())) + } +} + +impl Serialize for CommandError { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + if let Self::Anyhow(err) = self { + serializer.serialize_str(format!("{err:#}").as_ref()) + } else { + serializer.serialize_str(self.to_string().as_ref()) + } + } +} + +pub type CommandResult = std::result::Result; + +#[derive(Debug, Default, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BaseOptions { + base_dir: Option, +} + +#[tauri::command] +pub fn create( + webview: Webview, + global_scope: GlobalScope, + command_scope: CommandScope, + path: SafeFilePath, + options: Option, +) -> CommandResult { + let resolved_path = resolve_path( + &webview, + &global_scope, + &command_scope, + path, + options.and_then(|o| o.base_dir), + )?; + let file = File::create(&resolved_path).map_err(|e| { + format!( + "failed to create file at path: {} with error: {e}", + resolved_path.display() + ) + })?; + let rid = webview.resources_table().add(StdFileResource::new(file)); + Ok(rid) +} + +#[derive(Debug, Default, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OpenOptions { + #[serde(flatten)] + base: BaseOptions, + #[serde(flatten)] + options: crate::OpenOptions, +} + +#[tauri::command] +pub fn open( + webview: Webview, + global_scope: GlobalScope, + command_scope: CommandScope, + path: SafeFilePath, + options: Option, +) -> CommandResult { + let (file, _path) = resolve_file( + &webview, + &global_scope, + &command_scope, + path, + if let Some(opts) = options { + OpenOptions { + base: opts.base, + options: opts.options, + } + } else { + OpenOptions { + base: BaseOptions { base_dir: None }, + options: crate::OpenOptions { + read: true, + write: false, + truncate: false, + create: false, + create_new: false, + append: false, + mode: None, + custom_flags: None, + }, + } + }, + )?; + + let rid = webview.resources_table().add(StdFileResource::new(file)); + + Ok(rid) +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CopyFileOptions { + from_path_base_dir: Option, + to_path_base_dir: Option, +} + +#[tauri::command] +pub async fn copy_file( + webview: Webview, + global_scope: GlobalScope, + command_scope: CommandScope, + from_path: SafeFilePath, + to_path: SafeFilePath, + options: Option, +) -> CommandResult<()> { + let resolved_from_path = resolve_path( + &webview, + &global_scope, + &command_scope, + from_path, + options.as_ref().and_then(|o| o.from_path_base_dir), + )?; + let resolved_to_path = resolve_path( + &webview, + &global_scope, + &command_scope, + to_path, + options.as_ref().and_then(|o| o.to_path_base_dir), + )?; + std::fs::copy(&resolved_from_path, &resolved_to_path).map_err(|e| { + format!( + "failed to copy file from path: {}, to path: {} with error: {e}", + resolved_from_path.display(), + resolved_to_path.display() + ) + })?; + Ok(()) +} + +#[derive(Debug, Clone, Deserialize)] +pub struct MkdirOptions { + #[serde(flatten)] + base: BaseOptions, + #[allow(unused)] + mode: Option, + recursive: Option, +} + +#[tauri::command] +pub fn mkdir( + webview: Webview, + global_scope: GlobalScope, + command_scope: CommandScope, + path: SafeFilePath, + options: Option, +) -> CommandResult<()> { + let resolved_path = resolve_path( + &webview, + &global_scope, + &command_scope, + path, + options.as_ref().and_then(|o| o.base.base_dir), + )?; + + let mut builder = std::fs::DirBuilder::new(); + builder.recursive(options.as_ref().and_then(|o| o.recursive).unwrap_or(false)); + + #[cfg(unix)] + { + use std::os::unix::fs::DirBuilderExt; + let mode = options.as_ref().and_then(|o| o.mode).unwrap_or(0o777) & 0o777; + builder.mode(mode); + } + + builder + .create(&resolved_path) + .map_err(|e| { + format!( + "failed to create directory at path: {} with error: {e}", + resolved_path.display() + ) + }) + .map_err(Into::into) +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct DirEntry { + pub name: String, + pub is_directory: bool, + pub is_file: bool, + pub is_symlink: bool, +} + +#[tauri::command] +pub async fn read_dir( + webview: Webview, + global_scope: GlobalScope, + command_scope: CommandScope, + path: SafeFilePath, + options: Option, +) -> CommandResult> { + let resolved_path = resolve_path( + &webview, + &global_scope, + &command_scope, + path, + options.as_ref().and_then(|o| o.base_dir), + )?; + + let entries = std::fs::read_dir(&resolved_path).map_err(|e| { + format!( + "failed to read directory at path: {} with error: {e}", + resolved_path.display() + ) + })?; + + let entries = entries + .filter_map(|entry| { + let entry = entry.ok()?; + let name = entry.file_name().into_string().ok()?; + let metadata = entry.file_type(); + macro_rules! method_or_false { + ($method:ident) => { + if let Ok(metadata) = &metadata { + metadata.$method() + } else { + false + } + }; + } + Some(DirEntry { + name, + is_file: method_or_false!(is_file), + is_directory: method_or_false!(is_dir), + is_symlink: method_or_false!(is_symlink), + }) + }) + .collect(); + + Ok(entries) +} + +#[tauri::command] +pub async fn read( + webview: Webview, + rid: ResourceId, + len: usize, +) -> CommandResult { + let mut data = vec![0; len]; + let file = webview.resources_table().get::(rid)?; + let nread = StdFileResource::with_lock(&file, |mut file| file.read(&mut data)) + .map_err(|e| format!("faied to read bytes from file with error: {e}"))?; + + // This is an optimization to include the number of read bytes (as bigendian bytes) + // at the end of returned vector so we can use `tauri::ipc::Response` + // and avoid serialization overhead of separate values. + #[cfg(target_pointer_width = "16")] + let nread = { + let nread = nread.to_be_bytes(); + let mut out = [0; 8]; + out[6..].copy_from_slice(&nread); + out + }; + #[cfg(target_pointer_width = "32")] + let nread = { + let nread = nread.to_be_bytes(); + let mut out = [0; 8]; + out[4..].copy_from_slice(&nread); + out + }; + #[cfg(target_pointer_width = "64")] + let nread = nread.to_be_bytes(); + + data.extend(nread); + + Ok(tauri::ipc::Response::new(data)) +} + +#[tauri::command] +pub async fn read_file( + webview: Webview, + global_scope: GlobalScope, + command_scope: CommandScope, + path: SafeFilePath, + options: Option, +) -> CommandResult { + let (mut file, path) = resolve_file( + &webview, + &global_scope, + &command_scope, + path, + OpenOptions { + base: BaseOptions { + base_dir: options.as_ref().and_then(|o| o.base_dir), + }, + options: crate::OpenOptions { + read: true, + ..Default::default() + }, + }, + )?; + + let mut contents = Vec::new(); + + file.read_to_end(&mut contents).map_err(|e| { + format!( + "failed to read file as text at path: {} with error: {e}", + path.display() + ) + })?; + + Ok(tauri::ipc::Response::new(contents)) +} + +// TODO, remove in v3, rely on `read_file` command instead +#[tauri::command] +pub async fn read_text_file( + webview: Webview, + global_scope: GlobalScope, + command_scope: CommandScope, + path: SafeFilePath, + options: Option, +) -> CommandResult { + read_file(webview, global_scope, command_scope, path, options).await +} + +#[tauri::command] +pub fn read_text_file_lines( + webview: Webview, + global_scope: GlobalScope, + command_scope: CommandScope, + path: SafeFilePath, + options: Option, +) -> CommandResult { + let resolved_path = resolve_path( + &webview, + &global_scope, + &command_scope, + path, + options.as_ref().and_then(|o| o.base_dir), + )?; + + let file = File::open(&resolved_path).map_err(|e| { + format!( + "failed to open file at path: {} with error: {e}", + resolved_path.display() + ) + })?; + + let lines = BufReader::new(file); + let rid = webview.resources_table().add(StdLinesResource::new(lines)); + + Ok(rid) +} + +#[tauri::command] +pub async fn read_text_file_lines_next( + webview: Webview, + rid: ResourceId, +) -> CommandResult { + let mut resource_table = webview.resources_table(); + let lines = resource_table.get::(rid)?; + + let ret = StdLinesResource::with_lock(&lines, |lines| -> CommandResult> { + // This is an optimization to include wether we finished iteration or not (1 or 0) + // at the end of returned vector so we can use `tauri::ipc::Response` + // and avoid serialization overhead of separate values. + match lines.next() { + Some(Ok(mut bytes)) => { + bytes.push(false as u8); + Ok(bytes) + } + Some(Err(_)) => Ok(vec![false as u8]), + None => { + resource_table.close(rid)?; + Ok(vec![true as u8]) + } + } + }); + + ret.map(tauri::ipc::Response::new) +} + +#[derive(Debug, Clone, Deserialize)] +pub struct RemoveOptions { + #[serde(flatten)] + base: BaseOptions, + recursive: Option, +} + +#[tauri::command] +pub fn remove( + webview: Webview, + global_scope: GlobalScope, + command_scope: CommandScope, + path: SafeFilePath, + options: Option, +) -> CommandResult<()> { + let resolved_path = resolve_path( + &webview, + &global_scope, + &command_scope, + path, + options.as_ref().and_then(|o| o.base.base_dir), + )?; + + let metadata = std::fs::symlink_metadata(&resolved_path).map_err(|e| { + format!( + "failed to get metadata of path: {} with error: {e}", + resolved_path.display() + ) + })?; + + let file_type = metadata.file_type(); + + // taken from deno source code: https://github.com/denoland/deno/blob/429759fe8b4207240709c240a8344d12a1e39566/runtime/ops/fs.rs#L728 + let res = if file_type.is_file() { + std::fs::remove_file(&resolved_path) + } else if options.as_ref().and_then(|o| o.recursive).unwrap_or(false) { + std::fs::remove_dir_all(&resolved_path) + } else if file_type.is_symlink() { + #[cfg(unix)] + { + std::fs::remove_file(&resolved_path) + } + #[cfg(not(unix))] + { + use std::os::windows::fs::MetadataExt; + const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x00000010; + if metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 { + std::fs::remove_dir(&resolved_path) + } else { + std::fs::remove_file(&resolved_path) + } + } + } else if file_type.is_dir() { + std::fs::remove_dir(&resolved_path) + } else { + // pipes, sockets, etc... + std::fs::remove_file(&resolved_path) + }; + + res.map_err(|e| { + format!( + "failed to remove path: {} with error: {e}", + resolved_path.display() + ) + }) + .map_err(Into::into) +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RenameOptions { + new_path_base_dir: Option, + old_path_base_dir: Option, +} + +#[tauri::command] +pub fn rename( + webview: Webview, + global_scope: GlobalScope, + command_scope: CommandScope, + old_path: SafeFilePath, + new_path: SafeFilePath, + options: Option, +) -> CommandResult<()> { + let resolved_old_path = resolve_path( + &webview, + &global_scope, + &command_scope, + old_path, + options.as_ref().and_then(|o| o.old_path_base_dir), + )?; + let resolved_new_path = resolve_path( + &webview, + &global_scope, + &command_scope, + new_path, + options.as_ref().and_then(|o| o.new_path_base_dir), + )?; + std::fs::rename(&resolved_old_path, &resolved_new_path) + .map_err(|e| { + format!( + "failed to rename old path: {} to new path: {} with error: {e}", + resolved_old_path.display(), + resolved_new_path.display() + ) + }) + .map_err(Into::into) +} + +#[derive(Serialize_repr, Deserialize_repr, Clone, Copy, Debug)] +#[repr(u16)] +pub enum SeekMode { + Start = 0, + Current = 1, + End = 2, +} + +#[tauri::command] +pub async fn seek( + webview: Webview, + rid: ResourceId, + offset: i64, + whence: SeekMode, +) -> CommandResult { + use std::io::{Seek, SeekFrom}; + let file = webview.resources_table().get::(rid)?; + StdFileResource::with_lock(&file, |mut file| { + file.seek(match whence { + SeekMode::Start => SeekFrom::Start(offset as u64), + SeekMode::Current => SeekFrom::Current(offset), + SeekMode::End => SeekFrom::End(offset), + }) + }) + .map_err(|e| format!("failed to seek file with error: {e}")) + .map_err(Into::into) +} + +#[cfg(target_os = "android")] +fn get_metadata std::io::Result>( + metadata_fn: F, + webview: &Webview, + global_scope: &GlobalScope, + command_scope: &CommandScope, + path: SafeFilePath, + options: Option, +) -> CommandResult { + match path { + SafeFilePath::Url(url) => { + let (file, path) = resolve_file( + webview, + global_scope, + command_scope, + SafeFilePath::Url(url), + OpenOptions { + base: BaseOptions { base_dir: None }, + options: crate::OpenOptions { + read: true, + ..Default::default() + }, + }, + )?; + file.metadata().map_err(|e| { + format!( + "failed to get metadata of path: {} with error: {e}", + path.display() + ) + .into() + }) + } + SafeFilePath::Path(p) => get_fs_metadata( + metadata_fn, + webview, + global_scope, + command_scope, + SafeFilePath::Path(p), + options, + ), + } +} + +#[cfg(not(target_os = "android"))] +fn get_metadata std::io::Result>( + metadata_fn: F, + webview: &Webview, + global_scope: &GlobalScope, + command_scope: &CommandScope, + path: SafeFilePath, + options: Option, +) -> CommandResult { + get_fs_metadata( + metadata_fn, + webview, + global_scope, + command_scope, + path, + options, + ) +} + +fn get_fs_metadata std::io::Result>( + metadata_fn: F, + webview: &Webview, + global_scope: &GlobalScope, + command_scope: &CommandScope, + path: SafeFilePath, + options: Option, +) -> CommandResult { + let resolved_path = resolve_path( + webview, + global_scope, + command_scope, + path, + options.as_ref().and_then(|o| o.base_dir), + )?; + let metadata = metadata_fn(&resolved_path).map_err(|e| { + format!( + "failed to get metadata of path: {} with error: {e}", + resolved_path.display() + ) + })?; + Ok(metadata) +} + +#[tauri::command] +pub fn stat( + webview: Webview, + global_scope: GlobalScope, + command_scope: CommandScope, + path: SafeFilePath, + options: Option, +) -> CommandResult { + let metadata = get_metadata( + |p| std::fs::metadata(p), + &webview, + &global_scope, + &command_scope, + path, + options, + )?; + + Ok(get_stat(metadata)) +} + +#[tauri::command] +pub fn lstat( + webview: Webview, + global_scope: GlobalScope, + command_scope: CommandScope, + path: SafeFilePath, + options: Option, +) -> CommandResult { + let metadata = get_metadata( + |p| std::fs::symlink_metadata(p), + &webview, + &global_scope, + &command_scope, + path, + options, + )?; + Ok(get_stat(metadata)) +} + +#[tauri::command] +pub fn fstat(webview: Webview, rid: ResourceId) -> CommandResult { + let file = webview.resources_table().get::(rid)?; + let metadata = StdFileResource::with_lock(&file, |file| file.metadata()) + .map_err(|e| format!("failed to get metadata of file with error: {e}"))?; + Ok(get_stat(metadata)) +} + +#[tauri::command] +pub async fn truncate( + webview: Webview, + global_scope: GlobalScope, + command_scope: CommandScope, + path: SafeFilePath, + len: Option, + options: Option, +) -> CommandResult<()> { + let resolved_path = resolve_path( + &webview, + &global_scope, + &command_scope, + path, + options.as_ref().and_then(|o| o.base_dir), + )?; + let f = std::fs::OpenOptions::new() + .write(true) + .open(&resolved_path) + .map_err(|e| { + format!( + "failed to open file at path: {} with error: {e}", + resolved_path.display() + ) + })?; + f.set_len(len.unwrap_or(0)) + .map_err(|e| { + format!( + "failed to truncate file at path: {} with error: {e}", + resolved_path.display() + ) + }) + .map_err(Into::into) +} + +#[tauri::command] +pub async fn ftruncate( + webview: Webview, + rid: ResourceId, + len: Option, +) -> CommandResult<()> { + let file = webview.resources_table().get::(rid)?; + StdFileResource::with_lock(&file, |file| file.set_len(len.unwrap_or(0))) + .map_err(|e| format!("failed to truncate file with error: {e}")) + .map_err(Into::into) +} + +#[tauri::command] +pub async fn write( + webview: Webview, + rid: ResourceId, + data: Vec, +) -> CommandResult { + let file = webview.resources_table().get::(rid)?; + StdFileResource::with_lock(&file, |mut file| file.write(&data)) + .map_err(|e| format!("failed to write bytes to file with error: {e}")) + .map_err(Into::into) +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WriteFileOptions { + #[serde(flatten)] + base: BaseOptions, + #[serde(default)] + append: bool, + #[serde(default = "default_create_value")] + create: bool, + #[serde(default)] + create_new: bool, + #[allow(unused)] + mode: Option, +} + +fn default_create_value() -> bool { + true +} + +#[tauri::command] +pub async fn write_file( + webview: Webview, + global_scope: GlobalScope, + command_scope: CommandScope, + request: tauri::ipc::Request<'_>, +) -> CommandResult<()> { + let data = match request.body() { + tauri::ipc::InvokeBody::Raw(data) => Cow::Borrowed(data), + tauri::ipc::InvokeBody::Json(serde_json::Value::Array(data)) => Cow::Owned( + data.iter() + .flat_map(|v| v.as_number().and_then(|v| v.as_u64().map(|v| v as u8))) + .collect(), + ), + _ => return Err(anyhow::anyhow!("unexpected invoke body").into()), + }; + + let path = request + .headers() + .get("path") + .ok_or_else(|| anyhow::anyhow!("missing file path").into()) + .and_then(|p| { + percent_encoding::percent_decode(p.as_ref()) + .decode_utf8() + .map_err(|_| anyhow::anyhow!("path is not a valid UTF-8").into()) + }) + .and_then(|p| SafeFilePath::from_str(&p).map_err(CommandError::from))?; + let options: Option = request + .headers() + .get("options") + .and_then(|p| p.to_str().ok()) + .and_then(|opts| serde_json::from_str(opts).ok()); + + let (mut file, path) = resolve_file( + &webview, + &global_scope, + &command_scope, + path, + if let Some(opts) = options { + OpenOptions { + base: opts.base, + options: crate::OpenOptions { + read: false, + write: true, + create: opts.create, + truncate: !opts.append, + append: opts.append, + create_new: opts.create_new, + mode: opts.mode, + custom_flags: None, + }, + } + } else { + OpenOptions { + base: BaseOptions { base_dir: None }, + options: crate::OpenOptions { + read: false, + write: true, + truncate: true, + create: true, + create_new: false, + append: false, + mode: None, + custom_flags: None, + }, + } + }, + )?; + + file.write_all(&data) + .map_err(|e| { + format!( + "failed to write bytes to file at path: {} with error: {e}", + path.display() + ) + }) + .map_err(Into::into) +} + +// TODO, remove in v3, rely on `write_file` command instead +#[tauri::command] +pub async fn write_text_file( + webview: Webview, + global_scope: GlobalScope, + command_scope: CommandScope, + request: tauri::ipc::Request<'_>, +) -> CommandResult<()> { + write_file(webview, global_scope, command_scope, request).await +} + +#[tauri::command] +pub fn exists( + webview: Webview, + global_scope: GlobalScope, + command_scope: CommandScope, + path: SafeFilePath, + options: Option, +) -> CommandResult { + let resolved_path = resolve_path( + &webview, + &global_scope, + &command_scope, + path, + options.as_ref().and_then(|o| o.base_dir), + )?; + Ok(resolved_path.exists()) +} + +#[tauri::command] +pub async fn size( + webview: Webview, + global_scope: GlobalScope, + command_scope: CommandScope, + path: SafeFilePath, + options: Option, +) -> CommandResult { + let resolved_path = resolve_path( + &webview, + &global_scope, + &command_scope, + path, + options.as_ref().and_then(|o| o.base_dir), + )?; + + let metadata = resolved_path.metadata()?; + + if metadata.is_file() { + Ok(metadata.len()) + } else { + let size = get_dir_size(&resolved_path).map_err(|e| { + format!( + "failed to get size at path: {} with error: {e}", + resolved_path.display() + ) + })?; + + Ok(size) + } +} + +fn get_dir_size(path: &PathBuf) -> CommandResult { + let mut size = 0; + + for entry in std::fs::read_dir(path)? { + let entry = entry?; + let metadata = entry.metadata()?; + + if metadata.is_file() { + size += metadata.len(); + } else if metadata.is_dir() { + size += get_dir_size(&entry.path())?; + } + } + + Ok(size) +} + +#[cfg(not(target_os = "android"))] +pub fn resolve_file( + webview: &Webview, + global_scope: &GlobalScope, + command_scope: &CommandScope, + path: SafeFilePath, + open_options: OpenOptions, +) -> CommandResult<(File, PathBuf)> { + resolve_file_in_fs(webview, global_scope, command_scope, path, open_options) +} + +fn resolve_file_in_fs( + webview: &Webview, + global_scope: &GlobalScope, + command_scope: &CommandScope, + path: SafeFilePath, + open_options: OpenOptions, +) -> CommandResult<(File, PathBuf)> { + let path = resolve_path( + webview, + global_scope, + command_scope, + path, + open_options.base.base_dir, + )?; + + let file = std::fs::OpenOptions::from(open_options.options) + .open(&path) + .map_err(|e| { + format!( + "failed to open file at path: {} with error: {e}", + path.display() + ) + })?; + Ok((file, path)) +} + +#[cfg(target_os = "android")] +pub fn resolve_file( + webview: &Webview, + global_scope: &GlobalScope, + command_scope: &CommandScope, + path: SafeFilePath, + open_options: OpenOptions, +) -> CommandResult<(File, PathBuf)> { + use crate::FsExt; + + match path { + SafeFilePath::Url(url) => { + let path = url.as_str().into(); + let file = webview + .fs() + .open(SafeFilePath::Url(url), open_options.options)?; + Ok((file, path)) + } + SafeFilePath::Path(path) => resolve_file_in_fs( + webview, + global_scope, + command_scope, + SafeFilePath::Path(path), + open_options, + ), + } +} + +pub fn resolve_path( + webview: &Webview, + global_scope: &GlobalScope, + command_scope: &CommandScope, + path: SafeFilePath, + base_dir: Option, +) -> CommandResult { + let path = path.into_path()?; + let path = if let Some(base_dir) = base_dir { + webview.path().resolve(&path, base_dir)? + } else { + path + }; + + let fs_scope = webview.state::(); + + let scope = tauri::scope::fs::Scope::new( + webview, + &FsScope::Scope { + allow: global_scope + .allows() + .iter() + .filter_map(|e| e.path.clone()) + .chain(command_scope.allows().iter().filter_map(|e| e.path.clone())) + .collect(), + deny: global_scope + .denies() + .iter() + .filter_map(|e| e.path.clone()) + .chain(command_scope.denies().iter().filter_map(|e| e.path.clone())) + .collect(), + require_literal_leading_dot: fs_scope.require_literal_leading_dot, + }, + )?; + + let require_literal_leading_dot = fs_scope.require_literal_leading_dot.unwrap_or(cfg!(unix)); + + if is_forbidden(&fs_scope.scope, &path, require_literal_leading_dot) + || is_forbidden(&scope, &path, require_literal_leading_dot) + { + return Err(CommandError::Plugin(Error::PathForbidden(path))); + } + + if fs_scope.scope.is_allowed(&path) || scope.is_allowed(&path) { + Ok(path) + } else { + Err(CommandError::Plugin(Error::PathForbidden(path))) + } +} + +fn is_forbidden>( + scope: &tauri::fs::Scope, + path: P, + require_literal_leading_dot: bool, +) -> bool { + let path = path.as_ref(); + let path = if path.is_symlink() { + match std::fs::read_link(path) { + Ok(p) => p, + Err(_) => return false, + } + } else { + path.to_path_buf() + }; + let path = if !path.exists() { + crate::Result::Ok(path) + } else { + std::fs::canonicalize(path).map_err(Into::into) + }; + + if let Ok(path) = path { + let path: PathBuf = path.components().collect(); + scope.forbidden_patterns().iter().any(|p| { + p.matches_path_with( + &path, + glob::MatchOptions { + // this is needed so `/dir/*` doesn't match files within subdirectories such as `/dir/subdir/file.txt` + // see: + require_literal_separator: true, + require_literal_leading_dot, + ..Default::default() + }, + ) + }) + } else { + false + } +} + +struct StdFileResource(Mutex); + +impl StdFileResource { + fn new(file: File) -> Self { + Self(Mutex::new(file)) + } + + fn with_lock R>(&self, mut f: F) -> R { + let file = self.0.lock().unwrap(); + f(&file) + } +} + +impl Resource for StdFileResource {} + +/// Same as [std::io::Lines] but with bytes +struct LinesBytes(T); + +impl Iterator for LinesBytes { + type Item = std::io::Result>; + + fn next(&mut self) -> Option>> { + let mut buf = Vec::new(); + match self.0.read_until(b'\n', &mut buf) { + Ok(0) => None, + Ok(_n) => { + if buf.last() == Some(&b'\n') { + buf.pop(); + if buf.last() == Some(&b'\r') { + buf.pop(); + } + } + Some(Ok(buf)) + } + Err(e) => Some(Err(e)), + } + } +} + +struct StdLinesResource(Mutex>>); + +impl StdLinesResource { + fn new(lines: BufReader) -> Self { + Self(Mutex::new(LinesBytes(lines))) + } + + fn with_lock>) -> R>(&self, mut f: F) -> R { + let mut lines = self.0.lock().unwrap(); + f(&mut lines) + } +} + +impl Resource for StdLinesResource {} + +// taken from deno source code: https://github.com/denoland/deno/blob/ffffa2f7c44bd26aec5ae1957e0534487d099f48/runtime/ops/fs.rs#L913 +#[inline] +fn to_msec(maybe_time: std::result::Result) -> Option { + match maybe_time { + Ok(time) => { + let msec = time + .duration_since(UNIX_EPOCH) + .map(|t| t.as_millis() as u64) + .unwrap_or_else(|err| err.duration().as_millis() as u64); + Some(msec) + } + Err(_) => None, + } +} + +// taken from deno source code: https://github.com/denoland/deno/blob/ffffa2f7c44bd26aec5ae1957e0534487d099f48/runtime/ops/fs.rs#L926 +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct FileInfo { + is_file: bool, + is_directory: bool, + is_symlink: bool, + size: u64, + // In milliseconds, like JavaScript. Available on both Unix or Windows. + mtime: Option, + atime: Option, + birthtime: Option, + readonly: bool, + // Following are only valid under Windows. + file_attribues: Option, + // Following are only valid under Unix. + dev: Option, + ino: Option, + mode: Option, + nlink: Option, + uid: Option, + gid: Option, + rdev: Option, + blksize: Option, + blocks: Option, +} + +// taken from deno source code: https://github.com/denoland/deno/blob/ffffa2f7c44bd26aec5ae1957e0534487d099f48/runtime/ops/fs.rs#L950 +#[inline(always)] +fn get_stat(metadata: std::fs::Metadata) -> FileInfo { + // Unix stat member (number types only). 0 if not on unix. + macro_rules! usm { + ($member:ident) => {{ + #[cfg(unix)] + { + Some(metadata.$member()) + } + #[cfg(not(unix))] + { + None + } + }}; + } + + #[cfg(unix)] + use std::os::unix::fs::MetadataExt; + #[cfg(windows)] + use std::os::windows::fs::MetadataExt; + FileInfo { + is_file: metadata.is_file(), + is_directory: metadata.is_dir(), + is_symlink: metadata.file_type().is_symlink(), + size: metadata.len(), + // In milliseconds, like JavaScript. Available on both Unix or Windows. + mtime: to_msec(metadata.modified()), + atime: to_msec(metadata.accessed()), + birthtime: to_msec(metadata.created()), + readonly: metadata.permissions().readonly(), + // Following are only valid under Windows. + #[cfg(windows)] + file_attribues: Some(metadata.file_attributes()), + #[cfg(not(windows))] + file_attribues: None, + // Following are only valid under Unix. + dev: usm!(dev), + ino: usm!(ino), + mode: usm!(mode), + nlink: usm!(nlink), + uid: usm!(uid), + gid: usm!(gid), + rdev: usm!(rdev), + blksize: usm!(blksize), + blocks: usm!(blocks), + } +} + +#[cfg(test)] +mod test { + use std::io::{BufRead, BufReader}; + + use super::LinesBytes; + + #[test] + fn safe_file_path_parse() { + use super::SafeFilePath; + + assert!(matches!( + serde_json::from_str::("\"C:/Users\""), + Ok(SafeFilePath::Path(_)) + )); + assert!(matches!( + serde_json::from_str::("\"file:///C:/Users\""), + Ok(SafeFilePath::Url(_)) + )); + } + + #[test] + fn test_lines_bytes() { + let base = String::from("line 1\nline2\nline 3\nline 4"); + let bytes = base.as_bytes(); + + let string1 = base.lines().collect::(); + let string2 = BufReader::new(bytes) + .lines() + .map_while(Result::ok) + .collect::(); + let string3 = LinesBytes(BufReader::new(bytes)) + .flatten() + .flat_map(String::from_utf8) + .collect::(); + + assert_eq!(string1, string2); + assert_eq!(string1, string3); + assert_eq!(string2, string3); + } +} diff --git a/packages/kbot/gui/app/plugins/fs/src/config.rs b/packages/kbot/gui/app/plugins/fs/src/config.rs new file mode 100644 index 00000000..db3bae45 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/src/config.rs @@ -0,0 +1,19 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::Deserialize; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct Config { + /// Whether or not paths that contain components that start with a `.` + /// will require that `.` appears literally in the pattern; `*`, `?`, `**`, + /// or `[...]` will not match. This is useful because such files are + /// conventionally considered hidden on Unix systems and it might be + /// desirable to skip them when listing files. + /// + /// Defaults to `true` on Unix systems and `false` on Windows + // dotfiles are not supposed to be exposed by default on unix + pub require_literal_leading_dot: Option, +} diff --git a/packages/kbot/gui/app/plugins/fs/src/desktop.rs b/packages/kbot/gui/app/plugins/fs/src/desktop.rs new file mode 100644 index 00000000..477c0537 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/src/desktop.rs @@ -0,0 +1,35 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::PathBuf; + +use tauri::{AppHandle, Runtime}; + +use crate::{FilePath, OpenOptions}; + +pub struct Fs(pub(crate) AppHandle); + +fn path_or_err>(p: P) -> std::io::Result { + match p.into() { + FilePath::Path(p) => Ok(p), + FilePath::Url(u) if u.scheme() == "file" => u + .to_file_path() + .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid file URL")), + FilePath::Url(_) => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "cannot use a URL to load files on desktop and iOS", + )), + } +} + +impl Fs { + pub fn open>( + &self, + path: P, + opts: OpenOptions, + ) -> std::io::Result { + let path = path_or_err(path)?; + std::fs::OpenOptions::from(opts).open(path) + } +} diff --git a/packages/kbot/gui/app/plugins/fs/src/error.rs b/packages/kbot/gui/app/plugins/fs/src/error.rs new file mode 100644 index 00000000..0c98e83f --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/src/error.rs @@ -0,0 +1,43 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::PathBuf; + +use serde::{Serialize, Serializer}; + +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum Error { + #[error(transparent)] + Json(#[from] serde_json::Error), + #[error(transparent)] + Tauri(#[from] tauri::Error), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error("forbidden path: {0}")] + PathForbidden(PathBuf), + /// Invalid glob pattern. + #[error("invalid glob pattern: {0}")] + GlobPattern(#[from] glob::PatternError), + /// Watcher error. + #[cfg(feature = "watch")] + #[error(transparent)] + Watch(#[from] notify::Error), + #[cfg(target_os = "android")] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), + #[error("URL is not a valid path")] + InvalidPathUrl, + #[error("Unsafe PathBuf: {0}")] + UnsafePathBuf(&'static str), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/packages/kbot/gui/app/plugins/fs/src/file_path.rs b/packages/kbot/gui/app/plugins/fs/src/file_path.rs new file mode 100644 index 00000000..6316a248 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/src/file_path.rs @@ -0,0 +1,308 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{ + convert::Infallible, + path::{Path, PathBuf}, + str::FromStr, +}; + +use serde::Serialize; +use tauri::path::SafePathBuf; + +use crate::{Error, Result}; + +/// Represents either a filesystem path or a URI pointing to a file +/// such as `file://` URIs or Android `content://` URIs. +#[derive(Debug, Serialize, Clone)] +#[serde(untagged)] +pub enum FilePath { + /// `file://` URIs or Android `content://` URIs. + Url(url::Url), + /// Regular [`PathBuf`] + Path(PathBuf), +} + +/// Represents either a safe filesystem path or a URI pointing to a file +/// such as `file://` URIs or Android `content://` URIs. +#[derive(Debug, Clone, Serialize)] +pub enum SafeFilePath { + /// `file://` URIs or Android `content://` URIs. + Url(url::Url), + /// Safe [`PathBuf`], see [`SafePathBuf``]. + Path(SafePathBuf), +} + +impl FilePath { + /// Get a reference to the contained [`Path`] if the variant is [`FilePath::Path`]. + /// + /// Use [`FilePath::into_path`] to try to convert the [`FilePath::Url`] variant as well. + #[inline] + pub fn as_path(&self) -> Option<&Path> { + match self { + Self::Url(_) => None, + Self::Path(p) => Some(p), + } + } + + /// Try to convert into [`PathBuf`] if possible. + /// + /// This calls [`Url::to_file_path`](url::Url::to_file_path) if the variant is [`FilePath::Url`], + /// otherwise returns the contained [PathBuf] as is. + #[inline] + pub fn into_path(self) -> Result { + match self { + Self::Url(url) => url.to_file_path().map_err(|_| Error::InvalidPathUrl), + Self::Path(p) => Ok(p), + } + } + + /// Takes the contained [`PathBuf`] if the variant is [`FilePath::Path`], + /// and when possible, converts Windows UNC paths to regular paths. + #[inline] + pub fn simplified(self) -> Self { + match self { + Self::Url(url) => Self::Url(url), + Self::Path(p) => Self::Path(dunce::simplified(&p).to_path_buf()), + } + } +} + +impl SafeFilePath { + /// Get a reference to the contained [`Path`] if the variant is [`SafeFilePath::Path`]. + /// + /// Use [`SafeFilePath::into_path`] to try to convert the [`SafeFilePath::Url`] variant as well. + #[inline] + pub fn as_path(&self) -> Option<&Path> { + match self { + Self::Url(_) => None, + Self::Path(p) => Some(p.as_ref()), + } + } + + /// Try to convert into [`PathBuf`] if possible. + /// + /// This calls [`Url::to_file_path`](url::Url::to_file_path) if the variant is [`SafeFilePath::Url`], + /// otherwise returns the contained [PathBuf] as is. + #[inline] + pub fn into_path(self) -> Result { + match self { + Self::Url(url) => url.to_file_path().map_err(|_| Error::InvalidPathUrl), + Self::Path(p) => Ok(p.as_ref().to_owned()), + } + } + + /// Takes the contained [`PathBuf`] if the variant is [`SafeFilePath::Path`], + /// and when possible, converts Windows UNC paths to regular paths. + #[inline] + pub fn simplified(self) -> Self { + match self { + Self::Url(url) => Self::Url(url), + Self::Path(p) => { + // Safe to unwrap since it was a safe file path already + Self::Path(SafePathBuf::new(dunce::simplified(p.as_ref()).to_path_buf()).unwrap()) + } + } + } +} + +impl std::fmt::Display for FilePath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Url(u) => u.fmt(f), + Self::Path(p) => p.display().fmt(f), + } + } +} + +impl std::fmt::Display for SafeFilePath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Url(u) => u.fmt(f), + Self::Path(p) => p.display().fmt(f), + } + } +} + +impl<'de> serde::Deserialize<'de> for FilePath { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct FilePathVisitor; + + impl serde::de::Visitor<'_> for FilePathVisitor { + type Value = FilePath; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string representing an file URL or a path") + } + + fn visit_str(self, s: &str) -> std::result::Result + where + E: serde::de::Error, + { + FilePath::from_str(s).map_err(|e| { + serde::de::Error::invalid_value( + serde::de::Unexpected::Str(s), + &e.to_string().as_str(), + ) + }) + } + } + + deserializer.deserialize_str(FilePathVisitor) + } +} + +impl<'de> serde::Deserialize<'de> for SafeFilePath { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct SafeFilePathVisitor; + + impl serde::de::Visitor<'_> for SafeFilePathVisitor { + type Value = SafeFilePath; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string representing an file URL or a path") + } + + fn visit_str(self, s: &str) -> std::result::Result + where + E: serde::de::Error, + { + SafeFilePath::from_str(s).map_err(|e| { + serde::de::Error::invalid_value( + serde::de::Unexpected::Str(s), + &e.to_string().as_str(), + ) + }) + } + } + + deserializer.deserialize_str(SafeFilePathVisitor) + } +} + +impl FromStr for FilePath { + type Err = Infallible; + fn from_str(s: &str) -> std::result::Result { + if let Ok(url) = url::Url::from_str(s) { + if url.scheme().len() != 1 { + return Ok(Self::Url(url)); + } + } + Ok(Self::Path(PathBuf::from(s))) + } +} + +impl FromStr for SafeFilePath { + type Err = Error; + fn from_str(s: &str) -> Result { + if let Ok(url) = url::Url::from_str(s) { + if url.scheme().len() != 1 { + return Ok(Self::Url(url)); + } + } + + SafePathBuf::new(s.into()) + .map(SafeFilePath::Path) + .map_err(Error::UnsafePathBuf) + } +} + +impl From for FilePath { + fn from(value: PathBuf) -> Self { + Self::Path(value) + } +} + +impl TryFrom for SafeFilePath { + type Error = Error; + fn try_from(value: PathBuf) -> Result { + SafePathBuf::new(value) + .map(SafeFilePath::Path) + .map_err(Error::UnsafePathBuf) + } +} + +impl From<&Path> for FilePath { + fn from(value: &Path) -> Self { + Self::Path(value.to_owned()) + } +} + +impl TryFrom<&Path> for SafeFilePath { + type Error = Error; + fn try_from(value: &Path) -> Result { + SafePathBuf::new(value.to_path_buf()) + .map(SafeFilePath::Path) + .map_err(Error::UnsafePathBuf) + } +} + +impl From<&PathBuf> for FilePath { + fn from(value: &PathBuf) -> Self { + Self::Path(value.to_owned()) + } +} + +impl TryFrom<&PathBuf> for SafeFilePath { + type Error = Error; + fn try_from(value: &PathBuf) -> Result { + SafePathBuf::new(value.to_owned()) + .map(SafeFilePath::Path) + .map_err(Error::UnsafePathBuf) + } +} + +impl From for FilePath { + fn from(value: url::Url) -> Self { + Self::Url(value) + } +} + +impl From for SafeFilePath { + fn from(value: url::Url) -> Self { + Self::Url(value) + } +} + +impl TryFrom for PathBuf { + type Error = Error; + fn try_from(value: FilePath) -> Result { + value.into_path() + } +} + +impl TryFrom for PathBuf { + type Error = Error; + fn try_from(value: SafeFilePath) -> Result { + value.into_path() + } +} + +impl From for FilePath { + fn from(value: SafeFilePath) -> Self { + match value { + SafeFilePath::Url(url) => FilePath::Url(url), + SafeFilePath::Path(p) => FilePath::Path(p.as_ref().to_owned()), + } + } +} + +impl TryFrom for SafeFilePath { + type Error = Error; + + fn try_from(value: FilePath) -> Result { + match value { + FilePath::Url(url) => Ok(SafeFilePath::Url(url)), + FilePath::Path(p) => SafePathBuf::new(p) + .map(SafeFilePath::Path) + .map_err(Error::UnsafePathBuf), + } + } +} diff --git a/packages/kbot/gui/app/plugins/fs/src/lib.rs b/packages/kbot/gui/app/plugins/fs/src/lib.rs new file mode 100644 index 00000000..bdc6b170 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/src/lib.rs @@ -0,0 +1,461 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Access the file system. + +#![doc( + html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", + html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" +)] + +use std::io::Read; + +use serde::Deserialize; +use tauri::{ + ipc::ScopeObject, + plugin::{Builder as PluginBuilder, TauriPlugin}, + utils::{acl::Value, config::FsScope}, + AppHandle, DragDropEvent, Manager, RunEvent, Runtime, WindowEvent, +}; + +mod commands; +mod config; +#[cfg(not(target_os = "android"))] +mod desktop; +mod error; +mod file_path; +#[cfg(target_os = "android")] +mod mobile; +#[cfg(target_os = "android")] +mod models; +mod scope; +#[cfg(feature = "watch")] +mod watcher; + +#[cfg(not(target_os = "android"))] +pub use desktop::Fs; +#[cfg(target_os = "android")] +pub use mobile::Fs; + +pub use error::Error; + +pub use file_path::FilePath; +pub use file_path::SafeFilePath; + +type Result = std::result::Result; + +#[derive(Debug, Default, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OpenOptions { + #[serde(default = "default_true")] + read: bool, + #[serde(default)] + write: bool, + #[serde(default)] + append: bool, + #[serde(default)] + truncate: bool, + #[serde(default)] + create: bool, + #[serde(default)] + create_new: bool, + #[serde(default)] + #[allow(unused)] + mode: Option, + #[serde(default)] + #[allow(unused)] + custom_flags: Option, +} + +fn default_true() -> bool { + true +} + +impl From for std::fs::OpenOptions { + fn from(open_options: OpenOptions) -> Self { + let mut opts = std::fs::OpenOptions::new(); + + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + if let Some(mode) = open_options.mode { + opts.mode(mode); + } + if let Some(flags) = open_options.custom_flags { + opts.custom_flags(flags); + } + } + + opts.read(open_options.read) + .write(open_options.write) + .create(open_options.create) + .append(open_options.append) + .truncate(open_options.truncate) + .create_new(open_options.create_new); + + opts + } +} + +impl OpenOptions { + /// Creates a blank new set of options ready for configuration. + /// + /// All options are initially set to `false`. + /// + /// # Examples + /// + /// ```no_run + /// use tauri_plugin_fs::OpenOptions; + /// + /// let mut options = OpenOptions::new(); + /// let file = options.read(true).open("foo.txt"); + /// ``` + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Sets the option for read access. + /// + /// This option, when true, will indicate that the file should be + /// `read`-able if opened. + /// + /// # Examples + /// + /// ```no_run + /// use tauri_plugin_fs::OpenOptions; + /// + /// let file = OpenOptions::new().read(true).open("foo.txt"); + /// ``` + pub fn read(&mut self, read: bool) -> &mut Self { + self.read = read; + self + } + + /// Sets the option for write access. + /// + /// This option, when true, will indicate that the file should be + /// `write`-able if opened. + /// + /// If the file already exists, any write calls on it will overwrite its + /// contents, without truncating it. + /// + /// # Examples + /// + /// ```no_run + /// use tauri_plugin_fs::OpenOptions; + /// + /// let file = OpenOptions::new().write(true).open("foo.txt"); + /// ``` + pub fn write(&mut self, write: bool) -> &mut Self { + self.write = write; + self + } + + /// Sets the option for the append mode. + /// + /// This option, when true, means that writes will append to a file instead + /// of overwriting previous contents. + /// Note that setting `.write(true).append(true)` has the same effect as + /// setting only `.append(true)`. + /// + /// Append mode guarantees that writes will be positioned at the current end of file, + /// even when there are other processes or threads appending to the same file. This is + /// unlike [seek]\([SeekFrom]::[End]\(0)) followed by `write()`, which + /// has a race between seeking and writing during which another writer can write, with + /// our `write()` overwriting their data. + /// + /// Keep in mind that this does not necessarily guarantee that data appended by + /// different processes or threads does not interleave. The amount of data accepted a + /// single `write()` call depends on the operating system and file system. A + /// successful `write()` is allowed to write only part of the given data, so even if + /// you're careful to provide the whole message in a single call to `write()`, there + /// is no guarantee that it will be written out in full. If you rely on the filesystem + /// accepting the message in a single write, make sure that all data that belongs + /// together is written in one operation. This can be done by concatenating strings + /// before passing them to [`write()`]. + /// + /// If a file is opened with both read and append access, beware that after + /// opening, and after every write, the position for reading may be set at the + /// end of the file. So, before writing, save the current position (using + /// [Seek]::[stream_position]), and restore it before the next read. + /// + /// ## Note + /// + /// This function doesn't create the file if it doesn't exist. Use the + /// [`OpenOptions::create`] method to do so. + /// + /// [`write()`]: Write::write "io::Write::write" + /// [`flush()`]: Write::flush "io::Write::flush" + /// [stream_position]: Seek::stream_position "io::Seek::stream_position" + /// [seek]: Seek::seek "io::Seek::seek" + /// [Current]: SeekFrom::Current "io::SeekFrom::Current" + /// [End]: SeekFrom::End "io::SeekFrom::End" + /// + /// # Examples + /// + /// ```no_run + /// use tauri_plugin_fs::OpenOptions; + /// + /// let file = OpenOptions::new().append(true).open("foo.txt"); + /// ``` + pub fn append(&mut self, append: bool) -> &mut Self { + self.append = append; + self + } + + /// Sets the option for truncating a previous file. + /// + /// If a file is successfully opened with this option set it will truncate + /// the file to 0 length if it already exists. + /// + /// The file must be opened with write access for truncate to work. + /// + /// # Examples + /// + /// ```no_run + /// use tauri_plugin_fs::OpenOptions; + /// + /// let file = OpenOptions::new().write(true).truncate(true).open("foo.txt"); + /// ``` + pub fn truncate(&mut self, truncate: bool) -> &mut Self { + self.truncate = truncate; + self + } + + /// Sets the option to create a new file, or open it if it already exists. + /// + /// In order for the file to be created, [`OpenOptions::write`] or + /// [`OpenOptions::append`] access must be used. + /// + /// + /// # Examples + /// + /// ```no_run + /// use tauri_plugin_fs::OpenOptions; + /// + /// let file = OpenOptions::new().write(true).create(true).open("foo.txt"); + /// ``` + pub fn create(&mut self, create: bool) -> &mut Self { + self.create = create; + self + } + + /// Sets the option to create a new file, failing if it already exists. + /// + /// No file is allowed to exist at the target location, also no (dangling) symlink. In this + /// way, if the call succeeds, the file returned is guaranteed to be new. + /// If a file exists at the target location, creating a new file will fail with [`AlreadyExists`] + /// or another error based on the situation. See [`OpenOptions::open`] for a + /// non-exhaustive list of likely errors. + /// + /// This option is useful because it is atomic. Otherwise between checking + /// whether a file exists and creating a new one, the file may have been + /// created by another process (a TOCTOU race condition / attack). + /// + /// If `.create_new(true)` is set, [`.create()`] and [`.truncate()`] are + /// ignored. + /// + /// The file must be opened with write or append access in order to create + /// a new file. + /// + /// [`.create()`]: OpenOptions::create + /// [`.truncate()`]: OpenOptions::truncate + /// [`AlreadyExists`]: io::ErrorKind::AlreadyExists + /// + /// # Examples + /// + /// ```no_run + /// use tauri_plugin_fs::OpenOptions; + /// + /// let file = OpenOptions::new().write(true) + /// .create_new(true) + /// .open("foo.txt"); + /// ``` + pub fn create_new(&mut self, create_new: bool) -> &mut Self { + self.create_new = create_new; + self + } +} + +#[cfg(unix)] +impl std::os::unix::fs::OpenOptionsExt for OpenOptions { + fn custom_flags(&mut self, flags: i32) -> &mut Self { + self.custom_flags.replace(flags); + self + } + + fn mode(&mut self, mode: u32) -> &mut Self { + self.mode.replace(mode); + self + } +} + +impl OpenOptions { + #[cfg(target_os = "android")] + fn android_mode(&self) -> String { + let mut mode = String::new(); + + if self.read { + mode.push('r'); + } + if self.write { + mode.push('w'); + } + if self.truncate { + mode.push('t'); + } + if self.append { + mode.push('a'); + } + + mode + } +} + +impl Fs { + pub fn read_to_string>(&self, path: P) -> std::io::Result { + let mut s = String::new(); + self.open( + path, + OpenOptions { + read: true, + ..Default::default() + }, + )? + .read_to_string(&mut s)?; + Ok(s) + } + + pub fn read>(&self, path: P) -> std::io::Result> { + let mut buf = Vec::new(); + self.open( + path, + OpenOptions { + read: true, + ..Default::default() + }, + )? + .read_to_end(&mut buf)?; + Ok(buf) + } +} + +// implement ScopeObject here instead of in the scope module because it is also used on the build script +// and we don't want to add tauri as a build dependency +impl ScopeObject for scope::Entry { + type Error = Error; + fn deserialize( + app: &AppHandle, + raw: Value, + ) -> std::result::Result { + let path = serde_json::from_value(raw.into()).map(|raw| match raw { + scope::EntryRaw::Value(path) => path, + scope::EntryRaw::Object { path } => path, + })?; + + match app.path().parse(path) { + Ok(path) => Ok(Self { path: Some(path) }), + #[cfg(not(target_os = "android"))] + Err(tauri::Error::UnknownPath) => Ok(Self { path: None }), + Err(err) => Err(err.into()), + } + } +} + +pub(crate) struct Scope { + pub(crate) scope: tauri::fs::Scope, + pub(crate) require_literal_leading_dot: Option, +} + +pub trait FsExt { + fn fs_scope(&self) -> tauri::fs::Scope; + fn try_fs_scope(&self) -> Option; + + /// Cross platform file system APIs that also support manipulating Android files. + fn fs(&self) -> &Fs; +} + +impl> FsExt for T { + fn fs_scope(&self) -> tauri::fs::Scope { + self.state::().scope.clone() + } + + fn try_fs_scope(&self) -> Option { + self.try_state::().map(|s| s.scope.clone()) + } + + fn fs(&self) -> &Fs { + self.state::>().inner() + } +} + +pub fn init() -> TauriPlugin> { + PluginBuilder::>::new("fs") + .invoke_handler(tauri::generate_handler![ + commands::create, + commands::open, + commands::copy_file, + commands::mkdir, + commands::read_dir, + commands::read, + commands::read_file, + commands::read_text_file, + commands::read_text_file_lines, + commands::read_text_file_lines_next, + commands::remove, + commands::rename, + commands::seek, + commands::stat, + commands::lstat, + commands::fstat, + commands::truncate, + commands::ftruncate, + commands::write, + commands::write_file, + commands::write_text_file, + commands::exists, + commands::size, + #[cfg(feature = "watch")] + watcher::watch, + ]) + .setup(|app, api| { + let scope = Scope { + require_literal_leading_dot: api + .config() + .as_ref() + .and_then(|c| c.require_literal_leading_dot), + scope: tauri::fs::Scope::new(app, &FsScope::default())?, + }; + + #[cfg(target_os = "android")] + { + let fs = mobile::init(app, api)?; + app.manage(fs); + } + #[cfg(not(target_os = "android"))] + app.manage(Fs(app.clone())); + + app.manage(scope); + Ok(()) + }) + .on_event(|app, event| { + if let RunEvent::WindowEvent { + label: _, + event: WindowEvent::DragDrop(DragDropEvent::Drop { paths, position: _ }), + .. + } = event + { + let scope = app.fs_scope(); + for path in paths { + if path.is_file() { + let _ = scope.allow_file(path); + } else { + let _ = scope.allow_directory(path, true); + } + } + } + }) + .build() +} diff --git a/packages/kbot/gui/app/plugins/fs/src/mobile.rs b/packages/kbot/gui/app/plugins/fs/src/mobile.rs new file mode 100644 index 00000000..06422be6 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/src/mobile.rs @@ -0,0 +1,96 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::de::DeserializeOwned; +use tauri::{ + plugin::{PluginApi, PluginHandle}, + AppHandle, Runtime, +}; + +use crate::{models::*, FilePath, OpenOptions}; + +#[cfg(target_os = "android")] +const PLUGIN_IDENTIFIER: &str = "com.plugin.fs"; + +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_fs); + +// initializes the Kotlin or Swift plugin classes +pub fn init( + _app: &AppHandle, + api: PluginApi, +) -> crate::Result> { + #[cfg(target_os = "android")] + let handle = api + .register_android_plugin(PLUGIN_IDENTIFIER, "FsPlugin") + .unwrap(); + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_android - intent - send)?; + Ok(Fs(handle)) +} + +/// Access to the android-intent-send APIs. +pub struct Fs(PluginHandle); + +impl Fs { + pub fn open>( + &self, + path: P, + opts: OpenOptions, + ) -> std::io::Result { + match path.into() { + FilePath::Url(u) => self + .resolve_content_uri(u.to_string(), opts.android_mode()) + .map_err(|e| { + std::io::Error::new( + std::io::ErrorKind::Other, + format!("failed to open file: {e}"), + ) + }), + FilePath::Path(p) => { + // tauri::utils::platform::resources_dir() returns a PathBuf with the Android asset URI prefix + // we must resolve that file with the Android API + if p.strip_prefix(tauri::utils::platform::ANDROID_ASSET_PROTOCOL_URI_PREFIX) + .is_ok() + { + self.resolve_content_uri(p.to_string_lossy(), opts.android_mode()) + .map_err(|e| { + std::io::Error::new( + std::io::ErrorKind::Other, + format!("failed to open file: {e}"), + ) + }) + } else { + std::fs::OpenOptions::from(opts).open(p) + } + } + } + } + + #[cfg(target_os = "android")] + fn resolve_content_uri( + &self, + uri: impl Into, + mode: impl Into, + ) -> crate::Result { + #[cfg(target_os = "android")] + { + let result = self.0.run_mobile_plugin::( + "getFileDescriptor", + GetFileDescriptorPayload { + uri: uri.into(), + mode: mode.into(), + }, + )?; + if let Some(fd) = result.fd { + Ok(unsafe { + use std::os::fd::FromRawFd; + std::fs::File::from_raw_fd(fd) + }) + } else { + todo!() + } + } + } +} diff --git a/packages/kbot/gui/app/plugins/fs/src/models.rs b/packages/kbot/gui/app/plugins/fs/src/models.rs new file mode 100644 index 00000000..b9edc2cb --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/src/models.rs @@ -0,0 +1,18 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GetFileDescriptorPayload { + pub uri: String, + pub mode: String, +} + +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GetFileDescriptorResponse { + pub fd: Option, +} diff --git a/packages/kbot/gui/app/plugins/fs/src/scope.rs b/packages/kbot/gui/app/plugins/fs/src/scope.rs new file mode 100644 index 00000000..7914706a --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/src/scope.rs @@ -0,0 +1,19 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::PathBuf; + +use serde::Deserialize; + +#[derive(Debug)] +pub struct Entry { + pub path: Option, +} + +#[derive(Deserialize)] +#[serde(untagged)] +pub(crate) enum EntryRaw { + Value(PathBuf), + Object { path: PathBuf }, +} diff --git a/packages/kbot/gui/app/plugins/fs/src/watcher.rs b/packages/kbot/gui/app/plugins/fs/src/watcher.rs new file mode 100644 index 00000000..89446b88 --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/src/watcher.rs @@ -0,0 +1,103 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher}; +use notify_debouncer_full::{new_debouncer, DebouncedEvent, Debouncer, RecommendedCache}; +use serde::Deserialize; +use tauri::{ + ipc::{Channel, CommandScope, GlobalScope}, + path::BaseDirectory, + Manager, Resource, ResourceId, Runtime, Webview, +}; + +use std::time::Duration; + +use crate::{ + commands::{resolve_path, CommandResult}, + scope::Entry, + SafeFilePath, +}; + +#[allow(unused)] +enum WatcherKind { + Debouncer(Debouncer), + Watcher(RecommendedWatcher), +} + +impl Resource for WatcherKind {} + +#[derive(Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WatchOptions { + base_dir: Option, + #[serde(default)] + recursive: bool, + delay_ms: Option, +} + +#[tauri::command] +pub fn watch( + webview: Webview, + paths: Vec, + options: WatchOptions, + on_event: Channel, + global_scope: GlobalScope, + command_scope: CommandScope, +) -> CommandResult { + let resolved_paths = paths + .into_iter() + .map(|path| { + resolve_path( + &webview, + &global_scope, + &command_scope, + path, + options.base_dir, + ) + }) + .collect::>>()?; + + let recursive_mode = if options.recursive { + RecursiveMode::Recursive + } else { + RecursiveMode::NonRecursive + }; + + let watcher_kind = if let Some(delay) = options.delay_ms { + let mut debouncer = new_debouncer( + Duration::from_millis(delay), + None, + move |events: Result, Vec>| { + if let Ok(events) = events { + for event in events { + // TODO: Should errors be emitted too? + let _ = on_event.send(event.event); + } + } + }, + )?; + for path in &resolved_paths { + debouncer.watch(path, recursive_mode)?; + } + WatcherKind::Debouncer(debouncer) + } else { + let mut watcher = RecommendedWatcher::new( + move |event| { + if let Ok(event) = event { + // TODO: Should errors be emitted too? + let _ = on_event.send(event); + } + }, + Config::default(), + )?; + for path in &resolved_paths { + watcher.watch(path, recursive_mode)?; + } + WatcherKind::Watcher(watcher) + }; + + let rid = webview.resources_table().add(watcher_kind); + + Ok(rid) +} diff --git a/packages/kbot/gui/app/plugins/fs/tsconfig.json b/packages/kbot/gui/app/plugins/fs/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/fs/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/geolocation/CHANGELOG.md b/packages/kbot/gui/app/plugins/geolocation/CHANGELOG.md new file mode 100644 index 00000000..b66edd33 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/CHANGELOG.md @@ -0,0 +1,65 @@ +# Changelog + +## \[2.3.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.2.5] + +- [`f634e524`](https://github.com/tauri-apps/plugins-workspace/commit/f634e5248ebe428f8305a59f74c13fc15147fb8e) This is an "empty" release to update the plugins' source files on crates.io and docs.rs. This should fix docs.rs build failures for projects using tauri plugins as dependencies. + +## \[2.2.4] + +- [`a1b3fa27`](https://github.com/tauri-apps/plugins-workspace/commit/a1b3fa27f11022c9b6622b4fab12d93239eb05de) ([#2515](https://github.com/tauri-apps/plugins-workspace/pull/2515) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Re-exported the `Geolocation`, `Haptics`, `Notification`, and `Os` structs so that they show up on docs.rs. + +## \[2.2.3] + +- [`406e6f48`](https://github.com/tauri-apps/plugins-workspace/commit/406e6f484cdc13d35c50fb949f7489ca9eeccc44) ([#2323](https://github.com/tauri-apps/plugins-workspace/pull/2323) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused build failures when the `haptics` or `geolocation` plugin was used without their `specta` feature flag enabled. + +## \[2.2.2] + +- [`c9c13a0f`](https://github.com/tauri-apps/plugins-workspace/commit/c9c13a0fe7cdaac223843f5ba33176252f8e22f5) ([#2316](https://github.com/tauri-apps/plugins-workspace/pull/2316) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) **Breaking change:** `specta` integration is now behind a `specta` feature flag like in Tauri. +- [`c9c13a0f`](https://github.com/tauri-apps/plugins-workspace/commit/c9c13a0fe7cdaac223843f5ba33176252f8e22f5) ([#2316](https://github.com/tauri-apps/plugins-workspace/pull/2316) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Unlock and widen `specta` version range to match Tauri. No API changes. + +## \[2.2.1] + +- [`fb67ab2b`](https://github.com/tauri-apps/plugins-workspace/commit/fb67ab2b926502bfc20d6b43fbdd156691ea8526) ([#2281](https://github.com/tauri-apps/plugins-workspace/pull/2281) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Added `specta-util` to fix a "dependency not found" compilation error. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.2] + +- [`60765694`](https://github.com/tauri-apps/plugins-workspace/commit/60765694f54875e22b8eb70b1d2e32dbf0c585c7) ([#1773](https://github.com/tauri-apps/plugins-workspace/pull/1773) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update API to match other plugins. + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Use `PermissionState` from the `tauri` crate, which now also includes a "prompt with rationale" variant for Android (returned when your app must explain to the user why it needs the permission). +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.2] + +- [`b9147758`](https://github.com/tauri-apps/plugins-workspace/commit/b914775898c2bee7ceb20bd17ee595005cd17a64) ([#1679](https://github.com/tauri-apps/plugins-workspace/pull/1679) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Explicitly set a minimum macOS version for the Swift package. + +## \[2.0.0-rc.1] + +- [`5d170a54`](https://github.com/tauri-apps/plugins-workspace/commit/5d170a5444982dcc14135f6f1fc3e5da359f0eb0) ([#1671](https://github.com/tauri-apps/plugins-workspace/pull/1671) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.3. + +### changes + +- [`6b079cfd`](https://github.com/tauri-apps/plugins-workspace/commit/6b079cfdd107c94abc2c7300f6af00bac3ff4040) ([#1649](https://github.com/tauri-apps/plugins-workspace/pull/1649) by [@ahqsoftwares](https://github.com/tauri-apps/plugins-workspace/../../ahqsoftwares)) Remove targetSdk from build.kts files as it is deprecated and will be removed from DSL v9.0 + +## \[2.0.0-rc.0] + +- [`9606089b`](https://github.com/tauri-apps/plugins-workspace/commit/9606089b2add4a17f80ed5a09d59ce94824bd672) ([#1599](https://github.com/tauri-apps/plugins-workspace/pull/1599)) Initial release. +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. diff --git a/packages/kbot/gui/app/plugins/geolocation/Cargo.toml b/packages/kbot/gui/app/plugins/geolocation/Cargo.toml new file mode 100644 index 00000000..95c52b0e --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "tauri-plugin-geolocation" +description = "Get and track the device's current position" +version = "2.3.0" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-geolocation" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] +targets = ["x86_64-linux-android"] + +[package.metadata.platforms.support] +windows = { level = "none", notes = "" } +linux = { level = "none", notes = "" } +macos = { level = "none", notes = "" } +android = { level = "full", notes = "" } +ios = { level = "full", notes = "" } + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } +specta = { workspace = true, optional = true } + +[target.'cfg(target_os = "ios")'.dependencies] +tauri = { workspace = true, features = ["wry"] } + +[features] +specta = ["dep:specta", "tauri/specta"] diff --git a/packages/kbot/gui/app/plugins/geolocation/LICENSE.spdx b/packages/kbot/gui/app/plugins/geolocation/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/geolocation/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/geolocation/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/geolocation/LICENSE_MIT b/packages/kbot/gui/app/plugins/geolocation/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/geolocation/README.md b/packages/kbot/gui/app/plugins/geolocation/README.md new file mode 100644 index 00000000..3de41ef7 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/README.md @@ -0,0 +1,173 @@ +![geolocation](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/geolocation/banner.png) + +This plugin provides APIs for getting and tracking the device's current position, including information about altitude, heading, and speed (if available). + +| Platform | Supported | +| -------- | --------- | +| Linux | x | +| Windows | x | +| macOS | x | +| Android | ✓ | +| iOS | ✓ | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-geolocation = "2.0.0" +# alternatively with Git: +tauri-plugin-geolocation = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + + + +```sh +pnpm add @tauri-apps/plugin-geolocation +# or +npm add @tauri-apps/plugin-geolocation +# or +yarn add @tauri-apps/plugin-geolocation +``` + +## Setting up + +### iOS + +Apple requires privacy descriptions to be specified in `Info.plist` for location information: + +- `NSLocationWhenInUseDescription` + +### Android + +This plugin automatically adds the following permissions to your `AndroidManifest.xml` file: + +```xml + + +``` + +If your app requires GPS functionality to function, **you** should add the following to your `AndroidManifest.xml` file: + +```xml + +``` + +The Google Play Store uses this property to decide whether it should show the app to devices without GPS capabilities. + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_geolocation::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Then, for instance, grant the plugin the permission to check or request permissions from the user and to read the device position + +`src-tauri/capabilities/default.json` + +```json + "permissions": [ + "core:default", + "geolocation:allow-check-permissions", + "geolocation:allow-request-permissions", + "geolocation:allow-get-current-position", + "geolocation:allow-watch-position", + ] +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { + checkPermissions, + requestPermissions, + getCurrentPosition, + watchPosition +} from '@tauri-apps/plugin-geolocation' + +let permissions = await checkPermissions() +if ( + permissions.location === 'prompt' + || permissions.location === 'prompt-with-rationale' +) { + permissions = await requestPermissions(['location']) +} + +if (permissions.location === 'granted') { + const pos = await getCurrentPosition() + + await watchPosition( + { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 }, + (pos) => { + console.log(pos) + } + ) +} +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Contributed By + + + + + + + + +
+ + CrabNebula + + + + Rescue.co + +
+ +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/geolocation/SECURITY.md b/packages/kbot/gui/app/plugins/geolocation/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/geolocation/android/.gitignore b/packages/kbot/gui/app/plugins/geolocation/android/.gitignore new file mode 100644 index 00000000..c0f21ec2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/android/.gitignore @@ -0,0 +1,2 @@ +/build +/.tauri diff --git a/packages/kbot/gui/app/plugins/geolocation/android/build.gradle.kts b/packages/kbot/gui/app/plugins/geolocation/android/build.gradle.kts new file mode 100644 index 00000000..18fba0f3 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/android/build.gradle.kts @@ -0,0 +1,45 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "app.tauri.geolocation" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.0") + implementation("com.google.android.material:material:1.7.0") + implementation("com.google.android.gms:play-services-location:21.3.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + implementation(project(":tauri-android")) +} diff --git a/packages/kbot/gui/app/plugins/geolocation/android/proguard-rules.pro b/packages/kbot/gui/app/plugins/geolocation/android/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/geolocation/android/settings.gradle b/packages/kbot/gui/app/plugins/geolocation/android/settings.gradle new file mode 100644 index 00000000..14a752e4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/android/settings.gradle @@ -0,0 +1,2 @@ +include ':tauri-android' +project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/packages/kbot/gui/app/plugins/geolocation/android/src/androidTest/java/ExampleInstrumentedTest.kt b/packages/kbot/gui/app/plugins/geolocation/android/src/androidTest/java/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..a301dc2d --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/android/src/androidTest/java/ExampleInstrumentedTest.kt @@ -0,0 +1,28 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.geolocation + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("app.tauri.geolocation", appContext.packageName) + } +} diff --git a/packages/kbot/gui/app/plugins/geolocation/android/src/main/AndroidManifest.xml b/packages/kbot/gui/app/plugins/geolocation/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..a47edd1c --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/android/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/kbot/gui/app/plugins/geolocation/android/src/main/java/Geolocation.kt b/packages/kbot/gui/app/plugins/geolocation/android/src/main/java/Geolocation.kt new file mode 100644 index 00000000..b16a4482 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/android/src/main/java/Geolocation.kt @@ -0,0 +1,148 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.geolocation + +import android.annotation.SuppressLint +import android.content.Context +import android.location.Location +import android.location.LocationManager +import android.os.SystemClock +import androidx.core.location.LocationManagerCompat +import app.tauri.Logger +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GoogleApiAvailability +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationCallback +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationResult +import com.google.android.gms.location.LocationServices +import com.google.android.gms.location.Priority + + +public class Geolocation(private val context: Context) { + private var fusedLocationClient: FusedLocationProviderClient? = null + private var locationCallback: LocationCallback? = null + + + fun isLocationServicesEnabled(): Boolean { + val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + return LocationManagerCompat.isLocationEnabled(lm) + } + + @SuppressWarnings("MissingPermission") + fun sendLocation(enableHighAccuracy: Boolean, successCallback: (location: Location) -> Unit, errorCallback: (error: String) -> Unit) { + val resultCode = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context); + if (resultCode == ConnectionResult.SUCCESS) { + val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + + if (this.isLocationServicesEnabled()) { + var networkEnabled = false + + try { + networkEnabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER) + } catch (_: Exception) { + Logger.error("isProviderEnabled failed") + } + + val lowPrio = if (networkEnabled) Priority.PRIORITY_BALANCED_POWER_ACCURACY else Priority.PRIORITY_LOW_POWER + val prio = if (enableHighAccuracy) Priority.PRIORITY_HIGH_ACCURACY else lowPrio + + Logger.error(prio.toString()) + + LocationServices + .getFusedLocationProviderClient(context) + .getCurrentLocation(prio, null) + .addOnFailureListener { e -> e.message?.let { errorCallback(it) } } + .addOnSuccessListener { location -> + if (location == null) { + errorCallback("Location unavailable.") + } else { + successCallback(location) + } + } + } else { + errorCallback("Location disabled.") + } + } else { + errorCallback("Google Play Services unavailable.") + } + } + + @SuppressLint("MissingPermission") + fun requestLocationUpdates(enableHighAccuracy: Boolean, timeout: Long, successCallback: (location: Location) -> Unit, errorCallback: (error: String) -> Unit) { + val resultCode = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context); + if (resultCode == ConnectionResult.SUCCESS) { + clearLocationUpdates() + fusedLocationClient = LocationServices.getFusedLocationProviderClient(context) + + val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + + if (this.isLocationServicesEnabled()) { + var networkEnabled = false + + try { + networkEnabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER) + } catch (_: Exception) { + Logger.error("isProviderEnabled failed") + } + + val lowPrio = if (networkEnabled) Priority.PRIORITY_BALANCED_POWER_ACCURACY else Priority.PRIORITY_LOW_POWER + val prio = if (enableHighAccuracy) Priority.PRIORITY_HIGH_ACCURACY else lowPrio + + Logger.error(prio.toString()) + + val locationRequest = LocationRequest.Builder(10000) + .setMaxUpdateDelayMillis(timeout) + .setMinUpdateIntervalMillis(5000) + .setPriority(prio) + .build() + + locationCallback = + object : LocationCallback() { + override fun onLocationResult(locationResult: LocationResult) { + val lastLocation = locationResult.lastLocation + if (lastLocation == null) { + errorCallback("Location unavailable.") + } else { + successCallback(lastLocation) + } + } + } + + fusedLocationClient?.requestLocationUpdates(locationRequest, locationCallback!!, null) + } else { + errorCallback("Location disabled.") + } + } else { + errorCallback("Google Play Services not available.") + } + } + + fun clearLocationUpdates() { + if (locationCallback != null) { + fusedLocationClient?.removeLocationUpdates(locationCallback!!) + locationCallback = null + } + } + + @SuppressLint("MissingPermission") + fun getLastLocation(maximumAge: Long): Location? { + var lastLoc: Location? = null + val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + + for (provider in lm.allProviders) { + val tmpLoc = lm.getLastKnownLocation(provider) + if (tmpLoc != null) { + val locationAge = SystemClock.elapsedRealtimeNanos() - tmpLoc.elapsedRealtimeNanos + val maxAgeNano = maximumAge * 1000000L + if (locationAge <= maxAgeNano && (lastLoc == null || lastLoc.elapsedRealtimeNanos > tmpLoc.elapsedRealtimeNanos)) { + lastLoc = tmpLoc + } + } + } + + return lastLoc + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/geolocation/android/src/main/java/GeolocationPlugin.kt b/packages/kbot/gui/app/plugins/geolocation/android/src/main/java/GeolocationPlugin.kt new file mode 100644 index 00000000..cf81f5e3 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/android/src/main/java/GeolocationPlugin.kt @@ -0,0 +1,172 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.geolocation + +import android.Manifest +import android.app.Activity +import android.location.Location +import android.os.Build +import android.webkit.WebView +import app.tauri.Logger +import app.tauri.PermissionState +import app.tauri.annotation.Command +import app.tauri.annotation.InvokeArg +import app.tauri.annotation.Permission +import app.tauri.annotation.PermissionCallback +import app.tauri.annotation.TauriPlugin +import app.tauri.plugin.Channel +import app.tauri.plugin.Invoke +import app.tauri.plugin.JSObject +import app.tauri.plugin.Plugin + +@InvokeArg +class PositionOptions { + var enableHighAccuracy: Boolean = false + var maximumAge: Long = 0 + var timeout: Long = 10000 +} + +@InvokeArg +class WatchArgs { + var options: PositionOptions = PositionOptions() + lateinit var channel: Channel +} + +@InvokeArg +class ClearWatchArgs { + var channelId: Long = 0 +} + +// TODO: Plugin does not ask user to enable google location services (like gmaps does) + +private const val ALIAS_LOCATION: String = "location" +private const val ALIAS_COARSE_LOCATION: String = "coarseLocation" + +@TauriPlugin( + permissions = [ + Permission(strings = [ + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ], + alias = ALIAS_LOCATION + ), + Permission(strings = [ + Manifest.permission.ACCESS_COARSE_LOCATION + ], + alias = ALIAS_COARSE_LOCATION + ) + ] +) +class GeolocationPlugin(private val activity: Activity): Plugin(activity) { + private lateinit var implementation: Geolocation + private var watchers = hashMapOf>() + + override fun load(webView: WebView) { + super.load(webView) + implementation = Geolocation(activity.applicationContext) + } + + override fun onPause() { + super.onPause() + // Clear all location updates on pause to avoid possible background location calls + implementation.clearLocationUpdates() + } + + override fun onResume() { + super.onResume() + // resume watchers + for ((watcher, args) in watchers.values) { + startWatch(watcher, args) + } + } + + @Command + override fun checkPermissions(invoke: Invoke) { + if (implementation.isLocationServicesEnabled()) { + super.checkPermissions(invoke) + } else { + invoke.reject("Location services are disabled.") + } + } + + @Command + override fun requestPermissions(invoke: Invoke) { + if (implementation.isLocationServicesEnabled()) { + super.requestPermissions(invoke) + } else { + invoke.reject("Location services are disabled.") + } + } + + @Command + fun getCurrentPosition(invoke: Invoke) { + val args = invoke.parseArgs(PositionOptions::class.java) + + val location = implementation.getLastLocation(args.maximumAge) + if (location != null) { + invoke.resolve(convertLocation(location)) + } else { + implementation.sendLocation(args.enableHighAccuracy, + { loc -> invoke.resolve(convertLocation(loc)) }, + { error -> invoke.reject(error) }) + } + } + + @PermissionCallback + private fun positionPermissionCallback(invoke: Invoke) { + val permissionsResultJSON = JSObject() + permissionsResultJSON.put("location", getPermissionState(ALIAS_LOCATION)) + permissionsResultJSON.put("coarseLocation", getPermissionState(ALIAS_COARSE_LOCATION)) + invoke.resolve(permissionsResultJSON) + } + + @Command + fun watchPosition(invoke: Invoke) { + val args = invoke.parseArgs(WatchArgs::class.java) + startWatch(invoke, args) + } + + private fun startWatch(invoke: Invoke, args: WatchArgs) { + implementation.requestLocationUpdates( + args.options.enableHighAccuracy, + args.options.timeout, + { location -> args.channel.send(convertLocation(location)) }, + { error -> args.channel.sendObject(error) }) + + watchers[args.channel.id] = Pair(invoke, args) + } + + @Command + fun clearWatch(invoke: Invoke) { + val args = invoke.parseArgs(ClearWatchArgs::class.java) + + watchers.remove(args.channelId) + + if (watchers.isEmpty()) { + implementation.clearLocationUpdates() + } + + invoke.resolve() + } + + private fun convertLocation(location: Location): JSObject { + val ret = JSObject() + val coords = JSObject() + + coords.put("latitude", location.latitude) + coords.put("longitude", location.longitude) + coords.put("accuracy", location.accuracy) + coords.put("altitude", location.altitude) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + coords.put("altitudeAccuracy", location.verticalAccuracyMeters) + } + coords.put("speed", location.speed) + coords.put("heading", location.bearing) + ret.put("timestamp", location.time) + ret.put("coords", coords) + + return ret + } +} diff --git a/packages/kbot/gui/app/plugins/geolocation/android/src/test/java/ExampleUnitTest.kt b/packages/kbot/gui/app/plugins/geolocation/android/src/test/java/ExampleUnitTest.kt new file mode 100644 index 00000000..03c99f7d --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/android/src/test/java/ExampleUnitTest.kt @@ -0,0 +1,21 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.geolocation + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/packages/kbot/gui/app/plugins/geolocation/api-iife.js b/packages/kbot/gui/app/plugins/geolocation/api-iife.js new file mode 100644 index 00000000..47c5207b --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_GEOLOCATION__=function(t){"use strict";function n(t,n,i,e){if("function"==typeof n?t!==n||!e:!n.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===i?e:"a"===i?e.call(t):e?e.value:n.get(t)}function i(t,n,i,e,s){if("function"==typeof n||!n.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return n.set(t,i),i}var e,s,o,a;"function"==typeof SuppressedError&&SuppressedError;const r="__TAURI_TO_IPC_KEY__";class c{constructor(t){e.set(this,void 0),s.set(this,0),o.set(this,[]),a.set(this,void 0),i(this,e,t||(()=>{})),this.id=function(t,n=!1){return window.__TAURI_INTERNALS__.transformCallback(t,n)}((t=>{const r=t.index;if("end"in t)return void(r==n(this,s,"f")?this.cleanupCallback():i(this,a,r));const c=t.message;if(r==n(this,s,"f")){for(n(this,e,"f").call(this,c),i(this,s,n(this,s,"f")+1);n(this,s,"f")in n(this,o,"f");){const t=n(this,o,"f")[n(this,s,"f")];n(this,e,"f").call(this,t),delete n(this,o,"f")[n(this,s,"f")],i(this,s,n(this,s,"f")+1)}n(this,s,"f")===n(this,a,"f")&&this.cleanupCallback()}else n(this,o,"f")[r]=c}))}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(t){i(this,e,t)}get onmessage(){return n(this,e,"f")}[(e=new WeakMap,s=new WeakMap,o=new WeakMap,a=new WeakMap,r)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[r]()}}async function _(t,n={},i){return window.__TAURI_INTERNALS__.invoke(t,n,i)}return t.checkPermissions=async function(){return await async function(t){return _(`plugin:${t}|check_permissions`)}("geolocation")},t.clearWatch=async function(t){await _("plugin:geolocation|clear_watch",{channelId:t})},t.getCurrentPosition=async function(t){return await _("plugin:geolocation|get_current_position",{options:t})},t.requestPermissions=async function(t){return await _("plugin:geolocation|request_permissions",{permissions:t})},t.watchPosition=async function(t,n){const i=new c;return i.onmessage=t=>{"string"==typeof t?n(null,t):n(t)},await _("plugin:geolocation|watch_position",{options:t,channel:i}),i.id},t}({});Object.defineProperty(window.__TAURI__,"geolocation",{value:__TAURI_PLUGIN_GEOLOCATION__})} diff --git a/packages/kbot/gui/app/plugins/geolocation/build.rs b/packages/kbot/gui/app/plugins/geolocation/build.rs new file mode 100644 index 00000000..b5249761 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/build.rs @@ -0,0 +1,24 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +const COMMANDS: &[&str] = &[ + "get_current_position", + "watch_position", + "clear_watch", + "check_permissions", + "request_permissions", +]; + +fn main() { + let result = tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .android_path("android") + .ios_path("ios") + .try_build(); + + // when building documentation for Android the plugin build result is always Err() and is irrelevant to the crate documentation build + if !(cfg!(docsrs) && std::env::var("TARGET").unwrap().contains("android")) { + result.unwrap(); + } +} diff --git a/packages/kbot/gui/app/plugins/geolocation/contributors/crabnebula.svg b/packages/kbot/gui/app/plugins/geolocation/contributors/crabnebula.svg new file mode 100644 index 00000000..40e24131 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/contributors/crabnebula.svg @@ -0,0 +1,31 @@ + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/geolocation/contributors/rescue.png b/packages/kbot/gui/app/plugins/geolocation/contributors/rescue.png new file mode 100644 index 00000000..2b5916f4 Binary files /dev/null and b/packages/kbot/gui/app/plugins/geolocation/contributors/rescue.png differ diff --git a/packages/kbot/gui/app/plugins/geolocation/guest-js/index.ts b/packages/kbot/gui/app/plugins/geolocation/guest-js/index.ts new file mode 100644 index 00000000..8ef5c533 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/guest-js/index.ts @@ -0,0 +1,138 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { + Channel, + invoke, + PermissionState, + checkPermissions as checkPluginPermissions +} from '@tauri-apps/api/core' + +export type Coordinates = { + /** + * Latitude in decimal degrees. + */ + latitude: number + /** + * Longitude in decimal degrees. + */ + longitude: number + /** + * Accuracy level of the latitude and longitude coordinates in meters. + */ + accuracy: number + /** + * Accuracy level of the altitude coordinate in meters, if available. + * Available on all iOS versions and on Android 8 and above. + */ + altitudeAccuracy: number | null + /** + * The altitude the user is at, if available. + */ + altitude: number | null + speed: number | null + /** + * The heading the user is facing, if available. + */ + heading: number | null +} + +export type PermissionStatus = { + /** + * Permission state for the location alias. + * + * On Android it requests/checks both ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION permissions. + * + * On iOS it requests/checks location permissions. + */ + location: PermissionState + /** + * Permissions state for the coarseLoaction alias. + * + * On Android it requests/checks ACCESS_COARSE_LOCATION. + * + * On Android 12+, users can choose between Approximate location (ACCESS_COARSE_LOCATION) and Precise location (ACCESS_FINE_LOCATION). + * + * On iOS it will have the same value as the `location` alias. + */ + coarseLocation: PermissionState +} + +export type PermissionType = 'location' | 'coarseLocation' + +export type Position = { + /** + * Creation time for these coordinates. + */ + timestamp: number + /** + * The GPD coordinates along with the accuracy of the data. + */ + coords: Coordinates +} + +export type PositionOptions = { + /** + * High accuracy mode (such as GPS, if available) + * Will be ignored on Android 12+ if users didn't grant the ACCESS_FINE_LOCATION permission (`coarseLocation` permission). + */ + enableHighAccuracy: boolean + /** + * The maximum wait time in milliseconds for location updates. + * On Android the timeout gets ignored for getCurrentPosition. + * Ignored on iOS + */ + timeout: number + /** + * The maximum age in milliseconds of a possible cached position that is acceptable to return. + * Default: 0 + * Ignored on iOS + */ + maximumAge: number +} + +export async function watchPosition( + options: PositionOptions, + cb: (location: Position | null, error?: string) => void +): Promise { + const channel = new Channel() + channel.onmessage = (message) => { + if (typeof message === 'string') { + cb(null, message) + } else { + cb(message) + } + } + await invoke('plugin:geolocation|watch_position', { + options, + channel + }) + return channel.id +} + +export async function getCurrentPosition( + options?: PositionOptions +): Promise { + return await invoke('plugin:geolocation|get_current_position', { + options + }) +} + +export async function clearWatch(channelId: number): Promise { + await invoke('plugin:geolocation|clear_watch', { + channelId + }) +} + +export async function checkPermissions(): Promise { + return await checkPluginPermissions('geolocation') +} + +export async function requestPermissions( + permissions: PermissionType[] | null +): Promise { + return await invoke('plugin:geolocation|request_permissions', { + permissions + }) +} diff --git a/packages/kbot/gui/app/plugins/geolocation/ios/.gitignore b/packages/kbot/gui/app/plugins/geolocation/ios/.gitignore new file mode 100644 index 00000000..5922fdaa --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/ios/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Package.resolved diff --git a/packages/kbot/gui/app/plugins/geolocation/ios/Package.swift b/packages/kbot/gui/app/plugins/geolocation/ios/Package.swift new file mode 100644 index 00000000..f8cfb73f --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/ios/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version:5.3 +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import PackageDescription + +let package = Package( + name: "tauri-plugin-geolocation", + platforms: [ + .macOS(.v10_13), + .iOS(.v13), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "tauri-plugin-geolocation", + type: .static, + targets: ["tauri-plugin-geolocation"]) + ], + dependencies: [ + .package(name: "Tauri", path: "../.tauri/tauri-api") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "tauri-plugin-geolocation", + dependencies: [ + .byName(name: "Tauri") + ], + path: "Sources") + ] +) diff --git a/packages/kbot/gui/app/plugins/geolocation/ios/README.md b/packages/kbot/gui/app/plugins/geolocation/ios/README.md new file mode 100644 index 00000000..5612ac82 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/ios/README.md @@ -0,0 +1,3 @@ +# Tauri Plugin Geolocation + +A description of this package. diff --git a/packages/kbot/gui/app/plugins/geolocation/ios/Sources/GeolocationPlugin.swift b/packages/kbot/gui/app/plugins/geolocation/ios/Sources/GeolocationPlugin.swift new file mode 100644 index 00000000..7a2b57a9 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/ios/Sources/GeolocationPlugin.swift @@ -0,0 +1,250 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import CoreLocation +import SwiftRs +import Tauri +import UIKit +import WebKit + +class GetPositionArgs: Decodable { + var enableHighAccuracy: Bool? +} + +class WatchPositionArgs: Decodable { + let options: GetPositionArgs + let channel: Channel +} + +class ClearWatchArgs: Decodable { + let channelId: UInt32 +} + +class GeolocationPlugin: Plugin, CLLocationManagerDelegate { + private let locationManager = CLLocationManager() + private var isUpdatingLocation: Bool = false + private var permissionRequests: [Invoke] = [] + private var positionRequests: [Invoke] = [] + private var watcherChannels: [Channel] = [] + + override init() { + super.init() + locationManager.delegate = self + } + + // + // Tauri commands + // + + @objc public func getCurrentPosition(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(GetPositionArgs.self) + + self.positionRequests.append(invoke) + + DispatchQueue.main.async { + if args.enableHighAccuracy == true { + self.locationManager.desiredAccuracy = kCLLocationAccuracyBest + } else { + self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer + } + + // TODO: Use the authorizationStatus instance property with locationManagerDidChangeAuthorization(_:) instead. + if CLLocationManager.authorizationStatus() == .notDetermined { + self.locationManager.requestWhenInUseAuthorization() + } else { + self.locationManager.requestLocation() + } + } + } + + @objc public func watchPosition(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(WatchPositionArgs.self) + + self.watcherChannels.append(args.channel) + + DispatchQueue.main.async { + if args.options.enableHighAccuracy == true { + self.locationManager.desiredAccuracy = kCLLocationAccuracyBest + } else { + self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer + } + + // TODO: Use the authorizationStatus instance property with locationManagerDidChangeAuthorization(_:) instead. + if CLLocationManager.authorizationStatus() == .notDetermined { + self.locationManager.requestWhenInUseAuthorization() + } else { + self.locationManager.startUpdatingLocation() + self.isUpdatingLocation = true + } + } + + invoke.resolve() + } + + @objc public func clearWatch(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(ClearWatchArgs.self) + + self.watcherChannels = self.watcherChannels.filter { $0.id != args.channelId } + + // TODO: capacitor plugin calls stopUpdating unconditionally + if self.watcherChannels.isEmpty { + self.stopUpdating() + } + + invoke.resolve() + } + + @objc override public func checkPermissions(_ invoke: Invoke) { + var status: String = "" + + if CLLocationManager.locationServicesEnabled() { + // TODO: Use the authorizationStatus instance property with locationManagerDidChangeAuthorization(_:) instead. + switch CLLocationManager.authorizationStatus() { + case .notDetermined: + status = "prompt" + case .restricted, .denied: + status = "denied" + case .authorizedAlways, .authorizedWhenInUse: + status = "granted" + @unknown default: + status = "prompt" + } + } else { + invoke.reject("Location services are not enabled.") + return + } + + let result = ["location": status, "coarseLocation": status] + + invoke.resolve(result) + } + + @objc override public func requestPermissions(_ invoke: Invoke) { + if CLLocationManager.locationServicesEnabled() { + // TODO: Use the authorizationStatus instance property with locationManagerDidChangeAuthorization(_:) instead. + if CLLocationManager.authorizationStatus() == .notDetermined { + self.permissionRequests.append(invoke) + + DispatchQueue.main.async { + self.locationManager.requestWhenInUseAuthorization() + } + } else { + checkPermissions(invoke) + } + } else { + invoke.reject("Location services are not enabled.") + } + } + + // + // Delegate methods + // + + public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { + Logger.error(error) + + let requests = self.positionRequests + self.permissionRequests + self.positionRequests.removeAll() + self.permissionRequests.removeAll() + + for request in requests { + request.reject(error.localizedDescription) + } + + for channel in self.watcherChannels { + do { + try channel.send(error.localizedDescription) + } catch { + Logger.error(error) + } + } + } + + public func locationManager( + _ manager: CLLocationManager, didUpdateLocations locations: [CLLocation] + ) { + // Respond to all getCurrentPosition() calls. + for request in self.positionRequests { + // The capacitor plugin uses locations.first but .last should be the most current one + // and i don't see a reason to use old locations + if let location = locations.last { + let result = convertLocation(location) + request.resolve(result) + } else { + request.reject("Location service returned an empty Location array.") + } + } + + for channel in self.watcherChannels { + // The capacitor plugin uses locations.first but .last should be the most recent one + // and i don't see a reason to use old locations + if let location = locations.last { + let result = convertLocation(location) + do { + try channel.send(result) + } catch { + Logger.error(error) + } + } else { + do { + try channel.send("Location service returned an empty Location array.") + } catch { + Logger.error(error) + } + } + } + } + + public func locationManager( + _ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus + ) { + let requests = self.permissionRequests + self.permissionRequests.removeAll() + + for request in requests { + checkPermissions(request) + } + + if !self.positionRequests.isEmpty { + self.locationManager.requestLocation() + } + + if !self.watcherChannels.isEmpty && !self.isUpdatingLocation { + self.locationManager.startUpdatingLocation() + self.isUpdatingLocation = true + } + } + + // + // Internal/Helper methods + // + + // TODO: Why is this pub in capacitor + private func stopUpdating() { + self.locationManager.stopUpdatingLocation() + self.isUpdatingLocation = false + } + + private func convertLocation(_ location: CLLocation) -> JsonObject { + var ret: JsonObject = [:] + var coords: JsonObject = [:] + + coords["latitude"] = location.coordinate.latitude + coords["longitude"] = location.coordinate.longitude + coords["accuracy"] = location.horizontalAccuracy + coords["altitude"] = location.altitude + coords["altitudeAccuracy"] = location.verticalAccuracy + coords["speed"] = location.speed + coords["heading"] = location.course + ret["timestamp"] = Int((location.timestamp.timeIntervalSince1970 * 1000)) + ret["coords"] = coords + + return ret + } +} + +@_cdecl("init_plugin_geolocation") +func initPlugin() -> Plugin { + return GeolocationPlugin() +} diff --git a/packages/kbot/gui/app/plugins/geolocation/ios/Tests/PluginTests/PluginTests.swift b/packages/kbot/gui/app/plugins/geolocation/ios/Tests/PluginTests/PluginTests.swift new file mode 100644 index 00000000..99992ce4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/ios/Tests/PluginTests/PluginTests.swift @@ -0,0 +1,12 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import XCTest +@testable import ExamplePlugin + +final class ExamplePluginTests: XCTestCase { + func testExample() throws { + let plugin = ExamplePlugin() + } +} diff --git a/packages/kbot/gui/app/plugins/geolocation/package.json b/packages/kbot/gui/app/plugins/geolocation/package.json new file mode 100644 index 00000000..937b96dd --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/package.json @@ -0,0 +1,29 @@ +{ + "name": "@tauri-apps/plugin-geolocation", + "version": "2.3.0", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/commands/check_permissions.toml b/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/commands/check_permissions.toml new file mode 100644 index 00000000..f5af08b1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/commands/check_permissions.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-check-permissions" +description = "Enables the check_permissions command without any pre-configured scope." +commands.allow = ["check_permissions"] + +[[permission]] +identifier = "deny-check-permissions" +description = "Denies the check_permissions command without any pre-configured scope." +commands.deny = ["check_permissions"] diff --git a/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/commands/clear_permissions.toml b/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/commands/clear_permissions.toml new file mode 100644 index 00000000..a3e6ab5c --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/commands/clear_permissions.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-clear-permissions" +description = "Enables the clear_permissions command without any pre-configured scope." +commands.allow = ["clear_permissions"] + +[[permission]] +identifier = "deny-clear-permissions" +description = "Denies the clear_permissions command without any pre-configured scope." +commands.deny = ["clear_permissions"] diff --git a/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/commands/clear_watch.toml b/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/commands/clear_watch.toml new file mode 100644 index 00000000..19fb5776 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/commands/clear_watch.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-clear-watch" +description = "Enables the clear_watch command without any pre-configured scope." +commands.allow = ["clear_watch"] + +[[permission]] +identifier = "deny-clear-watch" +description = "Denies the clear_watch command without any pre-configured scope." +commands.deny = ["clear_watch"] diff --git a/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/commands/get_current_position.toml b/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/commands/get_current_position.toml new file mode 100644 index 00000000..fefb951b --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/commands/get_current_position.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-get-current-position" +description = "Enables the get_current_position command without any pre-configured scope." +commands.allow = ["get_current_position"] + +[[permission]] +identifier = "deny-get-current-position" +description = "Denies the get_current_position command without any pre-configured scope." +commands.deny = ["get_current_position"] diff --git a/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/commands/request_permissions.toml b/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/commands/request_permissions.toml new file mode 100644 index 00000000..02dcd627 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/commands/request_permissions.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-request-permissions" +description = "Enables the request_permissions command without any pre-configured scope." +commands.allow = ["request_permissions"] + +[[permission]] +identifier = "deny-request-permissions" +description = "Denies the request_permissions command without any pre-configured scope." +commands.deny = ["request_permissions"] diff --git a/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/commands/watch_position.toml b/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/commands/watch_position.toml new file mode 100644 index 00000000..5878ba1a --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/commands/watch_position.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-watch-position" +description = "Enables the watch_position command without any pre-configured scope." +commands.allow = ["watch_position"] + +[[permission]] +identifier = "deny-watch-position" +description = "Denies the watch_position command without any pre-configured scope." +commands.deny = ["watch_position"] diff --git a/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/reference.md new file mode 100644 index 00000000..0215a998 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/permissions/autogenerated/reference.md @@ -0,0 +1,165 @@ +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`geolocation:allow-check-permissions` + + + +Enables the check_permissions command without any pre-configured scope. + +
+ +`geolocation:deny-check-permissions` + + + +Denies the check_permissions command without any pre-configured scope. + +
+ +`geolocation:allow-clear-permissions` + + + +Enables the clear_permissions command without any pre-configured scope. + +
+ +`geolocation:deny-clear-permissions` + + + +Denies the clear_permissions command without any pre-configured scope. + +
+ +`geolocation:allow-clear-watch` + + + +Enables the clear_watch command without any pre-configured scope. + +
+ +`geolocation:deny-clear-watch` + + + +Denies the clear_watch command without any pre-configured scope. + +
+ +`geolocation:allow-get-current-position` + + + +Enables the get_current_position command without any pre-configured scope. + +
+ +`geolocation:deny-get-current-position` + + + +Denies the get_current_position command without any pre-configured scope. + +
+ +`geolocation:allow-request-permissions` + + + +Enables the request_permissions command without any pre-configured scope. + +
+ +`geolocation:deny-request-permissions` + + + +Denies the request_permissions command without any pre-configured scope. + +
+ +`geolocation:allow-watch-position` + + + +Enables the watch_position command without any pre-configured scope. + +
+ +`geolocation:deny-watch-position` + + + +Denies the watch_position command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/geolocation/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/geolocation/permissions/schemas/schema.json new file mode 100644 index 00000000..237b445d --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/permissions/schemas/schema.json @@ -0,0 +1,372 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the check_permissions command without any pre-configured scope.", + "type": "string", + "const": "allow-check-permissions", + "markdownDescription": "Enables the check_permissions command without any pre-configured scope." + }, + { + "description": "Denies the check_permissions command without any pre-configured scope.", + "type": "string", + "const": "deny-check-permissions", + "markdownDescription": "Denies the check_permissions command without any pre-configured scope." + }, + { + "description": "Enables the clear_permissions command without any pre-configured scope.", + "type": "string", + "const": "allow-clear-permissions", + "markdownDescription": "Enables the clear_permissions command without any pre-configured scope." + }, + { + "description": "Denies the clear_permissions command without any pre-configured scope.", + "type": "string", + "const": "deny-clear-permissions", + "markdownDescription": "Denies the clear_permissions command without any pre-configured scope." + }, + { + "description": "Enables the clear_watch command without any pre-configured scope.", + "type": "string", + "const": "allow-clear-watch", + "markdownDescription": "Enables the clear_watch command without any pre-configured scope." + }, + { + "description": "Denies the clear_watch command without any pre-configured scope.", + "type": "string", + "const": "deny-clear-watch", + "markdownDescription": "Denies the clear_watch command without any pre-configured scope." + }, + { + "description": "Enables the get_current_position command without any pre-configured scope.", + "type": "string", + "const": "allow-get-current-position", + "markdownDescription": "Enables the get_current_position command without any pre-configured scope." + }, + { + "description": "Denies the get_current_position command without any pre-configured scope.", + "type": "string", + "const": "deny-get-current-position", + "markdownDescription": "Denies the get_current_position command without any pre-configured scope." + }, + { + "description": "Enables the request_permissions command without any pre-configured scope.", + "type": "string", + "const": "allow-request-permissions", + "markdownDescription": "Enables the request_permissions command without any pre-configured scope." + }, + { + "description": "Denies the request_permissions command without any pre-configured scope.", + "type": "string", + "const": "deny-request-permissions", + "markdownDescription": "Denies the request_permissions command without any pre-configured scope." + }, + { + "description": "Enables the watch_position command without any pre-configured scope.", + "type": "string", + "const": "allow-watch-position", + "markdownDescription": "Enables the watch_position command without any pre-configured scope." + }, + { + "description": "Denies the watch_position command without any pre-configured scope.", + "type": "string", + "const": "deny-watch-position", + "markdownDescription": "Denies the watch_position command without any pre-configured scope." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/geolocation/rollup.config.js b/packages/kbot/gui/app/plugins/geolocation/rollup.config.js new file mode 100644 index 00000000..1f349ec8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/rollup.config.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' + +export default createConfig() diff --git a/packages/kbot/gui/app/plugins/geolocation/src/commands.rs b/packages/kbot/gui/app/plugins/geolocation/src/commands.rs new file mode 100644 index 00000000..d2cae848 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/src/commands.rs @@ -0,0 +1,42 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tauri::{command, ipc::Channel, AppHandle, Runtime}; + +use crate::{GeolocationExt, PermissionStatus, PermissionType, Position, PositionOptions, Result}; + +#[command] +pub(crate) async fn get_current_position( + app: AppHandle, + options: Option, +) -> Result { + app.geolocation().get_current_position(options) +} + +#[command] +pub(crate) async fn watch_position( + app: AppHandle, + options: PositionOptions, + channel: Channel, +) -> Result<()> { + app.geolocation().watch_position_inner(options, channel) +} + +#[command] +pub(crate) async fn clear_watch(app: AppHandle, channel_id: u32) -> Result<()> { + app.geolocation().clear_watch(channel_id) +} + +#[command] +pub(crate) async fn check_permissions(app: AppHandle) -> Result { + app.geolocation().check_permissions() +} + +#[command] +pub(crate) async fn request_permissions( + app: AppHandle, + permissions: Option>, +) -> Result { + app.geolocation().request_permissions(permissions) +} diff --git a/packages/kbot/gui/app/plugins/geolocation/src/desktop.rs b/packages/kbot/gui/app/plugins/geolocation/src/desktop.rs new file mode 100644 index 00000000..00da1fad --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/src/desktop.rs @@ -0,0 +1,95 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{de::DeserializeOwned, Serialize}; +use tauri::{ + ipc::{Channel, InvokeResponseBody}, + plugin::PluginApi, + AppHandle, Runtime, +}; + +use crate::models::*; + +pub fn init( + app: &AppHandle, + _api: PluginApi, +) -> crate::Result> { + Ok(Geolocation(app.clone())) +} + +/// Access to the geolocation APIs. +pub struct Geolocation(AppHandle); + +impl Geolocation { + pub fn get_current_position( + &self, + _options: Option, + ) -> crate::Result { + Ok(Position::default()) + } + + pub fn watch_position( + &self, + options: PositionOptions, + callback: F, + ) -> crate::Result { + let channel = Channel::new(move |event| { + let payload = match event { + InvokeResponseBody::Json(payload) => serde_json::from_str::(&payload) + .unwrap_or_else(|error| { + WatchEvent::Error(format!( + "Couldn't deserialize watch event payload: `{error}`" + )) + }), + _ => WatchEvent::Error("Unexpected watch event payload.".to_string()), + }; + + callback(payload); + + Ok(()) + }); + let id = channel.id(); + + self.watch_position_inner(options, channel)?; + + Ok(id) + } + + pub(crate) fn watch_position_inner( + &self, + _options: PositionOptions, + _callback_channel: Channel, + ) -> crate::Result<()> { + Ok(()) + } + + pub fn clear_watch(&self, _channel_id: u32) -> crate::Result<()> { + Ok(()) + } + + pub fn check_permissions(&self) -> crate::Result { + Ok(PermissionStatus::default()) + } + + pub fn request_permissions( + &self, + _permissions: Option>, + ) -> crate::Result { + Ok(PermissionStatus::default()) + } +} + +#[derive(Serialize)] +#[allow(unused)] // TODO: +struct WatchPayload { + options: PositionOptions, + channel: Channel, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +#[allow(unused)] // TODO: +struct ClearWatchPayload { + channel_id: u32, +} diff --git a/packages/kbot/gui/app/plugins/geolocation/src/error.rs b/packages/kbot/gui/app/plugins/geolocation/src/error.rs new file mode 100644 index 00000000..0fba5445 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/src/error.rs @@ -0,0 +1,30 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{ser::Serializer, Serialize}; + +pub type Result = std::result::Result; + +// TODO: Improve Error handling (different typed errors instead of one (stringified) PluginInvokeError for all mobile errors) + +#[derive(Debug, thiserror::Error)] +#[cfg_attr(feature = "specta", derive(specta::Type))] +pub enum Error { + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke( + #[cfg_attr(feature = "specta", serde(skip))] + #[from] + tauri::plugin::mobile::PluginInvokeError, + ), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/packages/kbot/gui/app/plugins/geolocation/src/lib.rs b/packages/kbot/gui/app/plugins/geolocation/src/lib.rs new file mode 100644 index 00000000..588f96e3 --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/src/lib.rs @@ -0,0 +1,58 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tauri::{ + plugin::{Builder, TauriPlugin}, + Manager, Runtime, +}; + +pub use models::*; + +#[cfg(desktop)] +mod desktop; +#[cfg(mobile)] +mod mobile; + +mod commands; +mod error; +mod models; + +pub use error::{Error, Result}; + +#[cfg(desktop)] +pub use desktop::Geolocation; +#[cfg(mobile)] +pub use mobile::Geolocation; + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the geolocation APIs. +pub trait GeolocationExt { + fn geolocation(&self) -> &Geolocation; +} + +impl> crate::GeolocationExt for T { + fn geolocation(&self) -> &Geolocation { + self.state::>().inner() + } +} + +/// Initializes the plugin. +pub fn init() -> TauriPlugin { + Builder::new("geolocation") + .invoke_handler(tauri::generate_handler![ + commands::get_current_position, + commands::watch_position, + commands::clear_watch, + commands::check_permissions, + commands::request_permissions + ]) + .setup(|app, api| { + #[cfg(mobile)] + let geolocation = mobile::init(app, api)?; + #[cfg(desktop)] + let geolocation = desktop::init(app, api)?; + app.manage(geolocation); + Ok(()) + }) + .build() +} diff --git a/packages/kbot/gui/app/plugins/geolocation/src/mobile.rs b/packages/kbot/gui/app/plugins/geolocation/src/mobile.rs new file mode 100644 index 00000000..48a3f5de --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/src/mobile.rs @@ -0,0 +1,119 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{de::DeserializeOwned, Serialize}; +use tauri::{ + ipc::{Channel, InvokeResponseBody}, + plugin::{PluginApi, PluginHandle}, + AppHandle, Runtime, +}; + +use crate::models::*; + +#[cfg(target_os = "android")] +const PLUGIN_IDENTIFIER: &str = "app.tauri.geolocation"; + +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_geolocation); + +// initializes the Kotlin or Swift plugin classes +pub fn init( + _app: &AppHandle, + api: PluginApi, +) -> crate::Result> { + #[cfg(target_os = "android")] + let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "GeolocationPlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_geolocation)?; + Ok(Geolocation(handle)) +} + +/// Access to the geolocation APIs. +pub struct Geolocation(PluginHandle); + +impl Geolocation { + pub fn get_current_position( + &self, + options: Option, + ) -> crate::Result { + // TODO: We may have to send over None if that's better on Android + self.0 + .run_mobile_plugin("getCurrentPosition", options.unwrap_or_default()) + .map_err(Into::into) + } + + /// Register a position watcher. This method returns an id to use in `clear_watch`. + pub fn watch_position( + &self, + options: PositionOptions, + callback: F, + ) -> crate::Result { + let channel = Channel::new(move |event| { + let payload = match event { + InvokeResponseBody::Json(payload) => serde_json::from_str::(&payload) + .unwrap_or_else(|error| { + WatchEvent::Error(format!( + "Couldn't deserialize watch event payload: `{error}`" + )) + }), + _ => WatchEvent::Error("Unexpected watch event payload.".to_string()), + }; + + callback(payload); + + Ok(()) + }); + let id = channel.id(); + + self.watch_position_inner(options, channel)?; + + Ok(id) + } + + pub(crate) fn watch_position_inner( + &self, + options: PositionOptions, + channel: Channel, + ) -> crate::Result<()> { + self.0 + .run_mobile_plugin("watchPosition", WatchPayload { options, channel }) + .map_err(Into::into) + } + + pub fn clear_watch(&self, channel_id: u32) -> crate::Result<()> { + self.0 + .run_mobile_plugin("clearWatch", ClearWatchPayload { channel_id }) + .map_err(Into::into) + } + + pub fn check_permissions(&self) -> crate::Result { + self.0 + .run_mobile_plugin("checkPermissions", ()) + .map_err(Into::into) + } + + pub fn request_permissions( + &self, + permissions: Option>, + ) -> crate::Result { + self.0 + .run_mobile_plugin( + "requestPermissions", + serde_json::json!({ "permissions": permissions }), + ) + .map_err(Into::into) + } +} + +#[derive(Serialize)] +struct WatchPayload { + options: PositionOptions, + channel: Channel, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct ClearWatchPayload { + channel_id: u32, +} diff --git a/packages/kbot/gui/app/plugins/geolocation/src/models.rs b/packages/kbot/gui/app/plugins/geolocation/src/models.rs new file mode 100644 index 00000000..c08bb27a --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/src/models.rs @@ -0,0 +1,96 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{Deserialize, Serialize}; +use tauri::plugin::PermissionState; + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "specta", derive(specta::Type))] +#[serde(rename_all = "camelCase")] +pub struct PermissionStatus { + /// Permission state for the location alias. + /// + /// On Android it requests/checks both ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION permissions. + /// + /// On iOS it requests/checks location permissions. + pub location: PermissionState, + /// Permissions state for the coarseLoaction alias. + /// + /// On Android it requests/checks ACCESS_COARSE_LOCATION. + /// + /// On Android 12+, users can choose between Approximate location (ACCESS_COARSE_LOCATION) and Precise location (ACCESS_FINE_LOCATION). + /// + /// On iOS it will have the same value as the `location` alias. + pub coarse_location: PermissionState, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "specta", derive(specta::Type))] +#[serde(rename_all = "camelCase")] +pub struct PositionOptions { + /// High accuracy mode (such as GPS, if available) + /// Will be ignored on Android 12+ if users didn't grant the ACCESS_FINE_LOCATION permission. + pub enable_high_accuracy: bool, + /// The maximum wait time in milliseconds for location updates. + /// Default: 10000 + /// On Android the timeout gets ignored for getCurrentPosition. + /// Ignored on iOS. + // TODO: Handle Infinity and default to it. + // TODO: Should be u64+ but specta doesn't like that? + pub timeout: u32, + /// The maximum age in milliseconds of a possible cached position that is acceptable to return. + /// Default: 0 + /// Ignored on iOS. + // TODO: Handle Infinity. + // TODO: Should be u64+ but specta doesn't like that? + pub maximum_age: u32, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "specta", derive(specta::Type))] +#[serde(rename_all = "camelCase")] +pub enum PermissionType { + Location, + CoarseLocation, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "specta", derive(specta::Type))] +#[serde(rename_all = "camelCase")] +pub struct Coordinates { + /// Latitude in decimal degrees. + pub latitude: f64, + /// Longitude in decimal degrees. + pub longitude: f64, + /// Accuracy level of the latitude and longitude coordinates in meters. + pub accuracy: f64, + /// Accuracy level of the altitude coordinate in meters, if available. + /// Available on all iOS versions and on Android 8 and above. + pub altitude_accuracy: Option, + /// The altitude the user is at, if available. + pub altitude: Option, + // The speed the user is traveling, if available. + pub speed: Option, + /// The heading the user is facing, if available. + pub heading: Option, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "specta", derive(specta::Type))] +#[serde(rename_all = "camelCase")] +pub struct Position { + /// Creation time for these coordinates. + // TODO: Check if we're actually losing precision. + pub timestamp: u64, + /// The GPS coordinates along with the accuracy of the data. + pub coords: Coordinates, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "specta", derive(specta::Type))] +#[serde(untagged)] +pub enum WatchEvent { + Position(Position), + Error(String), +} diff --git a/packages/kbot/gui/app/plugins/geolocation/tsconfig.json b/packages/kbot/gui/app/plugins/geolocation/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/geolocation/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/global-shortcut/CHANGELOG.md b/packages/kbot/gui/app/plugins/global-shortcut/CHANGELOG.md new file mode 100644 index 00000000..ee1a6c40 --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/CHANGELOG.md @@ -0,0 +1,118 @@ +# Changelog + +## \[2.3.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.2.1] + +- [`494d1fea`](https://github.com/tauri-apps/plugins-workspace/commit/494d1fea137ffd60da98b25305c9d666df62cc63) ([#2684](https://github.com/tauri-apps/plugins-workspace/pull/2684) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated `global-hotkey` crate to `0.7` to fix a panic when trying to register a key combination which was already registered by another program. No API changes. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.1] + +- [`2c00c029`](https://github.com/tauri-apps/plugins-workspace/commit/2c00c0292c9127b81567de46691e8c0f73557261) ([#1630](https://github.com/tauri-apps/plugins-workspace/pull/1630) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused multi-word IIFE names to not be formatted correctly. For example the `barcode-scanner` was defined as `window.__TAURI_PLUGIN_CLIPBOARDMANAGER__` instead of `window.__TAURI_PLUGIN_CLIPBOARD_MANAGER__`. + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +## \[2.0.0-beta.8] + +- [`99d6ac0f`](https://github.com/tauri-apps/plugins-workspace/commit/99d6ac0f9506a6a4a1aa59c728157190a7441af6) ([#1606](https://github.com/tauri-apps/plugins-workspace/pull/1606) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The JS packages now specify the *minimum* `@tauri-apps/api` version instead of a single exact version. +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.7] + +- [`381a466d`](https://github.com/tauri-apps/plugins-workspace/commit/381a466db344e59a76b2a4d5785b2a0b64d4d373) ([#1117](https://github.com/tauri-apps/plugins-workspace/pull/1117) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Refactored the JS APIs: + + - Enhanced `register` and `unregister` to take either a single shortcut or an array. + - Removed `registerAll` instead use `register` with an array. +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.6] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.5] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.4] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.3] + +- [`9c7eb359`](https://github.com/tauri-apps/plugins-workspace/commit/9c7eb35967ad10659eb4976bd6f77d9d270bfeed)([#1244](https://github.com/tauri-apps/plugins-workspace/pull/1244)) Refactored APIs to introduce new pressed and released events: + + - Added `ShortcutEvent` and `ShortcutState` types in Rust. + - Changed the handler function passed to `GlobalShortcut::on_shortcut`, `GlobalShortcut::on_all_shortcuts` and `Builder::with_handler` to take a 3rd argument of type `ShortcutEvent`. + - Added `ShortcutEvent` interface in JS. + - Changed `ShortcutHandler` type alias (which affects the JS `register` and `registerAll` APIs) to take `ShortcutEvent` instead of a string. +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.3] + +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. +- [`62dafda`](https://github.com/tauri-apps/plugins-workspace/commit/62dafda6526899b407a7c5a1bb269c5c0dfb2630)([#969](https://github.com/tauri-apps/plugins-workspace/pull/969)) **Breaking change** Refactored the plugin Rust APIs for better DX and flexibility: + + - Changed `Builder::with_handler` to be a method instead of a static method, it will also be triggered for any and all shortcuts even if the shortcut is registered through JS. + - Added `Builder::with_shortcut` and `Builder::with_shortcuts` to register shortcuts on the plugin builder. + - Added `on_shortcut` and `on_all_shortcuts` to register shortcuts with a handler. + +## \[2.0.0-beta.2] + +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Add permissions. + +## \[2.0.0-alpha.5] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.13. + +## \[2.0.0-alpha.4] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.12. + +## \[2.0.0-alpha.3] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to @tauri-apps/api v2.0.0-alpha.11. + +## \[2.0.0-alpha.2] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update to @tauri-apps/api v2.0.0-alpha.9. + +## \[2.0.0-alpha.2] + +- [`4e2cef9`](https://github.com/tauri-apps/plugins-workspace/commit/4e2cef9b702bbbb9cf4ee17de50791cb21f1b2a4)([#593](https://github.com/tauri-apps/plugins-workspace/pull/593)) Update to alpha.12. + +## \[2.0.0-alpha.1] + +- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11. + +## \[2.0.0-alpha.0] + +- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! diff --git a/packages/kbot/gui/app/plugins/global-shortcut/Cargo.toml b/packages/kbot/gui/app/plugins/global-shortcut/Cargo.toml new file mode 100644 index 00000000..b6db0b02 --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "tauri-plugin-global-shortcut" +version = "2.3.0" +description = "Register global hotkeys listeners on your Tauri application." +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-global-shortcut" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.platforms.support] +windows = { level = "full", notes = "" } +linux = { level = "full", notes = "" } +macos = { level = "full", notes = "" } +android = { level = "none", notes = "" } +ios = { level = "none", notes = "" } + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } + +[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies] +global-hotkey = { version = "0.7", features = ["serde"] } diff --git a/packages/kbot/gui/app/plugins/global-shortcut/LICENSE.spdx b/packages/kbot/gui/app/plugins/global-shortcut/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/global-shortcut/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/global-shortcut/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/global-shortcut/LICENSE_MIT b/packages/kbot/gui/app/plugins/global-shortcut/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/global-shortcut/README.md b/packages/kbot/gui/app/plugins/global-shortcut/README.md new file mode 100644 index 00000000..9e215aea --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/README.md @@ -0,0 +1,119 @@ +![plugin-global-shortcut](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/global-shortcut/banner.png) + +Register global shortcuts. + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | x | +| iOS | x | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +# you can add the dependencies on the `[dependencies]` section if you do not target mobile +[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies] +tauri-plugin-global-shortcut = "2.0.0" +# alternatively with Git: +tauri-plugin-global-shortcut = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +```sh +pnpm add @tauri-apps/plugin-global-shortcut +# or +npm add @tauri-apps/plugin-global-shortcut +# or +yarn add @tauri-apps/plugin-global-shortcut +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .setup(|app| { + #[cfg(desktop)] + { + use tauri::Manager; + use tauri_plugin_global_shortcut::{Code, Modifiers, ShortcutState}; + + app.handle().plugin( + tauri_plugin_global_shortcut::Builder::new() + .with_shortcuts(["ctrl+d", "alt+space"])? + .with_handler(|app, shortcut, event| { + if event.state == ShortcutState::Pressed { + if shortcut.matches(Modifiers::CONTROL, Code::KeyD) { + let _ = app.emit("shortcut-event", "Ctrl+D triggered"); + } + if shortcut.matches(Modifiers::ALT, Code::Space) { + let _ = app.emit("shortcut-event", "Alt+Space triggered"); + } + } + }) + .build(), + )?; + } + + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript bindings: + +```javascript +import { register } from '@tauri-apps/plugin-global-shortcut' +await register('CommandOrControl+Shift+C', (event) => { + if (event.state === 'Pressed') { + console.log('Shortcut triggered') + } +}) +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/global-shortcut/SECURITY.md b/packages/kbot/gui/app/plugins/global-shortcut/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/global-shortcut/api-iife.js b/packages/kbot/gui/app/plugins/global-shortcut/api-iife.js new file mode 100644 index 00000000..65902d32 --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_GLOBAL_SHORTCUT__=function(t){"use strict";function e(t,e,s,i){if("function"==typeof e?t!==e||!i:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===s?i:"a"===s?i.call(t):i?i.value:e.get(t)}function s(t,e,s,i,r){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,s),s}var i,r,n,a;"function"==typeof SuppressedError&&SuppressedError;const o="__TAURI_TO_IPC_KEY__";class c{constructor(t){i.set(this,void 0),r.set(this,0),n.set(this,[]),a.set(this,void 0),s(this,i,t||(()=>{})),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((t=>{const o=t.index;if("end"in t)return void(o==e(this,r,"f")?this.cleanupCallback():s(this,a,o));const c=t.message;if(o==e(this,r,"f")){for(e(this,i,"f").call(this,c),s(this,r,e(this,r,"f")+1);e(this,r,"f")in e(this,n,"f");){const t=e(this,n,"f")[e(this,r,"f")];e(this,i,"f").call(this,t),delete e(this,n,"f")[e(this,r,"f")],s(this,r,e(this,r,"f")+1)}e(this,r,"f")===e(this,a,"f")&&this.cleanupCallback()}else e(this,n,"f")[o]=c}))}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(t){s(this,i,t)}get onmessage(){return e(this,i,"f")}[(i=new WeakMap,r=new WeakMap,n=new WeakMap,a=new WeakMap,o)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[o]()}}async function u(t,e={},s){return window.__TAURI_INTERNALS__.invoke(t,e,s)}return t.isRegistered=async function(t){return await u("plugin:global-shortcut|is_registered",{shortcut:t})},t.register=async function(t,e){const s=new c;return s.onmessage=e,await u("plugin:global-shortcut|register",{shortcuts:Array.isArray(t)?t:[t],handler:s})},t.unregister=async function(t){return await u("plugin:global-shortcut|unregister",{shortcuts:Array.isArray(t)?t:[t]})},t.unregisterAll=async function(){return await u("plugin:global-shortcut|unregister_all",{})},t}({});Object.defineProperty(window.__TAURI__,"globalShortcut",{value:__TAURI_PLUGIN_GLOBAL_SHORTCUT__})} diff --git a/packages/kbot/gui/app/plugins/global-shortcut/banner.png b/packages/kbot/gui/app/plugins/global-shortcut/banner.png new file mode 100644 index 00000000..d74203a5 Binary files /dev/null and b/packages/kbot/gui/app/plugins/global-shortcut/banner.png differ diff --git a/packages/kbot/gui/app/plugins/global-shortcut/build.rs b/packages/kbot/gui/app/plugins/global-shortcut/build.rs new file mode 100644 index 00000000..20b7b7f8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/build.rs @@ -0,0 +1,11 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +const COMMANDS: &[&str] = &["register", "unregister", "unregister_all", "is_registered"]; + +fn main() { + tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .build(); +} diff --git a/packages/kbot/gui/app/plugins/global-shortcut/guest-js/index.ts b/packages/kbot/gui/app/plugins/global-shortcut/guest-js/index.ts new file mode 100644 index 00000000..13e8e50e --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/guest-js/index.ts @@ -0,0 +1,123 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/** + * Register global shortcuts. + * + * @module + */ + +import { invoke, Channel } from '@tauri-apps/api/core' + +export interface ShortcutEvent { + shortcut: string + id: number + state: 'Released' | 'Pressed' +} + +export type ShortcutHandler = (event: ShortcutEvent) => void + +/** + * Register a global shortcut or a list of shortcuts. + * + * The handler is called when any of the registered shortcuts are pressed by the user. + * + * If the shortcut is already taken by another application, the handler will not be triggered. + * Make sure the shortcut is as unique as possible while still taking user experience into consideration. + * + * @example + * ```typescript + * import { register } from '@tauri-apps/plugin-global-shortcut'; + * + * // register a single hotkey + * await register('CommandOrControl+Shift+C', (event) => { + * if (event.state === "Pressed") { + * console.log('Shortcut triggered'); + * } + * }); + * + * // or register multiple hotkeys at once + * await register(['CommandOrControl+Shift+C', 'Alt+A'], (event) => { + * console.log(`Shortcut ${event.shortcut} triggered`); + * }); + * ``` + * + * @param shortcut Shortcut definition, modifiers and key separated by "+" e.g. CmdOrControl+Q + * @param handler Shortcut handler callback - takes the triggered shortcut as argument + * + * @since 2.0.0 + */ +async function register( + shortcuts: string | string[], + handler: ShortcutHandler +): Promise { + const h = new Channel() + h.onmessage = handler + + return await invoke('plugin:global-shortcut|register', { + shortcuts: Array.isArray(shortcuts) ? shortcuts : [shortcuts], + handler: h + }) +} + +/** + * Unregister a global shortcut or a list of shortcuts. + * + * @example + * ```typescript + * import { unregister } from '@tauri-apps/plugin-global-shortcut'; + * + * // unregister a single hotkey + * await unregister('CmdOrControl+Space'); + * + * // or unregister multiple hotkeys at the same time + * await unregister(['CmdOrControl+Space', 'Alt+A']); + * ``` + * + * @param shortcut shortcut definition (modifiers and key separated by "+" e.g. CmdOrControl+Q), also accepts a list of shortcuts + * + * @since 2.0.0 + */ +async function unregister(shortcuts: string | string[]): Promise { + return await invoke('plugin:global-shortcut|unregister', { + shortcuts: Array.isArray(shortcuts) ? shortcuts : [shortcuts] + }) +} + +/** + * Unregister all global shortcuts. + * + * @example + * ```typescript + * import { unregisterAll } from '@tauri-apps/plugin-global-shortcut'; + * await unregisterAll(); + * ``` + * @since 2.0.0 + */ +async function unregisterAll(): Promise { + return await invoke('plugin:global-shortcut|unregister_all', {}) +} + +/** + * Determines whether the given shortcut is registered by this application or not. + * + * If the shortcut is registered by another application, it will still return `false`. + * + * @example + * ```typescript + * import { isRegistered } from '@tauri-apps/plugin-global-shortcut'; + * const isRegistered = await isRegistered('CommandOrControl+P'); + * ``` + * + * @param shortcut shortcut definition, modifiers and key separated by "+" e.g. CmdOrControl+Q + * + * @since 2.0.0 + */ +async function isRegistered(shortcut: string): Promise { + return await invoke('plugin:global-shortcut|is_registered', { + shortcut + }) +} + +export { register, unregister, unregisterAll, isRegistered } diff --git a/packages/kbot/gui/app/plugins/global-shortcut/package.json b/packages/kbot/gui/app/plugins/global-shortcut/package.json new file mode 100644 index 00000000..2b796c7a --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/package.json @@ -0,0 +1,29 @@ +{ + "name": "@tauri-apps/plugin-global-shortcut", + "version": "2.3.0", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/global-shortcut/permissions/autogenerated/commands/is_registered.toml b/packages/kbot/gui/app/plugins/global-shortcut/permissions/autogenerated/commands/is_registered.toml new file mode 100644 index 00000000..2dd73ace --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/permissions/autogenerated/commands/is_registered.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-is-registered" +description = "Enables the is_registered command without any pre-configured scope." +commands.allow = ["is_registered"] + +[[permission]] +identifier = "deny-is-registered" +description = "Denies the is_registered command without any pre-configured scope." +commands.deny = ["is_registered"] diff --git a/packages/kbot/gui/app/plugins/global-shortcut/permissions/autogenerated/commands/register.toml b/packages/kbot/gui/app/plugins/global-shortcut/permissions/autogenerated/commands/register.toml new file mode 100644 index 00000000..4eec17dc --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/permissions/autogenerated/commands/register.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-register" +description = "Enables the register command without any pre-configured scope." +commands.allow = ["register"] + +[[permission]] +identifier = "deny-register" +description = "Denies the register command without any pre-configured scope." +commands.deny = ["register"] diff --git a/packages/kbot/gui/app/plugins/global-shortcut/permissions/autogenerated/commands/register_all.toml b/packages/kbot/gui/app/plugins/global-shortcut/permissions/autogenerated/commands/register_all.toml new file mode 100644 index 00000000..df5d0ecf --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/permissions/autogenerated/commands/register_all.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-register-all" +description = "Enables the register_all command without any pre-configured scope." +commands.allow = ["register_all"] + +[[permission]] +identifier = "deny-register-all" +description = "Denies the register_all command without any pre-configured scope." +commands.deny = ["register_all"] diff --git a/packages/kbot/gui/app/plugins/global-shortcut/permissions/autogenerated/commands/unregister.toml b/packages/kbot/gui/app/plugins/global-shortcut/permissions/autogenerated/commands/unregister.toml new file mode 100644 index 00000000..5d33c97c --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/permissions/autogenerated/commands/unregister.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-unregister" +description = "Enables the unregister command without any pre-configured scope." +commands.allow = ["unregister"] + +[[permission]] +identifier = "deny-unregister" +description = "Denies the unregister command without any pre-configured scope." +commands.deny = ["unregister"] diff --git a/packages/kbot/gui/app/plugins/global-shortcut/permissions/autogenerated/commands/unregister_all.toml b/packages/kbot/gui/app/plugins/global-shortcut/permissions/autogenerated/commands/unregister_all.toml new file mode 100644 index 00000000..12c12f8b --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/permissions/autogenerated/commands/unregister_all.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-unregister-all" +description = "Enables the unregister_all command without any pre-configured scope." +commands.allow = ["unregister_all"] + +[[permission]] +identifier = "deny-unregister-all" +description = "Denies the unregister_all command without any pre-configured scope." +commands.deny = ["unregister_all"] diff --git a/packages/kbot/gui/app/plugins/global-shortcut/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/global-shortcut/permissions/autogenerated/reference.md new file mode 100644 index 00000000..240224cd --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/permissions/autogenerated/reference.md @@ -0,0 +1,146 @@ +## Default Permission + +No features are enabled by default, as we believe +the shortcuts can be inherently dangerous and it is +application specific if specific shortcuts should be +registered or unregistered. + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`global-shortcut:allow-is-registered` + + + +Enables the is_registered command without any pre-configured scope. + +
+ +`global-shortcut:deny-is-registered` + + + +Denies the is_registered command without any pre-configured scope. + +
+ +`global-shortcut:allow-register` + + + +Enables the register command without any pre-configured scope. + +
+ +`global-shortcut:deny-register` + + + +Denies the register command without any pre-configured scope. + +
+ +`global-shortcut:allow-register-all` + + + +Enables the register_all command without any pre-configured scope. + +
+ +`global-shortcut:deny-register-all` + + + +Denies the register_all command without any pre-configured scope. + +
+ +`global-shortcut:allow-unregister` + + + +Enables the unregister command without any pre-configured scope. + +
+ +`global-shortcut:deny-unregister` + + + +Denies the unregister command without any pre-configured scope. + +
+ +`global-shortcut:allow-unregister-all` + + + +Enables the unregister_all command without any pre-configured scope. + +
+ +`global-shortcut:deny-unregister-all` + + + +Denies the unregister_all command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/global-shortcut/permissions/default.toml b/packages/kbot/gui/app/plugins/global-shortcut/permissions/default.toml new file mode 100644 index 00000000..fd32c51e --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/permissions/default.toml @@ -0,0 +1,10 @@ +"$schema" = "schemas/schema.json" +[default] +description = """ +No features are enabled by default, as we believe +the shortcuts can be inherently dangerous and it is +application specific if specific shortcuts should be +registered or unregistered. +""" + +permissions = [] diff --git a/packages/kbot/gui/app/plugins/global-shortcut/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/global-shortcut/permissions/schemas/schema.json new file mode 100644 index 00000000..1224869c --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/permissions/schemas/schema.json @@ -0,0 +1,366 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the is_registered command without any pre-configured scope.", + "type": "string", + "const": "allow-is-registered", + "markdownDescription": "Enables the is_registered command without any pre-configured scope." + }, + { + "description": "Denies the is_registered command without any pre-configured scope.", + "type": "string", + "const": "deny-is-registered", + "markdownDescription": "Denies the is_registered command without any pre-configured scope." + }, + { + "description": "Enables the register command without any pre-configured scope.", + "type": "string", + "const": "allow-register", + "markdownDescription": "Enables the register command without any pre-configured scope." + }, + { + "description": "Denies the register command without any pre-configured scope.", + "type": "string", + "const": "deny-register", + "markdownDescription": "Denies the register command without any pre-configured scope." + }, + { + "description": "Enables the register_all command without any pre-configured scope.", + "type": "string", + "const": "allow-register-all", + "markdownDescription": "Enables the register_all command without any pre-configured scope." + }, + { + "description": "Denies the register_all command without any pre-configured scope.", + "type": "string", + "const": "deny-register-all", + "markdownDescription": "Denies the register_all command without any pre-configured scope." + }, + { + "description": "Enables the unregister command without any pre-configured scope.", + "type": "string", + "const": "allow-unregister", + "markdownDescription": "Enables the unregister command without any pre-configured scope." + }, + { + "description": "Denies the unregister command without any pre-configured scope.", + "type": "string", + "const": "deny-unregister", + "markdownDescription": "Denies the unregister command without any pre-configured scope." + }, + { + "description": "Enables the unregister_all command without any pre-configured scope.", + "type": "string", + "const": "allow-unregister-all", + "markdownDescription": "Enables the unregister_all command without any pre-configured scope." + }, + { + "description": "Denies the unregister_all command without any pre-configured scope.", + "type": "string", + "const": "deny-unregister-all", + "markdownDescription": "Denies the unregister_all command without any pre-configured scope." + }, + { + "description": "No features are enabled by default, as we believe\nthe shortcuts can be inherently dangerous and it is\napplication specific if specific shortcuts should be\nregistered or unregistered.\n", + "type": "string", + "const": "default", + "markdownDescription": "No features are enabled by default, as we believe\nthe shortcuts can be inherently dangerous and it is\napplication specific if specific shortcuts should be\nregistered or unregistered.\n" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/global-shortcut/rollup.config.js b/packages/kbot/gui/app/plugins/global-shortcut/rollup.config.js new file mode 100644 index 00000000..1f349ec8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/rollup.config.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' + +export default createConfig() diff --git a/packages/kbot/gui/app/plugins/global-shortcut/src/error.rs b/packages/kbot/gui/app/plugins/global-shortcut/src/error.rs new file mode 100644 index 00000000..37392b32 --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/src/error.rs @@ -0,0 +1,37 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{Serialize, Serializer}; + +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum Error { + #[error("{0}")] + GlobalHotkey(String), + #[error(transparent)] + RecvError(#[from] std::sync::mpsc::RecvError), + #[error(transparent)] + Tauri(#[from] tauri::Error), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} + +impl From for Error { + fn from(value: global_hotkey::Error) -> Self { + Self::GlobalHotkey(value.to_string()) + } +} + +impl From for Error { + fn from(value: global_hotkey::hotkey::HotKeyParseError) -> Self { + Self::GlobalHotkey(value.to_string()) + } +} diff --git a/packages/kbot/gui/app/plugins/global-shortcut/src/lib.rs b/packages/kbot/gui/app/plugins/global-shortcut/src/lib.rs new file mode 100644 index 00000000..450bb598 --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/src/lib.rs @@ -0,0 +1,436 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Register global shortcuts. +//! +//! - Supported platforms: Windows, Linux and macOS. + +#![doc( + html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", + html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" +)] +#![cfg(not(any(target_os = "android", target_os = "ios")))] + +use std::{ + collections::HashMap, + str::FromStr, + sync::{Arc, Mutex}, +}; + +use global_hotkey::GlobalHotKeyEvent; +pub use global_hotkey::{ + hotkey::{Code, HotKey as Shortcut, Modifiers}, + GlobalHotKeyEvent as ShortcutEvent, HotKeyState as ShortcutState, +}; +use serde::Serialize; +use tauri::{ + ipc::Channel, + plugin::{Builder as PluginBuilder, TauriPlugin}, + AppHandle, Manager, Runtime, State, +}; + +mod error; + +pub use error::Error; +type Result = std::result::Result; + +type HotKeyId = u32; +type HandlerFn = Box, &Shortcut, ShortcutEvent) + Send + Sync + 'static>; + +pub struct ShortcutWrapper(Shortcut); + +impl From for ShortcutWrapper { + fn from(value: Shortcut) -> Self { + Self(value) + } +} + +impl TryFrom<&str> for ShortcutWrapper { + type Error = global_hotkey::hotkey::HotKeyParseError; + fn try_from(value: &str) -> std::result::Result { + Shortcut::from_str(value).map(ShortcutWrapper) + } +} + +struct RegisteredShortcut { + shortcut: Shortcut, + handler: Option>>, +} + +struct GlobalHotKeyManager(global_hotkey::GlobalHotKeyManager); + +/// SAFETY: we ensure it is run on main thread only +unsafe impl Send for GlobalHotKeyManager {} +/// SAFETY: we ensure it is run on main thread only +unsafe impl Sync for GlobalHotKeyManager {} + +pub struct GlobalShortcut { + #[allow(dead_code)] + app: AppHandle, + manager: Arc, + shortcuts: Arc>>>, +} + +macro_rules! run_main_thread { + ($handle:expr, $manager:expr, |$m:ident| $ex:expr) => {{ + let (tx, rx) = std::sync::mpsc::channel(); + let manager = $manager.clone(); + let task = move || { + let f = |$m: &GlobalHotKeyManager| $ex; + let _ = tx.send(f(&*manager)); + }; + $handle.run_on_main_thread(task)?; + rx.recv()? + }}; +} + +impl GlobalShortcut { + fn register_internal, &Shortcut, ShortcutEvent) + Send + Sync + 'static>( + &self, + shortcut: Shortcut, + handler: Option, + ) -> Result<()> { + let id = shortcut.id(); + let handler = handler.map(|h| Arc::new(Box::new(h) as HandlerFn)); + run_main_thread!(self.app, self.manager, |m| m.0.register(shortcut))?; + self.shortcuts + .lock() + .unwrap() + .insert(id, RegisteredShortcut { shortcut, handler }); + Ok(()) + } + + fn register_multiple_internal(&self, shortcuts: S, handler: Option) -> Result<()> + where + S: IntoIterator, + F: Fn(&AppHandle, &Shortcut, ShortcutEvent) + Send + Sync + 'static, + { + let handler = handler.map(|h| Arc::new(Box::new(h) as HandlerFn)); + + let hotkeys = shortcuts.into_iter().collect::>(); + + let mut shortcuts = self.shortcuts.lock().unwrap(); + for shortcut in hotkeys { + run_main_thread!(self.app, self.manager, |m| m.0.register(shortcut))?; + shortcuts.insert( + shortcut.id(), + RegisteredShortcut { + shortcut, + handler: handler.clone(), + }, + ); + } + + Ok(()) + } +} + +impl GlobalShortcut { + /// Register a shortcut. + pub fn register(&self, shortcut: S) -> Result<()> + where + S: TryInto, + S::Error: std::error::Error, + { + self.register_internal( + try_into_shortcut(shortcut)?, + None::, &Shortcut, ShortcutEvent)>, + ) + } + + /// Register a shortcut with a handler. + pub fn on_shortcut(&self, shortcut: S, handler: F) -> Result<()> + where + S: TryInto, + S::Error: std::error::Error, + F: Fn(&AppHandle, &Shortcut, ShortcutEvent) + Send + Sync + 'static, + { + self.register_internal(try_into_shortcut(shortcut)?, Some(handler)) + } + + /// Register multiple shortcuts. + pub fn register_multiple(&self, shortcuts: S) -> Result<()> + where + S: IntoIterator, + T: TryInto, + T::Error: std::error::Error, + { + let mut s = Vec::new(); + for shortcut in shortcuts { + s.push(try_into_shortcut(shortcut)?); + } + self.register_multiple_internal(s, None::, &Shortcut, ShortcutEvent)>) + } + + /// Register multiple shortcuts with a handler. + pub fn on_shortcuts(&self, shortcuts: S, handler: F) -> Result<()> + where + S: IntoIterator, + T: TryInto, + T::Error: std::error::Error, + F: Fn(&AppHandle, &Shortcut, ShortcutEvent) + Send + Sync + 'static, + { + let mut s = Vec::new(); + for shortcut in shortcuts { + s.push(try_into_shortcut(shortcut)?); + } + self.register_multiple_internal(s, Some(handler)) + } + + /// Unregister a shortcut + pub fn unregister>(&self, shortcut: S) -> Result<()> + where + S::Error: std::error::Error, + { + let shortcut = try_into_shortcut(shortcut)?; + run_main_thread!(self.app, self.manager, |m| m.0.unregister(shortcut))?; + self.shortcuts.lock().unwrap().remove(&shortcut.id()); + Ok(()) + } + + /// Unregister multiple shortcuts. + pub fn unregister_multiple, S: IntoIterator>( + &self, + shortcuts: S, + ) -> Result<()> + where + T::Error: std::error::Error, + { + let mut mapped_shortcuts = Vec::new(); + for shortcut in shortcuts { + mapped_shortcuts.push(try_into_shortcut(shortcut)?); + } + + { + let mapped_shortcuts = mapped_shortcuts.clone(); + #[rustfmt::skip] + run_main_thread!(self.app, self.manager, |m| m.0.unregister_all(&mapped_shortcuts))?; + } + + let mut shortcuts = self.shortcuts.lock().unwrap(); + for s in mapped_shortcuts { + shortcuts.remove(&s.id()); + } + + Ok(()) + } + + /// Unregister all registered shortcuts. + pub fn unregister_all(&self) -> Result<()> { + let mut shortcuts = self.shortcuts.lock().unwrap(); + let hotkeys = std::mem::take(&mut *shortcuts); + let hotkeys = hotkeys.values().map(|s| s.shortcut).collect::>(); + #[rustfmt::skip] + let res = run_main_thread!(self.app, self.manager, |m| m.0.unregister_all(hotkeys.as_slice())); + res.map_err(Into::into) + } + + /// Determines whether the given shortcut is registered by this application or not. + /// + /// If the shortcut is registered by another application, it will still return `false`. + pub fn is_registered>(&self, shortcut: S) -> bool + where + S::Error: std::error::Error, + { + if let Ok(shortcut) = try_into_shortcut(shortcut) { + self.shortcuts.lock().unwrap().contains_key(&shortcut.id()) + } else { + false + } + } +} + +pub trait GlobalShortcutExt { + fn global_shortcut(&self) -> &GlobalShortcut; +} + +impl> GlobalShortcutExt for T { + fn global_shortcut(&self) -> &GlobalShortcut { + self.state::>().inner() + } +} + +fn parse_shortcut>(shortcut: S) -> Result { + shortcut.as_ref().parse().map_err(Into::into) +} + +fn try_into_shortcut>(shortcut: S) -> Result +where + S::Error: std::error::Error, +{ + shortcut + .try_into() + .map(|s| s.0) + .map_err(|e| Error::GlobalHotkey(e.to_string())) +} + +#[derive(Clone, Serialize)] +struct ShortcutJsEvent { + shortcut: String, + id: u32, + state: ShortcutState, +} + +#[tauri::command] +fn register( + _app: AppHandle, + global_shortcut: State<'_, GlobalShortcut>, + shortcuts: Vec, + handler: Channel, +) -> Result<()> { + let mut hotkeys = Vec::new(); + + let mut shortcut_map = HashMap::new(); + for shortcut in shortcuts { + let hotkey = parse_shortcut(&shortcut)?; + shortcut_map.insert(hotkey.id(), shortcut); + hotkeys.push(hotkey); + } + + global_shortcut.register_multiple_internal( + hotkeys, + Some( + move |_app: &AppHandle, shortcut: &Shortcut, e: ShortcutEvent| { + let js_event = ShortcutJsEvent { + id: e.id, + state: e.state, + shortcut: shortcut.into_string(), + }; + let _ = handler.send(js_event); + }, + ), + ) +} + +#[tauri::command] +fn unregister( + _app: AppHandle, + global_shortcut: State<'_, GlobalShortcut>, + shortcuts: Vec, +) -> Result<()> { + let mut hotkeys = Vec::new(); + for shortcut in shortcuts { + hotkeys.push(parse_shortcut(&shortcut)?); + } + global_shortcut.unregister_multiple(hotkeys) +} + +#[tauri::command] +fn unregister_all( + _app: AppHandle, + global_shortcut: State<'_, GlobalShortcut>, +) -> Result<()> { + global_shortcut.unregister_all() +} + +#[tauri::command] +fn is_registered( + _app: AppHandle, + global_shortcut: State<'_, GlobalShortcut>, + shortcut: String, +) -> Result { + Ok(global_shortcut.is_registered(parse_shortcut(shortcut)?)) +} + +pub struct Builder { + shortcuts: Vec, + handler: Option>, +} + +impl Default for Builder { + fn default() -> Self { + Self { + shortcuts: Vec::new(), + handler: Default::default(), + } + } +} + +impl Builder { + pub fn new() -> Self { + Self::default() + } + + /// Add a shortcut to be registerd. + pub fn with_shortcut(mut self, shortcut: T) -> Result + where + T: TryInto, + T::Error: std::error::Error, + { + self.shortcuts.push(try_into_shortcut(shortcut)?); + Ok(self) + } + + /// Add multiple shortcuts to be registerd. + pub fn with_shortcuts(mut self, shortcuts: S) -> Result + where + S: IntoIterator, + T: TryInto, + T::Error: std::error::Error, + { + for shortcut in shortcuts { + self.shortcuts.push(try_into_shortcut(shortcut)?); + } + + Ok(self) + } + + /// Specify a global shortcut handler that will be triggered for any and all shortcuts. + pub fn with_handler, &Shortcut, ShortcutEvent) + Send + Sync + 'static>( + mut self, + handler: F, + ) -> Self { + self.handler.replace(Box::new(handler)); + self + } + + pub fn build(self) -> TauriPlugin { + let handler = self.handler; + let shortcuts = self.shortcuts; + PluginBuilder::new("global-shortcut") + .invoke_handler(tauri::generate_handler![ + register, + unregister, + unregister_all, + is_registered, + ]) + .setup(move |app, _api| { + let manager = global_hotkey::GlobalHotKeyManager::new()?; + let mut store = HashMap::>::new(); + for shortcut in shortcuts { + manager.register(shortcut)?; + store.insert( + shortcut.id(), + RegisteredShortcut { + shortcut, + handler: None, + }, + ); + } + + let shortcuts = Arc::new(Mutex::new(store)); + let shortcuts_ = shortcuts.clone(); + + let app_handle = app.clone(); + GlobalHotKeyEvent::set_event_handler(Some(move |e: GlobalHotKeyEvent| { + if let Some(shortcut) = shortcuts_.lock().unwrap().get(&e.id) { + if let Some(handler) = &shortcut.handler { + handler(&app_handle, &shortcut.shortcut, e); + } + if let Some(handler) = &handler { + handler(&app_handle, &shortcut.shortcut, e); + } + } + })); + + app.manage(GlobalShortcut { + app: app.clone(), + manager: Arc::new(GlobalHotKeyManager(manager)), + shortcuts, + }); + Ok(()) + }) + .build() + } +} diff --git a/packages/kbot/gui/app/plugins/global-shortcut/tsconfig.json b/packages/kbot/gui/app/plugins/global-shortcut/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/global-shortcut/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/haptics/CHANGELOG.md b/packages/kbot/gui/app/plugins/haptics/CHANGELOG.md new file mode 100644 index 00000000..862f860e --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/CHANGELOG.md @@ -0,0 +1,57 @@ +# Changelog + +## \[2.3.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.2.5] + +- [`f634e524`](https://github.com/tauri-apps/plugins-workspace/commit/f634e5248ebe428f8305a59f74c13fc15147fb8e) This is an "empty" release to update the plugins' source files on crates.io and docs.rs. This should fix docs.rs build failures for projects using tauri plugins as dependencies. + +## \[2.2.4] + +- [`a1b3fa27`](https://github.com/tauri-apps/plugins-workspace/commit/a1b3fa27f11022c9b6622b4fab12d93239eb05de) ([#2515](https://github.com/tauri-apps/plugins-workspace/pull/2515) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Re-exported the `Geolocation`, `Haptics`, `Notification`, and `Os` structs so that they show up on docs.rs. + +## \[2.2.3] + +- [`406e6f48`](https://github.com/tauri-apps/plugins-workspace/commit/406e6f484cdc13d35c50fb949f7489ca9eeccc44) ([#2323](https://github.com/tauri-apps/plugins-workspace/pull/2323) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused build failures when the `haptics` or `geolocation` plugin was used without their `specta` feature flag enabled. + +## \[2.2.2] + +- [`c9c13a0f`](https://github.com/tauri-apps/plugins-workspace/commit/c9c13a0fe7cdaac223843f5ba33176252f8e22f5) ([#2316](https://github.com/tauri-apps/plugins-workspace/pull/2316) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) **Breaking change:** `specta` integration is now behind a `specta` feature flag like in Tauri. +- [`c9c13a0f`](https://github.com/tauri-apps/plugins-workspace/commit/c9c13a0fe7cdaac223843f5ba33176252f8e22f5) ([#2316](https://github.com/tauri-apps/plugins-workspace/pull/2316) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Unlock and widen `specta` version range to match Tauri. No API changes. + +## \[2.2.1] + +- [`fb67ab2b`](https://github.com/tauri-apps/plugins-workspace/commit/fb67ab2b926502bfc20d6b43fbdd156691ea8526) ([#2281](https://github.com/tauri-apps/plugins-workspace/pull/2281) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Added `specta-util` to fix a "dependency not found" compilation error. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.2] + +- [`b9147758`](https://github.com/tauri-apps/plugins-workspace/commit/b914775898c2bee7ceb20bd17ee595005cd17a64) ([#1679](https://github.com/tauri-apps/plugins-workspace/pull/1679) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Explicitly set a minimum macOS version for the Swift package. + +## \[2.0.0-rc.1] + +### changes + +- [`6b079cfd`](https://github.com/tauri-apps/plugins-workspace/commit/6b079cfdd107c94abc2c7300f6af00bac3ff4040) ([#1649](https://github.com/tauri-apps/plugins-workspace/pull/1649) by [@ahqsoftwares](https://github.com/tauri-apps/plugins-workspace/../../ahqsoftwares)) Remove targetSdk from build.kts files as it is deprecated and will be removed from DSL v9.0 + +## \[2.0.0-rc.0] + +- [`9606089b`](https://github.com/tauri-apps/plugins-workspace/commit/9606089b2add4a17f80ed5a09d59ce94824bd672) ([#1599](https://github.com/tauri-apps/plugins-workspace/pull/1599)) Initial release. +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. diff --git a/packages/kbot/gui/app/plugins/haptics/Cargo.toml b/packages/kbot/gui/app/plugins/haptics/Cargo.toml new file mode 100644 index 00000000..c7463eb3 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "tauri-plugin-haptics" +description = "Haptic feedback and vibrations on Android and iOS" +version = "2.3.0" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-haptics" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] +targets = ["x86_64-linux-android"] + +[package.metadata.platforms.support] +windows = { level = "none", notes = "" } +linux = { level = "none", notes = "" } +macos = { level = "none", notes = "" } +android = { level = "full", notes = "" } +ios = { level = "full", notes = "" } + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } +specta = { workspace = true, optional = true } + +[target.'cfg(target_os = "ios")'.dependencies] +tauri = { workspace = true, features = ["wry"] } + +[features] +specta = ["dep:specta", "tauri/specta"] diff --git a/packages/kbot/gui/app/plugins/haptics/LICENSE.spdx b/packages/kbot/gui/app/plugins/haptics/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/haptics/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/haptics/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/haptics/LICENSE_MIT b/packages/kbot/gui/app/plugins/haptics/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/haptics/README.md b/packages/kbot/gui/app/plugins/haptics/README.md new file mode 100644 index 00000000..8a7cd845 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/README.md @@ -0,0 +1,135 @@ +![haptics](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/haptics/banner.png) + +Haptic feedback and vibrations on Android and iOS. + +There are no standards/requirements for vibration support on Android, so the `feedback` APIs may not work correctly on more affordable phones, including recently released ones. + +| Platform | Supported | +| -------- | --------- | +| Linux | x | +| Windows | x | +| macOS | x | +| Android | ✓ | +| iOS | ✓ | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-haptics = "2.0.0" +# alternatively with Git: +tauri-plugin-haptics = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + + + +```sh +pnpm add @tauri-apps/plugin-haptics +# or +npm add @tauri-apps/plugin-haptics +# or +yarn add @tauri-apps/plugin-haptics +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_haptics::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Second, add the required permissions in the project: + +`src-tauri/capabilities/default.json` + +```json + "permissions": [ + "haptics:allow-impact-feedback", + "haptics:allow-notification-feedback", + "haptics:allow-selection-feedback", + "haptics:allow-vibrate" + ] +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { + vibrate, + impactFeedback, + notificationFeedback, + selectionFeedback +} from '@tauri-apps/plugin-haptics' + +await vibrate(1) +await impactFeedback('medium') +await notificationFeedback('warning') +await selectionFeedback() +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Contributed By + + + + + + + + +
+ + CrabNebula + + + + Rescue.co + +
+ +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/haptics/SECURITY.md b/packages/kbot/gui/app/plugins/haptics/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/haptics/android/.gitignore b/packages/kbot/gui/app/plugins/haptics/android/.gitignore new file mode 100644 index 00000000..c0f21ec2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/android/.gitignore @@ -0,0 +1,2 @@ +/build +/.tauri diff --git a/packages/kbot/gui/app/plugins/haptics/android/build.gradle.kts b/packages/kbot/gui/app/plugins/haptics/android/build.gradle.kts new file mode 100644 index 00000000..7de1d372 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/android/build.gradle.kts @@ -0,0 +1,45 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "app.tauri.haptics" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.0") + implementation("com.google.android.material:material:1.7.0") + implementation("com.fasterxml.jackson.core:jackson-databind:2.15.3") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + implementation(project(":tauri-android")) +} diff --git a/packages/kbot/gui/app/plugins/haptics/android/proguard-rules.pro b/packages/kbot/gui/app/plugins/haptics/android/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/haptics/android/settings.gradle b/packages/kbot/gui/app/plugins/haptics/android/settings.gradle new file mode 100644 index 00000000..14a752e4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/android/settings.gradle @@ -0,0 +1,2 @@ +include ':tauri-android' +project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/packages/kbot/gui/app/plugins/haptics/android/src/androidTest/java/ExampleInstrumentedTest.kt b/packages/kbot/gui/app/plugins/haptics/android/src/androidTest/java/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..1b408ac8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/android/src/androidTest/java/ExampleInstrumentedTest.kt @@ -0,0 +1,28 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.haptics + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("app.tauri.haptics", appContext.packageName) + } +} diff --git a/packages/kbot/gui/app/plugins/haptics/android/src/main/AndroidManifest.xml b/packages/kbot/gui/app/plugins/haptics/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..042e61a7 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/android/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/packages/kbot/gui/app/plugins/haptics/android/src/main/java/HapticsPlugin.kt b/packages/kbot/gui/app/plugins/haptics/android/src/main/java/HapticsPlugin.kt new file mode 100644 index 00000000..5a6bf1b8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/android/src/main/java/HapticsPlugin.kt @@ -0,0 +1,143 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.haptics + +import android.app.Activity +import android.content.Context +import android.os.Build +import android.os.VibrationEffect +import android.os.Vibrator +import android.os.VibratorManager +import app.tauri.Logger +import app.tauri.annotation.Command +import app.tauri.annotation.InvokeArg +import app.tauri.annotation.TauriPlugin +import app.tauri.haptics.patterns.ImpactPatternHeavy +import app.tauri.haptics.patterns.ImpactPatternLight +import app.tauri.haptics.patterns.ImpactPatternMedium +import app.tauri.haptics.patterns.ImpactPatternRigid +import app.tauri.haptics.patterns.ImpactPatternSoft +import app.tauri.haptics.patterns.NotificationPatternError +import app.tauri.haptics.patterns.NotificationPatternSuccess +import app.tauri.haptics.patterns.NotificationPatternWarning +import app.tauri.haptics.patterns.Pattern +import app.tauri.haptics.patterns.SelectionPattern +import app.tauri.plugin.Invoke +import app.tauri.plugin.Plugin +import com.fasterxml.jackson.annotation.JsonProperty + +@InvokeArg +class HapticsOptions { + var duration: Long = 300 +} + +@InvokeArg +class NotificationFeedbackArgs { + val type: NotificationFeedbackType = NotificationFeedbackType.Success +} + +@InvokeArg +enum class NotificationFeedbackType { + @JsonProperty("success") + Success, + @JsonProperty("warning") + Warning, + @JsonProperty("error") + Error; + + fun into(): Pattern { + return when(this) { + Success -> NotificationPatternSuccess + Warning -> NotificationPatternWarning + Error -> NotificationPatternError + } + } +} + +@InvokeArg +class ImpactFeedbackArgs { + val style: ImpactFeedbackStyle = ImpactFeedbackStyle.Medium +} + +@InvokeArg +enum class ImpactFeedbackStyle { + @JsonProperty("light") + Light, + @JsonProperty("medium") + Medium, + @JsonProperty("heavy") + Heavy, + @JsonProperty("soft") + Soft, + @JsonProperty("rigid") + Rigid; + + fun into(): Pattern { + return when(this) { + Light -> ImpactPatternLight + Medium -> ImpactPatternMedium + Heavy -> ImpactPatternHeavy + Soft -> ImpactPatternSoft + Rigid -> ImpactPatternRigid + } + } +} + +@TauriPlugin +class HapticsPlugin(private val activity: Activity): Plugin(activity) { + private val vibrator: Vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val vibManager = activity.applicationContext.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager + vibManager.defaultVibrator + } else { + @Suppress("DEPRECATION") + activity.applicationContext.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + } + + // + // TAURI COMMANDS + // + + @Command + fun vibrate(invoke: Invoke) { + val args = invoke.parseArgs(HapticsOptions::class.java) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + vibrator.vibrate(VibrationEffect.createOneShot(args.duration, VibrationEffect.DEFAULT_AMPLITUDE)) + } else { + vibrator.vibrate(args.duration) + } + invoke.resolve() + } + + @Command + fun impactFeedback(invoke: Invoke) { + val args = invoke.parseArgs(ImpactFeedbackArgs::class.java) + vibratePattern(args.style.into()) + invoke.resolve() + } + + @Command + fun notificationFeedback(invoke: Invoke) { + val args = invoke.parseArgs(NotificationFeedbackArgs::class.java) + vibratePattern(args.type.into()) + invoke.resolve() + } + + // TODO: Consider breaking this up into Start,Change,End like capacitor + @Command + fun selectionFeedback(invoke: Invoke) { + vibratePattern(SelectionPattern) + invoke.resolve() + } + + // INTERNAL FUNCTIONS + + private fun vibratePattern(pattern: Pattern) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + vibrator.vibrate(VibrationEffect.createWaveform(pattern.timings, pattern.amplitudes, -1)) + } else { + vibrator.vibrate(pattern.oldSDKPattern, -1) + } + } +} diff --git a/packages/kbot/gui/app/plugins/haptics/android/src/main/java/patterns/Impact.kt b/packages/kbot/gui/app/plugins/haptics/android/src/main/java/patterns/Impact.kt new file mode 100644 index 00000000..d40e750d --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/android/src/main/java/patterns/Impact.kt @@ -0,0 +1,35 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.haptics.patterns + +val ImpactPatternLight = Pattern( + longArrayOf(0, 50), + intArrayOf(0, 30), + longArrayOf(0, 20) +) + +val ImpactPatternMedium = Pattern( + longArrayOf(0, 43), + intArrayOf(0, 50), + longArrayOf(0, 43) +) + +val ImpactPatternHeavy = Pattern( + longArrayOf(0, 60), + intArrayOf(0, 70), + longArrayOf(0, 61) +) + +val ImpactPatternSoft = Pattern( + longArrayOf(0, 50), + intArrayOf(0, 30), + longArrayOf(0, 20) +) + +val ImpactPatternRigid = Pattern( + longArrayOf(0, 43), + intArrayOf(0, 50), + longArrayOf(0, 43) +) \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/haptics/android/src/main/java/patterns/Notification.kt b/packages/kbot/gui/app/plugins/haptics/android/src/main/java/patterns/Notification.kt new file mode 100644 index 00000000..2f70c23e --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/android/src/main/java/patterns/Notification.kt @@ -0,0 +1,23 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.haptics.patterns + +val NotificationPatternSuccess = Pattern( + longArrayOf(0, 40, 100, 40), + intArrayOf(0, 50, 0, 60), + longArrayOf(0, 40, 100, 40) +) + +val NotificationPatternWarning = Pattern( + longArrayOf(0, 40, 120, 60), + intArrayOf(0, 40, 0, 60), + longArrayOf(0, 40, 120, 60) +) + +val NotificationPatternError = Pattern( + longArrayOf(0, 60, 100, 40, 80, 50), + intArrayOf(0, 50, 0, 40, 0, 50), + longArrayOf(0, 60, 100, 40, 80, 50) +) diff --git a/packages/kbot/gui/app/plugins/haptics/android/src/main/java/patterns/Pattern.kt b/packages/kbot/gui/app/plugins/haptics/android/src/main/java/patterns/Pattern.kt new file mode 100644 index 00000000..5f99b02c --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/android/src/main/java/patterns/Pattern.kt @@ -0,0 +1,11 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.haptics.patterns + +class Pattern ( + val timings: LongArray, + val amplitudes: IntArray, + val oldSDKPattern: LongArray +) {} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/haptics/android/src/main/java/patterns/Selection.kt b/packages/kbot/gui/app/plugins/haptics/android/src/main/java/patterns/Selection.kt new file mode 100644 index 00000000..02dce4c2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/android/src/main/java/patterns/Selection.kt @@ -0,0 +1,11 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.haptics.patterns + +val SelectionPattern = Pattern ( + timings = longArrayOf(0, 50), + amplitudes = intArrayOf(0, 30), + oldSDKPattern = longArrayOf(0, 70) +) \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/haptics/android/src/test/java/ExampleUnitTest.kt b/packages/kbot/gui/app/plugins/haptics/android/src/test/java/ExampleUnitTest.kt new file mode 100644 index 00000000..562188f0 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/android/src/test/java/ExampleUnitTest.kt @@ -0,0 +1,21 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.haptics + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/packages/kbot/gui/app/plugins/haptics/api-iife.js b/packages/kbot/gui/app/plugins/haptics/api-iife.js new file mode 100644 index 00000000..5cccb15f --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_HAPTICS__=function(r){"use strict";async function t(r,t={},e){return window.__TAURI_INTERNALS__.invoke(r,t,e)}var e;"function"==typeof SuppressedError&&SuppressedError,function(r){r.WINDOW_RESIZED="tauri://resize",r.WINDOW_MOVED="tauri://move",r.WINDOW_CLOSE_REQUESTED="tauri://close-requested",r.WINDOW_DESTROYED="tauri://destroyed",r.WINDOW_FOCUS="tauri://focus",r.WINDOW_BLUR="tauri://blur",r.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",r.WINDOW_THEME_CHANGED="tauri://theme-changed",r.WINDOW_CREATED="tauri://window-created",r.WEBVIEW_CREATED="tauri://webview-created",r.DRAG_ENTER="tauri://drag-enter",r.DRAG_OVER="tauri://drag-over",r.DRAG_DROP="tauri://drag-drop",r.DRAG_LEAVE="tauri://drag-leave"}(e||(e={}));const a={async vibrate(r){try{return{status:"ok",data:await t("plugin:haptics|vibrate",{duration:r})}}catch(r){if(r instanceof Error)throw r;return{status:"error",error:r}}},async impactFeedback(r){try{return{status:"ok",data:await t("plugin:haptics|impact_feedback",{style:r})}}catch(r){if(r instanceof Error)throw r;return{status:"error",error:r}}},async notificationFeedback(r){try{return{status:"ok",data:await t("plugin:haptics|notification_feedback",{type:r})}}catch(r){if(r instanceof Error)throw r;return{status:"error",error:r}}},async selectionFeedback(){try{return{status:"ok",data:await t("plugin:haptics|selection_feedback")}}catch(r){if(r instanceof Error)throw r;return{status:"error",error:r}}}},{vibrate:i,impactFeedback:c,notificationFeedback:n,selectionFeedback:o}=a;return r.impactFeedback=c,r.notificationFeedback=n,r.selectionFeedback=o,r.vibrate=i,r}({});Object.defineProperty(window.__TAURI__,"haptics",{value:__TAURI_PLUGIN_HAPTICS__})} diff --git a/packages/kbot/gui/app/plugins/haptics/build.rs b/packages/kbot/gui/app/plugins/haptics/build.rs new file mode 100644 index 00000000..2556cb67 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/build.rs @@ -0,0 +1,23 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +const COMMANDS: &[&str] = &[ + "vibrate", + "impact_feedback", + "notification_feedback", + "selection_feedback", +]; + +fn main() { + let result = tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .android_path("android") + .ios_path("ios") + .try_build(); + + // when building documentation for Android the plugin build result is always Err() and is irrelevant to the crate documentation build + if !(cfg!(docsrs) && std::env::var("TARGET").unwrap().contains("android")) { + result.unwrap(); + } +} diff --git a/packages/kbot/gui/app/plugins/haptics/contributors/crabnebula.svg b/packages/kbot/gui/app/plugins/haptics/contributors/crabnebula.svg new file mode 100644 index 00000000..40e24131 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/contributors/crabnebula.svg @@ -0,0 +1,31 @@ + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/haptics/contributors/rescue.png b/packages/kbot/gui/app/plugins/haptics/contributors/rescue.png new file mode 100644 index 00000000..2b5916f4 Binary files /dev/null and b/packages/kbot/gui/app/plugins/haptics/contributors/rescue.png differ diff --git a/packages/kbot/gui/app/plugins/haptics/guest-js/bindings.ts b/packages/kbot/gui/app/plugins/haptics/guest-js/bindings.ts new file mode 100644 index 00000000..d12920d8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/guest-js/bindings.ts @@ -0,0 +1,140 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +// @ts-nocheck +// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. + +/** user-defined commands **/ + +export const commands = { + async vibrate(duration: number): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('plugin:haptics|vibrate', { duration }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async impactFeedback( + style: ImpactFeedbackStyle + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('plugin:haptics|impact_feedback', { style }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async notificationFeedback( + type: NotificationFeedbackType + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('plugin:haptics|notification_feedback', { + type + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async selectionFeedback(): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('plugin:haptics|selection_feedback') + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + } +} + +/** user-defined events **/ + +/* export const events = __makeEvents__<{ + randomNumber: RandomNumber; +}>({ + randomNumber: "plugin:haptics:random-number", +}); */ + +/** user-defined statics **/ + +/** user-defined types **/ + +export type Error = never +export type ImpactFeedbackStyle = + | 'light' + | 'medium' + | 'heavy' + | 'soft' + | 'rigid' +export type NotificationFeedbackType = 'success' | 'warning' | 'error' +//export type RandomNumber = number; + +/** tauri-specta globals **/ + +import { invoke as TAURI_INVOKE } from '@tauri-apps/api/core' +import * as TAURI_API_EVENT from '@tauri-apps/api/event' +import { type WebviewWindow as __WebviewWindow__ } from '@tauri-apps/api/webviewWindow' + +type __EventObj__ = { + listen: ( + cb: TAURI_API_EVENT.EventCallback + ) => ReturnType> + once: ( + cb: TAURI_API_EVENT.EventCallback + ) => ReturnType> + emit: T extends null + ? (payload?: T) => ReturnType + : (payload: T) => ReturnType +} + +export type Result = + | { status: 'ok'; data: T } + | { status: 'error'; error: E } + +function __makeEvents__>( + mappings: Record +) { + return new Proxy( + {} as unknown as { + [K in keyof T]: __EventObj__ & { + (handle: __WebviewWindow__): __EventObj__ + } + }, + { + get: (_, event) => { + const name = mappings[event as keyof T] + + return new Proxy((() => {}) as any, { + apply: (_, __, [window]: [__WebviewWindow__]) => ({ + listen: (arg: any) => window.listen(name, arg), + once: (arg: any) => window.once(name, arg), + emit: (arg: any) => window.emit(name, arg) + }), + get: (_, command: keyof __EventObj__) => { + switch (command) { + case 'listen': + return (arg: any) => TAURI_API_EVENT.listen(name, arg) + case 'once': + return (arg: any) => TAURI_API_EVENT.once(name, arg) + case 'emit': + return (arg: any) => TAURI_API_EVENT.emit(name, arg) + } + } + }) + } + } + ) +} diff --git a/packages/kbot/gui/app/plugins/haptics/guest-js/index.ts b/packages/kbot/gui/app/plugins/haptics/guest-js/index.ts new file mode 100644 index 00000000..23485bdf --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/guest-js/index.ts @@ -0,0 +1,18 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/* eslint-disable @typescript-eslint/unbound-method */ + +import { commands } from './bindings' + +export const { + vibrate, + impactFeedback, + notificationFeedback, + selectionFeedback +} = commands + +export { ImpactFeedbackStyle, NotificationFeedbackType } from './bindings' + +// export { events }; diff --git a/packages/kbot/gui/app/plugins/haptics/ios/.gitignore b/packages/kbot/gui/app/plugins/haptics/ios/.gitignore new file mode 100644 index 00000000..5922fdaa --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/ios/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Package.resolved diff --git a/packages/kbot/gui/app/plugins/haptics/ios/Package.swift b/packages/kbot/gui/app/plugins/haptics/ios/Package.swift new file mode 100644 index 00000000..a64b9890 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/ios/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version:5.3 +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import PackageDescription + +let package = Package( + name: "tauri-plugin-haptics", + platforms: [ + .macOS(.v10_13), + .iOS(.v13), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "tauri-plugin-haptics", + type: .static, + targets: ["tauri-plugin-haptics"]) + ], + dependencies: [ + .package(name: "Tauri", path: "../.tauri/tauri-api") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "tauri-plugin-haptics", + dependencies: [ + .byName(name: "Tauri") + ], + path: "Sources") + ] +) diff --git a/packages/kbot/gui/app/plugins/haptics/ios/README.md b/packages/kbot/gui/app/plugins/haptics/ios/README.md new file mode 100644 index 00000000..3f7138b5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/ios/README.md @@ -0,0 +1,3 @@ +# Tauri Plugin Haptics + +A description of this package. diff --git a/packages/kbot/gui/app/plugins/haptics/ios/Sources/HapticsPlugin.swift b/packages/kbot/gui/app/plugins/haptics/ios/Sources/HapticsPlugin.swift new file mode 100644 index 00000000..96e3f275 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/ios/Sources/HapticsPlugin.swift @@ -0,0 +1,133 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import SwiftRs +import Tauri +import UIKit +import WebKit +import CoreHaptics +import AudioToolbox + +class ImpactFeedbackOptions: Decodable { + let style: ImpactFeedbackStyle +} + +enum ImpactFeedbackStyle: String, Decodable { + case light, medium, heavy, soft, rigid + + func into() -> UIImpactFeedbackGenerator.FeedbackStyle { + switch self { + case .light: + return .light + case .medium: + return .medium + case .heavy: + return .heavy + case .soft: + return .soft + case .rigid: + return .rigid + } + } +} + +class NotificationFeedbackOptions: Decodable { + let type: NotificationFeedbackType +} + +enum NotificationFeedbackType: String, Decodable { + case success, warning, error + + func into() -> UINotificationFeedbackGenerator.FeedbackType { + switch self { + case .success: + return .success + case .warning: + return .warning + case .error: + return .error + } + } +} + +class VibrateOptions: Decodable { + // TODO: Array + let duration: Double +} + +class HapticsPlugin: Plugin { + // + // Tauri commands + // + + @objc public func vibrate(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(VibrateOptions.self) + if CHHapticEngine.capabilitiesForHardware().supportsHaptics { + do { + let engine = try CHHapticEngine() + try engine.start() + engine.resetHandler = { [] in + do { + try engine.start() + } catch { + AudioServicesPlayAlertSound(kSystemSoundID_Vibrate) + } + } + // TODO: Make some of this (or all) configurable? + let intensity: CHHapticEventParameter = CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0) + let sharpness: CHHapticEventParameter = CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0) + let continuousEvent = CHHapticEvent( + eventType: .hapticContinuous, + parameters: [intensity, sharpness], + relativeTime: 0.0, + duration: args.duration/1000 + ) + let pattern = try CHHapticPattern(events: [continuousEvent], parameters: []) + let player = try engine.makePlayer(with: pattern) + + try player.start(atTime: 0) + } catch { + AudioServicesPlayAlertSound(kSystemSoundID_Vibrate) + } + } else { + AudioServicesPlayAlertSound(kSystemSoundID_Vibrate) + } + + Logger.error("VIBRATE END") + + invoke.resolve() + } + + @objc public func impactFeedback(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(ImpactFeedbackOptions.self) + let generator = UIImpactFeedbackGenerator(style: args.style.into()) + generator.prepare() + generator.impactOccurred() + + invoke.resolve() + } + + @objc public func notificationFeedback(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(NotificationFeedbackOptions.self) + let generator = UINotificationFeedbackGenerator() + generator.prepare() + generator.notificationOccurred(args.type.into()) + + invoke.resolve() + } + + // TODO: Consider breaking this up into Start,Change,End like capacitor + @objc public func selectionFeedback(_ invoke: Invoke) throws { + let generator = UISelectionFeedbackGenerator() + generator.prepare() + generator.selectionChanged() + + invoke.resolve() + } +} + +@_cdecl("init_plugin_haptics") +func initPlugin() -> Plugin { + return HapticsPlugin() +} diff --git a/packages/kbot/gui/app/plugins/haptics/ios/Tests/PluginTests/PluginTests.swift b/packages/kbot/gui/app/plugins/haptics/ios/Tests/PluginTests/PluginTests.swift new file mode 100644 index 00000000..99992ce4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/ios/Tests/PluginTests/PluginTests.swift @@ -0,0 +1,12 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import XCTest +@testable import ExamplePlugin + +final class ExamplePluginTests: XCTestCase { + func testExample() throws { + let plugin = ExamplePlugin() + } +} diff --git a/packages/kbot/gui/app/plugins/haptics/package.json b/packages/kbot/gui/app/plugins/haptics/package.json new file mode 100644 index 00000000..6111cef4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/package.json @@ -0,0 +1,29 @@ +{ + "name": "@tauri-apps/plugin-haptics", + "version": "2.3.0", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/haptics/permissions/autogenerated/commands/impact_feedback.toml b/packages/kbot/gui/app/plugins/haptics/permissions/autogenerated/commands/impact_feedback.toml new file mode 100644 index 00000000..13fce392 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/permissions/autogenerated/commands/impact_feedback.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-impact-feedback" +description = "Enables the impact_feedback command without any pre-configured scope." +commands.allow = ["impact_feedback"] + +[[permission]] +identifier = "deny-impact-feedback" +description = "Denies the impact_feedback command without any pre-configured scope." +commands.deny = ["impact_feedback"] diff --git a/packages/kbot/gui/app/plugins/haptics/permissions/autogenerated/commands/notification_feedback.toml b/packages/kbot/gui/app/plugins/haptics/permissions/autogenerated/commands/notification_feedback.toml new file mode 100644 index 00000000..0c2782c5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/permissions/autogenerated/commands/notification_feedback.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-notification-feedback" +description = "Enables the notification_feedback command without any pre-configured scope." +commands.allow = ["notification_feedback"] + +[[permission]] +identifier = "deny-notification-feedback" +description = "Denies the notification_feedback command without any pre-configured scope." +commands.deny = ["notification_feedback"] diff --git a/packages/kbot/gui/app/plugins/haptics/permissions/autogenerated/commands/selection_feedback.toml b/packages/kbot/gui/app/plugins/haptics/permissions/autogenerated/commands/selection_feedback.toml new file mode 100644 index 00000000..63e645d8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/permissions/autogenerated/commands/selection_feedback.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-selection-feedback" +description = "Enables the selection_feedback command without any pre-configured scope." +commands.allow = ["selection_feedback"] + +[[permission]] +identifier = "deny-selection-feedback" +description = "Denies the selection_feedback command without any pre-configured scope." +commands.deny = ["selection_feedback"] diff --git a/packages/kbot/gui/app/plugins/haptics/permissions/autogenerated/commands/vibrate.toml b/packages/kbot/gui/app/plugins/haptics/permissions/autogenerated/commands/vibrate.toml new file mode 100644 index 00000000..4c2fe94e --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/permissions/autogenerated/commands/vibrate.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-vibrate" +description = "Enables the vibrate command without any pre-configured scope." +commands.allow = ["vibrate"] + +[[permission]] +identifier = "deny-vibrate" +description = "Denies the vibrate command without any pre-configured scope." +commands.deny = ["vibrate"] diff --git a/packages/kbot/gui/app/plugins/haptics/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/haptics/permissions/autogenerated/reference.md new file mode 100644 index 00000000..82a911bf --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/permissions/autogenerated/reference.md @@ -0,0 +1,113 @@ +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`haptics:allow-impact-feedback` + + + +Enables the impact_feedback command without any pre-configured scope. + +
+ +`haptics:deny-impact-feedback` + + + +Denies the impact_feedback command without any pre-configured scope. + +
+ +`haptics:allow-notification-feedback` + + + +Enables the notification_feedback command without any pre-configured scope. + +
+ +`haptics:deny-notification-feedback` + + + +Denies the notification_feedback command without any pre-configured scope. + +
+ +`haptics:allow-selection-feedback` + + + +Enables the selection_feedback command without any pre-configured scope. + +
+ +`haptics:deny-selection-feedback` + + + +Denies the selection_feedback command without any pre-configured scope. + +
+ +`haptics:allow-vibrate` + + + +Enables the vibrate command without any pre-configured scope. + +
+ +`haptics:deny-vibrate` + + + +Denies the vibrate command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/haptics/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/haptics/permissions/schemas/schema.json new file mode 100644 index 00000000..ed217dc4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/permissions/schemas/schema.json @@ -0,0 +1,348 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the impact_feedback command without any pre-configured scope.", + "type": "string", + "const": "allow-impact-feedback", + "markdownDescription": "Enables the impact_feedback command without any pre-configured scope." + }, + { + "description": "Denies the impact_feedback command without any pre-configured scope.", + "type": "string", + "const": "deny-impact-feedback", + "markdownDescription": "Denies the impact_feedback command without any pre-configured scope." + }, + { + "description": "Enables the notification_feedback command without any pre-configured scope.", + "type": "string", + "const": "allow-notification-feedback", + "markdownDescription": "Enables the notification_feedback command without any pre-configured scope." + }, + { + "description": "Denies the notification_feedback command without any pre-configured scope.", + "type": "string", + "const": "deny-notification-feedback", + "markdownDescription": "Denies the notification_feedback command without any pre-configured scope." + }, + { + "description": "Enables the selection_feedback command without any pre-configured scope.", + "type": "string", + "const": "allow-selection-feedback", + "markdownDescription": "Enables the selection_feedback command without any pre-configured scope." + }, + { + "description": "Denies the selection_feedback command without any pre-configured scope.", + "type": "string", + "const": "deny-selection-feedback", + "markdownDescription": "Denies the selection_feedback command without any pre-configured scope." + }, + { + "description": "Enables the vibrate command without any pre-configured scope.", + "type": "string", + "const": "allow-vibrate", + "markdownDescription": "Enables the vibrate command without any pre-configured scope." + }, + { + "description": "Denies the vibrate command without any pre-configured scope.", + "type": "string", + "const": "deny-vibrate", + "markdownDescription": "Denies the vibrate command without any pre-configured scope." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/haptics/rollup.config.js b/packages/kbot/gui/app/plugins/haptics/rollup.config.js new file mode 100644 index 00000000..1f349ec8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/rollup.config.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' + +export default createConfig() diff --git a/packages/kbot/gui/app/plugins/haptics/src/commands.rs b/packages/kbot/gui/app/plugins/haptics/src/commands.rs new file mode 100644 index 00000000..76f097c0 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/src/commands.rs @@ -0,0 +1,33 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tauri::{command, AppHandle, Runtime}; + +use crate::{HapticsExt, ImpactFeedbackStyle, NotificationFeedbackType, Result}; + +#[command] +pub(crate) async fn vibrate(app: AppHandle, duration: u32) -> Result<()> { + app.haptics().vibrate(duration) +} + +#[command] +pub(crate) async fn impact_feedback( + app: AppHandle, + style: ImpactFeedbackStyle, +) -> Result<()> { + app.haptics().impact_feedback(style) +} + +#[command] +pub(crate) async fn notification_feedback( + app: AppHandle, + r#type: NotificationFeedbackType, +) -> Result<()> { + app.haptics().notification_feedback(r#type) +} + +#[command] +pub(crate) async fn selection_feedback(app: AppHandle) -> Result<()> { + app.haptics().selection_feedback() +} diff --git a/packages/kbot/gui/app/plugins/haptics/src/desktop.rs b/packages/kbot/gui/app/plugins/haptics/src/desktop.rs new file mode 100644 index 00000000..b04b7567 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/src/desktop.rs @@ -0,0 +1,36 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::de::DeserializeOwned; +use tauri::{plugin::PluginApi, AppHandle, Runtime}; + +use crate::models::*; + +pub fn init( + app: &AppHandle, + _api: PluginApi, +) -> crate::Result> { + Ok(Haptics(app.clone())) +} + +/// Access to the haptics APIs. +pub struct Haptics(AppHandle); + +impl Haptics { + pub fn vibrate(&self, _duration: u32) -> crate::Result<()> { + Ok(()) + } + + pub fn impact_feedback(&self, _style: ImpactFeedbackStyle) -> crate::Result<()> { + Ok(()) + } + + pub fn notification_feedback(&self, _type: NotificationFeedbackType) -> crate::Result<()> { + Ok(()) + } + + pub fn selection_feedback(&self) -> crate::Result<()> { + Ok(()) + } +} diff --git a/packages/kbot/gui/app/plugins/haptics/src/error.rs b/packages/kbot/gui/app/plugins/haptics/src/error.rs new file mode 100644 index 00000000..0fba5445 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/src/error.rs @@ -0,0 +1,30 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{ser::Serializer, Serialize}; + +pub type Result = std::result::Result; + +// TODO: Improve Error handling (different typed errors instead of one (stringified) PluginInvokeError for all mobile errors) + +#[derive(Debug, thiserror::Error)] +#[cfg_attr(feature = "specta", derive(specta::Type))] +pub enum Error { + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke( + #[cfg_attr(feature = "specta", serde(skip))] + #[from] + tauri::plugin::mobile::PluginInvokeError, + ), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/packages/kbot/gui/app/plugins/haptics/src/lib.rs b/packages/kbot/gui/app/plugins/haptics/src/lib.rs new file mode 100644 index 00000000..31798743 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/src/lib.rs @@ -0,0 +1,57 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tauri::{ + plugin::{Builder, TauriPlugin}, + Manager, Runtime, +}; + +pub use models::*; + +#[cfg(desktop)] +mod desktop; +#[cfg(mobile)] +mod mobile; + +mod commands; +mod error; +mod models; + +pub use error::{Error, Result}; + +#[cfg(desktop)] +pub use desktop::Haptics; +#[cfg(mobile)] +pub use mobile::Haptics; + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the haptics APIs. +pub trait HapticsExt { + fn haptics(&self) -> &Haptics; +} + +impl> crate::HapticsExt for T { + fn haptics(&self) -> &Haptics { + self.state::>().inner() + } +} + +/// Initializes the plugin. +pub fn init() -> TauriPlugin { + Builder::new("haptics") + .invoke_handler(tauri::generate_handler![ + commands::vibrate, + commands::impact_feedback, + commands::notification_feedback, + commands::selection_feedback + ]) + .setup(|app, api| { + #[cfg(mobile)] + let haptics = mobile::init(app, api)?; + #[cfg(desktop)] + let haptics = desktop::init(app, api)?; + app.manage(haptics); + Ok(()) + }) + .build() +} diff --git a/packages/kbot/gui/app/plugins/haptics/src/mobile.rs b/packages/kbot/gui/app/plugins/haptics/src/mobile.rs new file mode 100644 index 00000000..2b1e2036 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/src/mobile.rs @@ -0,0 +1,76 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{de::DeserializeOwned, Serialize}; +use tauri::{ + plugin::{PluginApi, PluginHandle}, + AppHandle, Runtime, +}; + +use crate::models::*; + +#[cfg(target_os = "android")] +const PLUGIN_IDENTIFIER: &str = "app.tauri.haptics"; + +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_haptics); + +// initializes the Kotlin or Swift plugin classes +pub fn init( + _app: &AppHandle, + api: PluginApi, +) -> crate::Result> { + #[cfg(target_os = "android")] + let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "HapticsPlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_haptics)?; + Ok(Haptics(handle)) +} + +/// Access to the haptics APIs. +pub struct Haptics(PluginHandle); + +impl Haptics { + pub fn vibrate(&self, duration: u32) -> crate::Result<()> { + self.0 + .run_mobile_plugin("vibrate", VibratePayload { duration }) + .map_err(Into::into) + } + + pub fn impact_feedback(&self, style: ImpactFeedbackStyle) -> crate::Result<()> { + self.0 + .run_mobile_plugin("impactFeedback", ImpactFeedbackPayload { style }) + .map_err(Into::into) + } + + pub fn notification_feedback(&self, r#type: NotificationFeedbackType) -> crate::Result<()> { + self.0 + .run_mobile_plugin( + "notificationFeedback", + NotificationFeedbackPayload { r#type }, + ) + .map_err(Into::into) + } + + pub fn selection_feedback(&self) -> crate::Result<()> { + self.0 + .run_mobile_plugin("selectionFeedback", ()) + .map_err(Into::into) + } +} + +#[derive(Serialize)] +struct VibratePayload { + duration: u32, +} + +#[derive(Serialize)] +struct ImpactFeedbackPayload { + style: ImpactFeedbackStyle, +} + +#[derive(Serialize)] +struct NotificationFeedbackPayload { + r#type: NotificationFeedbackType, +} diff --git a/packages/kbot/gui/app/plugins/haptics/src/models.rs b/packages/kbot/gui/app/plugins/haptics/src/models.rs new file mode 100644 index 00000000..50a1fb16 --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/src/models.rs @@ -0,0 +1,36 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{Deserialize, Serialize}; +/* +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "specta", derive(specta::Type))] +#[serde(rename_all = "camelCase")] +pub struct HapticsOptions { + // TODO: support array to match web api + pub duration: u32, +} + */ + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "specta", derive(specta::Type))] +#[serde(rename_all = "camelCase")] +pub enum ImpactFeedbackStyle { + Light, + #[default] + Medium, + Heavy, + Soft, + Rigid, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "specta", derive(specta::Type))] +#[serde(rename_all = "camelCase")] +pub enum NotificationFeedbackType { + #[default] + Success, + Warning, + Error, +} diff --git a/packages/kbot/gui/app/plugins/haptics/tsconfig.json b/packages/kbot/gui/app/plugins/haptics/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/haptics/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/http/CHANGELOG.md b/packages/kbot/gui/app/plugins/http/CHANGELOG.md new file mode 100644 index 00000000..95bc6a40 --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/CHANGELOG.md @@ -0,0 +1,277 @@ +# Changelog + +## \[2.5.2] + +### Dependencies + +- Upgraded to `fs-js@2.4.2` + +## \[2.5.1] + +### Dependencies + +- Upgraded to `fs-js@2.4.1` + +## \[2.5.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +### Dependencies + +- Upgraded to `fs-js@2.4.0` + +## \[2.4.4] + +- [`ff384cba`](https://github.com/tauri-apps/plugins-workspace/commit/ff384cbabe82ae715798a4ee49fd07ffcfbcdb5d) ([#2636](https://github.com/tauri-apps/plugins-workspace/pull/2636) by [@asomethings](https://github.com/tauri-apps/plugins-workspace/../../asomethings)) Properly handle responses with status code 204. + +### Dependencies + +- Upgraded to `fs-js@2.3.0` + +## \[2.4.3] + +- [`37c0477a`](https://github.com/tauri-apps/plugins-workspace/commit/37c0477afe926d326573f1827045875ce8bf8187) ([#2561](https://github.com/tauri-apps/plugins-workspace/pull/2561)) Add `zstd` cargo feature flag to enable `reqwest/zstd` flag. +- [`9ebbfb2e`](https://github.com/tauri-apps/plugins-workspace/commit/9ebbfb2e3ccef8e0f277a0c02fe6b399b41feeb6) ([#1978](https://github.com/tauri-apps/plugins-workspace/pull/1978)) Persist cookies to disk and load it on next app start. + +### Dependencies + +- Upgraded to `fs-js@2.2.1` + +## \[2.4.2] + +- [`a15eedf3`](https://github.com/tauri-apps/plugins-workspace/commit/a15eedf37854344f7ffbcb0d373d848563817011) ([#2535](https://github.com/tauri-apps/plugins-workspace/pull/2535) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Fix `fetch` occasionally throwing an error due to trying to close the underline stream twice. + +## \[2.4.1] + +- [`d3183aa9`](https://github.com/tauri-apps/plugins-workspace/commit/d3183aa99da7ca67e627394132ddeb3b85ccef06) ([#2522](https://github.com/tauri-apps/plugins-workspace/pull/2522) by [@adrieljss](https://github.com/tauri-apps/plugins-workspace/../../adrieljss)) Fix `fetch` blocking until the whole response is read even if it was a streaming response. + +## \[2.4.0] + +- [`cb38f54f`](https://github.com/tauri-apps/plugins-workspace/commit/cb38f54f4a4ef30995283cd82166c62da17bac44) ([#2479](https://github.com/tauri-apps/plugins-workspace/pull/2479) by [@adrieljss](https://github.com/tauri-apps/plugins-workspace/../../adrieljss)) Add stream support for HTTP stream responses. + +## \[2.3.0] + +- [`10513649`](https://github.com/tauri-apps/plugins-workspace/commit/105136494c5a5bf4b1f1cc06cc71815412d17ec8) ([#2204](https://github.com/tauri-apps/plugins-workspace/pull/2204) by [@RickeyWard](https://github.com/tauri-apps/plugins-workspace/../../RickeyWard)) Add `dangerous-settings` feature flag and new JS `danger` option to disable tls hostname/certificate validation. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +### Dependencies + +- Upgraded to `fs@2.2.0` + +## \[2.0.2] + +### Dependencies + +- Upgraded to `fs-js@2.0.4` + +## \[2.0.4] + +- [`a3b553dd`](https://github.com/tauri-apps/plugins-workspace/commit/a3b553ddb403771aa699362c4e69a064b7731da5) ([#2079](https://github.com/tauri-apps/plugins-workspace/pull/2079) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Add tracing logs for requestes and responses behind `tracing` feature flag. + +### Dependencies + +- Upgraded to `fs@2.1.0` + +## \[2.0.3] + +### Dependencies + +- Upgraded to `fs@2.0.3` + +## \[2.0.1] + +- [`cfd48b3b`](https://github.com/tauri-apps/plugins-workspace/commit/cfd48b3b2ec0fccfc162197518694ed59ceda22c) ([#1941](https://github.com/tauri-apps/plugins-workspace/pull/1941) by [@Nipsuli](https://github.com/tauri-apps/plugins-workspace/../../Nipsuli)) Allow skipping sending `Origin` header in HTTP requests by setting `Origin` header to an empty string when calling `fetch`. +- [`9b2840db`](https://github.com/tauri-apps/plugins-workspace/commit/9b2840db9464cf08db75806270e4441f0af81e5d) ([#1884](https://github.com/tauri-apps/plugins-workspace/pull/1884) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Retain headers order. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +### Dependencies + +- Upgraded to `fs@2.0.1` + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +### Dependencies + +- Upgraded to `fs@2.0.0` + +## \[2.0.0-rc.6] + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.6` + +## \[2.0.0-rc.5] + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.5` + +## \[2.0.0-rc.4] + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.4` + +## \[2.0.0-rc.3] + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.3` + +## \[2.0.0-rc.2] + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.2` + +## \[2.0.0-rc.2] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.1] + +- [`84f8bd5e`](https://github.com/tauri-apps/plugins-workspace/commit/84f8bd5e1ef22e664267380b5bbf756ebc5389c3) ([#1662](https://github.com/tauri-apps/plugins-workspace/pull/1662) by [@twlite](https://github.com/tauri-apps/plugins-workspace/../../twlite)) Fixed an issue with abort signal not aborting the fetch request. + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.0` + +## \[2.0.0-beta.9] + +- [`99d6ac0f`](https://github.com/tauri-apps/plugins-workspace/commit/99d6ac0f9506a6a4a1aa59c728157190a7441af6) ([#1606](https://github.com/tauri-apps/plugins-workspace/pull/1606) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The JS packages now specify the *minimum* `@tauri-apps/api` version instead of a single exact version. +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.8] + +- [`ac9a25cc`](https://github.com/tauri-apps/plugins-workspace/commit/ac9a25cc12ee2b325f00212ba74316da3369bde5) ([#1395](https://github.com/tauri-apps/plugins-workspace/pull/1395) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Fix cancelling requests using `AbortSignal`. +- [`a6654932`](https://github.com/tauri-apps/plugins-workspace/commit/a66549329c60dea35e3a06a38c357e368c9053a1) ([#1526](https://github.com/tauri-apps/plugins-workspace/pull/1526) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Fix missing `Set-Cookie` headers in the response which meant `request.headers.getSetCookie()` always returned empty array. +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.7] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.6] + +- [`0f739dbc`](https://github.com/tauri-apps/plugins-workspace/commit/0f739dbc483a1f091977cbe575c3862fd39f8cf1) ([#1392](https://github.com/tauri-apps/plugins-workspace/pull/1392) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Allow setting `Origin` header when `unsafe-headers` feature flag is active. + +## \[2.0.0-beta.5] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.4] + +- [`9d7ae45b`](https://github.com/tauri-apps/plugins-workspace/commit/9d7ae45b0edf9b22c73e7d7c413a784bb35c3d77)([#1354](https://github.com/tauri-apps/plugins-workspace/pull/1354)) Include headers created by browser if not declared by user, which fixes missing headers like `Content-Type` when using `FormData`. +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.3] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.6] + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.6` + +## \[2.0.0-beta.5] + +- [`500ff10`](https://github.com/tauri-apps/plugins-workspace/commit/500ff10fbd89fdfc73caf9d153029dad567b4ff1)([#1166](https://github.com/tauri-apps/plugins-workspace/pull/1166)) **Breaking change:** Removed the `default-tls` feature flag. The `rustls-tls`, `http2`, `macos-system-configuration`, and `charset` feature flags are now enabled by default. +- [`e3d41f4`](https://github.com/tauri-apps/plugins-workspace/commit/e3d41f4011bd3ea3ce281bb38bbe31d3709f8e0f)([#1191](https://github.com/tauri-apps/plugins-workspace/pull/1191)) Internally use the webview scoped resources table instead of the app one, so other webviews can't access other webviews resources. +- [`7e2fcc5`](https://github.com/tauri-apps/plugins-workspace/commit/7e2fcc5e74df7c3c718e40f75bfb0eafc7d69d8d)([#1146](https://github.com/tauri-apps/plugins-workspace/pull/1146)) Update dependencies to align with tauri 2.0.0-beta.14. +- [`e3d41f4`](https://github.com/tauri-apps/plugins-workspace/commit/e3d41f4011bd3ea3ce281bb38bbe31d3709f8e0f)([#1191](https://github.com/tauri-apps/plugins-workspace/pull/1191)) Update for tauri 2.0.0-beta.15. + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.5` + +## \[2.0.0-beta.4] + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.4` + +## \[2.0.0-beta.3] + +- [`c873e4d`](https://github.com/tauri-apps/plugins-workspace/commit/c873e4d6c74e759742f7c9a88e35cff10a75122a)([#1059](https://github.com/tauri-apps/plugins-workspace/pull/1059)) Fixes scope not allowing subpaths, query parameters and hash when those values are empty. +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. +- [`753c7be`](https://github.com/tauri-apps/plugins-workspace/commit/753c7be0a6a78121d2e88ea0efc3040580c885b4)([#1050](https://github.com/tauri-apps/plugins-workspace/pull/1050)) Add `unsafe-headers` cargo feature flag to allow using [forbidden headers](https://fetch.spec.whatwg.org/#terminology-headers). + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.3` + +## \[2.0.0-beta.2] + +- [`ae56b13`](https://github.com/tauri-apps/plugins-workspace/commit/ae56b13a4d49dbf922b8a0fbb0d557bb63c1d72b)([#983](https://github.com/tauri-apps/plugins-workspace/pull/983)) Allow `User-Agent` header to be set. +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. +- [`1a34720`](https://github.com/tauri-apps/plugins-workspace/commit/1a347203a54eccc954749d11c4ee81fdd9a0cde7)([#858](https://github.com/tauri-apps/plugins-workspace/pull/858)) Fix http fetch client option init with parameter `connectTimeout` + +## \[2.0.0-alpha.9] + +### Dependencies + +- Upgraded to `fs@2.0.0-alpha.7` + +## \[2.0.0-alpha.6] + +- [`bfa87da`](https://github.com/tauri-apps/plugins-workspace/commit/bfa87da848f9f1da2abae3354eed632881eddf11)([#824](https://github.com/tauri-apps/plugins-workspace/pull/824)) Add `proxy` field to `fetch` options to configure proxy. + +## \[2.0.0-alpha.5] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.13. + +## \[2.0.0-alpha.4] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.12. + +## \[2.0.0-alpha.3] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to @tauri-apps/api v2.0.0-alpha.11. + +## \[2.0.0-alpha.2] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update to @tauri-apps/api v2.0.0-alpha.9. + +## \[2.0.0-alpha.3] + +- [`2cb0fa7`](https://github.com/tauri-apps/plugins-workspace/commit/2cb0fa719b8b1f5ac07dada93520dbbcf637d64c)([#587](https://github.com/tauri-apps/plugins-workspace/pull/587)) Remove `cmd` property which breaks invoke call. +- [`4e2cef9`](https://github.com/tauri-apps/plugins-workspace/commit/4e2cef9b702bbbb9cf4ee17de50791cb21f1b2a4)([#593](https://github.com/tauri-apps/plugins-workspace/pull/593)) Update to alpha.12. + +### Dependencies + +- Upgraded to `fs@2.0.0-alpha.2` + +## \[2.0.0-alpha.2] + +- [`aec17a9`](https://github.com/tauri-apps/plugins-workspace/commit/aec17a90fc365774c70c4876b94a899416120e26)([#558](https://github.com/tauri-apps/plugins-workspace/pull/558)) Improve response performance by using the new IPC streaming data. + +## \[2.0.0-alpha.1] + +- [`7d9df72`](https://github.com/tauri-apps/plugins-workspace/commit/7d9df7297a221a64d9de945ffc2cd8313d3104dc)([#428](https://github.com/tauri-apps/plugins-workspace/pull/428)) Multipart requests are now handled in JavaScript by the `Request` JavaScript class so you just need to use a `FormData` body and not set the content-type header to `multipart/form-data`. `application/x-www-form-urlencoded` requests must be done manually. +- [`7d9df72`](https://github.com/tauri-apps/plugins-workspace/commit/7d9df7297a221a64d9de945ffc2cd8313d3104dc)([#428](https://github.com/tauri-apps/plugins-workspace/pull/428)) The http plugin has been rewritten from scratch and now only exposes a `fetch` function in Javascript and Re-exports `reqwest` crate in Rust. The new `fetch` method tries to be as close and compliant to the `fetch` Web API as possible. +- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11. + +## \[2.0.0-alpha.0] + +- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! diff --git a/packages/kbot/gui/app/plugins/http/Cargo.toml b/packages/kbot/gui/app/plugins/http/Cargo.toml new file mode 100644 index 00000000..66530117 --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/Cargo.toml @@ -0,0 +1,79 @@ +[package] +name = "tauri-plugin-http" +version = "2.5.2" +description = "Access an HTTP client written in Rust." +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-http" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.platforms.support] +windows = { level = "full", notes = "" } +linux = { level = "full", notes = "" } +macos = { level = "full", notes = "" } +android = { level = "full", notes = "" } +ios = { level = "full", notes = "" } + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } +schemars = { workspace = true } +serde = { workspace = true } +url = { workspace = true } +urlpattern = "0.3" +regex = "1" + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +thiserror = { workspace = true } +tokio = { version = "1", features = ["sync", "macros"] } +tauri-plugin-fs = { path = "../fs", version = "2.4.2" } +urlpattern = "0.3" +regex = "1" +http = "1" +reqwest = { version = "0.12", default-features = false } +url = { workspace = true } +data-url = "0.3" +cookie_store = { version = "0.21.1", optional = true, features = ["serde"] } +bytes = { version = "1.9", optional = true } +tracing = { workspace = true, optional = true } + +[features] +default = [ + "rustls-tls", + "http2", + "charset", + "macos-system-configuration", + "cookies", +] +multipart = ["reqwest/multipart"] +json = ["reqwest/json"] +stream = ["reqwest/stream"] +native-tls = ["reqwest/native-tls"] +native-tls-vendored = ["reqwest/native-tls-vendored"] +native-tls-alpn = ["reqwest/native-tls-alpn"] +rustls-tls = ["reqwest/rustls-tls"] +rustls-tls-manual-roots = ["reqwest/rustls-tls-manual-roots"] +rustls-tls-webpki-roots = ["reqwest/rustls-tls-webpki-roots"] +rustls-tls-native-roots = ["reqwest/rustls-tls-native-roots"] +blocking = ["reqwest/blocking"] +cookies = ["reqwest/cookies", "dep:cookie_store", "dep:bytes"] +gzip = ["reqwest/gzip"] +brotli = ["reqwest/brotli"] +deflate = ["reqwest/deflate"] +zstd = ["reqwest/zstd"] +trust-dns = ["reqwest/trust-dns"] +socks = ["reqwest/socks"] +http2 = ["reqwest/http2"] +charset = ["reqwest/charset"] +macos-system-configuration = ["reqwest/macos-system-configuration"] +unsafe-headers = [] +tracing = ["dep:tracing"] +dangerous-settings = [] diff --git a/packages/kbot/gui/app/plugins/http/LICENSE.spdx b/packages/kbot/gui/app/plugins/http/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/http/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/http/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/http/LICENSE_MIT b/packages/kbot/gui/app/plugins/http/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/http/README.md b/packages/kbot/gui/app/plugins/http/README.md new file mode 100644 index 00000000..086d7f25 --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/README.md @@ -0,0 +1,93 @@ +![plugin-http](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/http/banner.png) + +Access the HTTP client written in Rust. + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | ✓ | +| iOS | ✓ | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-http = "2.0.0" +# alternatively with Git: +tauri-plugin-http = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +```sh +pnpm add @tauri-apps/plugin-http +# or +npm add @tauri-apps/plugin-http +# or +yarn add @tauri-apps/plugin-http +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_http::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { fetch } from '@tauri-apps/plugin-http' +const response = await fetch('http://localhost:3003/users/2', { + method: 'GET', + timeout: 30 +}) +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/http/SECURITY.md b/packages/kbot/gui/app/plugins/http/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/http/api-iife.js b/packages/kbot/gui/app/plugins/http/api-iife.js new file mode 100644 index 00000000..738e3cd1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_HTTP__=function(e){"use strict";function t(e,t,r,n){if("function"==typeof t?e!==t||!n:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?n:"a"===r?n.call(e):n?n.value:t.get(e)}function r(e,t,r,n,s){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,r),r}var n,s,i,a;"function"==typeof SuppressedError&&SuppressedError;const o="__TAURI_TO_IPC_KEY__";class c{constructor(e){n.set(this,void 0),s.set(this,0),i.set(this,[]),a.set(this,void 0),r(this,n,e||(()=>{})),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((e=>{const o=e.index;if("end"in e)return void(o==t(this,s,"f")?this.cleanupCallback():r(this,a,o));const c=e.message;if(o==t(this,s,"f")){for(t(this,n,"f").call(this,c),r(this,s,t(this,s,"f")+1);t(this,s,"f")in t(this,i,"f");){const e=t(this,i,"f")[t(this,s,"f")];t(this,n,"f").call(this,e),delete t(this,i,"f")[t(this,s,"f")],r(this,s,t(this,s,"f")+1)}t(this,s,"f")===t(this,a,"f")&&this.cleanupCallback()}else t(this,i,"f")[o]=c}))}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(e){r(this,n,e)}get onmessage(){return t(this,n,"f")}[(n=new WeakMap,s=new WeakMap,i=new WeakMap,a=new WeakMap,o)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[o]()}}async function d(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}const h="Request cancelled";return e.fetch=async function(e,t){const r=t?.signal;if(r?.aborted)throw new Error(h);const n=t?.maxRedirections,s=t?.connectTimeout,i=t?.proxy,a=t?.danger;t&&(delete t.maxRedirections,delete t.connectTimeout,delete t.proxy,delete t.danger);const o=t?.headers?t.headers instanceof Headers?t.headers:new Headers(t.headers):new Headers,f=new Request(e,t),l=await f.arrayBuffer(),u=0!==l.byteLength?Array.from(new Uint8Array(l)):null;for(const[e,t]of f.headers)o.get(e)||o.set(e,t);const _=(o instanceof Headers?Array.from(o.entries()):Array.isArray(o)?o:Object.entries(o)).map((([e,t])=>[e,"string"==typeof t?t:t.toString()]));if(r?.aborted)throw new Error(h);const w=await d("plugin:http|fetch",{clientConfig:{method:f.method,url:f.url,headers:_,data:u,maxRedirections:n,connectTimeout:s,proxy:i,danger:a}}),p=()=>d("plugin:http|fetch_cancel",{rid:w});if(r?.aborted)throw p(),new Error(h);r?.addEventListener("abort",(()=>{p()}));const{status:y,statusText:m,url:b,headers:T,rid:g}=await d("plugin:http|fetch_send",{rid:w}),A=[101,103,204,205,304].includes(y)?null:new ReadableStream({start:e=>{const t=new c;t.onmessage=t=>{if(r?.aborted)return void e.error(h);const n=new Uint8Array(t),s=n[n.byteLength-1],i=n.slice(0,n.byteLength-1);1!=s?e.enqueue(i):e.close()},d("plugin:http|fetch_read_body",{rid:g,streamChannel:t}).catch((t=>{e.error(t)}))}}),R=new Response(A,{status:y,statusText:m});return Object.defineProperty(R,"url",{value:b}),Object.defineProperty(R,"headers",{value:new Headers(T)}),R},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_PLUGIN_HTTP__})} diff --git a/packages/kbot/gui/app/plugins/http/banner.png b/packages/kbot/gui/app/plugins/http/banner.png new file mode 100644 index 00000000..121741e5 Binary files /dev/null and b/packages/kbot/gui/app/plugins/http/banner.png differ diff --git a/packages/kbot/gui/app/plugins/http/build.rs b/packages/kbot/gui/app/plugins/http/build.rs new file mode 100644 index 00000000..a4b802ad --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/build.rs @@ -0,0 +1,67 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#[path = "src/scope.rs"] +#[allow(dead_code)] +mod scope; + +const COMMANDS: &[&str] = &["fetch", "fetch_cancel", "fetch_send", "fetch_read_body"]; + +/// HTTP scope entry. +#[derive(schemars::JsonSchema)] +#[serde(untagged)] +#[allow(unused)] +enum HttpScopeEntry { + /// A URL that can be accessed by the webview when using the HTTP APIs. + /// Wildcards can be used following the URL pattern standard. + /// + /// See [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information. + /// + /// Examples: + /// + /// - "https://*" : allows all HTTPS origin on port 443 + /// + /// - "https://*:*" : allows all HTTPS origin on any port + /// + /// - "https://*.github.com/tauri-apps/tauri": allows any subdomain of "github.com" with the "tauri-apps/api" path + /// + /// - "https://myapi.service.com/users/*": allows access to any URLs that begins with "https://myapi.service.com/users/" + Value(String), + Object { + /// A URL that can be accessed by the webview when using the HTTP APIs. + /// Wildcards can be used following the URL pattern standard. + /// + /// See [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information. + /// + /// Examples: + /// + /// - "https://*" : allows all HTTPS origin on port 443 + /// + /// - "https://*:*" : allows all HTTPS origin on any port + /// + /// - "https://*.github.com/tauri-apps/tauri": allows any subdomain of "github.com" with the "tauri-apps/api" path + /// + /// - "https://myapi.service.com/users/*": allows access to any URLs that begins with "https://myapi.service.com/users/" + url: String, + }, +} + +// Ensure `HttpScopeEntry` and `scope::EntryRaw` is kept in sync +fn _f() { + match scope::EntryRaw::Value(String::new()) { + scope::EntryRaw::Value(url) => HttpScopeEntry::Value(url), + scope::EntryRaw::Object { url } => HttpScopeEntry::Object { url }, + }; + match HttpScopeEntry::Value(String::new()) { + HttpScopeEntry::Value(url) => scope::EntryRaw::Value(url), + HttpScopeEntry::Object { url } => scope::EntryRaw::Object { url }, + }; +} + +fn main() { + tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .global_scope_schema(schemars::schema_for!(HttpScopeEntry)) + .build(); +} diff --git a/packages/kbot/gui/app/plugins/http/guest-js/index.ts b/packages/kbot/gui/app/plugins/http/guest-js/index.ts new file mode 100644 index 00000000..4b38f8bf --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/guest-js/index.ts @@ -0,0 +1,285 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/** + * Make HTTP requests with the Rust backend. + * + * ## Security + * + * This API has a scope configuration that forces you to restrict the URLs that can be accessed using glob patterns. + * + * For instance, this scope configuration only allows making HTTP requests to all subdomains for `tauri.app` except for `https://private.tauri.app`: + * ```json + * { + * "permissions": [ + * { + * "identifier": "http:default", + * "allow": [{ "url": "https://*.tauri.app" }], + * "deny": [{ "url": "https://private.tauri.app" }] + * } + * ] + * } + * ``` + * Trying to execute any API with a URL not configured on the scope results in a promise rejection due to denied access. + * + * @module + */ + +import { Channel, invoke } from '@tauri-apps/api/core' + +/** + * Configuration of a proxy that a Client should pass requests to. + * + * @since 2.0.0 + */ +export interface Proxy { + /** + * Proxy all traffic to the passed URL. + */ + all?: string | ProxyConfig + /** + * Proxy all HTTP traffic to the passed URL. + */ + http?: string | ProxyConfig + /** + * Proxy all HTTPS traffic to the passed URL. + */ + https?: string | ProxyConfig +} + +export interface ProxyConfig { + /** + * The URL of the proxy server. + */ + url: string + /** + * Set the `Proxy-Authorization` header using Basic auth. + */ + basicAuth?: { + username: string + password: string + } + /** + * A configuration for filtering out requests that shouldn't be proxied. + * Entries are expected to be comma-separated (whitespace between entries is ignored) + */ + noProxy?: string +} + +/** + * Options to configure the Rust client used to make fetch requests + * + * @since 2.0.0 + */ +export interface ClientOptions { + /** + * Defines the maximum number of redirects the client should follow. + * If set to 0, no redirects will be followed. + */ + maxRedirections?: number + /** Timeout in milliseconds */ + connectTimeout?: number + /** + * Configuration of a proxy that a Client should pass requests to. + */ + proxy?: Proxy + /** + * Configuration for dangerous settings on the client such as disabling SSL verification. + */ + danger?: DangerousSettings +} + +/** + * Configuration for dangerous settings on the client such as disabling SSL verification. + * + * @since 2.3.0 + */ +export interface DangerousSettings { + /** + * Disables SSL verification. + */ + acceptInvalidCerts?: boolean + /** + * Disables hostname verification. + */ + acceptInvalidHostnames?: boolean +} + +const ERROR_REQUEST_CANCELLED = 'Request cancelled' + +/** + * Fetch a resource from the network. It returns a `Promise` that resolves to the + * `Response` to that `Request`, whether it is successful or not. + * + * @example + * ```typescript + * const response = await fetch("http://my.json.host/data.json"); + * console.log(response.status); // e.g. 200 + * console.log(response.statusText); // e.g. "OK" + * const jsonData = await response.json(); + * ``` + * + * @since 2.0.0 + */ +export async function fetch( + input: URL | Request | string, + init?: RequestInit & ClientOptions +): Promise { + // abort early here if needed + const signal = init?.signal + if (signal?.aborted) { + throw new Error(ERROR_REQUEST_CANCELLED) + } + + const maxRedirections = init?.maxRedirections + const connectTimeout = init?.connectTimeout + const proxy = init?.proxy + const danger = init?.danger + + // Remove these fields before creating the request + if (init) { + delete init.maxRedirections + delete init.connectTimeout + delete init.proxy + delete init.danger + } + + const headers = init?.headers + ? init.headers instanceof Headers + ? init.headers + : new Headers(init.headers) + : new Headers() + + const req = new Request(input, init) + const buffer = await req.arrayBuffer() + const data = + buffer.byteLength !== 0 ? Array.from(new Uint8Array(buffer)) : null + + // append new headers created by the browser `Request` implementation, + // if not already declared by the caller of this function + for (const [key, value] of req.headers) { + if (!headers.get(key)) { + headers.set(key, value) + } + } + + const headersArray = + headers instanceof Headers + ? Array.from(headers.entries()) + : Array.isArray(headers) + ? headers + : Object.entries(headers) + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const mappedHeaders: Array<[string, string]> = headersArray.map( + ([name, val]) => [ + name, + // we need to ensure we have all header values as strings + // eslint-disable-next-line + typeof val === 'string' ? val : (val as any).toString() + ] + ) + + // abort early here if needed + if (signal?.aborted) { + throw new Error(ERROR_REQUEST_CANCELLED) + } + + const rid = await invoke('plugin:http|fetch', { + clientConfig: { + method: req.method, + url: req.url, + headers: mappedHeaders, + data, + maxRedirections, + connectTimeout, + proxy, + danger + } + }) + + const abort = () => invoke('plugin:http|fetch_cancel', { rid }) + + // abort early here if needed + if (signal?.aborted) { + // we don't care about the result of this proimse + // eslint-disable-next-line @typescript-eslint/no-floating-promises + abort() + throw new Error(ERROR_REQUEST_CANCELLED) + } + + signal?.addEventListener('abort', () => void abort()) + + interface FetchSendResponse { + status: number + statusText: string + headers: [[string, string]] + url: string + rid: number + } + + const { + status, + statusText, + url, + headers: responseHeaders, + rid: responseRid + } = await invoke('plugin:http|fetch_send', { + rid + }) + + // no body for 101, 103, 204, 205 and 304 + // see https://fetch.spec.whatwg.org/#null-body-status + const body = [101, 103, 204, 205, 304].includes(status) + ? null + : new ReadableStream({ + start: (controller) => { + const streamChannel = new Channel() + streamChannel.onmessage = (res: ArrayBuffer | number[]) => { + // close early if aborted + if (signal?.aborted) { + controller.error(ERROR_REQUEST_CANCELLED) + return + } + + const resUint8 = new Uint8Array(res) + const lastByte = resUint8[resUint8.byteLength - 1] + const actualRes = resUint8.slice(0, resUint8.byteLength - 1) + + // close when the signal to close (last byte is 1) is sent from the IPC. + if (lastByte == 1) { + controller.close() + return + } + + controller.enqueue(actualRes) + } + + // run a non-blocking body stream fetch + invoke('plugin:http|fetch_read_body', { + rid: responseRid, + streamChannel + }).catch((e) => { + controller.error(e) + }) + } + }) + + const res = new Response(body, { + status, + statusText + }) + + // Set `Response` properties that are ignored by the + // constructor, like url and some headers + // + // Since url and headers are read only properties + // this is the only way to set them. + Object.defineProperty(res, 'url', { value: url }) + Object.defineProperty(res, 'headers', { + value: new Headers(responseHeaders) + }) + + return res +} diff --git a/packages/kbot/gui/app/plugins/http/package.json b/packages/kbot/gui/app/plugins/http/package.json new file mode 100644 index 00000000..e897de2f --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/package.json @@ -0,0 +1,29 @@ +{ + "name": "@tauri-apps/plugin-http", + "version": "2.5.2", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/http/permissions/autogenerated/commands/fetch.toml b/packages/kbot/gui/app/plugins/http/permissions/autogenerated/commands/fetch.toml new file mode 100644 index 00000000..c4e068a7 --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/permissions/autogenerated/commands/fetch.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-fetch" +description = "Enables the fetch command without any pre-configured scope." +commands.allow = ["fetch"] + +[[permission]] +identifier = "deny-fetch" +description = "Denies the fetch command without any pre-configured scope." +commands.deny = ["fetch"] diff --git a/packages/kbot/gui/app/plugins/http/permissions/autogenerated/commands/fetch_cancel.toml b/packages/kbot/gui/app/plugins/http/permissions/autogenerated/commands/fetch_cancel.toml new file mode 100644 index 00000000..70c1139b --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/permissions/autogenerated/commands/fetch_cancel.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-fetch-cancel" +description = "Enables the fetch_cancel command without any pre-configured scope." +commands.allow = ["fetch_cancel"] + +[[permission]] +identifier = "deny-fetch-cancel" +description = "Denies the fetch_cancel command without any pre-configured scope." +commands.deny = ["fetch_cancel"] diff --git a/packages/kbot/gui/app/plugins/http/permissions/autogenerated/commands/fetch_read_body.toml b/packages/kbot/gui/app/plugins/http/permissions/autogenerated/commands/fetch_read_body.toml new file mode 100644 index 00000000..b5867e99 --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/permissions/autogenerated/commands/fetch_read_body.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-fetch-read-body" +description = "Enables the fetch_read_body command without any pre-configured scope." +commands.allow = ["fetch_read_body"] + +[[permission]] +identifier = "deny-fetch-read-body" +description = "Denies the fetch_read_body command without any pre-configured scope." +commands.deny = ["fetch_read_body"] diff --git a/packages/kbot/gui/app/plugins/http/permissions/autogenerated/commands/fetch_send.toml b/packages/kbot/gui/app/plugins/http/permissions/autogenerated/commands/fetch_send.toml new file mode 100644 index 00000000..f6a55d28 --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/permissions/autogenerated/commands/fetch_send.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-fetch-send" +description = "Enables the fetch_send command without any pre-configured scope." +commands.allow = ["fetch_send"] + +[[permission]] +identifier = "deny-fetch-send" +description = "Denies the fetch_send command without any pre-configured scope." +commands.deny = ["fetch_send"] diff --git a/packages/kbot/gui/app/plugins/http/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/http/permissions/autogenerated/reference.md new file mode 100644 index 00000000..40c41cac --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/permissions/autogenerated/reference.md @@ -0,0 +1,133 @@ +## Default Permission + +This permission set configures what kind of +fetch operations are available from the http plugin. + +This enables all fetch operations but does not +allow explicitly any origins to be fetched. This needs to +be manually configured before usage. + +#### Granted Permissions + +All fetch operations are enabled. + +#### This default permission set includes the following: + +- `allow-fetch` +- `allow-fetch-cancel` +- `allow-fetch-read-body` +- `allow-fetch-send` + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`http:allow-fetch` + + + +Enables the fetch command without any pre-configured scope. + +
+ +`http:deny-fetch` + + + +Denies the fetch command without any pre-configured scope. + +
+ +`http:allow-fetch-cancel` + + + +Enables the fetch_cancel command without any pre-configured scope. + +
+ +`http:deny-fetch-cancel` + + + +Denies the fetch_cancel command without any pre-configured scope. + +
+ +`http:allow-fetch-read-body` + + + +Enables the fetch_read_body command without any pre-configured scope. + +
+ +`http:deny-fetch-read-body` + + + +Denies the fetch_read_body command without any pre-configured scope. + +
+ +`http:allow-fetch-send` + + + +Enables the fetch_send command without any pre-configured scope. + +
+ +`http:deny-fetch-send` + + + +Denies the fetch_send command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/http/permissions/default.toml b/packages/kbot/gui/app/plugins/http/permissions/default.toml new file mode 100644 index 00000000..b469536d --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/permissions/default.toml @@ -0,0 +1,22 @@ +"$schema" = "schemas/schema.json" + +[default] +description = """ +This permission set configures what kind of +fetch operations are available from the http plugin. + +This enables all fetch operations but does not +allow explicitly any origins to be fetched. This needs to +be manually configured before usage. + +#### Granted Permissions + +All fetch operations are enabled. + +""" +permissions = [ + "allow-fetch", + "allow-fetch-cancel", + "allow-fetch-read-body", + "allow-fetch-send", +] diff --git a/packages/kbot/gui/app/plugins/http/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/http/permissions/schemas/schema.json new file mode 100644 index 00000000..ea774399 --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/permissions/schemas/schema.json @@ -0,0 +1,354 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the fetch command without any pre-configured scope.", + "type": "string", + "const": "allow-fetch", + "markdownDescription": "Enables the fetch command without any pre-configured scope." + }, + { + "description": "Denies the fetch command without any pre-configured scope.", + "type": "string", + "const": "deny-fetch", + "markdownDescription": "Denies the fetch command without any pre-configured scope." + }, + { + "description": "Enables the fetch_cancel command without any pre-configured scope.", + "type": "string", + "const": "allow-fetch-cancel", + "markdownDescription": "Enables the fetch_cancel command without any pre-configured scope." + }, + { + "description": "Denies the fetch_cancel command without any pre-configured scope.", + "type": "string", + "const": "deny-fetch-cancel", + "markdownDescription": "Denies the fetch_cancel command without any pre-configured scope." + }, + { + "description": "Enables the fetch_read_body command without any pre-configured scope.", + "type": "string", + "const": "allow-fetch-read-body", + "markdownDescription": "Enables the fetch_read_body command without any pre-configured scope." + }, + { + "description": "Denies the fetch_read_body command without any pre-configured scope.", + "type": "string", + "const": "deny-fetch-read-body", + "markdownDescription": "Denies the fetch_read_body command without any pre-configured scope." + }, + { + "description": "Enables the fetch_send command without any pre-configured scope.", + "type": "string", + "const": "allow-fetch-send", + "markdownDescription": "Enables the fetch_send command without any pre-configured scope." + }, + { + "description": "Denies the fetch_send command without any pre-configured scope.", + "type": "string", + "const": "deny-fetch-send", + "markdownDescription": "Denies the fetch_send command without any pre-configured scope." + }, + { + "description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`", + "type": "string", + "const": "default", + "markdownDescription": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/http/rollup.config.js b/packages/kbot/gui/app/plugins/http/rollup.config.js new file mode 100644 index 00000000..1f349ec8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/rollup.config.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' + +export default createConfig() diff --git a/packages/kbot/gui/app/plugins/http/src/commands.rs b/packages/kbot/gui/app/plugins/http/src/commands.rs new file mode 100644 index 00000000..bb47444e --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/src/commands.rs @@ -0,0 +1,469 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{future::Future, pin::Pin, str::FromStr, sync::Arc, time::Duration}; + +use http::{header, HeaderMap, HeaderName, HeaderValue, Method, StatusCode}; +use reqwest::{redirect::Policy, NoProxy}; +use serde::{Deserialize, Serialize}; +use tauri::{ + async_runtime::Mutex, + command, + ipc::{Channel, CommandScope, GlobalScope}, + Manager, ResourceId, ResourceTable, Runtime, State, Webview, +}; +use tokio::sync::oneshot::{channel, Receiver, Sender}; + +use crate::{ + scope::{Entry, Scope}, + Error, Http, Result, +}; + +const HTTP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),); + +struct ReqwestResponse(reqwest::Response); +impl tauri::Resource for ReqwestResponse {} + +type CancelableResponseResult = Result; +type CancelableResponseFuture = + Pin + Send + Sync>>; + +struct FetchRequest { + fut: Mutex, + abort_tx_rid: ResourceId, + abort_rx_rid: ResourceId, +} +impl tauri::Resource for FetchRequest {} + +struct AbortSender(Sender<()>); +impl tauri::Resource for AbortRecveiver {} + +impl AbortSender { + fn abort(self) { + let _ = self.0.send(()); + } +} + +struct AbortRecveiver(Receiver<()>); +impl tauri::Resource for AbortSender {} + +trait AddRequest { + fn add_request(&mut self, fut: CancelableResponseFuture) -> ResourceId; +} + +impl AddRequest for ResourceTable { + fn add_request(&mut self, fut: CancelableResponseFuture) -> ResourceId { + let (tx, rx) = channel::<()>(); + let (tx, rx) = (AbortSender(tx), AbortRecveiver(rx)); + let req = FetchRequest { + fut: Mutex::new(fut), + abort_tx_rid: self.add(tx), + abort_rx_rid: self.add(rx), + }; + self.add(req) + } +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct FetchResponse { + status: u16, + status_text: String, + headers: Vec<(String, String)>, + url: String, + rid: ResourceId, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +#[allow(dead_code)] //feature flags shoudln't affect api +pub struct DangerousSettings { + accept_invalid_certs: bool, + accept_invalid_hostnames: bool, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientConfig { + method: String, + url: url::Url, + headers: Vec<(String, String)>, + data: Option>, + connect_timeout: Option, + max_redirections: Option, + proxy: Option, + danger: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Proxy { + all: Option, + http: Option, + https: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(untagged)] +pub enum UrlOrConfig { + Url(String), + Config(ProxyConfig), +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ProxyConfig { + url: String, + basic_auth: Option, + no_proxy: Option, +} + +#[derive(Debug, Deserialize)] +pub struct BasicAuth { + username: String, + password: String, +} + +#[inline] +fn proxy_creator( + url_or_config: UrlOrConfig, + proxy_fn: fn(String) -> reqwest::Result, +) -> reqwest::Result { + match url_or_config { + UrlOrConfig::Url(url) => Ok(proxy_fn(url)?), + UrlOrConfig::Config(ProxyConfig { + url, + basic_auth, + no_proxy, + }) => { + let mut proxy = proxy_fn(url)?; + if let Some(basic_auth) = basic_auth { + proxy = proxy.basic_auth(&basic_auth.username, &basic_auth.password); + } + if let Some(no_proxy) = no_proxy { + proxy = proxy.no_proxy(NoProxy::from_string(&no_proxy)); + } + Ok(proxy) + } + } +} + +fn attach_proxy( + proxy: Proxy, + mut builder: reqwest::ClientBuilder, +) -> crate::Result { + let Proxy { all, http, https } = proxy; + + if let Some(all) = all { + let proxy = proxy_creator(all, reqwest::Proxy::all)?; + builder = builder.proxy(proxy); + } + + if let Some(http) = http { + let proxy = proxy_creator(http, reqwest::Proxy::http)?; + builder = builder.proxy(proxy); + } + + if let Some(https) = https { + let proxy = proxy_creator(https, reqwest::Proxy::https)?; + builder = builder.proxy(proxy); + } + + Ok(builder) +} + +#[command] +pub async fn fetch( + webview: Webview, + state: State<'_, Http>, + client_config: ClientConfig, + command_scope: CommandScope, + global_scope: GlobalScope, +) -> crate::Result { + let ClientConfig { + method, + url, + headers: headers_raw, + data, + connect_timeout, + max_redirections, + proxy, + danger, + } = client_config; + + let scheme = url.scheme(); + let method = Method::from_bytes(method.as_bytes())?; + + let mut headers = HeaderMap::new(); + for (h, v) in headers_raw { + let name = HeaderName::from_str(&h)?; + #[cfg(not(feature = "unsafe-headers"))] + if is_unsafe_header(&name) { + #[cfg(debug_assertions)] + { + eprintln!("[\x1b[33mWARNING\x1b[0m] Skipping {name} header as it is a forbidden header per fetch spec https://fetch.spec.whatwg.org/#terminology-headers"); + eprintln!("[\x1b[33mWARNING\x1b[0m] if keeping the header is a desired behavior, you can enable `unsafe-headers` feature flag in your Cargo.toml"); + } + continue; + } + + headers.append(name, HeaderValue::from_str(&v)?); + } + + match scheme { + "http" | "https" => { + if Scope::new( + command_scope + .allows() + .iter() + .chain(global_scope.allows()) + .collect(), + command_scope + .denies() + .iter() + .chain(global_scope.denies()) + .collect(), + ) + .is_allowed(&url) + { + let mut builder = reqwest::ClientBuilder::new(); + + if let Some(danger_config) = danger { + #[cfg(not(feature = "dangerous-settings"))] + { + #[cfg(debug_assertions)] + { + eprintln!("[\x1b[33mWARNING\x1b[0m] using dangerous settings requires `dangerous-settings` feature flag in your Cargo.toml"); + } + let _ = danger_config; + return Err(Error::DangerousSettings); + } + #[cfg(feature = "dangerous-settings")] + { + builder = builder + .danger_accept_invalid_certs(danger_config.accept_invalid_certs) + .danger_accept_invalid_hostnames(danger_config.accept_invalid_hostnames) + } + } + + if let Some(timeout) = connect_timeout { + builder = builder.connect_timeout(Duration::from_millis(timeout)); + } + + if let Some(max_redirections) = max_redirections { + builder = builder.redirect(if max_redirections == 0 { + Policy::none() + } else { + Policy::limited(max_redirections) + }); + } + + if let Some(proxy_config) = proxy { + builder = attach_proxy(proxy_config, builder)?; + } + + #[cfg(feature = "cookies")] + { + builder = builder.cookie_provider(state.cookies_jar.clone()); + } + + let mut request = builder.build()?.request(method.clone(), url); + + // POST and PUT requests should always have a 0 length content-length, + // if there is no body. https://fetch.spec.whatwg.org/#http-network-or-cache-fetch + if data.is_none() && matches!(method, Method::POST | Method::PUT) { + headers.append(header::CONTENT_LENGTH, HeaderValue::from_str("0")?); + } + + if headers.contains_key(header::RANGE) { + // https://fetch.spec.whatwg.org/#http-network-or-cache-fetch step 18 + // If httpRequest’s header list contains `Range`, then append (`Accept-Encoding`, `identity`) + headers.append(header::ACCEPT_ENCODING, HeaderValue::from_str("identity")?); + } + + if !headers.contains_key(header::USER_AGENT) { + headers.append(header::USER_AGENT, HeaderValue::from_str(HTTP_USER_AGENT)?); + } + + // ensure we have an Origin header set + if cfg!(not(feature = "unsafe-headers")) || !headers.contains_key(header::ORIGIN) { + if let Ok(url) = webview.url() { + headers.append( + header::ORIGIN, + HeaderValue::from_str(&url.origin().ascii_serialization())?, + ); + } + } + + // In case empty origin is passed, remove it. Some services do not like Origin header + // so this way we can remove it in explicit way. The default behaviour is still to set it + if cfg!(feature = "unsafe-headers") + && headers.get(header::ORIGIN) == Some(&HeaderValue::from_static("")) + { + headers.remove(header::ORIGIN); + }; + + if let Some(data) = data { + request = request.body(data); + } + + request = request.headers(headers); + + #[cfg(feature = "tracing")] + tracing::trace!("{:?}", request); + + let fut = async move { request.send().await.map_err(Into::into) }; + + let mut resources_table = webview.resources_table(); + let rid = resources_table.add_request(Box::pin(fut)); + + Ok(rid) + } else { + Err(Error::UrlNotAllowed(url)) + } + } + "data" => { + let data_url = + data_url::DataUrl::process(url.as_str()).map_err(|_| Error::DataUrlError)?; + let (body, _) = data_url + .decode_to_vec() + .map_err(|_| Error::DataUrlDecodeError)?; + + let response = http::Response::builder() + .status(StatusCode::OK) + .header(header::CONTENT_TYPE, data_url.mime_type().to_string()) + .body(reqwest::Body::from(body))?; + + #[cfg(feature = "tracing")] + tracing::trace!("{:?}", response); + + let fut = async move { Ok(reqwest::Response::from(response)) }; + let mut resources_table = webview.resources_table(); + let rid = resources_table.add_request(Box::pin(fut)); + Ok(rid) + } + _ => Err(Error::SchemeNotSupport(scheme.to_string())), + } +} + +#[command] +pub fn fetch_cancel(webview: Webview, rid: ResourceId) -> crate::Result<()> { + let mut resources_table = webview.resources_table(); + let req = resources_table.get::(rid)?; + let abort_tx = resources_table.take::(req.abort_tx_rid)?; + if let Some(abort_tx) = Arc::into_inner(abort_tx) { + abort_tx.abort(); + } + Ok(()) +} + +#[command] +pub async fn fetch_send( + webview: Webview, + rid: ResourceId, +) -> crate::Result { + let (req, abort_rx) = { + let mut resources_table = webview.resources_table(); + let req = resources_table.get::(rid)?; + let abort_rx = resources_table.take::(req.abort_rx_rid)?; + (req, abort_rx) + }; + + let Some(abort_rx) = Arc::into_inner(abort_rx) else { + return Err(Error::RequestCanceled); + }; + + let mut fut = req.fut.lock().await; + + let res = tokio::select! { + res = fut.as_mut() => res?, + _ = abort_rx.0 => { + let mut resources_table = webview.resources_table(); + resources_table.close(rid)?; + return Err(Error::RequestCanceled); + } + }; + + #[cfg(feature = "tracing")] + tracing::trace!("{:?}", res); + + let status = res.status(); + let url = res.url().to_string(); + let mut headers = Vec::new(); + for (key, val) in res.headers().iter() { + headers.push(( + key.as_str().into(), + String::from_utf8(val.as_bytes().to_vec())?, + )); + } + + let mut resources_table = webview.resources_table(); + let rid = resources_table.add(ReqwestResponse(res)); + + Ok(FetchResponse { + status: status.as_u16(), + status_text: status.canonical_reason().unwrap_or_default().to_string(), + headers, + url, + rid, + }) +} + +#[command] +pub async fn fetch_read_body( + webview: Webview, + rid: ResourceId, + stream_channel: Channel, +) -> crate::Result<()> { + let res = { + let mut resources_table = webview.resources_table(); + resources_table.take::(rid)? + }; + + let mut res = Arc::into_inner(res).unwrap().0; + + // send response through IPC channel + while let Some(chunk) = res.chunk().await? { + let mut chunk = chunk.to_vec(); + // append 0 to indicate we are not done yet + chunk.push(0); + stream_channel.send(tauri::ipc::InvokeResponseBody::Raw(chunk))?; + } + + // send 1 to indicate we are done + stream_channel.send(tauri::ipc::InvokeResponseBody::Raw(vec![1]))?; + + Ok(()) +} + +// forbidden headers per fetch spec https://fetch.spec.whatwg.org/#terminology-headers +#[cfg(not(feature = "unsafe-headers"))] +fn is_unsafe_header(header: &HeaderName) -> bool { + matches!( + *header, + header::ACCEPT_CHARSET + | header::ACCEPT_ENCODING + | header::ACCESS_CONTROL_REQUEST_HEADERS + | header::ACCESS_CONTROL_REQUEST_METHOD + | header::CONNECTION + | header::CONTENT_LENGTH + | header::COOKIE + | header::DATE + | header::DNT + | header::EXPECT + | header::HOST + | header::ORIGIN + | header::REFERER + | header::SET_COOKIE + | header::TE + | header::TRAILER + | header::TRANSFER_ENCODING + | header::UPGRADE + | header::VIA + ) || { + let lower = header.as_str().to_lowercase(); + lower.starts_with("proxy-") || lower.starts_with("sec-") + } +} diff --git a/packages/kbot/gui/app/plugins/http/src/error.rs b/packages/kbot/gui/app/plugins/http/src/error.rs new file mode 100644 index 00000000..ef8de0c5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/src/error.rs @@ -0,0 +1,57 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{Serialize, Serializer}; +use url::Url; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Json(#[from] serde_json::Error), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + Network(#[from] reqwest::Error), + #[error(transparent)] + Http(#[from] http::Error), + #[error(transparent)] + HttpInvalidHeaderName(#[from] http::header::InvalidHeaderName), + #[error(transparent)] + HttpInvalidHeaderValue(#[from] http::header::InvalidHeaderValue), + /// URL not allowed by the scope. + #[error("url not allowed on the configured scope: {0}")] + UrlNotAllowed(Url), + #[error(transparent)] + UrlParseError(#[from] url::ParseError), + /// HTTP method error. + #[error(transparent)] + HttpMethod(#[from] http::method::InvalidMethod), + #[error("scheme {0} not supported")] + SchemeNotSupport(String), + #[error("Request canceled")] + RequestCanceled, + #[error(transparent)] + FsError(#[from] tauri_plugin_fs::Error), + #[error("failed to process data url")] + DataUrlError, + #[error("failed to decode data url into bytes")] + DataUrlDecodeError, + #[error(transparent)] + Tauri(#[from] tauri::Error), + #[error(transparent)] + Utf8(#[from] std::string::FromUtf8Error), + #[error("dangerous settings used but are not enabled")] + DangerousSettings, +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} + +pub type Result = std::result::Result; diff --git a/packages/kbot/gui/app/plugins/http/src/lib.rs b/packages/kbot/gui/app/plugins/http/src/lib.rs new file mode 100644 index 00000000..5acc2b47 --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/src/lib.rs @@ -0,0 +1,90 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Access the HTTP client written in Rust. + +pub use reqwest; +use tauri::{ + plugin::{Builder, TauriPlugin}, + Manager, Runtime, +}; + +pub use error::{Error, Result}; + +mod commands; +mod error; +#[cfg(feature = "cookies")] +mod reqwest_cookie_store; +mod scope; + +#[cfg(feature = "cookies")] +const COOKIES_FILENAME: &str = ".cookies"; + +pub(crate) struct Http { + #[cfg(feature = "cookies")] + cookies_jar: std::sync::Arc, +} + +pub fn init() -> TauriPlugin { + Builder::::new("http") + .setup(|app, _| { + #[cfg(feature = "cookies")] + let cookies_jar = { + use crate::reqwest_cookie_store::*; + use std::fs::File; + use std::io::BufReader; + + let cache_dir = app.path().app_cache_dir()?; + std::fs::create_dir_all(&cache_dir)?; + + let path = cache_dir.join(COOKIES_FILENAME); + let file = File::options() + .create(true) + .append(true) + .read(true) + .open(&path)?; + + let reader = BufReader::new(file); + CookieStoreMutex::load(path.clone(), reader).unwrap_or_else(|_e| { + #[cfg(feature = "tracing")] + tracing::warn!( + "failed to load cookie store: {_e}, falling back to empty store" + ); + CookieStoreMutex::new(path, Default::default()) + }) + }; + + let state = Http { + #[cfg(feature = "cookies")] + cookies_jar: std::sync::Arc::new(cookies_jar), + }; + + app.manage(state); + + Ok(()) + }) + .on_event(|app, event| { + #[cfg(feature = "cookies")] + if let tauri::RunEvent::Exit = event { + let state = app.state::(); + + match state.cookies_jar.request_save() { + Ok(rx) => { + let _ = rx.recv(); + } + Err(_e) => { + #[cfg(feature = "tracing")] + tracing::error!("failed to save cookie jar: {_e}"); + } + } + } + }) + .invoke_handler(tauri::generate_handler![ + commands::fetch, + commands::fetch_cancel, + commands::fetch_send, + commands::fetch_read_body + ]) + .build() +} diff --git a/packages/kbot/gui/app/plugins/http/src/reqwest_cookie_store.rs b/packages/kbot/gui/app/plugins/http/src/reqwest_cookie_store.rs new file mode 100644 index 00000000..0b71902b --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/src/reqwest_cookie_store.rs @@ -0,0 +1,133 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +// taken from https://github.com/pfernie/reqwest_cookie_store/blob/2ec4afabcd55e24d3afe3f0626ee6dc97bed938d/src/lib.rs + +use std::{ + path::PathBuf, + sync::{mpsc::Receiver, Mutex}, +}; + +use cookie_store::{CookieStore, RawCookie, RawCookieParseError}; +use reqwest::header::HeaderValue; + +fn set_cookies( + cookie_store: &mut CookieStore, + cookie_headers: &mut dyn Iterator, + url: &url::Url, +) { + let cookies = cookie_headers.filter_map(|val| { + std::str::from_utf8(val.as_bytes()) + .map_err(RawCookieParseError::from) + .and_then(RawCookie::parse) + .map(|c| c.into_owned()) + .ok() + }); + cookie_store.store_response_cookies(cookies, url); +} + +fn cookies(cookie_store: &CookieStore, url: &url::Url) -> Option { + let s = cookie_store + .get_request_values(url) + .map(|(name, value)| format!("{name}={value}")) + .collect::>() + .join("; "); + + if s.is_empty() { + return None; + } + + HeaderValue::from_maybe_shared(bytes::Bytes::from(s)).ok() +} + +/// A [`cookie_store::CookieStore`] wrapped internally by a [`std::sync::Mutex`], suitable for use in +/// async/concurrent contexts. +#[derive(Debug)] +pub struct CookieStoreMutex { + pub path: PathBuf, + store: Mutex, + save_task: Mutex>, +} + +impl CookieStoreMutex { + /// Create a new [`CookieStoreMutex`] from an existing [`cookie_store::CookieStore`]. + pub fn new(path: PathBuf, cookie_store: CookieStore) -> CookieStoreMutex { + CookieStoreMutex { + path, + store: Mutex::new(cookie_store), + save_task: Default::default(), + } + } + + pub fn load( + path: PathBuf, + reader: R, + ) -> cookie_store::Result { + cookie_store::serde::load(reader, |c| serde_json::from_str(c)) + .map(|store| CookieStoreMutex::new(path, store)) + } + + fn cookies_to_str(&self) -> Result { + let mut cookies = Vec::new(); + for cookie in self + .store + .lock() + .expect("poisoned cookie jar mutex") + .iter_unexpired() + { + if cookie.is_persistent() { + cookies.push(cookie.clone()); + } + } + serde_json::to_string(&cookies) + } + + pub fn request_save(&self) -> cookie_store::Result> { + let cookie_str = self.cookies_to_str()?; + let path = self.path.clone(); + let (tx, rx) = std::sync::mpsc::channel(); + let task = tauri::async_runtime::spawn(async move { + match tokio::fs::write(&path, &cookie_str).await { + Ok(()) => { + let _ = tx.send(()); + } + Err(_e) => { + #[cfg(feature = "tracing")] + tracing::error!("failed to save cookie jar: {_e}"); + } + } + }); + self.save_task + .lock() + .unwrap() + .replace(CancellableTask(task)); + Ok(rx) + } +} + +impl reqwest::cookie::CookieStore for CookieStoreMutex { + fn set_cookies(&self, cookie_headers: &mut dyn Iterator, url: &url::Url) { + set_cookies(&mut self.store.lock().unwrap(), cookie_headers, url); + + // try to persist cookies immediately asynchronously + if let Err(_e) = self.request_save() { + #[cfg(feature = "tracing")] + tracing::error!("failed to save cookie jar: {_e}"); + } + } + + fn cookies(&self, url: &url::Url) -> Option { + let store = self.store.lock().unwrap(); + cookies(&store, url) + } +} + +#[derive(Debug)] +struct CancellableTask(tauri::async_runtime::JoinHandle<()>); + +impl Drop for CancellableTask { + fn drop(&mut self) { + self.0.abort(); + } +} diff --git a/packages/kbot/gui/app/plugins/http/src/scope.rs b/packages/kbot/gui/app/plugins/http/src/scope.rs new file mode 100644 index 00000000..e7638c2c --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/src/scope.rs @@ -0,0 +1,231 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::sync::Arc; + +use serde::{Deserialize, Deserializer}; +use url::Url; +use urlpattern::{UrlPattern, UrlPatternMatchInput}; + +#[allow(rustdoc::bare_urls)] +#[derive(Debug)] +pub struct Entry { + pub url: UrlPattern, +} + +fn parse_url_pattern(s: &str) -> Result { + let mut init = urlpattern::UrlPatternInit::parse_constructor_string::(s, None)?; + if init.search.as_ref().map(|p| p.is_empty()).unwrap_or(true) { + init.search.replace("*".to_string()); + } + if init.hash.as_ref().map(|p| p.is_empty()).unwrap_or(true) { + init.hash.replace("*".to_string()); + } + if init + .pathname + .as_ref() + .map(|p| p.is_empty() || p == "/") + .unwrap_or(true) + { + init.pathname.replace("*".to_string()); + } + UrlPattern::parse(init, Default::default()) +} + +#[derive(Deserialize)] +#[serde(untagged)] +pub(crate) enum EntryRaw { + Value(String), + Object { url: String }, +} + +impl<'de> Deserialize<'de> for Entry { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + EntryRaw::deserialize(deserializer).and_then(|raw| { + let url = match raw { + EntryRaw::Value(url) => url, + EntryRaw::Object { url } => url, + }; + Ok(Entry { + url: parse_url_pattern(&url).map_err(|e| { + serde::de::Error::custom(format!("`{url}` is not a valid URL pattern: {e}")) + })?, + }) + }) + } +} + +/// Scope for filesystem access. +#[derive(Debug)] +pub struct Scope<'a> { + allowed: Vec<&'a Arc>, + denied: Vec<&'a Arc>, +} + +impl<'a> Scope<'a> { + /// Creates a new scope from the scope configuration. + pub(crate) fn new(allowed: Vec<&'a Arc>, denied: Vec<&'a Arc>) -> Self { + Self { allowed, denied } + } + + /// Determines if the given URL is allowed on this scope. + pub fn is_allowed(&self, url: &Url) -> bool { + let denied = self.denied.iter().any(|entry| { + entry + .url + .test(UrlPatternMatchInput::Url(url.clone())) + .unwrap_or_default() + }); + if denied { + false + } else { + self.allowed.iter().any(|entry| { + entry + .url + .test(UrlPatternMatchInput::Url(url.clone())) + .unwrap_or_default() + }) + } + } +} + +#[cfg(test)] +mod tests { + use std::{str::FromStr, sync::Arc}; + + use super::Entry; + + impl FromStr for Entry { + type Err = urlpattern::quirks::Error; + + fn from_str(s: &str) -> Result { + let pattern = super::parse_url_pattern(s)?; + Ok(Self { url: pattern }) + } + } + + #[test] + fn denied_takes_precedence() { + let allow = Arc::new("http://localhost:8080/file.png".parse().unwrap()); + let deny = Arc::new("http://localhost:8080/*".parse().unwrap()); + let scope = super::Scope::new(vec![&allow], vec![&deny]); + assert!(!scope.is_allowed(&"http://localhost:8080/file.png".parse().unwrap())); + assert!(!scope.is_allowed(&"http://localhost:8080?framework=tauri".parse().unwrap())); + } + + #[test] + fn fixed_url() { + // plain URL + let entry = Arc::new("http://localhost:8080".parse().unwrap()); + let scope = super::Scope::new(vec![&entry], Vec::new()); + assert!(scope.is_allowed(&"http://localhost:8080".parse().unwrap())); + assert!(scope.is_allowed(&"http://localhost:8080/".parse().unwrap())); + assert!(scope.is_allowed(&"http://localhost:8080/file".parse().unwrap())); + assert!(scope.is_allowed(&"http://localhost:8080/path/to/asset.png".parse().unwrap())); + assert!(scope.is_allowed(&"http://localhost:8080/path/list?limit=50".parse().unwrap())); + + assert!(!scope.is_allowed(&"https://localhost:8080".parse().unwrap())); + assert!(!scope.is_allowed(&"http://localhost:8081".parse().unwrap())); + assert!(!scope.is_allowed(&"http://local:8080".parse().unwrap())); + } + + #[test] + fn fixed_path() { + // URL with fixed path + let entry = Arc::new("http://localhost:8080/file.png".parse().unwrap()); + let scope = super::Scope::new(vec![&entry], Vec::new()); + + assert!(scope.is_allowed(&"http://localhost:8080/file.png".parse().unwrap())); + assert!(scope.is_allowed(&"http://localhost:8080/file.png?q=1".parse().unwrap())); + + assert!(!scope.is_allowed(&"http://localhost:8080".parse().unwrap())); + assert!(!scope.is_allowed(&"http://localhost:8080/file".parse().unwrap())); + assert!(!scope.is_allowed(&"http://localhost:8080/file.png/other.jpg".parse().unwrap())); + } + + #[test] + fn pattern_wildcard() { + let entry = Arc::new("http://localhost:8080/*.png".parse().unwrap()); + let scope = super::Scope::new(vec![&entry], Vec::new()); + + assert!(scope.is_allowed(&"http://localhost:8080/file.png".parse().unwrap())); + assert!(scope.is_allowed(&"http://localhost:8080/file.png#head".parse().unwrap())); + assert!(scope.is_allowed(&"http://localhost:8080/assets/file.png".parse().unwrap())); + assert!(scope.is_allowed( + &"http://localhost:8080/assets/file.png?width=100&height=200" + .parse() + .unwrap() + )); + + assert!(!scope.is_allowed(&"http://localhost:8080/file.jpeg".parse().unwrap())); + } + + #[test] + fn domain_wildcard() { + let entry = Arc::new("http://*".parse().unwrap()); + let scope = super::Scope::new(vec![&entry], Vec::new()); + + assert!(scope.is_allowed(&"http://something.else".parse().unwrap())); + assert!(scope.is_allowed(&"http://something.else#tauri".parse().unwrap())); + assert!(scope.is_allowed(&"http://something.else/path/to/file".parse().unwrap())); + assert!(scope.is_allowed(&"http://something.else?rel=tauri".parse().unwrap())); + assert!(scope.is_allowed( + &"http://something.else/path/to/file.mp4?start=500" + .parse() + .unwrap() + )); + + assert!(!scope.is_allowed(&"https://something.else".parse().unwrap())); + + let entry = Arc::new("http://*/*".parse().unwrap()); + let scope = super::Scope::new(vec![&entry], Vec::new()); + + assert!(scope.is_allowed(&"http://something.else".parse().unwrap())); + assert!(scope.is_allowed(&"http://something.else/path/to/file".parse().unwrap())); + } + + #[test] + fn scheme_wildcard() { + let entry = Arc::new("*://*".parse().unwrap()); + let scope = super::Scope::new(vec![&entry], Vec::new()); + + assert!(scope.is_allowed(&"http://something.else".parse().unwrap())); + assert!(scope.is_allowed(&"http://something.else/path/to/file".parse().unwrap())); + assert!(scope.is_allowed(&"file://path".parse().unwrap())); + assert!(scope.is_allowed(&"file://path/to/file".parse().unwrap())); + assert!(scope.is_allowed(&"https://something.else".parse().unwrap())); + assert!(scope.is_allowed(&"https://something.else?x=1#frag".parse().unwrap())); + + let entry = Arc::new("*://*/*".parse().unwrap()); + let scope = super::Scope::new(vec![&entry], Vec::new()); + + assert!(scope.is_allowed(&"http://something.else".parse().unwrap())); + assert!(scope.is_allowed(&"http://something.else/path/to/file".parse().unwrap())); + assert!(scope.is_allowed(&"file://path/to/file".parse().unwrap())); + assert!(scope.is_allowed(&"https://something.else".parse().unwrap())); + } + + #[test] + fn validate_query() { + let entry = Arc::new("https://tauri.app/path?x=*".parse().unwrap()); + let scope = super::Scope::new(vec![&entry], Vec::new()); + + assert!(scope.is_allowed(&"https://tauri.app/path?x=5".parse().unwrap())); + + assert!(!scope.is_allowed(&"https://tauri.app/path?y=5".parse().unwrap())); + } + + #[test] + fn validate_hash() { + let entry = Arc::new("https://tauri.app/path#frame*".parse().unwrap()); + let scope = super::Scope::new(vec![&entry], Vec::new()); + + assert!(scope.is_allowed(&"https://tauri.app/path#frame".parse().unwrap())); + + assert!(!scope.is_allowed(&"https://tauri.app/path#work".parse().unwrap())); + } +} diff --git a/packages/kbot/gui/app/plugins/http/tsconfig.json b/packages/kbot/gui/app/plugins/http/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/http/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/localhost/CHANGELOG.md b/packages/kbot/gui/app/plugins/localhost/CHANGELOG.md new file mode 100644 index 00000000..9c05d13b --- /dev/null +++ b/packages/kbot/gui/app/plugins/localhost/CHANGELOG.md @@ -0,0 +1,105 @@ +# Changelog + +## \[2.3.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.2.1] + +- [`f634e524`](https://github.com/tauri-apps/plugins-workspace/commit/f634e5248ebe428f8305a59f74c13fc15147fb8e) This is an "empty" release to update the plugins' source files on crates.io and docs.rs. This should fix docs.rs build failures for projects using tauri plugins as dependencies. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.1.0] + +- [`3449dd5a`](https://github.com/tauri-apps/plugins-workspace/commit/3449dd5a8f6d12fee8d6389c034fe47e19d72bcd) ([#1982](https://github.com/tauri-apps/plugins-workspace/pull/1982) by [@arihav](https://github.com/tauri-apps/plugins-workspace/../../arihav)) Add custom host binding to allow external access + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +## \[2.0.0-beta.9] + +- [`e847cedc`](https://github.com/tauri-apps/plugins-workspace/commit/e847cedc1f46f3e7a2ad81ea579b620bc5b992d7) ([#1402](https://github.com/tauri-apps/plugins-workspace/pull/1402) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Use no default features on tauri for all plugins so that consumers can use `default-features = false` on tauri, note that this will still enable wry feature on iOS +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.8] + +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.7] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.6] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.5] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.4] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.3] + +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. + +## \[2.0.0-beta.2] + +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`14f381a`](https://github.com/tauri-apps/plugins-workspace/commit/14f381acf8fe690acecc676922c6f05939b95734) Update MSRV to 1.75. +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Add permissions. + +## \[2.0.0-alpha.6] + +- [`2cf8faa`](https://github.com/tauri-apps/plugins-workspace/commit/2cf8faa3e149af55eb86e5aba8ebfc54210ca703)([#839](https://github.com/tauri-apps/plugins-workspace/pull/839)) Update to tauri@alpha.20. + +## \[2.0.0-alpha.5] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to tauri@alpha.18. + +## \[2.0.0-alpha.4] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to tauri@alpha.17. + +## \[2.0.0-alpha.3] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update MSRV to 1.75. + +## \[2.0.0-alpha.2] + +- [`4e2cef9`](https://github.com/tauri-apps/plugins-workspace/commit/4e2cef9b702bbbb9cf4ee17de50791cb21f1b2a4)([#593](https://github.com/tauri-apps/plugins-workspace/pull/593)) Update to alpha.12. + +## \[2.0.0-alpha.1] + +- [`d5a7c77`](https://github.com/tauri-apps/plugins-workspace/commit/d5a7c77a8d0e7912a6b07b22ed329004edd6e80b)([#545](https://github.com/tauri-apps/plugins-workspace/pull/545)) Fixes docs.rs build by enabling the `tauri/dox` feature flag. +- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11. + +## \[2.0.0-alpha.0] + +- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! diff --git a/packages/kbot/gui/app/plugins/localhost/Cargo.toml b/packages/kbot/gui/app/plugins/localhost/Cargo.toml new file mode 100644 index 00000000..0166c3f7 --- /dev/null +++ b/packages/kbot/gui/app/plugins/localhost/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "tauri-plugin-localhost" +version = "2.3.0" +description = "Expose your apps assets through a localhost server instead of the default custom protocol." +authors = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.platforms.support] +windows = { level = "full", notes = "" } +linux = { level = "full", notes = "" } +macos = { level = "full", notes = "" } +android = { level = "none", notes = "" } +ios = { level = "none", notes = "" } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } +tiny_http = "0.12" +http = "1" diff --git a/packages/kbot/gui/app/plugins/localhost/LICENSE.spdx b/packages/kbot/gui/app/plugins/localhost/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/localhost/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/localhost/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/localhost/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/localhost/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/localhost/LICENSE_MIT b/packages/kbot/gui/app/plugins/localhost/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/localhost/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/localhost/README.md b/packages/kbot/gui/app/plugins/localhost/README.md new file mode 100644 index 00000000..dc0d98d2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/localhost/README.md @@ -0,0 +1,92 @@ +![plugin-localhost](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/localhost/banner.png) + +Expose your apps assets through a localhost server instead of the default custom protocol. + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | ✓ | +| iOS | ✓ | + +> Note: This plugins brings considerable security risks and you should only use it if you know what your are doing. If in doubt, use the default custom protocol implementation. + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +portpicker = "0.1" # used in the example to pick a random free port +tauri-plugin-localhost = "2.0.0" +# alternatively with Git: +tauri-plugin-localhost = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +use tauri::{Manager, window::WindowBuilder, WindowUrl}; + +fn main() { + let port = portpicker::pick_unused_port().expect("failed to find unused port"); + + tauri::Builder::default() + .plugin(tauri_plugin_localhost::Builder::new(port).build()) + .setup(move |app| { + app.ipc_scope().configure_remote_access( + RemoteDomainAccessScope::new("localhost") + .add_window("main") + ); + + let url = format!("http://localhost:{}", port).parse().unwrap(); + WindowBuilder::new(app, "main".to_string(), WindowUrl::External(url)) + .title("Localhost Example") + .build()?; + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/localhost/SECURITY.md b/packages/kbot/gui/app/plugins/localhost/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/localhost/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/localhost/banner.png b/packages/kbot/gui/app/plugins/localhost/banner.png new file mode 100644 index 00000000..3fe615c1 Binary files /dev/null and b/packages/kbot/gui/app/plugins/localhost/banner.png differ diff --git a/packages/kbot/gui/app/plugins/localhost/src/lib.rs b/packages/kbot/gui/app/plugins/localhost/src/lib.rs new file mode 100644 index 00000000..5b00087f --- /dev/null +++ b/packages/kbot/gui/app/plugins/localhost/src/lib.rs @@ -0,0 +1,126 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Expose your apps assets through a localhost server instead of the default custom protocol. +//! +//! **Note: This plugins brings considerable security risks and you should only use it if you know what your are doing. If in doubt, use the default custom protocol implementation.** + +#![doc( + html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", + html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" +)] + +use std::collections::HashMap; + +use http::Uri; +use tauri::{ + plugin::{Builder as PluginBuilder, TauriPlugin}, + Runtime, +}; +use tiny_http::{Header, Response as HttpResponse, Server}; + +pub struct Request { + url: String, +} + +impl Request { + pub fn url(&self) -> &str { + &self.url + } +} + +pub struct Response { + headers: HashMap, +} + +impl Response { + pub fn add_header, V: Into>(&mut self, header: H, value: V) { + self.headers.insert(header.into(), value.into()); + } +} + +type OnRequest = Option>; + +pub struct Builder { + port: u16, + host: Option, + on_request: OnRequest, +} + +impl Builder { + pub fn new(port: u16) -> Self { + Self { + port, + host: None, + on_request: None, + } + } + + // Change the host the plugin binds to. Defaults to `localhost`. + pub fn host>(mut self, host: H) -> Self { + self.host = Some(host.into()); + self + } + + pub fn on_request( + mut self, + f: F, + ) -> Self { + self.on_request.replace(Box::new(f)); + self + } + + pub fn build(mut self) -> TauriPlugin { + let port = self.port; + let host = self.host.unwrap_or("localhost".to_string()); + let on_request = self.on_request.take(); + + PluginBuilder::new("localhost") + .setup(move |app, _api| { + let asset_resolver = app.asset_resolver(); + std::thread::spawn(move || { + let server = + Server::http(format!("{host}:{port}")).expect("Unable to spawn server"); + for req in server.incoming_requests() { + let path = req + .url() + .parse::() + .map(|uri| uri.path().into()) + .unwrap_or_else(|_| req.url().into()); + + #[allow(unused_mut)] + if let Some(mut asset) = asset_resolver.get(path) { + let request = Request { + url: req.url().into(), + }; + let mut response = Response { + headers: Default::default(), + }; + + response.add_header("Content-Type", asset.mime_type); + if let Some(csp) = asset.csp_header { + response + .headers + .insert("Content-Security-Policy".into(), csp); + } + + if let Some(on_request) = &on_request { + on_request(&request, &mut response); + } + + let mut resp = HttpResponse::from_data(asset.bytes); + for (header, value) in response.headers { + if let Ok(h) = Header::from_bytes(header.as_bytes(), value) { + resp.add_header(h); + } + } + req.respond(resp).expect("unable to setup response"); + } + } + }); + Ok(()) + }) + .build() + } +} diff --git a/packages/kbot/gui/app/plugins/log/CHANGELOG.md b/packages/kbot/gui/app/plugins/log/CHANGELOG.md new file mode 100644 index 00000000..138f3c4f --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/CHANGELOG.md @@ -0,0 +1,164 @@ +# Changelog + +## \[2.7.0] + +- [`625bb1c0`](https://github.com/tauri-apps/plugins-workspace/commit/625bb1c0965394b88522643731f78ccbcca84add) ([#2965](https://github.com/tauri-apps/plugins-workspace/pull/2965) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Re-export the log crate. + +## \[2.6.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.5.1] + +### bug + +- [`9799f0db`](https://github.com/tauri-apps/plugins-workspace/commit/9799f0dbabea0b572a9b9111954fbf9aca63da71) ([#2802](https://github.com/tauri-apps/plugins-workspace/pull/2802) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Fixes iOS simulator still freezing sometimes due to early logging. + +## \[2.5.0] + +- [`106e46ed`](https://github.com/tauri-apps/plugins-workspace/commit/106e46ed5125be33d0427cab9c5c066802f68791) ([#677](https://github.com/tauri-apps/plugins-workspace/pull/677)) Added the `KeepSome` rotation strategy. Like `KeepAll` it will rename files when the max file size is exceeded but will keep only the specified amount of files around. + +## \[2.4.0] + +- [`c9b21f6f`](https://github.com/tauri-apps/plugins-workspace/commit/c9b21f6f4345806eff5f495885f20dea0082b7d7) ([#2625](https://github.com/tauri-apps/plugins-workspace/pull/2625) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Export the `LogLevel` type. +- [`9629c2f4`](https://github.com/tauri-apps/plugins-workspace/commit/9629c2f4f90a56b5c2d265d1d13d3af40fc0c525) ([#2600](https://github.com/tauri-apps/plugins-workspace/pull/2600) by [@exoego](https://github.com/tauri-apps/plugins-workspace/../../exoego)) Adds a new varient `TargetKind::Dispatch` that allows you to construct arbitrary log targets +- [`686a839c`](https://github.com/tauri-apps/plugins-workspace/commit/686a839c96fae1b0334f2df9dc76ca5cdbe00dbe) ([#2626](https://github.com/tauri-apps/plugins-workspace/pull/2626) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Fix iOS app stuck when using the iOS Simulator and the log plugin due to a deadlock when calling os_log too early. + +### feat + +- [`60fc35d3`](https://github.com/tauri-apps/plugins-workspace/commit/60fc35d35cccaf1654eceb4446ecf0f89dc15502) ([#2576](https://github.com/tauri-apps/plugins-workspace/pull/2576) by [@3lpsy](https://github.com/tauri-apps/plugins-workspace/../../3lpsy)) Add a `tracing` feature to the `log` plugin that emits log messages to the `tracing` system. + +## \[2.3.1] + +- [`1bb1ced5`](https://github.com/tauri-apps/plugins-workspace/commit/1bb1ced53820127204aa7adf57510c1cbce55e12) ([#2524](https://github.com/tauri-apps/plugins-workspace/pull/2524) by [@elwerene](https://github.com/tauri-apps/plugins-workspace/../../elwerene)) enable TargetKind::LogDir on mobile + +## \[2.3.0] + +### feat + +- [`02481501`](https://github.com/tauri-apps/plugins-workspace/commit/024815018fbc63a37afc716796a454925aa7d25e) ([#2377](https://github.com/tauri-apps/plugins-workspace/pull/2377) by [@3lpsy](https://github.com/tauri-apps/plugins-workspace/../../3lpsy)) Add a `is_skip_logger` flag to the Log Plugin `Builder` struct, a `skip_logger()` method to the Builder, and logic to avoid acquiring (creating) a logger and attaching it to the global logger. Since acquire_logger is pub, a `LoggerNotInitialized` is added and returned if it's called when the `is_skip_looger` flag is set. Overall, this feature permits a user to avoid calling `attach_logger` which can only be called once in a program's lifetime and allows the user to control the logger returned from `logger()`. Additionally, it also will allow users to generate multiple Tauri Mock apps in test suites that run and parallel and have the `log` plugin attached (assuming they use `skip_logger()`). + +## \[2.2.3] + +- [`1a984659`](https://github.com/tauri-apps/plugins-workspace/commit/1a9846599b6a71faf330845847a30f6bf9735898) ([#2469](https://github.com/tauri-apps/plugins-workspace/pull/2469) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Update `objc2` crate to 0.6. No user facing changes. + +## \[2.2.2] + +- [`6b4c3917`](https://github.com/tauri-apps/plugins-workspace/commit/6b4c3917389f4bc489d03b48a837557ac0584175) ([#2401](https://github.com/tauri-apps/plugins-workspace/pull/2401) by [@Seishiin](https://github.com/tauri-apps/plugins-workspace/../../Seishiin)) Fix timezone_strategy overwriting previously set LogLevels. + +## \[2.2.1] + +- [`784a54a3`](https://github.com/tauri-apps/plugins-workspace/commit/784a54a39094dfbaaa8dd123eb083c04dc6c3bb2) ([#2344](https://github.com/tauri-apps/plugins-workspace/pull/2344) by [@madsmtm](https://github.com/tauri-apps/plugins-workspace/../../madsmtm)) Use `objc2` instead of `objc`. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.2] + +- [`69d508ee`](https://github.com/tauri-apps/plugins-workspace/commit/69d508ee6910ae4064f2398fbacb803b3944d6a8) ([#2157](https://github.com/tauri-apps/plugins-workspace/pull/2157) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Make log functions omit caller location when failed to parse it instead of throwing + +## \[2.0.1] + +- [`371a2f73`](https://github.com/tauri-apps/plugins-workspace/commit/371a2f7361e0b91cf66f1287ffb18b34414a6cb8) ([#2021](https://github.com/tauri-apps/plugins-workspace/pull/2021) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Make webview log target more consistent that it always starts with `webview` + +## \[2.0.2] + +- [`606fa08d`](https://github.com/tauri-apps/plugins-workspace/commit/606fa08dae1acd074b961fb360623f4c86f13ee8) ([#1997](https://github.com/tauri-apps/plugins-workspace/pull/1997) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) **Potentially breaking:** Updated `fern` from 0.6 to 0.7. This is technically a breaking change because `fern` is re-exported in `tauri-plugin-log`. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.1] + +- [`b9147758`](https://github.com/tauri-apps/plugins-workspace/commit/b914775898c2bee7ceb20bd17ee595005cd17a64) ([#1679](https://github.com/tauri-apps/plugins-workspace/pull/1679) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Explicitly set a minimum macOS version for the Swift package. + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +## \[2.0.0-beta.9] + +- [`99d6ac0f`](https://github.com/tauri-apps/plugins-workspace/commit/99d6ac0f9506a6a4a1aa59c728157190a7441af6) ([#1606](https://github.com/tauri-apps/plugins-workspace/pull/1606) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The JS packages now specify the *minimum* `@tauri-apps/api` version instead of a single exact version. +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.9] + +- [`20a1d24e`](https://github.com/tauri-apps/plugins-workspace/commit/20a1d24ee004e77c2d12a0e20d258ce120216ed1) ([#1579](https://github.com/tauri-apps/plugins-workspace/pull/1579) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Added `Builder::split` which returns the raw logger implementation so you can pipe to other loggers such as `multi_log` or `tauri-plugin-devtools`. + +## \[2.0.0-beta.8] + +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.7] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.6] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.5] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.4] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.3] + +- [`ed46dca`](https://github.com/tauri-apps/plugins-workspace/commit/ed46dca74ff3947dbbcb26a7b571c129bf925698) Added `attachLogger` helper function to register a function that should be called for each log entry. + +## \[2.0.0-beta.3] + +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. + +## \[2.0.0-beta.2] + +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. + +## \[2.0.0-alpha.5] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.13. + +## \[2.0.0-alpha.4] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.12. + +## \[2.0.0-alpha.3] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to @tauri-apps/api v2.0.0-alpha.11. + +## \[2.0.0-alpha.2] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update to @tauri-apps/api v2.0.0-alpha.9. + +## \[2.0.0-alpha.2] + +- [`4e2cef9`](https://github.com/tauri-apps/plugins-workspace/commit/4e2cef9b702bbbb9cf4ee17de50791cb21f1b2a4)([#593](https://github.com/tauri-apps/plugins-workspace/pull/593)) Update to alpha.12. + +## \[2.0.0-alpha.1] + +- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11. + +## \[2.0.0-alpha.0] + +- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! diff --git a/packages/kbot/gui/app/plugins/log/Cargo.toml b/packages/kbot/gui/app/plugins/log/Cargo.toml new file mode 100644 index 00000000..1cbe906e --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "tauri-plugin-log" +version = "2.7.0" +description = "Configurable logging for your Tauri app." +authors = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-log" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.platforms.support] +windows = { level = "full", notes = "" } +linux = { level = "full", notes = "" } +macos = { level = "full", notes = "" } +android = { level = "full", notes = "" } +ios = { level = "full", notes = "" } + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +thiserror = { workspace = true } +serde_repr = "0.1" +byte-unit = "5" +log = { workspace = true, features = ["kv_unstable"] } +time = { version = "0.3", features = [ + "formatting", + "local-offset", + "macros", + "parsing", +] } +fern = "0.7" +tracing = { workspace = true, optional = true } + +[target."cfg(target_os = \"android\")".dependencies] +android_logger = "0.15" + +[target."cfg(target_os = \"ios\")".dependencies] +swift-rs = "1" +objc2 = "0.6" +objc2-foundation = { version = "0.3", default-features = false, features = [ + "std", + "NSString", +] } + +[features] +colored = ["fern/colored"] +tracing = ["dep:tracing"] diff --git a/packages/kbot/gui/app/plugins/log/LICENSE.spdx b/packages/kbot/gui/app/plugins/log/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/log/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/log/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/log/LICENSE_MIT b/packages/kbot/gui/app/plugins/log/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/log/README.md b/packages/kbot/gui/app/plugins/log/README.md new file mode 100644 index 00000000..81ca9fd8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/README.md @@ -0,0 +1,132 @@ +![plugin-log](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/log/banner.png) + +Configurable logging for your Tauri app. + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | ✓ | +| iOS | ✓ | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-log = "2.0.0" +# alternatively with Git: +tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +If you want the single instance mechanism to only trigger for semver compatible instances of your apps, for example if you expect users to have multiple installations of your app installed, you can add `features = ["semver"]` to the dependency declaration in `Cargo.toml`. + +Then you can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +```sh +pnpm add @tauri-apps/plugin-log +# or +npm add @tauri-apps/plugin-log +# or +yarn add @tauri-apps/plugin-log +``` + +## Usage + +First, you should enable the `log:default` capability: + +```json +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": ["main"], + "permissions": [ + "core:default", + "opener:default", + "log:default" # add this! + ] +} +``` + +Then, you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +use tauri_plugin_log::{Target, TargetKind}; + +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_log::Builder::new().targets([ + Target::new(TargetKind::Stdout), + Target::new(TargetKind::LogDir { file_name: None }), + Target::new(TargetKind::Webview), + ]).build()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { trace, info, error, attachConsole } from '@tauri-apps/plugin-log' + +// with TargetKind::Webview enabled this function will print logs to the browser console +const detach = await attachConsole() + +trace('Trace') +info('Info') +error('Error') + +// detach the browser console from the log stream +detach() +``` + +To log from rust code, add the log crate to your `Cargo.toml`: + +```toml +[dependencies] +log = "^0.4" +``` + +Now, you can use the macros provided by the log crate to log messages from your backend. See the [docs](https://docs.rs/log/latest) for more details. + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/log/SECURITY.md b/packages/kbot/gui/app/plugins/log/SECURITY.md new file mode 100644 index 00000000..d013f6a6 --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/SECURITY.md @@ -0,0 +1,56 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). + +## Threat Model + +### Security Assumptions + +- The log file interpreting applications are hardened, as input is not sanitized +- No log events in the rust core are leaked to the frontend unless explicitly configured to output to the `TargetKind::Webview` component +- The log events generated in the frontend can be accessed from everywhere in the frontend +- There is no secret censoring inbuilt and developers need to take care of what they log in their application + +### Threats + +#### Secret Leakage + +One possible threat you need to consider when using this plugin is that secrets +in logs can theoretically be leaked when the application's frontend gets compromised. + +For this threat to be possible all of the following requirements need to be fulfilled: + +- `TargetKind::Webview` enabled OR secrets stem from frontend logs +- Frontend application is compromised via something like XSS (cross-site-scripting) OR logs are directly exposed +- Logs contain secrets or sensitive information + +If these requirements are not met, the leakage should not be possible. + +#### Out Of Scope + +- Any exploits on the log viewer/file viewer accessing the logs + +## Best Practices + +Do not log secrets or sensitive values in your logging and ensure that the upstream crates are not leaking such values in their logging events. +Ensure that logs are sanitized or trusted before opening them with third party tools. diff --git a/packages/kbot/gui/app/plugins/log/api-iife.js b/packages/kbot/gui/app/plugins/log/api-iife.js new file mode 100644 index 00000000..0b92891d --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_LOG__=function(e){"use strict";function n(e,n=!1){return window.__TAURI_INTERNALS__.transformCallback(e,n)}async function r(e,n={},r){return window.__TAURI_INTERNALS__.invoke(e,n,r)}var a,t;async function o(e,a,t){const o={kind:"Any"};return r("plugin:event|listen",{event:e,target:o,handler:n(a)}).then((n=>async()=>async function(e,n){window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(e,n),await r("plugin:event|unlisten",{event:e,eventId:n})}(e,n)))}async function i(e,n,a){const t=function(e){if(e){if(!e.startsWith("Error")){const n=e.split("\n").map((e=>e.split("@"))).filter((([e,n])=>e.length>0&&"[native code]"!==n));return n[2]?.filter((e=>e.length>0)).join("@")}{const n=e.split("\n"),r=n[3]?.trim();if(!r)return;const a=/at\s+(?.*?)\s+\((?.*?):(?\d+):(?\d+)\)/,t=r.match(a);if(t){const{functionName:e,fileName:n,lineNumber:r,columnNumber:a}=t.groups;return`${e}@${n}:${r}:${a}`}{const e=/at\s+(?.*?):(?\d+):(?\d+)/,n=r.match(e);if(n){const{fileName:e,lineNumber:r,columnNumber:a}=n.groups;return`@${e}:${r}:${a}`}}}}}((new Error).stack),{file:o,line:i,keyValues:u}=a??{};await r("plugin:log|log",{level:e,message:n,location:t,file:o,line:i,keyValues:u})}async function u(e){return await o("log://log",(n=>{const{level:r}=n.payload;let{message:a}=n.payload;a=a.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,""),e({message:a,level:r})}))}return"function"==typeof SuppressedError&&SuppressedError,function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG_ENTER="tauri://drag-enter",e.DRAG_OVER="tauri://drag-over",e.DRAG_DROP="tauri://drag-drop",e.DRAG_LEAVE="tauri://drag-leave"}(a||(a={})),e.LogLevel=void 0,(t=e.LogLevel||(e.LogLevel={}))[t.Trace=1]="Trace",t[t.Debug=2]="Debug",t[t.Info=3]="Info",t[t.Warn=4]="Warn",t[t.Error=5]="Error",e.attachConsole=async function(){return await u((({level:n,message:r})=>{switch(n){case e.LogLevel.Trace:console.log(r);break;case e.LogLevel.Debug:console.debug(r);break;case e.LogLevel.Info:console.info(r);break;case e.LogLevel.Warn:console.warn(r);break;case e.LogLevel.Error:console.error(r);break;default:throw new Error(`unknown log level ${n}`)}}))},e.attachLogger=u,e.debug=async function(n,r){await i(e.LogLevel.Debug,n,r)},e.error=async function(n,r){await i(e.LogLevel.Error,n,r)},e.info=async function(n,r){await i(e.LogLevel.Info,n,r)},e.trace=async function(n,r){await i(e.LogLevel.Trace,n,r)},e.warn=async function(n,r){await i(e.LogLevel.Warn,n,r)},e}({});Object.defineProperty(window.__TAURI__,"log",{value:__TAURI_PLUGIN_LOG__})} diff --git a/packages/kbot/gui/app/plugins/log/banner.png b/packages/kbot/gui/app/plugins/log/banner.png new file mode 100644 index 00000000..c72d62cf Binary files /dev/null and b/packages/kbot/gui/app/plugins/log/banner.png differ diff --git a/packages/kbot/gui/app/plugins/log/build.rs b/packages/kbot/gui/app/plugins/log/build.rs new file mode 100644 index 00000000..5969c1e9 --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/build.rs @@ -0,0 +1,12 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +const COMMANDS: &[&str] = &["log"]; + +fn main() { + tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .ios_path("ios") + .build(); +} diff --git a/packages/kbot/gui/app/plugins/log/guest-js/index.ts b/packages/kbot/gui/app/plugins/log/guest-js/index.ts new file mode 100644 index 00000000..93022a97 --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/guest-js/index.ts @@ -0,0 +1,300 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { invoke } from '@tauri-apps/api/core' +import { listen, type UnlistenFn, type Event } from '@tauri-apps/api/event' + +export interface LogOptions { + file?: string + line?: number + keyValues?: Record +} + +export enum LogLevel { + /** + * The "trace" level. + * + * Designates very low priority, often extremely verbose, information. + */ + Trace = 1, + /** + * The "debug" level. + * + * Designates lower priority information. + */ + Debug, + /** + * The "info" level. + * + * Designates useful information. + */ + Info, + /** + * The "warn" level. + * + * Designates hazardous situations. + */ + Warn, + /** + * The "error" level. + * + * Designates very serious errors. + */ + Error +} + +function getCallerLocation(stack?: string) { + if (!stack) { + return + } + + if (stack.startsWith('Error')) { + // Assume it's Chromium V8 + // + // Error + // at baz (filename.js:10:15) + // at bar (filename.js:6:3) + // at foo (filename.js:2:3) + // at filename.js:13:1 + + const lines = stack.split('\n') + // Find the third line (caller's caller of the current location) + const callerLine = lines[3]?.trim() + if (!callerLine) { + return + } + + const regex = + /at\s+(?.*?)\s+\((?.*?):(?\d+):(?\d+)\)/ + const match = callerLine.match(regex) + + if (match) { + const { functionName, fileName, lineNumber, columnNumber } = + match.groups as { + functionName: string + fileName: string + lineNumber: string + columnNumber: string + } + return `${functionName}@${fileName}:${lineNumber}:${columnNumber}` + } else { + // Handle cases where the regex does not match (e.g., last line without function name) + const regexNoFunction = + /at\s+(?.*?):(?\d+):(?\d+)/ + const matchNoFunction = callerLine.match(regexNoFunction) + if (matchNoFunction) { + const { fileName, lineNumber, columnNumber } = + matchNoFunction.groups as { + fileName: string + lineNumber: string + columnNumber: string + } + return `@${fileName}:${lineNumber}:${columnNumber}` + } + } + } else { + // Assume it's Webkit JavaScriptCore, example: + // + // baz@filename.js:10:24 + // bar@filename.js:6:6 + // foo@filename.js:2:6 + // global code@filename.js:13:4 + + const traces = stack.split('\n').map((line) => line.split('@')) + const filtered = traces.filter(([name, location]) => { + return name.length > 0 && location !== '[native code]' + }) + // Find the third line (caller's caller of the current location) + return filtered[2]?.filter((v) => v.length > 0).join('@') + } +} + +async function log( + level: LogLevel, + message: string, + options?: LogOptions +): Promise { + const location = getCallerLocation(new Error().stack) + + const { file, line, keyValues } = options ?? {} + + await invoke('plugin:log|log', { + level, + message, + location, + file, + line, + keyValues + }) +} + +/** + * Logs a message at the error level. + * + * @param message + * + * # Examples + * + * ```js + * import { error } from '@tauri-apps/plugin-log'; + * + * const err_info = "No connection"; + * const port = 22; + * + * error(`Error: ${err_info} on port ${port}`); + * ``` + */ +export async function error( + message: string, + options?: LogOptions +): Promise { + await log(LogLevel.Error, message, options) +} + +/** + * Logs a message at the warn level. + * + * @param message + * + * # Examples + * + * ```js + * import { warn } from '@tauri-apps/plugin-log'; + * + * const warn_description = "Invalid Input"; + * + * warn(`Warning! {warn_description}!`); + * ``` + */ +export async function warn( + message: string, + options?: LogOptions +): Promise { + await log(LogLevel.Warn, message, options) +} + +/** + * Logs a message at the info level. + * + * @param message + * + * # Examples + * + * ```js + * import { info } from '@tauri-apps/plugin-log'; + * + * const conn_info = { port: 40, speed: 3.20 }; + * + * info(`Connected to port {conn_info.port} at {conn_info.speed} Mb/s`); + * ``` + */ +export async function info( + message: string, + options?: LogOptions +): Promise { + await log(LogLevel.Info, message, options) +} + +/** + * Logs a message at the debug level. + * + * @param message + * + * # Examples + * + * ```js + * import { debug } from '@tauri-apps/plugin-log'; + * + * const pos = { x: 3.234, y: -1.223 }; + * + * debug(`New position: x: {pos.x}, y: {pos.y}`); + * ``` + */ +export async function debug( + message: string, + options?: LogOptions +): Promise { + await log(LogLevel.Debug, message, options) +} + +/** + * Logs a message at the trace level. + * + * @param message + * + * # Examples + * + * ```js + * import { trace } from '@tauri-apps/plugin-log'; + * + * let pos = { x: 3.234, y: -1.223 }; + * + * trace(`Position is: x: {pos.x}, y: {pos.y}`); + * ``` + */ +export async function trace( + message: string, + options?: LogOptions +): Promise { + await log(LogLevel.Trace, message, options) +} + +interface RecordPayload { + level: LogLevel + message: string +} + +type LoggerFn = (fn: RecordPayload) => void + +/** + * Attaches a listener for the log, and calls the passed function for each log entry. + * @param fn + * + * @returns a function to cancel the listener. + */ +export async function attachLogger(fn: LoggerFn): Promise { + return await listen('log://log', (event: Event) => { + const { level } = event.payload + let { message } = event.payload + + // Strip ANSI escape codes + message = message.replace( + // TODO: Investigate security/detect-unsafe-regex + // eslint-disable-next-line no-control-regex, security/detect-unsafe-regex + /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, + '' + ) + fn({ message, level }) + }) +} + +/** + * Attaches a listener that writes log entries to the console as they come in. + * + * @returns a function to cancel the listener. + */ +export async function attachConsole(): Promise { + return await attachLogger(({ level, message }: RecordPayload) => { + switch (level) { + case LogLevel.Trace: + console.log(message) + break + case LogLevel.Debug: + console.debug(message) + break + case LogLevel.Info: + console.info(message) + break + case LogLevel.Warn: + console.warn(message) + break + case LogLevel.Error: + console.error(message) + break + default: + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + throw new Error(`unknown log level ${level}`) + } + }) +} diff --git a/packages/kbot/gui/app/plugins/log/ios/.gitignore b/packages/kbot/gui/app/plugins/log/ios/.gitignore new file mode 100644 index 00000000..5922fdaa --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/ios/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Package.resolved diff --git a/packages/kbot/gui/app/plugins/log/ios/Package.swift b/packages/kbot/gui/app/plugins/log/ios/Package.swift new file mode 100644 index 00000000..1571f22e --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/ios/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version:5.3 +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import PackageDescription + +let package = Package( + name: "tauri-plugin-log", + platforms: [ + .macOS(.v10_13), + .iOS(.v11), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "tauri-plugin-log", + type: .static, + targets: ["tauri-plugin-log"]) + ], + dependencies: [ + .package(name: "Tauri", path: "../.tauri/tauri-api") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "tauri-plugin-log", + dependencies: [ + .byName(name: "Tauri") + ], + path: "Sources") + ] +) diff --git a/packages/kbot/gui/app/plugins/log/ios/README.md b/packages/kbot/gui/app/plugins/log/ios/README.md new file mode 100644 index 00000000..578d9ffa --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/ios/README.md @@ -0,0 +1,3 @@ +# Log + +Exposes a function log a message using the OSLog API. diff --git a/packages/kbot/gui/app/plugins/log/ios/Sources/LogPlugin.swift b/packages/kbot/gui/app/plugins/log/ios/Sources/LogPlugin.swift new file mode 100644 index 00000000..9903920f --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/ios/Sources/LogPlugin.swift @@ -0,0 +1,58 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import SwiftRs +import Tauri +import UIKit +import os.log + +#if targetEnvironment(simulator) + var logReady = false +#else + var logReady = true +#endif + +var pendingLogs: [(Int, NSString)] = [] +var elapsedTime: TimeInterval = 0 +var logFlushScheduled = false + +@_cdecl("tauri_log") +func log(level: Int, message: NSString) { + if logReady { + os_log(level, message) + } else { + pendingLogs.append((level, message)) + scheduleLogFlush() + } +} + +// delay logging when the logger isn't immediately available +// in some cases when using the simulator the app would hang when calling os_log too soon +// better be safe here and wait a few seconds than actually freeze the app in dev mode +// in production this isn't a problem +func scheduleLogFlush() { + guard !logFlushScheduled else { return } + logFlushScheduled = true + + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { + logReady = true + flushLogs() + } +} + +func flushLogs() { + for (level, message) in pendingLogs { + os_log(level, message) + } + pendingLogs.removeAll() +} + +func os_log(_ level: Int, _ message: NSString) { + switch level { + case 1: Logger.debug(message as String) + case 2: Logger.info(message as String) + case 3: Logger.error(message as String) + default: break + } +} diff --git a/packages/kbot/gui/app/plugins/log/package.json b/packages/kbot/gui/app/plugins/log/package.json new file mode 100644 index 00000000..d96fed28 --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/package.json @@ -0,0 +1,30 @@ +{ + "name": "@tauri-apps/plugin-log", + "version": "2.7.0", + "description": "Configurable logging for your Tauri app.", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/log/permissions/autogenerated/commands/log.toml b/packages/kbot/gui/app/plugins/log/permissions/autogenerated/commands/log.toml new file mode 100644 index 00000000..ba36eff5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/permissions/autogenerated/commands/log.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-log" +description = "Enables the log command without any pre-configured scope." +commands.allow = ["log"] + +[[permission]] +identifier = "deny-log" +description = "Denies the log command without any pre-configured scope." +commands.deny = ["log"] diff --git a/packages/kbot/gui/app/plugins/log/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/log/permissions/autogenerated/reference.md new file mode 100644 index 00000000..57d6c9f3 --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/permissions/autogenerated/reference.md @@ -0,0 +1,43 @@ +## Default Permission + +Allows the log command + +#### This default permission set includes the following: + +- `allow-log` + +## Permission Table + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`log:allow-log` + + + +Enables the log command without any pre-configured scope. + +
+ +`log:deny-log` + + + +Denies the log command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/log/permissions/default.toml b/packages/kbot/gui/app/plugins/log/permissions/default.toml new file mode 100644 index 00000000..b2cb7c3a --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/permissions/default.toml @@ -0,0 +1,4 @@ +"$schema" = "schemas/schema.json" +[default] +description = "Allows the log command" +permissions = ["allow-log"] diff --git a/packages/kbot/gui/app/plugins/log/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/log/permissions/schemas/schema.json new file mode 100644 index 00000000..cfee7e75 --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/permissions/schemas/schema.json @@ -0,0 +1,318 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the log command without any pre-configured scope.", + "type": "string", + "const": "allow-log", + "markdownDescription": "Enables the log command without any pre-configured scope." + }, + { + "description": "Denies the log command without any pre-configured scope.", + "type": "string", + "const": "deny-log", + "markdownDescription": "Denies the log command without any pre-configured scope." + }, + { + "description": "Allows the log command\n#### This default permission set includes:\n\n- `allow-log`", + "type": "string", + "const": "default", + "markdownDescription": "Allows the log command\n#### This default permission set includes:\n\n- `allow-log`" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/log/rollup.config.js b/packages/kbot/gui/app/plugins/log/rollup.config.js new file mode 100644 index 00000000..1f349ec8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/rollup.config.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' + +export default createConfig() diff --git a/packages/kbot/gui/app/plugins/log/src/commands.rs b/packages/kbot/gui/app/plugins/log/src/commands.rs new file mode 100644 index 00000000..c402db76 --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/src/commands.rs @@ -0,0 +1,73 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::collections::HashMap; + +use log::RecordBuilder; + +use crate::{LogLevel, WEBVIEW_TARGET}; + +#[tauri::command] +pub fn log( + level: LogLevel, + message: String, + location: Option<&str>, + file: Option<&str>, + line: Option, + key_values: Option>, +) { + let level = log::Level::from(level); + + let target = if let Some(location) = location { + format!("{WEBVIEW_TARGET}:{location}") + } else { + WEBVIEW_TARGET.to_string() + }; + + let mut builder = RecordBuilder::new(); + builder.level(level).target(&target).file(file).line(line); + + let key_values = key_values.unwrap_or_default(); + let mut kv = HashMap::new(); + for (k, v) in key_values.iter() { + kv.insert(k.as_str(), v.as_str()); + } + builder.key_values(&kv); + #[cfg(feature = "tracing")] + emit_trace(level, &message, location, file, line, &kv); + + log::logger().log(&builder.args(format_args!("{message}")).build()); +} + +// Target becomes default and location is added as a parameter +#[cfg(feature = "tracing")] +fn emit_trace( + level: log::Level, + message: &String, + location: Option<&str>, + file: Option<&str>, + line: Option, + kv: &HashMap<&str, &str>, +) { + macro_rules! emit_event { + ($level:expr) => { + tracing::event!( + target: WEBVIEW_TARGET, + $level, + message = %message, + location = location, + file, + line, + ?kv + ) + }; + } + match level { + log::Level::Error => emit_event!(tracing::Level::ERROR), + log::Level::Warn => emit_event!(tracing::Level::WARN), + log::Level::Info => emit_event!(tracing::Level::INFO), + log::Level::Debug => emit_event!(tracing::Level::DEBUG), + log::Level::Trace => emit_event!(tracing::Level::TRACE), + } +} diff --git a/packages/kbot/gui/app/plugins/log/src/lib.rs b/packages/kbot/gui/app/plugins/log/src/lib.rs new file mode 100644 index 00000000..c0642d41 --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/src/lib.rs @@ -0,0 +1,603 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Logging for Tauri applications. + +#![doc( + html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", + html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" +)] + +use fern::{Filter, FormatCallback}; +use log::{LevelFilter, Record}; +use serde::Serialize; +use serde_repr::{Deserialize_repr, Serialize_repr}; +use std::borrow::Cow; +use std::{ + fmt::Arguments, + fs::{self, File}, + iter::FromIterator, + path::{Path, PathBuf}, +}; +use tauri::{ + plugin::{self, TauriPlugin}, + Manager, Runtime, +}; +use tauri::{AppHandle, Emitter}; +use time::{macros::format_description, OffsetDateTime}; + +pub use fern; +pub use log; + +mod commands; + +pub const WEBVIEW_TARGET: &str = "webview"; + +#[cfg(target_os = "ios")] +mod ios { + swift_rs::swift!(pub fn tauri_log( + level: u8, message: *const std::ffi::c_void + )); +} + +const DEFAULT_MAX_FILE_SIZE: u128 = 40000; +const DEFAULT_ROTATION_STRATEGY: RotationStrategy = RotationStrategy::KeepOne; +const DEFAULT_TIMEZONE_STRATEGY: TimezoneStrategy = TimezoneStrategy::UseUtc; +const DEFAULT_LOG_TARGETS: [Target; 2] = [ + Target::new(TargetKind::Stdout), + Target::new(TargetKind::LogDir { file_name: None }), +]; +const LOG_DATE_FORMAT: &str = "[year]-[month]-[day]_[hour]-[minute]-[second]"; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Tauri(#[from] tauri::Error), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + TimeFormat(#[from] time::error::Format), + #[error(transparent)] + InvalidFormatDescription(#[from] time::error::InvalidFormatDescription), + #[error("Internal logger disabled and cannot be acquired or attached")] + LoggerNotInitialized, +} + +/// An enum representing the available verbosity levels of the logger. +/// +/// It is very similar to the [`log::Level`], but serializes to unsigned ints instead of strings. +#[derive(Debug, Clone, Deserialize_repr, Serialize_repr)] +#[repr(u16)] +pub enum LogLevel { + /// The "trace" level. + /// + /// Designates very low priority, often extremely verbose, information. + Trace = 1, + /// The "debug" level. + /// + /// Designates lower priority information. + Debug, + /// The "info" level. + /// + /// Designates useful information. + Info, + /// The "warn" level. + /// + /// Designates hazardous situations. + Warn, + /// The "error" level. + /// + /// Designates very serious errors. + Error, +} + +impl From for log::Level { + fn from(log_level: LogLevel) -> Self { + match log_level { + LogLevel::Trace => log::Level::Trace, + LogLevel::Debug => log::Level::Debug, + LogLevel::Info => log::Level::Info, + LogLevel::Warn => log::Level::Warn, + LogLevel::Error => log::Level::Error, + } + } +} + +impl From for LogLevel { + fn from(log_level: log::Level) -> Self { + match log_level { + log::Level::Trace => LogLevel::Trace, + log::Level::Debug => LogLevel::Debug, + log::Level::Info => LogLevel::Info, + log::Level::Warn => LogLevel::Warn, + log::Level::Error => LogLevel::Error, + } + } +} + +pub enum RotationStrategy { + /// Will keep all the logs, renaming them to include the date. + KeepAll, + /// Will only keep the most recent log up to its maximal size. + KeepOne, + /// Will keep some of the most recent logs, renaming them to include the date. + KeepSome(usize), +} + +#[derive(Debug, Clone)] +pub enum TimezoneStrategy { + UseUtc, + UseLocal, +} + +impl TimezoneStrategy { + pub fn get_now(&self) -> OffsetDateTime { + match self { + TimezoneStrategy::UseUtc => OffsetDateTime::now_utc(), + TimezoneStrategy::UseLocal => { + OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()) + } // Fallback to UTC since Rust cannot determine local timezone + } + } +} + +#[derive(Debug, Serialize, Clone)] +struct RecordPayload { + message: String, + level: LogLevel, +} + +/// An enum representing the available targets of the logger. +pub enum TargetKind { + /// Print logs to stdout. + Stdout, + /// Print logs to stderr. + Stderr, + /// Write logs to the given directory. + /// + /// The plugin will ensure the directory exists before writing logs. + Folder { + path: PathBuf, + file_name: Option, + }, + /// Write logs to the OS specific logs directory. + /// + /// ### Platform-specific + /// + /// |Platform | Value | Example | + /// | --------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------- | + /// | Linux | `$XDG_DATA_HOME/{bundleIdentifier}/logs` or `$HOME/.local/share/{bundleIdentifier}/logs` | `/home/alice/.local/share/com.tauri.dev/logs` | + /// | macOS/iOS | `{homeDir}/Library/Logs/{bundleIdentifier}` | `/Users/Alice/Library/Logs/com.tauri.dev` | + /// | Windows | `{FOLDERID_LocalAppData}/{bundleIdentifier}/logs` | `C:\Users\Alice\AppData\Local\com.tauri.dev\logs` | + /// | Android | `{ConfigDir}/logs` | `/data/data/com.tauri.dev/files/logs` | + LogDir { file_name: Option }, + /// Forward logs to the webview (via the `log://log` event). + /// + /// This requires the webview to subscribe to log events, via this plugins `attachConsole` function. + Webview, + /// Send logs to a [`fern::Dispatch`] + /// + /// You can use this to construct arbitrary log targets. + Dispatch(fern::Dispatch), +} + +/// A log target. +pub struct Target { + kind: TargetKind, + filters: Vec>, +} + +impl Target { + #[inline] + pub const fn new(kind: TargetKind) -> Self { + Self { + kind, + filters: Vec::new(), + } + } + + #[inline] + pub fn filter(mut self, filter: F) -> Self + where + F: Fn(&log::Metadata) -> bool + Send + Sync + 'static, + { + self.filters.push(Box::new(filter)); + self + } +} + +pub struct Builder { + dispatch: fern::Dispatch, + rotation_strategy: RotationStrategy, + timezone_strategy: TimezoneStrategy, + max_file_size: u128, + targets: Vec, + is_skip_logger: bool, +} + +impl Default for Builder { + fn default() -> Self { + #[cfg(desktop)] + let format = format_description!("[[[year]-[month]-[day]][[[hour]:[minute]:[second]]"); + let dispatch = fern::Dispatch::new().format(move |out, message, record| { + out.finish( + #[cfg(mobile)] + format_args!("[{}] {}", record.target(), message), + #[cfg(desktop)] + format_args!( + "{}[{}][{}] {}", + DEFAULT_TIMEZONE_STRATEGY.get_now().format(&format).unwrap(), + record.target(), + record.level(), + message + ), + ) + }); + Self { + dispatch, + rotation_strategy: DEFAULT_ROTATION_STRATEGY, + timezone_strategy: DEFAULT_TIMEZONE_STRATEGY, + max_file_size: DEFAULT_MAX_FILE_SIZE, + targets: DEFAULT_LOG_TARGETS.into(), + is_skip_logger: false, + } + } +} + +impl Builder { + pub fn new() -> Self { + Default::default() + } + + pub fn rotation_strategy(mut self, rotation_strategy: RotationStrategy) -> Self { + self.rotation_strategy = rotation_strategy; + self + } + + pub fn timezone_strategy(mut self, timezone_strategy: TimezoneStrategy) -> Self { + self.timezone_strategy = timezone_strategy.clone(); + + let format = format_description!("[[[year]-[month]-[day]][[[hour]:[minute]:[second]]"); + self.dispatch = self.dispatch.format(move |out, message, record| { + out.finish(format_args!( + "{}[{}][{}] {}", + timezone_strategy.get_now().format(&format).unwrap(), + record.level(), + record.target(), + message + )) + }); + self + } + + pub fn max_file_size(mut self, max_file_size: u128) -> Self { + self.max_file_size = max_file_size; + self + } + + pub fn format(mut self, formatter: F) -> Self + where + F: Fn(FormatCallback, &Arguments, &Record) + Sync + Send + 'static, + { + self.dispatch = self.dispatch.format(formatter); + self + } + + pub fn level(mut self, level_filter: impl Into) -> Self { + self.dispatch = self.dispatch.level(level_filter.into()); + self + } + + pub fn level_for(mut self, module: impl Into>, level: LevelFilter) -> Self { + self.dispatch = self.dispatch.level_for(module, level); + self + } + + pub fn filter(mut self, filter: F) -> Self + where + F: Fn(&log::Metadata) -> bool + Send + Sync + 'static, + { + self.dispatch = self.dispatch.filter(filter); + self + } + + /// Removes all targets. Useful to ignore the default targets and reconfigure them. + pub fn clear_targets(mut self) -> Self { + self.targets.clear(); + self + } + + /// Adds a log target to the logger. + /// + /// ```rust + /// use tauri_plugin_log::{Target, TargetKind}; + /// tauri_plugin_log::Builder::new() + /// .target(Target::new(TargetKind::Webview)); + /// ``` + pub fn target(mut self, target: Target) -> Self { + self.targets.push(target); + self + } + + /// Skip the creation and global registration of a logger + /// + /// If you wish to use your own global logger, you must call `skip_logger` so that the plugin does not attempt to set a second global logger. In this configuration, no logger will be created and the plugin's `log` command will rely on the result of `log::logger()`. You will be responsible for configuring the logger yourself and any included targets will be ignored. If ever initializing the plugin multiple times, such as if registering the plugin while testing, call this method to avoid panicking when registering multiple loggers. For interacting with `tracing`, you can leverage the `tracing-log` logger to forward logs to `tracing` or enable the `tracing` feature for this plugin to emit events directly to the tracing system. Both scenarios require calling this method. + /// ```rust + /// static LOGGER: SimpleLogger = SimpleLogger; + /// + /// log::set_logger(&SimpleLogger)?; + /// log::set_max_level(LevelFilter::Info); + /// tauri_plugin_log::Builder::new() + /// .skip_logger(); + /// ``` + pub fn skip_logger(mut self) -> Self { + self.is_skip_logger = true; + self + } + + /// Replaces the targets of the logger. + /// + /// ```rust + /// use tauri_plugin_log::{Target, TargetKind, WEBVIEW_TARGET}; + /// tauri_plugin_log::Builder::new() + /// .targets([ + /// Target::new(TargetKind::Webview), + /// Target::new(TargetKind::LogDir { file_name: Some("webview".into()) }).filter(|metadata| metadata.target().starts_with(WEBVIEW_TARGET)), + /// Target::new(TargetKind::LogDir { file_name: Some("rust".into()) }).filter(|metadata| !metadata.target().starts_with(WEBVIEW_TARGET)), + /// ]); + /// ``` + pub fn targets(mut self, targets: impl IntoIterator) -> Self { + self.targets = Vec::from_iter(targets); + self + } + + #[cfg(feature = "colored")] + pub fn with_colors(self, colors: fern::colors::ColoredLevelConfig) -> Self { + let format = format_description!("[[[year]-[month]-[day]][[[hour]:[minute]:[second]]"); + + let timezone_strategy = self.timezone_strategy.clone(); + self.format(move |out, message, record| { + out.finish(format_args!( + "{}[{}][{}] {}", + timezone_strategy.get_now().format(&format).unwrap(), + colors.color(record.level()), + record.target(), + message + )) + }) + } + + fn acquire_logger( + app_handle: &AppHandle, + mut dispatch: fern::Dispatch, + rotation_strategy: RotationStrategy, + timezone_strategy: TimezoneStrategy, + max_file_size: u128, + targets: Vec, + ) -> Result<(log::LevelFilter, Box), Error> { + let app_name = &app_handle.package_info().name; + + // setup targets + for target in targets { + let mut target_dispatch = fern::Dispatch::new(); + for filter in target.filters { + target_dispatch = target_dispatch.filter(filter); + } + + let logger = match target.kind { + #[cfg(target_os = "android")] + TargetKind::Stdout | TargetKind::Stderr => fern::Output::call(android_logger::log), + #[cfg(target_os = "ios")] + TargetKind::Stdout | TargetKind::Stderr => fern::Output::call(move |record| { + let message = format!("{}", record.args()); + unsafe { + ios::tauri_log( + match record.level() { + log::Level::Trace | log::Level::Debug => 1, + log::Level::Info => 2, + log::Level::Warn | log::Level::Error => 3, + }, + // The string is allocated in rust, so we must + // autorelease it rust to give it to the Swift + // runtime. + objc2::rc::Retained::autorelease_ptr( + objc2_foundation::NSString::from_str(message.as_str()), + ) as _, + ); + } + }), + #[cfg(desktop)] + TargetKind::Stdout => std::io::stdout().into(), + #[cfg(desktop)] + TargetKind::Stderr => std::io::stderr().into(), + TargetKind::Folder { path, file_name } => { + if !path.exists() { + fs::create_dir_all(&path)?; + } + + fern::log_file(get_log_file_path( + &path, + file_name.as_deref().unwrap_or(app_name), + &rotation_strategy, + &timezone_strategy, + max_file_size, + )?)? + .into() + } + TargetKind::LogDir { file_name } => { + let path = app_handle.path().app_log_dir()?; + if !path.exists() { + fs::create_dir_all(&path)?; + } + + fern::log_file(get_log_file_path( + &path, + file_name.as_deref().unwrap_or(app_name), + &rotation_strategy, + &timezone_strategy, + max_file_size, + )?)? + .into() + } + TargetKind::Webview => { + let app_handle = app_handle.clone(); + + fern::Output::call(move |record| { + let payload = RecordPayload { + message: record.args().to_string(), + level: record.level().into(), + }; + let app_handle = app_handle.clone(); + tauri::async_runtime::spawn(async move { + let _ = app_handle.emit("log://log", payload); + }); + }) + } + TargetKind::Dispatch(dispatch) => dispatch.into(), + }; + target_dispatch = target_dispatch.chain(logger); + + dispatch = dispatch.chain(target_dispatch); + } + + Ok(dispatch.into_log()) + } + + fn plugin_builder() -> plugin::Builder { + plugin::Builder::new("log").invoke_handler(tauri::generate_handler![commands::log]) + } + + #[allow(clippy::type_complexity)] + pub fn split( + self, + app_handle: &AppHandle, + ) -> Result<(TauriPlugin, log::LevelFilter, Box), Error> { + if self.is_skip_logger { + return Err(Error::LoggerNotInitialized); + } + let plugin = Self::plugin_builder(); + let (max_level, log) = Self::acquire_logger( + app_handle, + self.dispatch, + self.rotation_strategy, + self.timezone_strategy, + self.max_file_size, + self.targets, + )?; + + Ok((plugin.build(), max_level, log)) + } + + pub fn build(self) -> TauriPlugin { + Self::plugin_builder() + .setup(move |app_handle, _api| { + if !self.is_skip_logger { + let (max_level, log) = Self::acquire_logger( + app_handle, + self.dispatch, + self.rotation_strategy, + self.timezone_strategy, + self.max_file_size, + self.targets, + )?; + attach_logger(max_level, log)?; + } + Ok(()) + }) + .build() + } +} + +/// Attaches the given logger +pub fn attach_logger( + max_level: log::LevelFilter, + log: Box, +) -> Result<(), log::SetLoggerError> { + log::set_boxed_logger(log)?; + log::set_max_level(max_level); + Ok(()) +} + +fn rename_file_to_dated( + path: &impl AsRef, + dir: &impl AsRef, + file_name: &str, + timezone_strategy: &TimezoneStrategy, +) -> Result<(), Error> { + let to = dir.as_ref().join(format!( + "{}_{}.log", + file_name, + timezone_strategy + .get_now() + .format(&time::format_description::parse(LOG_DATE_FORMAT).unwrap()) + .unwrap(), + )); + if to.is_file() { + // designated rotated log file name already exists + // highly unlikely but defensively handle anyway by adding .bak to filename + let mut to_bak = to.clone(); + to_bak.set_file_name(format!( + "{}.bak", + to_bak.file_name().unwrap().to_string_lossy() + )); + fs::rename(&to, to_bak)?; + } + fs::rename(path, to)?; + Ok(()) +} + +fn get_log_file_path( + dir: &impl AsRef, + file_name: &str, + rotation_strategy: &RotationStrategy, + timezone_strategy: &TimezoneStrategy, + max_file_size: u128, +) -> Result { + let path = dir.as_ref().join(format!("{file_name}.log")); + + if path.exists() { + let log_size = File::open(&path)?.metadata()?.len() as u128; + if log_size > max_file_size { + match rotation_strategy { + RotationStrategy::KeepAll => { + rename_file_to_dated(&path, dir, file_name, timezone_strategy)?; + } + RotationStrategy::KeepSome(how_many) => { + let mut files = fs::read_dir(dir)? + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + let old_file_name = path.file_name()?.to_string_lossy().into_owned(); + if old_file_name.starts_with(file_name) { + let date = old_file_name + .strip_prefix(file_name)? + .strip_prefix("_")? + .strip_suffix(".log")?; + Some((path, date.to_string())) + } else { + None + } + }) + .collect::>(); + // Regular sorting, so the oldest files are first. Lexicographical + // sorting is fine due to the date format. + files.sort_by(|a, b| a.1.cmp(&b.1)); + // We want to make space for the file we will be soon renaming, AND + // the file we will be creating. Thus we need to keep how_many - 2 files. + if files.len() > (*how_many - 2) { + files.truncate(files.len() + 2 - *how_many); + for (old_log_path, _) in files { + fs::remove_file(old_log_path)?; + } + } + rename_file_to_dated(&path, dir, file_name, timezone_strategy)?; + } + RotationStrategy::KeepOne => { + fs::remove_file(&path)?; + } + } + } + } + Ok(path) +} diff --git a/packages/kbot/gui/app/plugins/log/tsconfig.json b/packages/kbot/gui/app/plugins/log/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/log/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/mirrors.txt b/packages/kbot/gui/app/plugins/mirrors.txt new file mode 100644 index 00000000..d346da18 --- /dev/null +++ b/packages/kbot/gui/app/plugins/mirrors.txt @@ -0,0 +1,20 @@ +autostart +cli +clipboard-manager +dialog +fs +global-shortcut +http +log +notification +os +positioner +process +shell +sql +store +stronghold +updater +upload +websocket +window-state diff --git a/packages/kbot/gui/app/plugins/nfc/CHANGELOG.md b/packages/kbot/gui/app/plugins/nfc/CHANGELOG.md new file mode 100644 index 00000000..70a9b3f0 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/CHANGELOG.md @@ -0,0 +1,95 @@ +# Changelog + +## \[2.3.1] + +- [`fe23a5e0`](https://github.com/tauri-apps/plugins-workspace/commit/fe23a5e01399a6ad61426bf8a94a6bb97227cf88) ([#2885](https://github.com/tauri-apps/plugins-workspace/pull/2885) by [@zaphim12](https://github.com/tauri-apps/plugins-workspace/../../zaphim12)) On iOS, the reader session will now get closed properly on errors, preventing dangling invalid sessions that could prevent subsequent write attempts. + +## \[2.3.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.2.1] + +- [`f634e524`](https://github.com/tauri-apps/plugins-workspace/commit/f634e5248ebe428f8305a59f74c13fc15147fb8e) This is an "empty" release to update the plugins' source files on crates.io and docs.rs. This should fix docs.rs build failures for projects using tauri plugins as dependencies. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.2] + +- [`b9147758`](https://github.com/tauri-apps/plugins-workspace/commit/b914775898c2bee7ceb20bd17ee595005cd17a64) ([#1679](https://github.com/tauri-apps/plugins-workspace/pull/1679) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Explicitly set a minimum macOS version for the Swift package. + +## \[2.0.0-rc.1] + +### changes + +- [`6b079cfd`](https://github.com/tauri-apps/plugins-workspace/commit/6b079cfdd107c94abc2c7300f6af00bac3ff4040) ([#1649](https://github.com/tauri-apps/plugins-workspace/pull/1649) by [@ahqsoftwares](https://github.com/tauri-apps/plugins-workspace/../../ahqsoftwares)) Remove targetSdk from build.kts files as it is deprecated and will be removed from DSL v9.0 + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +## \[2.0.0-beta.8] + +- [`99d6ac0f`](https://github.com/tauri-apps/plugins-workspace/commit/99d6ac0f9506a6a4a1aa59c728157190a7441af6) ([#1606](https://github.com/tauri-apps/plugins-workspace/pull/1606) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The JS packages now specify the *minimum* `@tauri-apps/api` version instead of a single exact version. +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.7] + +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.6] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.5] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.4] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.3] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.3] + +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. + +## \[2.0.0-beta.2] + +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Add permissions. + +## \[2.0.0-alpha.0] + +- [`fe79adb`](https://github.com/tauri-apps/plugins-workspace/commit/fe79adb5c7febd0e912efb5581264d671709fbb0)([#830](https://github.com/tauri-apps/plugins-workspace/pull/830)) Initial release. + commit/fe79adb5c7febd0e912efb5581264d671709fbb0)([#830](https://github.com/tauri-apps/plugins-workspace/pull/830)) Initial release. + 30]\(https://github.com/tauri-apps/plugins-workspace/pull/830)) Initial release. + commit/fe79adb5c7febd0e912efb5581264d671709fbb0)([#830](https://github.com/tauri-apps/plugins-workspace/pull/830)) Initial release. + . + commit/fe79adb5c7febd0e912efb5581264d671709fbb0)([#830](https://github.com/tauri-apps/plugins-workspace/pull/830)) Initial release. + ithub.com/tauri-apps/plugins-workspace/pull/830)) Initial release. diff --git a/packages/kbot/gui/app/plugins/nfc/Cargo.toml b/packages/kbot/gui/app/plugins/nfc/Cargo.toml new file mode 100644 index 00000000..1cf4f020 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "tauri-plugin-nfc" +version = "2.3.1" +description = "Read and write NFC tags on Android and iOS." +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-nfc" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] +targets = ["x86_64-linux-android"] + +[package.metadata.platforms.support] +windows = { level = "none", notes = "" } +linux = { level = "none", notes = "" } +macos = { level = "none", notes = "" } +android = { level = "full", notes = "" } +ios = { level = "full", notes = "" } + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } +serde_repr = "0.1" diff --git a/packages/kbot/gui/app/plugins/nfc/LICENSE.spdx b/packages/kbot/gui/app/plugins/nfc/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/nfc/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/nfc/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/nfc/LICENSE_MIT b/packages/kbot/gui/app/plugins/nfc/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/nfc/README.md b/packages/kbot/gui/app/plugins/nfc/README.md new file mode 100644 index 00000000..ef1fa6f6 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/README.md @@ -0,0 +1,112 @@ +![NFC](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/nfc/banner.png) + +Read and write NFC tags on Android and iOS. + +| Platform | Supported | +| -------- | --------- | +| Linux | x | +| Windows | x | +| macOS | x | +| Android | ✓ | +| iOS | ✓ | + +## Install + +_This plugin requires a Rust version of at least **1.65**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-nfc = "2.0.0" +# alternatively with Git: +tauri-plugin-nfc = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + + + +```sh +pnpm add @tauri-apps/plugin-nfc +# or +npm add @tauri-apps/plugin-nfc +# or +yarn add @tauri-apps/plugin-nfc +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_nfc::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { scan, textRecord, write } from '@tauri-apps/plugin-nfc' +await scan({ type: 'tag', keepSessionAlive: true }) +await write([textRecord('Tauri is awesome!')]) +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Contributed By + + + + + + + + +
+ + CrabNebula + + + + Impierce + +
+ +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/nfc/SECURITY.md b/packages/kbot/gui/app/plugins/nfc/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/nfc/android/.gitignore b/packages/kbot/gui/app/plugins/nfc/android/.gitignore new file mode 100644 index 00000000..c0f21ec2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/android/.gitignore @@ -0,0 +1,2 @@ +/build +/.tauri diff --git a/packages/kbot/gui/app/plugins/nfc/android/build.gradle.kts b/packages/kbot/gui/app/plugins/nfc/android/build.gradle.kts new file mode 100644 index 00000000..595f9173 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/android/build.gradle.kts @@ -0,0 +1,45 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "app.tauri.nfc" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.0") + implementation("com.google.android.material:material:1.7.0") + implementation("com.fasterxml.jackson.core:jackson-databind:2.15.3") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + implementation(project(":tauri-android")) +} diff --git a/packages/kbot/gui/app/plugins/nfc/android/proguard-rules.pro b/packages/kbot/gui/app/plugins/nfc/android/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/nfc/android/settings.gradle b/packages/kbot/gui/app/plugins/nfc/android/settings.gradle new file mode 100644 index 00000000..14a752e4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/android/settings.gradle @@ -0,0 +1,2 @@ +include ':tauri-android' +project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/packages/kbot/gui/app/plugins/nfc/android/src/androidTest/java/ExampleInstrumentedTest.kt b/packages/kbot/gui/app/plugins/nfc/android/src/androidTest/java/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..f0f7b66e --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/android/src/androidTest/java/ExampleInstrumentedTest.kt @@ -0,0 +1,28 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.nfc + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("app.tauri.nfc", appContext.packageName) + } +} diff --git a/packages/kbot/gui/app/plugins/nfc/android/src/main/AndroidManifest.xml b/packages/kbot/gui/app/plugins/nfc/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..7603a356 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/android/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/packages/kbot/gui/app/plugins/nfc/android/src/main/java/NfcPlugin.kt b/packages/kbot/gui/app/plugins/nfc/android/src/main/java/NfcPlugin.kt new file mode 100644 index 00000000..4deaab44 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/android/src/main/java/NfcPlugin.kt @@ -0,0 +1,519 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.nfc + +import android.app.Activity +import android.app.PendingIntent +import android.content.Intent +import android.content.IntentFilter +import android.nfc.NdefMessage +import android.nfc.NdefRecord +import android.nfc.NfcAdapter +import android.nfc.Tag +import android.nfc.tech.Ndef +import android.nfc.tech.NdefFormatable +import android.os.Build +import android.os.Parcelable +import android.os.PatternMatcher +import android.webkit.WebView +import app.tauri.Logger +import app.tauri.annotation.Command +import app.tauri.annotation.InvokeArg +import app.tauri.annotation.TauriPlugin +import app.tauri.plugin.Invoke +import app.tauri.plugin.JSArray +import app.tauri.plugin.JSObject +import app.tauri.plugin.Plugin +import com.fasterxml.jackson.annotation.JsonValue +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import org.json.JSONArray +import java.io.IOException +import kotlin.concurrent.thread + +sealed class NfcAction { + object Read : NfcAction() + data class Write(val message: NdefMessage) : NfcAction() +} + +@InvokeArg +class UriFilter { + var scheme: String? = null + var host: String? = null + var pathPrefix: String? = null +} + +@InvokeArg +enum class TechKind(@JsonValue val value: String) { + IsoDep("IsoDep"), + MifareClassic("MifareClassic"), + MifareUltralight("MifareUltralight"), + Ndef("Ndef"), + NdefFormatable("NdefFormatable"), + NfcA("NfcA"), + NfcB("NfcB"), + NfcBarcode("NfcBarcode"), + NfcF("NfcF"), + NfcV("NfcV"); + + fun className(): String { + return when (this) { + IsoDep -> { + android.nfc.tech.IsoDep::class.java.name + } + MifareClassic -> { + android.nfc.tech.MifareClassic::class.java.name + } + MifareUltralight -> { + android.nfc.tech.MifareUltralight::class.java.name + } + Ndef -> { + android.nfc.tech.Ndef::class.java.name + } + NdefFormatable -> { + android.nfc.tech.NdefFormatable::class.java.name + } + NfcA -> { + android.nfc.tech.NfcA::class.java.name + } + NfcB -> { + android.nfc.tech.NfcB::class.java.name + } + NfcBarcode -> { + android.nfc.tech.NfcBarcode::class.java.name + } + NfcF -> { + android.nfc.tech.NfcF::class.java.name + } + NfcV -> { + android.nfc.tech.NfcV::class.java.name + } + } + } +} + +private fun addDataFilters(intentFilter: IntentFilter, uri: UriFilter?, mimeType: String?) { + uri?.let { it -> { + it.scheme?.let { + intentFilter.addDataScheme(it) + } + it.host?.let { + intentFilter.addDataAuthority(it, null) + } + it.pathPrefix?.let { + intentFilter.addDataPath(it, PatternMatcher.PATTERN_PREFIX) + } + }} + mimeType?.let { + intentFilter.addDataType(it) + } +} + +@InvokeArg +@JsonDeserialize(using = ScanKindDeserializer::class) +sealed class ScanKind { + @JsonDeserialize + class Tag: ScanKind() { + var mimeType: String? = null + var uri: UriFilter? = null + } + @JsonDeserialize + class Ndef: ScanKind() { + var mimeType: String? = null + var uri: UriFilter? = null + var techLists: Array>? = null + } + + fun filters(): Array? { + return when (this) { + is Tag -> { + val intentFilter = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED) + addDataFilters(intentFilter, uri, mimeType) + arrayOf(intentFilter) + } + is Ndef -> { + val intentFilter = IntentFilter(if (techLists == null) NfcAdapter.ACTION_NDEF_DISCOVERED else NfcAdapter.ACTION_TECH_DISCOVERED) + addDataFilters(intentFilter, uri, mimeType) + arrayOf(intentFilter) + } + } + } + + fun techLists(): Array>? { + return when (this) { + is Tag -> null + is Ndef -> { + techLists?.let { + val techs = mutableListOf>() + for (techList in it) { + val list = mutableListOf() + for (tech in techList) { + list.add(tech.className()) + } + techs.add(list.toTypedArray()) + } + techs.toTypedArray() + } ?: run { + null + } + } + } + } +} + +internal class ScanKindDeserializer: JsonDeserializer() { + override fun deserialize( + jsonParser: JsonParser, + deserializationContext: DeserializationContext + ): ScanKind { + val node: JsonNode = jsonParser.codec.readTree(jsonParser) + node.get("tag")?.let { + return jsonParser.codec.treeToValue(it, ScanKind.Tag::class.java) + } ?: node.get("ndef")?.let { + return jsonParser.codec.treeToValue(it, ScanKind.Ndef::class.java) + } ?: run { + throw Error("unknown scan kind $node") + } + } +} + +@InvokeArg +class ScanOptions { + lateinit var kind: ScanKind + var keepSessionAlive: Boolean = false +} + +@InvokeArg +class NDEFRecordData { + var format: Short = 0 + var kind: ByteArray = ByteArray(0) + var id: ByteArray = ByteArray(0) + var payload: ByteArray = ByteArray(0) +} + +@InvokeArg +class WriteOptions { + var kind: ScanKind? = null + lateinit var records: Array +} + +class Session( + val action: NfcAction, + val invoke: Invoke, + val keepAlive: Boolean, + var tag: Tag? = null, + val filters: Array? = null, + val techLists: Array>? = null +) + +@TauriPlugin +class NfcPlugin(private val activity: Activity) : Plugin(activity) { + private lateinit var webView: WebView + + private var nfcAdapter: NfcAdapter? = null + private var session: Session? = null + + override fun load(webView: WebView) { + super.load(webView) + this.webView = webView + this.nfcAdapter = NfcAdapter.getDefaultAdapter(activity.applicationContext) + } + + override fun onNewIntent(intent: Intent) { + Logger.info("NFC", "onNewIntent") + super.onNewIntent(intent) + + val extraTag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra(NfcAdapter.EXTRA_TAG, Tag::class.java) + } else { + @Suppress("DEPRECATION") + intent.getParcelableExtra(NfcAdapter.EXTRA_TAG) + } + + extraTag?.let { tag -> + session?.let { + if (it.keepAlive) { + it.tag = tag + } + } + + when (session?.action) { + is NfcAction.Read -> readTag(tag, intent) + is NfcAction.Write -> thread { + if (session?.action is NfcAction.Write) { + try { + writeTag(tag, (session?.action as NfcAction.Write).message) + session?.invoke?.resolve() + } catch (e: Exception) { + session?.invoke?.reject(e.toString()) + } finally { + if (this.session?.keepAlive != true) { + this.session = null + disableNFCInForeground() + } + } + } + } + + else -> {} + } + } + + } + + override fun onPause() { + disableNFCInForeground() + super.onPause() + Logger.info("NFC", "onPause") + } + + override fun onResume() { + super.onResume() + Logger.info("NFC", "onResume") + session?.let { + enableNFCInForeground(it.filters, it.techLists) + } + } + + private fun isAvailable(): Pair { + val available: Boolean + var errorReason: String? = null + + if (this.nfcAdapter === null) { + available = false + errorReason = "Device does not have NFC capabilities" + } else if (this.nfcAdapter?.isEnabled == false) { + available = false + errorReason = "NFC is disabled in device settings" + } else { + available = true + } + + return Pair(available, errorReason) + } + + @Command + fun isAvailable(invoke: Invoke) { + val ret = JSObject() + ret.put("available", isAvailable().first) + invoke.resolve(ret) + } + + @Command + fun scan(invoke: Invoke) { + val status = isAvailable() + if (!status.first) { + invoke.reject("NFC unavailable: " + status.second) + return + } + + val args = invoke.parseArgs(ScanOptions::class.java) + + val filters = args.kind.filters() + val techLists = args.kind.techLists() + enableNFCInForeground(filters, techLists) + + session = Session(NfcAction.Read, invoke, args.keepSessionAlive, null, filters, techLists) + } + + @Command + fun write(invoke: Invoke) { + val status = isAvailable() + if (!status.first) { + invoke.reject("NFC unavailable: " + status.second) + return + } + + val args = invoke.parseArgs(WriteOptions::class.java) + + val ndefRecords: MutableList = ArrayList() + for (record in args.records) { + ndefRecords.add(NdefRecord(record.format, record.kind, record.id, record.payload)) + } + + val message = NdefMessage(ndefRecords.toTypedArray()) + + session?.let { session -> + session.tag?.let { + try { + writeTag(it, message) + invoke.resolve() + } catch (e: Exception) { + invoke.reject(e.toString()) + } finally { + if (this.session?.keepAlive != true) { + this.session = null + disableNFCInForeground() + } + } + } ?: run { + invoke.reject("connected tag not found, please wait for it to be available and then call write()") + } + } ?: run { + args.kind?.let { kind -> { + val filters = kind.filters() + val techLists = kind.techLists() + enableNFCInForeground(filters, techLists) + session = Session(NfcAction.Write(message), invoke, true, null, filters, techLists) + Logger.warn("NFC", "Write Mode Enabled") + }} ?: run { + invoke.reject("Missing `kind` for write") + } + + } + } + + private fun readTag(tag: Tag, intent: Intent) { + try { + val rawMessages = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, Parcelable::class.java) + } else { + @Suppress("DEPRECATION") + intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES) + } + + when (intent.action) { + NfcAdapter.ACTION_NDEF_DISCOVERED -> { + // For some reason this one never triggers. + Logger.info("NFC", "new NDEF intent") + readTagInner(tag, rawMessages) + } + NfcAdapter.ACTION_TECH_DISCOVERED -> { + // For some reason this always triggers instead of NDEF_DISCOVERED even though we set ndef filters right now + Logger.info("NFC", "new TECH intent") + // TODO: handle different techs. Don't assume ndef. + readTagInner(tag, rawMessages) + } + NfcAdapter.ACTION_TAG_DISCOVERED -> { + // This should never trigger when an app handles NDEF and TECH + // TODO: Don't assume ndef. + readTagInner(tag, rawMessages) + } + } + } catch (e: Exception) { + session?.invoke?.reject("failed to read tag", e) + } finally { + if (this.session?.keepAlive != true) { + this.session = null + } + // TODO this crashes? disableNFCInForeground() + } + } + + private fun readTagInner(tag: Tag?, rawMessages: Array?) { + val ndefMessage = rawMessages?.get(0) as NdefMessage? + + val records = ndefMessage?.records ?: arrayOf() + + val jsonRecords = Array(records.size) { i -> recordToJson(records[i]) } + + val ret = JSObject() + if (tag !== null) { + ret.put("id", fromU8Array(tag.id)) + // TODO There's also ndef.type which returns the ndef spec type which may be interesting to know too? + ret.put("kind", JSArray.from(tag.techList)) + } + ret.put("records", JSArray.from(jsonRecords)) + + session?.invoke?.resolve(ret) + } + + private fun writeTag(tag: Tag, message: NdefMessage) { + // This should return tags that are already in ndef format + val ndefTag = Ndef.get(tag) + if (ndefTag !== null) { + // We have to connect first to check maxSize. + try { + ndefTag.connect() + } catch (e: IOException) { + throw Exception("Couldn't connect to NFC tag", e) + } + + if (ndefTag.maxSize < message.toByteArray().size) { + throw Exception("The message is too large for the provided NFC tag") + } else if (!ndefTag.isWritable) { + throw Exception("NFC tag is read-only") + } else { + try { + ndefTag.writeNdefMessage(message) + } catch (e: Exception) { + throw Exception("Couldn't write message to NFC tag", e) + } + } + + try { + ndefTag.close() + } catch (e: IOException) { + Logger.error("failed to close tag", e) + } + + return + } + + // This should cover tags that are not yet in ndef format but can be converted + val ndefFormatableTag = NdefFormatable.get(tag) + if (ndefFormatableTag !== null) { + try { + ndefFormatableTag.connect() + ndefFormatableTag.format(message) + } catch (e: Exception) { + throw Exception("Couldn't format tag as Ndef", e) + } + + try { + ndefFormatableTag.close() + } catch (e: IOException) { + Logger.error("failed to close tag", e) + } + + return + } + + // if we get to this line, the tag was neither Ndef nor NdefFormatable compatible + throw Exception("Tag doesn't support Ndef format") + } + + // TODO: Use ReaderMode instead of ForegroundDispatch + private fun enableNFCInForeground(filters: Array?, techLists: Array>?) { + val flag = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE else PendingIntent.FLAG_UPDATE_CURRENT + val pendingIntent = PendingIntent.getActivity( + activity, 0, + Intent( + activity, + activity.javaClass + ).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), + flag + ) + + nfcAdapter?.enableForegroundDispatch(activity, pendingIntent, filters, techLists) + } + + private fun disableNFCInForeground() { + activity.runOnUiThread { + nfcAdapter?.disableForegroundDispatch(activity) + } + } +} + +private fun fromU8Array(byteArray: ByteArray): JSONArray { + val json = JSONArray() + for (byte in byteArray) { + json.put(byte) + } + return json +} + +private fun recordToJson(record: NdefRecord): JSObject { + val json = JSObject() + json.put("tnf", record.tnf) + json.put("kind", fromU8Array(record.type)) + json.put("id", fromU8Array(record.id)) + json.put("payload", fromU8Array(record.payload)) + return json +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/nfc/android/src/main/res/xml/nfc_tech_filter.xml b/packages/kbot/gui/app/plugins/nfc/android/src/main/res/xml/nfc_tech_filter.xml new file mode 100644 index 00000000..994905a6 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/android/src/main/res/xml/nfc_tech_filter.xml @@ -0,0 +1,13 @@ + + + android.nfc.tech.IsoDep + android.nfc.tech.NfcA + android.nfc.tech.NfcB + android.nfc.tech.NfcF + android.nfc.tech.NfcV + android.nfc.tech.Ndef + android.nfc.tech.NdefFormatable + android.nfc.tech.MifareClassic + android.nfc.tech.MifareUltralight + + diff --git a/packages/kbot/gui/app/plugins/nfc/android/src/test/java/ExampleUnitTest.kt b/packages/kbot/gui/app/plugins/nfc/android/src/test/java/ExampleUnitTest.kt new file mode 100644 index 00000000..2af426f8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/android/src/test/java/ExampleUnitTest.kt @@ -0,0 +1,21 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.nfc + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/packages/kbot/gui/app/plugins/nfc/api-iife.js b/packages/kbot/gui/app/plugins/nfc/api-iife.js new file mode 100644 index 00000000..5939782f --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_NFC__=function(n){"use strict";async function e(n,e={},t){return window.__TAURI_INTERNALS__.invoke(n,e,t)}"function"==typeof SuppressedError&&SuppressedError;const t=[84],r=[85];var o,c;function a(n,e,t,r){return{format:n,kind:"string"==typeof e?Array.from((new TextEncoder).encode(e)):e,id:"string"==typeof t?Array.from((new TextEncoder).encode(t)):t,payload:"string"==typeof r?Array.from((new TextEncoder).encode(r)):r}}n.TechKind=void 0,(o=n.TechKind||(n.TechKind={}))[o.IsoDep=0]="IsoDep",o[o.MifareClassic=1]="MifareClassic",o[o.MifareUltralight=2]="MifareUltralight",o[o.Ndef=3]="Ndef",o[o.NdefFormatable=4]="NdefFormatable",o[o.NfcA=5]="NfcA",o[o.NfcB=6]="NfcB",o[o.NfcBarcode=7]="NfcBarcode",o[o.NfcF=8]="NfcF",o[o.NfcV=9]="NfcV",n.NFCTypeNameFormat=void 0,(c=n.NFCTypeNameFormat||(n.NFCTypeNameFormat={}))[c.Empty=0]="Empty",c[c.NfcWellKnown=1]="NfcWellKnown",c[c.Media=2]="Media",c[c.AbsoluteURI=3]="AbsoluteURI",c[c.NfcExternal=4]="NfcExternal",c[c.Unknown=5]="Unknown",c[c.Unchanged=6]="Unchanged";const i=["","http://www.","https://www.","http://","https://","tel:","mailto:","ftp://anonymous:anonymous@","ftp://ftp.","ftps://","sftp://","smb://","nfs://","ftp://","dav://","news:","telnet://","imap:","rtsp://","urn:","pop:","sip:","sips:","tftp:","btspp://","btl2cap://","btgoep://","tcpobex://","irdaobex://","file://","urn:epc:id:","urn:epc:tag:","urn:epc:pat:","urn:epc:raw:","urn:epc:","urn:nfc:"];function f(n){const{type:e,...t}=n;return{[e]:t}}return n.RTD_TEXT=t,n.RTD_URI=r,n.isAvailable=async function(){return await e("plugin:nfc|is_available")},n.record=a,n.scan=async function(n,t){return await e("plugin:nfc|scan",{kind:f(n),...t})},n.textRecord=function(e,r,o="en"){const c=Array.from((new TextEncoder).encode(o+e));return c.unshift(o.length),a(n.NFCTypeNameFormat.NfcWellKnown,t,r??[],c)},n.uriRecord=function(e,t){return a(n.NFCTypeNameFormat.NfcWellKnown,r,t??[],function(n){let e="";i.slice(1).forEach((function(t){0!==e.length&&"urn:"!==e||0!==n.indexOf(t)||(e=t)})),0===e.length&&(e="");const t=Array.from((new TextEncoder).encode(n.slice(e.length))),r=i.indexOf(e);return t.unshift(r),t}(e))},n.write=async function(n,t){const{kind:r,...o}=t??{};r&&(o.kind=f(r)),await e("plugin:nfc|write",{records:n,...o})},n}({});Object.defineProperty(window.__TAURI__,"nfc",{value:__TAURI_PLUGIN_NFC__})} diff --git a/packages/kbot/gui/app/plugins/nfc/build.rs b/packages/kbot/gui/app/plugins/nfc/build.rs new file mode 100644 index 00000000..bdcd84bf --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/build.rs @@ -0,0 +1,45 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +const COMMANDS: &[&str] = &["is_available", "write", "scan"]; + +fn main() { + let result = tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .android_path("android") + .ios_path("ios") + .try_build(); + + // when building documentation for Android the plugin build result is always Err() and is irrelevant to the crate documentation build + if !(cfg!(docsrs) && std::env::var("TARGET").unwrap().contains("android")) { + result.unwrap(); + } + + // TODO: triple check if this can reference the plugin's xml as it expects rn + // TODO: This has to be configurable if we want to support handling nfc tags when the app is not open. + tauri_plugin::mobile::update_android_manifest( + "NFC PLUGIN", + "activity", + r#" + + + + + + + + + + + + + + +"# + .to_string(), + ) + .expect("failed to rewrite AndroidManifest.xml"); +} diff --git a/packages/kbot/gui/app/plugins/nfc/contributors/crabnebula.svg b/packages/kbot/gui/app/plugins/nfc/contributors/crabnebula.svg new file mode 100644 index 00000000..a9bb4609 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/contributors/crabnebula.svg @@ -0,0 +1,31 @@ + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/nfc/contributors/impierce.svg b/packages/kbot/gui/app/plugins/nfc/contributors/impierce.svg new file mode 100644 index 00000000..9d2a510b --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/contributors/impierce.svg @@ -0,0 +1,21 @@ + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/nfc/guest-js/index.ts b/packages/kbot/gui/app/plugins/nfc/guest-js/index.ts new file mode 100644 index 00000000..051a1841 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/guest-js/index.ts @@ -0,0 +1,273 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { invoke } from '@tauri-apps/api/core' + +export const RTD_TEXT = [0x54] // "T" +export const RTD_URI = [0x55] // "U" + +export interface UriFilter { + scheme?: string + host?: string + pathPrefix?: string +} + +export enum TechKind { + IsoDep, + MifareClassic, + MifareUltralight, + Ndef, + NdefFormatable, + NfcA, + NfcB, + NfcBarcode, + NfcF, + NfcV +} + +export type ScanKind = + | { + type: 'tag' + uri?: UriFilter + mimeType?: string + } + | { + type: 'ndef' + uri?: UriFilter + mimeType?: string + /** + * Each of the tech-lists is considered independently and the activity is considered a match if + * any single tech-list matches the tag that was discovered. + * This provides AND and OR semantics for filtering desired techs. + * + * See for more information. + * + * Examples + * + * ```ts + * import type { TechKind } from "@tauri-apps/plugin-nfc" + * + * const techLists = [ + * // capture anything using NfcF + * [TechKind.NfcF], + * // capture all MIFARE Classics with NDEF payloads + * [TechKind.NfcA, TechKind.MifareClassic, TechKind.Ndef] + * ] + * ``` + */ + techLists?: TechKind[][] + } + +export interface ScanOptions { + keepSessionAlive?: boolean + /** Message displayed in the UI. iOS only. */ + message?: string + /** Message displayed in the UI when the message has been read. iOS only. */ + successMessage?: string +} + +export interface WriteOptions { + kind?: ScanKind + /** Message displayed in the UI when reading the tag. iOS only. */ + message?: string + /** Message displayed in the UI when the tag has been read. iOS only. */ + successfulReadMessage?: string + /** Message displayed in the UI when the message has been written. iOS only. */ + successMessage?: string +} + +export enum NFCTypeNameFormat { + Empty = 0, + NfcWellKnown = 1, + Media = 2, + AbsoluteURI = 3, + NfcExternal = 4, + Unknown = 5, + Unchanged = 6 +} + +export interface TagRecord { + tnf: NFCTypeNameFormat + kind: number[] + id: number[] + payload: number[] +} + +export interface Tag { + id: number[] + kind: string[] + records: TagRecord[] +} + +export interface NFCRecord { + format: NFCTypeNameFormat + kind: number[] + id: number[] + payload: number[] +} + +export function record( + format: NFCTypeNameFormat, + kind: string | number[], + id: string | number[], + payload: string | number[] +): NFCRecord { + return { + format, + kind: + typeof kind === 'string' + ? Array.from(new TextEncoder().encode(kind)) + : kind, + id: typeof id === 'string' ? Array.from(new TextEncoder().encode(id)) : id, + payload: + typeof payload === 'string' + ? Array.from(new TextEncoder().encode(payload)) + : payload + } +} + +export function textRecord( + text: string, + id?: string | number[], + language: string = 'en' +): NFCRecord { + const payload = Array.from(new TextEncoder().encode(language + text)) + payload.unshift(language.length) + return record(NFCTypeNameFormat.NfcWellKnown, RTD_TEXT, id ?? [], payload) +} + +const protocols = [ + '', + 'http://www.', + 'https://www.', + 'http://', + 'https://', + 'tel:', + 'mailto:', + 'ftp://anonymous:anonymous@', + 'ftp://ftp.', + 'ftps://', + 'sftp://', + 'smb://', + 'nfs://', + 'ftp://', + 'dav://', + 'news:', + 'telnet://', + 'imap:', + 'rtsp://', + 'urn:', + 'pop:', + 'sip:', + 'sips:', + 'tftp:', + 'btspp://', + 'btl2cap://', + 'btgoep://', + 'tcpobex://', + 'irdaobex://', + 'file://', + 'urn:epc:id:', + 'urn:epc:tag:', + 'urn:epc:pat:', + 'urn:epc:raw:', + 'urn:epc:', + 'urn:nfc:' +] + +function encodeURI(uri: string): number[] { + let prefix = '' + + protocols.slice(1).forEach(function (protocol) { + if ( + (prefix.length === 0 || prefix === 'urn:') + && uri.indexOf(protocol) === 0 + ) { + prefix = protocol + } + }) + + if (prefix.length === 0) { + prefix = '' + } + + const encoded = Array.from(new TextEncoder().encode(uri.slice(prefix.length))) + const protocolCode = protocols.indexOf(prefix) + // prepend protocol code + encoded.unshift(protocolCode) + + return encoded +} + +export function uriRecord(uri: string, id?: string | number[]): NFCRecord { + return record( + NFCTypeNameFormat.NfcWellKnown, + RTD_URI, + id ?? [], + encodeURI(uri) + ) +} + +function mapScanKind(kind: ScanKind): Record { + const { type: scanKind, ...kindOptions } = kind + return { [scanKind]: kindOptions } +} + +/** + * Scans an NFC tag. + * + * ```javascript + * import { scan } from "@tauri-apps/plugin-nfc"; + * await scan({ type: "tag" }); + * ``` + * + * See for more information. + * + * @param kind + * @param options + * @returns + */ +export async function scan( + kind: ScanKind, + options?: ScanOptions +): Promise { + return await invoke('plugin:nfc|scan', { + kind: mapScanKind(kind), + ...options + }) +} + +/** + * Write to an NFC tag. + * + * ```javascript + * import { uriRecord, write } from "@tauri-apps/plugin-nfc"; + * await write([uriRecord("https://tauri.app")], { kind: { type: "ndef" } }); + * ``` + * + * If you did not previously call {@link scan} with {@link ScanOptions.keepSessionAlive} set to true, + * it will first scan the tag then write to it. + * + * @param records + * @param options + * @returns + */ +export async function write( + records: NFCRecord[], + options?: WriteOptions +): Promise { + const { kind, ...opts } = options ?? {} + if (kind) { + // @ts-expect-error map the property + opts.kind = mapScanKind(kind) + } + await invoke('plugin:nfc|write', { + records, + ...opts + }) +} + +export async function isAvailable(): Promise { + return await invoke('plugin:nfc|is_available') +} diff --git a/packages/kbot/gui/app/plugins/nfc/ios/.gitignore b/packages/kbot/gui/app/plugins/nfc/ios/.gitignore new file mode 100644 index 00000000..5922fdaa --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/ios/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Package.resolved diff --git a/packages/kbot/gui/app/plugins/nfc/ios/Package.swift b/packages/kbot/gui/app/plugins/nfc/ios/Package.swift new file mode 100644 index 00000000..a028db7d --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/ios/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version:5.3 +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import PackageDescription + +let package = Package( + name: "tauri-plugin-nfc", + platforms: [ + .macOS(.v10_13), + .iOS(.v13), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "tauri-plugin-nfc", + type: .static, + targets: ["tauri-plugin-nfc"]) + ], + dependencies: [ + .package(name: "Tauri", path: "../.tauri/tauri-api") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "tauri-plugin-nfc", + dependencies: [ + .byName(name: "Tauri") + ], + path: "Sources") + ] +) diff --git a/packages/kbot/gui/app/plugins/nfc/ios/README.md b/packages/kbot/gui/app/plugins/nfc/ios/README.md new file mode 100644 index 00000000..88a429b7 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/ios/README.md @@ -0,0 +1,3 @@ +# Tauri Plugin Nfc + +A description of this package. diff --git a/packages/kbot/gui/app/plugins/nfc/ios/Sources/NfcPlugin.swift b/packages/kbot/gui/app/plugins/nfc/ios/Sources/NfcPlugin.swift new file mode 100644 index 00000000..af1a68d5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/ios/Sources/NfcPlugin.swift @@ -0,0 +1,523 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +// https://developer.apple.com/documentation/corenfc/building_an_nfc_tag-reader_app + +import CoreNFC +import SwiftRs +import Tauri +import UIKit +import WebKit + +enum ScanKind: Decodable { + case ndef, tag +} + +struct ScanOptions: Decodable { + let kind: ScanKind + var keepSessionAlive: Bool? + var message: String? + var successMessage: String? +} + +struct NDEFRecord: Decodable { + var format: UInt8? + var kind: [UInt8]? + var identifier: [UInt8]? + var payload: [UInt8]? +} + +struct WriteOptions: Decodable { + var kind: ScanKind? + let records: [NDEFRecord] + var message: String? + var successMessage: String? + var successfulReadMessage: String? +} + +enum TagProcessMode { + case write(message: NFCNDEFMessage) + case read +} + +class Session { + let nfcSession: NFCReaderSession? + let invoke: Invoke + var keepAlive: Bool + let tagProcessMode: TagProcessMode + var tagStatus: NFCNDEFStatus? + var tag: NFCNDEFTag? + let successfulReadMessage: String? + let successfulWriteAlertMessage: String? + + init( + nfcSession: NFCReaderSession?, + invoke: Invoke, + keepAlive: Bool, + tagProcessMode: TagProcessMode, + successfulReadMessage: String?, + successfulWriteAlertMessage: String? + ) { + self.nfcSession = nfcSession + self.invoke = invoke + self.keepAlive = keepAlive + self.tagProcessMode = tagProcessMode + self.successfulReadMessage = successfulReadMessage + self.successfulWriteAlertMessage = successfulWriteAlertMessage + } +} + +class NfcStatus { + let available: Bool + let errorReason: String? + + init(available: Bool, errorReason: String?) { + self.available = available + self.errorReason = errorReason + } +} + +class NfcPlugin: Plugin, NFCTagReaderSessionDelegate, NFCNDEFReaderSessionDelegate { + var session: Session? + var status: NfcStatus! + + public override func load(webview: WKWebView) { + var available = false + var errorReason: String? + + let entry = Bundle.main.infoDictionary?["NFCReaderUsageDescription"] as? String + + if entry == nil || entry?.count == 0 { + errorReason = "missing NFCReaderUsageDescription configuration on the Info.plist file" + } else if !NFCNDEFReaderSession.readingAvailable { + errorReason = + "NFC tag reading unavailable, make sure the Near-Field Communication capability on Xcode is enabled and the device supports NFC tag reading" + } else { + available = true + } + + if let error = errorReason { + Logger.error("\(error)") + } + + self.status = NfcStatus(available: available, errorReason: errorReason) + } + + func tagReaderSessionDidBecomeActive( + _ session: NFCTagReaderSession + ) { + Logger.info("tagReaderSessionDidBecomeActive") + } + + func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { + let tag = tags.first! + + session.connect( + to: tag, + completionHandler: { [self] (error) in + if let error = error { + self.closeSession(session, error: "cannot connect to tag: \(error)") + + } else { + let ndefTag: NFCNDEFTag + switch tag { + case let .feliCa(tag): + ndefTag = tag as NFCNDEFTag + break + case let .miFare(tag): + ndefTag = tag as NFCNDEFTag + break + case let .iso15693(tag): + ndefTag = tag as NFCNDEFTag + break + case let .iso7816(tag): + ndefTag = tag as NFCNDEFTag + break + default: + return + } + + self.processTag( + session: session, tag: ndefTag, metadata: tagMetadata(tag), + mode: self.session!.tagProcessMode) + } + } + ) + } + + func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) { + Logger.error("Tag reader session error \(error)") + self.session?.invoke.reject("session invalidated with error: \(error)") + self.session = nil + } + + func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) { + let message = messages.first! + // TODO: do we really need this hook? + self.session?.invoke.resolve(["records": ndefMessageRecords(message)]) + } + + func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) { + let tag = tags.first! + + session.connect( + to: tag, + completionHandler: { [self] (error) in + if let error = error { + self.closeSession(session, error: "cannot connect to tag: \(error)") + + } else { + var metadata: JsonObject = [:] + if tag.isKind(of: NFCFeliCaTag.self) { + metadata["kind"] = ["FeliCa"] + metadata["id"] = nil + } else if let t = tag as? NFCMiFareTag { + metadata["kind"] = ["MiFare"] + metadata["id"] = byteArrayFromData(t.identifier) + } else if let t = tag as? NFCISO15693Tag { + metadata["kind"] = ["ISO15693"] + metadata["id"] = byteArrayFromData(t.identifier) + } else if let t = tag as? NFCISO7816Tag { + metadata["kind"] = ["ISO7816Compatible"] + metadata["id"] = byteArrayFromData(t.identifier) + } + + self.processTag( + session: session, tag: tag, metadata: metadata, + mode: self.session!.tagProcessMode) + } + } + ) + + } + + func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) { + if (error as NSError).code + == NFCReaderError.Code.readerSessionInvalidationErrorFirstNDEFTagRead.rawValue + { + // not an error because we're using invalidateAfterFirstRead: true + Logger.debug("readerSessionInvalidationErrorFirstNDEFTagRead") + } else { + Logger.error("NDEF reader session error \(error)") + self.session?.invoke.reject("session invalidated with error: \(error)") + self.session = nil + } + } + + private func tagMetadata(_ tag: NFCTag) -> JsonObject { + var metadata: JsonObject = [:] + + switch tag { + case .feliCa: + metadata["kind"] = ["FeliCa"] + metadata["id"] = [] + break + case let .miFare(tag): + metadata["kind"] = ["MiFare"] + metadata["id"] = byteArrayFromData(tag.identifier) + break + case let .iso15693(tag): + metadata["kind"] = ["ISO15693"] + metadata["id"] = byteArrayFromData(tag.identifier) + break + case let .iso7816(tag): + metadata["kind"] = ["ISO7816Compatible"] + metadata["id"] = byteArrayFromData(tag.identifier) + break + default: + metadata["kind"] = ["Unknown"] + metadata["id"] = [] + break + } + + return metadata + } + + private func closeSession(_ session: NFCReaderSession) { + session.invalidate() + self.session = nil + } + + private func closeSession(_ session: NFCReaderSession, error: String) { + session.invalidate(errorMessage: error) + self.session = nil + } + + private func processTag( + session: NFCReaderSession, tag: T, metadata: JsonObject, mode: TagProcessMode + ) { + tag.queryNDEFStatus(completionHandler: { + [self] (status, capacity, error) in + if let error = error { + self.closeSession(session, error: "cannot connect to tag: \(error)") + } else { + switch mode { + case .write(let message): + self.writeNDEFTag( + session: session, status: status, tag: tag, message: message, + alertMessage: self.session?.successfulWriteAlertMessage) + break + case .read: + if self.session?.keepAlive == true { + self.session!.tagStatus = status + self.session!.tag = tag + } + self.readNDEFTag( + session: session, status: status, tag: tag, metadata: metadata, + alertMessage: self.session?.successfulReadMessage) + break + } + } + }) + } + + private func writeNDEFTag( + session: NFCReaderSession, status: NFCNDEFStatus, tag: T, message: NFCNDEFMessage, + alertMessage: String? + ) { + switch status { + case .notSupported: + self.closeSession(session, error: "Tag is not an NDEF-formatted tag") + break + case .readOnly: + self.closeSession(session, error: "Read only tag") + break + case .readWrite: + if let currentSession = self.session { + tag.writeNDEF( + message, + completionHandler: { (error) in + if let error = error { + self.closeSession(session, error: "cannot write to tag: \(error)") + } else { + if let message = alertMessage { + session.alertMessage = message + } + currentSession.invoke.resolve() + + self.closeSession(session) + + } + }) + } + break + default: + return + } + } + + private func readNDEFTag( + session: NFCReaderSession, status: NFCNDEFStatus, tag: T, metadata m: JsonObject, + alertMessage: String? + ) { + var metadata: JsonObject = [:] + metadata.merge(m) { (_, new) in new } + + switch status { + case .notSupported: + self.resolveInvoke(message: nil, metadata: metadata) + self.closeSession(session) + return + case .readOnly: + metadata["readOnly"] = true + break + case .readWrite: + metadata["readOnly"] = false + break + default: + break + } + + tag.readNDEF(completionHandler: { + [self] (message, error) in + if let error = error { + let code = (error as NSError).code + if code != 403 { + self.closeSession(session, error: "Failed to read: \(error)") + return + } + } + + if let message = alertMessage { + session.alertMessage = message + } + self.resolveInvoke(message: message, metadata: metadata) + + if self.session?.keepAlive != true { + self.closeSession(session) + } + }) + } + + private func resolveInvoke(message: NFCNDEFMessage?, metadata: JsonObject) { + var data: JsonObject = [:] + + data.merge(metadata) { (_, new) in new } + + if let message = message { + data["records"] = ndefMessageRecords(message) + } else { + data["records"] = [] + } + + self.session?.invoke.resolve(data) + } + + private func ndefMessageRecords(_ message: NFCNDEFMessage) -> [JsonObject] { + var records: [JsonObject] = [] + for record in message.records { + var recordJson: JsonObject = [:] + recordJson["tnf"] = record.typeNameFormat.rawValue + recordJson["kind"] = byteArrayFromData(record.type) + recordJson["id"] = byteArrayFromData(record.identifier) + recordJson["payload"] = byteArrayFromData(record.payload) + + records.append(recordJson) + } + + return records + } + + private func byteArrayFromData(_ data: Data) -> [UInt8] { + var arr: [UInt8] = [] + for b in data { + arr.append(b) + } + return arr + } + + private func dataFromByteArray(_ array: [UInt8]) -> Data { + var data = Data(capacity: array.count) + + data.append(contentsOf: array) + + return data + } + + @objc func isAvailable(_ invoke: Invoke) { + invoke.resolve([ + "available": self.status.available + ]) + } + + @objc public func write(_ invoke: Invoke) throws { + if !self.status.available { + invoke.reject("NFC reading unavailable: \(self.status.errorReason ?? "")") + return + } + + let args = try invoke.parseArgs(WriteOptions.self) + + var ndefPayloads = [NFCNDEFPayload]() + + for record in args.records { + ndefPayloads.append( + NFCNDEFPayload( + format: NFCTypeNameFormat(rawValue: record.format ?? 0) ?? .unknown, + type: dataFromByteArray(record.kind ?? []), + identifier: dataFromByteArray(record.identifier ?? []), + payload: dataFromByteArray(record.payload ?? []) + ) + ) + } + + if let session = self.session { + if let nfcSession = session.nfcSession, let tagStatus = session.tagStatus, + let tag = session.tag + { + session.keepAlive = false + self.writeNDEFTag( + session: nfcSession, status: tagStatus, tag: tag, + message: NFCNDEFMessage(records: ndefPayloads), + alertMessage: args.successMessage + ) + } else { + invoke.reject( + "connected tag not found, please wait for it to be available and then call write()") + } + } else { + self.startScanSession( + invoke: invoke, + kind: args.kind ?? .ndef, + keepAlive: true, + invalidateAfterFirstRead: false, + tagProcessMode: .write( + message: NFCNDEFMessage(records: ndefPayloads) + ), + alertMessage: args.message, + successfulReadMessage: args.successfulReadMessage, + successfulWriteAlertMessage: args.successMessage + ) + } + } + + @objc public func scan(_ invoke: Invoke) throws { + if !self.status.available { + invoke.reject("NFC reading unavailable: \(self.status.errorReason ?? "")") + return + } + + let args = try invoke.parseArgs(ScanOptions.self) + + self.startScanSession( + invoke: invoke, + kind: args.kind, + keepAlive: args.keepSessionAlive ?? false, + invalidateAfterFirstRead: true, + tagProcessMode: .read, + alertMessage: args.message, + successfulReadMessage: args.successMessage, + successfulWriteAlertMessage: nil + ) + } + + private func startScanSession( + invoke: Invoke, + kind: ScanKind, + keepAlive: Bool, + invalidateAfterFirstRead: Bool, + tagProcessMode: TagProcessMode, + alertMessage: String?, + successfulReadMessage: String?, + successfulWriteAlertMessage: String? + ) { + let nfcSession: NFCReaderSession? + + switch kind { + case .tag: + nfcSession = NFCTagReaderSession( + pollingOption: [.iso14443, .iso15693], + delegate: self, + queue: DispatchQueue.main + ) + break + case .ndef: + nfcSession = NFCNDEFReaderSession( + delegate: self, + queue: DispatchQueue.main, + invalidateAfterFirstRead: invalidateAfterFirstRead + ) + break + } + + if let message = alertMessage { + nfcSession?.alertMessage = message + } + nfcSession?.begin() + + self.session = Session( + nfcSession: nfcSession, + invoke: invoke, + keepAlive: keepAlive, + tagProcessMode: tagProcessMode, + successfulReadMessage: successfulReadMessage, + successfulWriteAlertMessage: successfulWriteAlertMessage + ) + } +} + +@_cdecl("init_plugin_nfc") +func initPlugin() -> Plugin { + return NfcPlugin() +} diff --git a/packages/kbot/gui/app/plugins/nfc/ios/Tests/PluginTests/PluginTests.swift b/packages/kbot/gui/app/plugins/nfc/ios/Tests/PluginTests/PluginTests.swift new file mode 100644 index 00000000..99992ce4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/ios/Tests/PluginTests/PluginTests.swift @@ -0,0 +1,12 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import XCTest +@testable import ExamplePlugin + +final class ExamplePluginTests: XCTestCase { + func testExample() throws { + let plugin = ExamplePlugin() + } +} diff --git a/packages/kbot/gui/app/plugins/nfc/package.json b/packages/kbot/gui/app/plugins/nfc/package.json new file mode 100644 index 00000000..423f45e2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/package.json @@ -0,0 +1,30 @@ +{ + "name": "@tauri-apps/plugin-nfc", + "version": "2.3.1", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "!dist-js/**/*.map", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/nfc/permissions/autogenerated/commands/is_available.toml b/packages/kbot/gui/app/plugins/nfc/permissions/autogenerated/commands/is_available.toml new file mode 100644 index 00000000..6f49c5ab --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/permissions/autogenerated/commands/is_available.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-is-available" +description = "Enables the is_available command without any pre-configured scope." +commands.allow = ["is_available"] + +[[permission]] +identifier = "deny-is-available" +description = "Denies the is_available command without any pre-configured scope." +commands.deny = ["is_available"] diff --git a/packages/kbot/gui/app/plugins/nfc/permissions/autogenerated/commands/scan.toml b/packages/kbot/gui/app/plugins/nfc/permissions/autogenerated/commands/scan.toml new file mode 100644 index 00000000..efa621dd --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/permissions/autogenerated/commands/scan.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-scan" +description = "Enables the scan command without any pre-configured scope." +commands.allow = ["scan"] + +[[permission]] +identifier = "deny-scan" +description = "Denies the scan command without any pre-configured scope." +commands.deny = ["scan"] diff --git a/packages/kbot/gui/app/plugins/nfc/permissions/autogenerated/commands/write.toml b/packages/kbot/gui/app/plugins/nfc/permissions/autogenerated/commands/write.toml new file mode 100644 index 00000000..73d1d387 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/permissions/autogenerated/commands/write.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-write" +description = "Enables the write command without any pre-configured scope." +commands.allow = ["write"] + +[[permission]] +identifier = "deny-write" +description = "Denies the write command without any pre-configured scope." +commands.deny = ["write"] diff --git a/packages/kbot/gui/app/plugins/nfc/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/nfc/permissions/autogenerated/reference.md new file mode 100644 index 00000000..0e408eb5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/permissions/autogenerated/reference.md @@ -0,0 +1,103 @@ +## Default Permission + +This permission set configures what kind of +operations are available from the nfc plugin. + +#### Granted Permissions + +Checking if the NFC functionality is available +and scanning nearby tags is allowed. +Writing to tags needs to be manually enabled. + +#### This default permission set includes the following: + +- `allow-is-available` +- `allow-scan` + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`nfc:allow-is-available` + + + +Enables the is_available command without any pre-configured scope. + +
+ +`nfc:deny-is-available` + + + +Denies the is_available command without any pre-configured scope. + +
+ +`nfc:allow-scan` + + + +Enables the scan command without any pre-configured scope. + +
+ +`nfc:deny-scan` + + + +Denies the scan command without any pre-configured scope. + +
+ +`nfc:allow-write` + + + +Enables the write command without any pre-configured scope. + +
+ +`nfc:deny-write` + + + +Denies the write command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/nfc/permissions/default.toml b/packages/kbot/gui/app/plugins/nfc/permissions/default.toml new file mode 100644 index 00000000..d69c7f1b --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/permissions/default.toml @@ -0,0 +1,15 @@ +"$schema" = "schemas/schema.json" + +[default] +description = """ +This permission set configures what kind of +operations are available from the nfc plugin. + +#### Granted Permissions + +Checking if the NFC functionality is available +and scanning nearby tags is allowed. +Writing to tags needs to be manually enabled. + +""" +permissions = ["allow-is-available", "allow-scan"] diff --git a/packages/kbot/gui/app/plugins/nfc/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/nfc/permissions/schemas/schema.json new file mode 100644 index 00000000..8a018e26 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/permissions/schemas/schema.json @@ -0,0 +1,342 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the is_available command without any pre-configured scope.", + "type": "string", + "const": "allow-is-available", + "markdownDescription": "Enables the is_available command without any pre-configured scope." + }, + { + "description": "Denies the is_available command without any pre-configured scope.", + "type": "string", + "const": "deny-is-available", + "markdownDescription": "Denies the is_available command without any pre-configured scope." + }, + { + "description": "Enables the scan command without any pre-configured scope.", + "type": "string", + "const": "allow-scan", + "markdownDescription": "Enables the scan command without any pre-configured scope." + }, + { + "description": "Denies the scan command without any pre-configured scope.", + "type": "string", + "const": "deny-scan", + "markdownDescription": "Denies the scan command without any pre-configured scope." + }, + { + "description": "Enables the write command without any pre-configured scope.", + "type": "string", + "const": "allow-write", + "markdownDescription": "Enables the write command without any pre-configured scope." + }, + { + "description": "Denies the write command without any pre-configured scope.", + "type": "string", + "const": "deny-write", + "markdownDescription": "Denies the write command without any pre-configured scope." + }, + { + "description": "This permission set configures what kind of\noperations are available from the nfc plugin.\n\n#### Granted Permissions\n\nChecking if the NFC functionality is available\nand scanning nearby tags is allowed.\nWriting to tags needs to be manually enabled.\n\n\n#### This default permission set includes:\n\n- `allow-is-available`\n- `allow-scan`", + "type": "string", + "const": "default", + "markdownDescription": "This permission set configures what kind of\noperations are available from the nfc plugin.\n\n#### Granted Permissions\n\nChecking if the NFC functionality is available\nand scanning nearby tags is allowed.\nWriting to tags needs to be manually enabled.\n\n\n#### This default permission set includes:\n\n- `allow-is-available`\n- `allow-scan`" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/nfc/rollup.config.js b/packages/kbot/gui/app/plugins/nfc/rollup.config.js new file mode 100644 index 00000000..1f349ec8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/rollup.config.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' + +export default createConfig() diff --git a/packages/kbot/gui/app/plugins/nfc/src/error.rs b/packages/kbot/gui/app/plugins/nfc/src/error.rs new file mode 100644 index 00000000..339e763b --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/src/error.rs @@ -0,0 +1,25 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{ser::Serializer, Serialize}; + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/packages/kbot/gui/app/plugins/nfc/src/lib.rs b/packages/kbot/gui/app/plugins/nfc/src/lib.rs new file mode 100644 index 00000000..d8708c1d --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/src/lib.rs @@ -0,0 +1,83 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#![cfg(mobile)] + +use serde::{Deserialize, Serialize}; +use tauri::{ + plugin::{Builder, PluginHandle, TauriPlugin}, + Manager, Runtime, +}; + +pub use models::*; + +mod error; +mod models; + +pub use error::{Error, Result}; + +#[cfg(target_os = "android")] +const PLUGIN_IDENTIFIER: &str = "app.tauri.nfc"; + +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_nfc); + +/// Access to the nfc APIs. +pub struct Nfc(PluginHandle); + +#[derive(Deserialize)] +struct IsAvailableResponse { + available: bool, +} + +#[derive(Serialize)] +struct WriteRequest { + records: Vec, +} + +impl Nfc { + pub fn is_available(&self) -> crate::Result { + self.0 + .run_mobile_plugin::("isAvailable", ()) + .map(|r| r.available) + .map_err(Into::into) + } + + pub fn scan(&self, payload: ScanRequest) -> crate::Result { + self.0 + .run_mobile_plugin("scan", payload) + .map_err(Into::into) + } + + pub fn write(&self, records: Vec) -> crate::Result<()> { + self.0 + .run_mobile_plugin("write", WriteRequest { records }) + .map_err(Into::into) + } +} + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the NFC APIs. +pub trait NfcExt { + fn nfc(&self) -> &Nfc; +} + +impl> crate::NfcExt for T { + fn nfc(&self) -> &Nfc { + self.state::>().inner() + } +} + +/// Initializes the plugin. +pub fn init() -> TauriPlugin { + Builder::new("nfc") + .setup(|app, api| { + #[cfg(target_os = "android")] + let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "NfcPlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_nfc)?; + app.manage(Nfc(handle)); + Ok(()) + }) + .build() +} diff --git a/packages/kbot/gui/app/plugins/nfc/src/mobile.rs b/packages/kbot/gui/app/plugins/nfc/src/mobile.rs new file mode 100644 index 00000000..cf34d45e --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/src/mobile.rs @@ -0,0 +1,11 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::de::DeserializeOwned; +use tauri::{ + plugin::{PluginApi, PluginHandle}, + AppHandle, Runtime, +}; + +use crate::models::*; diff --git a/packages/kbot/gui/app/plugins/nfc/src/models.rs b/packages/kbot/gui/app/plugins/nfc/src/models.rs new file mode 100644 index 00000000..eb05cf7a --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/src/models.rs @@ -0,0 +1,119 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{Deserialize, Serialize, Serializer}; +use std::fmt::Display; + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ScanRequest { + pub kind: ScanKind, + pub keep_session_alive: bool, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct NfcRecord { + pub format: NFCTypeNameFormat, + pub kind: Vec, + pub id: Vec, + pub payload: Vec, +} + +#[derive(serde_repr::Deserialize_repr, serde_repr::Serialize_repr)] +#[repr(u8)] +pub enum NFCTypeNameFormat { + Empty = 0, + NfcWellKnown = 1, + Media = 2, + AbsoluteURI = 3, + NfcExternal = 4, + Unknown = 5, + Unchanged = 6, +} + +#[derive(Deserialize)] +pub struct NfcTagRecord { + pub tnf: NFCTypeNameFormat, + pub kind: Vec, + pub id: Vec, + pub payload: Vec, +} + +#[derive(Deserialize)] +pub struct NfcTag { + pub id: String, + pub kind: String, + pub records: Vec, +} + +#[derive(Deserialize)] +pub struct ScanResponse { + pub tag: NfcTag, +} + +#[derive(Debug, Default, Serialize)] +pub struct UriFilter { + scheme: Option, + host: Option, + path_prefix: Option, +} + +#[derive(Debug)] +pub enum TechKind { + IsoDep, + MifareClassic, + MifareUltralight, + Ndef, + NdefFormatable, + NfcA, + NfcB, + NfcBarcode, + NfcF, + NfcV, +} + +impl Display for TechKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::IsoDep => "IsoDep", + Self::MifareClassic => "MifareClassic", + Self::MifareUltralight => "MifareUltralight", + Self::Ndef => "Ndef", + Self::NdefFormatable => "NdefFormatable", + Self::NfcA => "NfcA", + Self::NfcB => "NfcB", + Self::NfcBarcode => "NfcBarcode", + Self::NfcF => "NfcF", + Self::NfcV => "NfcV", + } + ) + } +} + +impl Serialize for TechKind { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum ScanKind { + Ndef { + mime_type: Option, + uri: Option, + tech_list: Option>>, + }, + Tag { + mime_type: Option, + uri: Option, + }, +} diff --git a/packages/kbot/gui/app/plugins/nfc/tsconfig.json b/packages/kbot/gui/app/plugins/nfc/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/nfc/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/notification/CHANGELOG.md b/packages/kbot/gui/app/plugins/notification/CHANGELOG.md new file mode 100644 index 00000000..3adc96e5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/CHANGELOG.md @@ -0,0 +1,151 @@ +# Changelog + +## \[2.3.1] + +- [`8abb31ee`](https://github.com/tauri-apps/plugins-workspace/commit/8abb31ee59c68197102c0aa699d690b34646ec3c) ([#2905](https://github.com/tauri-apps/plugins-workspace/pull/2905) by [@ChristianPavilonis](https://github.com/tauri-apps/plugins-workspace/../../ChristianPavilonis)) Fix notification scheduling on iOS. +- [`2d03e2ea`](https://github.com/tauri-apps/plugins-workspace/commit/2d03e2eac2c19ad997d81d23836ab6a219252ffb) ([#2678](https://github.com/tauri-apps/plugins-workspace/pull/2678) by [@Keerthi421](https://github.com/tauri-apps/plugins-workspace/../../Keerthi421)) Added sound support for desktop notifications which was previously only available on mobile platforms. + +## \[2.3.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.2.3] + +- [`f634e524`](https://github.com/tauri-apps/plugins-workspace/commit/f634e5248ebe428f8305a59f74c13fc15147fb8e) This is an "empty" release to update the plugins' source files on crates.io and docs.rs. This should fix docs.rs build failures for projects using tauri plugins as dependencies. + +## \[2.2.2] + +- [`a1b3fa27`](https://github.com/tauri-apps/plugins-workspace/commit/a1b3fa27f11022c9b6622b4fab12d93239eb05de) ([#2515](https://github.com/tauri-apps/plugins-workspace/pull/2515) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Re-exported the `Geolocation`, `Haptics`, `Notification`, and `Os` structs so that they show up on docs.rs. + +## \[2.2.1] + +- [`da5c59e2`](https://github.com/tauri-apps/plugins-workspace/commit/da5c59e2fe879d177e3cfd52fcacce85440423cb) ([#2271](https://github.com/tauri-apps/plugins-workspace/pull/2271) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated `zbus` dependency to version 5. No API changes. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.5] + +- [`fb85e5dd`](https://github.com/tauri-apps/plugins-workspace/commit/fb85e5dd76688f3ae836890160f9bde843b70167) ([#1785](https://github.com/tauri-apps/plugins-workspace/pull/1785)) Update to tauri 2.0.0-rc.12. + +## \[2.0.0-rc.4] + +- [`3d301c65`](https://github.com/tauri-apps/plugins-workspace/commit/3d301c654e6f5e7f343e0e0cbb57648002e98f04) ([#1737](https://github.com/tauri-apps/plugins-workspace/pull/1737) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) The notification body is now optional on iOS to match the other platforms. + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Use `PermissionState` from the `tauri` crate, which now also includes a "prompt with rationale" variant for Android (returned when your app must explain to the user why it needs the permission). +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) **Breaking change**: The permission type when using the API is now `'granted' | 'denied' | 'prompt' | 'prompt-with-rationale'` instead of `'granted' | 'denied' | 'default'` for consistency with Rust types. When using the `window.Notification` API the type is unchanged to match the Web API type. +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.2] + +- [`b9147758`](https://github.com/tauri-apps/plugins-workspace/commit/b914775898c2bee7ceb20bd17ee595005cd17a64) ([#1679](https://github.com/tauri-apps/plugins-workspace/pull/1679) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Explicitly set a minimum macOS version for the Swift package. + +## \[2.0.0-rc.1] + +### changes + +- [`6b079cfd`](https://github.com/tauri-apps/plugins-workspace/commit/6b079cfdd107c94abc2c7300f6af00bac3ff4040) ([#1649](https://github.com/tauri-apps/plugins-workspace/pull/1649) by [@ahqsoftwares](https://github.com/tauri-apps/plugins-workspace/../../ahqsoftwares)) Remove targetSdk from build.kts files as it is deprecated and will be removed from DSL v9.0 + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +## \[2.0.0-beta.8] + +- [`99d6ac0f`](https://github.com/tauri-apps/plugins-workspace/commit/99d6ac0f9506a6a4a1aa59c728157190a7441af6) ([#1606](https://github.com/tauri-apps/plugins-workspace/pull/1606) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The JS packages now specify the *minimum* `@tauri-apps/api` version instead of a single exact version. +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.11] + +- [`725ff429`](https://github.com/tauri-apps/plugins-workspace/commit/725ff4295e56df9c30c099813bd64b96fe61b945) ([#1556](https://github.com/tauri-apps/plugins-workspace/pull/1556) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused the `notification` plugin's initialization script to cause the WebView on Windows to throw a `STATUS_ACCESS_VIOLATION` error on remote websites. + +## \[2.0.0-beta.7] + +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.6] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.8] + +- [`3779fb50`](https://github.com/tauri-apps/plugins-workspace/commit/3779fb50634fba4d7e7eb0bfecc2216349b9d64d) ([#1432](https://github.com/tauri-apps/plugins-workspace/pull/1432) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Use notify_rust from crates.io instead of local fork. + +## \[2.0.0-beta.5] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.4] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.3] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.4] + +- [`326df688`](https://github.com/tauri-apps/plugins-workspace/commit/326df6883998d416fc0837583ed972854628bb52)([#1236](https://github.com/tauri-apps/plugins-workspace/pull/1236)) Fixes command argument parsing on iOS. + +## \[2.0.0-beta.3] + +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. +- [`62ce5df`](https://github.com/tauri-apps/plugins-workspace/commit/62ce5df52ca3c86786e711ef193a206e7b0dc0cf)([#1096](https://github.com/tauri-apps/plugins-workspace/pull/1096)) Fix development mode check to set the app ID on macOS. + +## \[2.0.0-beta.2] + +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. +- [`1b1d795`](https://github.com/tauri-apps/plugins-workspace/commit/1b1d795b5866e5524a9a9925f0fb7b2f8e3e3675)([#874](https://github.com/tauri-apps/plugins-workspace/pull/874)) Export the missing `Schedule` class. +- [`8dea78a`](https://github.com/tauri-apps/plugins-workspace/commit/8dea78ac7dcb502159e66bad464094696aa257d4)([#909](https://github.com/tauri-apps/plugins-workspace/pull/909)) Fixes deserialization and implementation bugs with scheduled notifications on Android. + +## \[2.0.0-alpha.5] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.13. + +## \[2.0.0-alpha.4] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.12. + +## \[2.0.0-alpha.3] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to @tauri-apps/api v2.0.0-alpha.11. + +## \[2.0.0-alpha.2] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update to @tauri-apps/api v2.0.0-alpha.9. + +## \[2.0.0-alpha.3] + +- [`4e2cef9`](https://github.com/tauri-apps/plugins-workspace/commit/4e2cef9b702bbbb9cf4ee17de50791cb21f1b2a4)([#593](https://github.com/tauri-apps/plugins-workspace/pull/593)) Update to alpha.12. + +## \[2.0.0-alpha.1] + +- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11. + +## \[2.0.0-alpha.1] + +- [`d8b4aca`](https://github.com/tauri-apps/plugins-workspace/commit/d8b4aca69f628b170804ecb982e2c319d026ef47)([#414](https://github.com/tauri-apps/plugins-workspace/pull/414)) Use `window.__TAURI_INVOKE__` instead of `window.__TAURI__` in init.js, fixes usage in apps without `withGlobalTauri` enabled. +- [`7d71ad4`](https://github.com/tauri-apps/plugins-workspace/commit/7d71ad4e587bcf47ea34645f5b226945e487b765) Play a default sound when showing a notification on Windows. + +## \[2.0.0-alpha.0] + +- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! diff --git a/packages/kbot/gui/app/plugins/notification/Cargo.toml b/packages/kbot/gui/app/plugins/notification/Cargo.toml new file mode 100644 index 00000000..ad51b265 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "tauri-plugin-notification" +version = "2.3.1" +description = "Send desktop and mobile notifications on your Tauri application." +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-notification" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] +targets = ["x86_64-unknown-linux-gnu", "x86_64-linux-android"] + +[package.metadata.platforms.support] +windows = { level = "full", notes = "Only works for installed apps. Shows powershell name & icon in development." } +linux = { level = "full", notes = "" } +macos = { level = "full", notes = "" } +android = { level = "full", notes = "" } +ios = { level = "full", notes = "" } + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } +rand = "0.9" +time = { version = "0.3", features = ["serde", "parsing", "formatting"] } +url = { version = "2", features = ["serde"] } +serde_repr = "0.1" + +[target.'cfg(target_os = "ios")'.dependencies] +tauri = { workspace = true, features = ["wry"] } + +[target."cfg(windows)".dependencies] +win7-notifications = { version = "0.4.5", optional = true } +windows-version = { version = "0.1", optional = true } + +[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] +notify-rust = "4.11" + +[dev-dependencies] +color-backtrace = "0.7" +ctor = "0.2" +maplit = "1" + +[features] +windows7-compat = ["win7-notifications", "windows-version"] diff --git a/packages/kbot/gui/app/plugins/notification/LICENSE.spdx b/packages/kbot/gui/app/plugins/notification/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/notification/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/notification/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/notification/LICENSE_MIT b/packages/kbot/gui/app/plugins/notification/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/notification/README.md b/packages/kbot/gui/app/plugins/notification/README.md new file mode 100644 index 00000000..e17efe02 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/README.md @@ -0,0 +1,161 @@ +![plugin-notification](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/notification/banner.png) + +Send message notifications (brief auto-expiring OS window element) to your user. Can also be used with the Notification Web API. + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | ✓ | +| iOS | ✓ | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-notification = "2.0.0" +# alternatively with Git: +tauri-plugin-notification = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +```sh +pnpm add @tauri-apps/plugin-notification +# or +npm add @tauri-apps/plugin-notification +# or +yarn add @tauri-apps/plugin-notification +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_notification::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Then you need to add the permissions to your capabilities file: + +`src-tauri/capabilities/main.json` + +```json +{ + ... + "permissions": [ + ... + "notification:default" + ], + ... +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { + isPermissionGranted, + requestPermission, + sendNotification +} from '@tauri-apps/plugin-notification' + +async function checkPermission() { + if (!(await isPermissionGranted())) { + return (await requestPermission()) === 'granted' + } + return true +} + +export async function enqueueNotification(title, body) { + if (!(await checkPermission())) { + return + } + sendNotification({ title, body }) +} +``` + +### Notification with Sound + +You can add sound to your notifications on all platforms (desktop and mobile): + +```javascript +import { sendNotification } from '@tauri-apps/plugin-notification' +import { platform } from '@tauri-apps/api/os' + +// Basic notification with sound +sendNotification({ + title: 'New Message', + body: 'You have a new message', + sound: 'notification.wav' // Path to sound file +}) + +// Platform-specific sounds +async function sendPlatformSpecificNotification() { + const platformName = platform() + + let soundPath + if (platformName === 'darwin') { + // On macOS: use system sounds or sound files in the app bundle + soundPath = 'Ping' // macOS system sound + } else if (platformName === 'linux') { + // On Linux: use XDG theme sounds or file paths + soundPath = 'message-new-instant' // XDG theme sound + } else { + // On Windows: use file paths + soundPath = 'notification.wav' + } + + sendNotification({ + title: 'Platform-specific Notification', + body: 'This notification uses platform-specific sound', + sound: soundPath + }) +} +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/notification/SECURITY.md b/packages/kbot/gui/app/plugins/notification/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/notification/android/.gitignore b/packages/kbot/gui/app/plugins/notification/android/.gitignore new file mode 100644 index 00000000..c0f21ec2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/android/.gitignore @@ -0,0 +1,2 @@ +/build +/.tauri diff --git a/packages/kbot/gui/app/plugins/notification/android/build.gradle.kts b/packages/kbot/gui/app/plugins/notification/android/build.gradle.kts new file mode 100644 index 00000000..0ac990e4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/android/build.gradle.kts @@ -0,0 +1,45 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "app.tauri.notification" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.0") + implementation("com.google.android.material:material:1.7.0") + implementation("com.fasterxml.jackson.core:jackson-databind:2.15.3") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + implementation(project(":tauri-android")) +} diff --git a/packages/kbot/gui/app/plugins/notification/android/proguard-rules.pro b/packages/kbot/gui/app/plugins/notification/android/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/notification/android/settings.gradle b/packages/kbot/gui/app/plugins/notification/android/settings.gradle new file mode 100644 index 00000000..14a752e4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/android/settings.gradle @@ -0,0 +1,2 @@ +include ':tauri-android' +project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/packages/kbot/gui/app/plugins/notification/android/src/androidTest/java/ExampleInstrumentedTest.kt b/packages/kbot/gui/app/plugins/notification/android/src/androidTest/java/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..88ede7f4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/android/src/androidTest/java/ExampleInstrumentedTest.kt @@ -0,0 +1,28 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.notification + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("app.tauri.notification", appContext.packageName) + } +} diff --git a/packages/kbot/gui/app/plugins/notification/android/src/main/AndroidManifest.xml b/packages/kbot/gui/app/plugins/notification/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c49808ee --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/android/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/notification/android/src/main/java/AssetUtils.kt b/packages/kbot/gui/app/plugins/notification/android/src/main/java/AssetUtils.kt new file mode 100644 index 00000000..fafccf7a --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/android/src/main/java/AssetUtils.kt @@ -0,0 +1,29 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.notification + +import android.annotation.SuppressLint +import android.content.Context + +class AssetUtils { + companion object { + const val RESOURCE_ID_ZERO_VALUE = 0 + + @SuppressLint("DiscouragedApi") + fun getResourceID(context: Context, resourceName: String?, dir: String?): Int { + return context.resources.getIdentifier(resourceName, dir, context.packageName) + } + + fun getResourceBaseName(resPath: String?): String? { + if (resPath == null) return null + if (resPath.contains("/")) { + return resPath.substring(resPath.lastIndexOf('/') + 1) + } + return if (resPath.contains(".")) { + resPath.substring(0, resPath.lastIndexOf('.')) + } else resPath + } + } +} diff --git a/packages/kbot/gui/app/plugins/notification/android/src/main/java/ChannelManager.kt b/packages/kbot/gui/app/plugins/notification/android/src/main/java/ChannelManager.kt new file mode 100644 index 00000000..206f340a --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/android/src/main/java/ChannelManager.kt @@ -0,0 +1,151 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.notification + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.ContentResolver +import android.content.Context +import android.graphics.Color +import android.media.AudioAttributes +import android.net.Uri +import android.os.Build +import app.tauri.Logger +import app.tauri.annotation.InvokeArg +import app.tauri.plugin.Invoke +import com.fasterxml.jackson.annotation.JsonValue + +enum class Importance(@JsonValue val value: Int) { + None(0), + Min(1), + Low(2), + Default(3), + High(4); +} + +enum class Visibility(@JsonValue val value: Int) { + Secret(-1), + Private(0), + Public(1); +} + +@InvokeArg +class Channel { + lateinit var id: String + lateinit var name: String + var description: String? = null + var sound: String? = null + var lights: Boolean? = null + var lightsColor: String? = null + var vibration: Boolean? = null + var importance: Importance? = null + var visibility: Visibility? = null +} + +@InvokeArg +class DeleteChannelArgs { + lateinit var id: String +} + +class ChannelManager(private var context: Context) { + private var notificationManager: NotificationManager? = null + + init { + notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager? + } + + fun createChannel(invoke: Invoke) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = invoke.parseArgs(Channel::class.java) + createChannel(channel) + invoke.resolve() + } else { + invoke.reject("channel not available") + } + } + + private fun createChannel(channel: Channel) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val notificationChannel = NotificationChannel( + channel.id, + channel.name, + (channel.importance ?: Importance.Default).value + ) + notificationChannel.description = channel.description + notificationChannel.lockscreenVisibility = (channel.visibility ?: Visibility.Private).value + notificationChannel.enableVibration(channel.vibration ?: false) + notificationChannel.enableLights(channel.lights ?: false) + val lightColor = channel.lightsColor ?: "" + if (lightColor.isNotEmpty()) { + try { + notificationChannel.lightColor = Color.parseColor(lightColor) + } catch (ex: IllegalArgumentException) { + Logger.error( + Logger.tags("NotificationChannel"), + "Invalid color provided for light color.", + null + ) + } + } + var sound = channel.sound ?: "" + if (sound.isNotEmpty()) { + if (sound.contains(".")) { + sound = sound.substring(0, sound.lastIndexOf('.')) + } + val audioAttributes = AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_NOTIFICATION) + .build() + val soundUri = + Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/raw/" + sound) + notificationChannel.setSound(soundUri, audioAttributes) + } + notificationManager?.createNotificationChannel(notificationChannel) + } + } + + fun deleteChannel(invoke: Invoke) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val args = invoke.parseArgs(DeleteChannelArgs::class.java) + notificationManager?.deleteNotificationChannel(args.id) + invoke.resolve() + } else { + invoke.reject("channel not available") + } + } + + fun listChannels(invoke: Invoke) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val notificationChannels: List = + notificationManager?.notificationChannels ?: listOf() + + val channels = mutableListOf() + + for (notificationChannel in notificationChannels) { + val channel = Channel() + channel.id = notificationChannel.id + channel.name = notificationChannel.name.toString() + channel.description = notificationChannel.description + channel.sound = notificationChannel.sound.toString() + channel.lights = notificationChannel.shouldShowLights() + String.format( + "#%06X", + 0xFFFFFF and notificationChannel.lightColor + ) + channel.vibration = notificationChannel.shouldVibrate() + channel.importance = Importance.values().firstOrNull { it.value == notificationChannel.importance } + channel.visibility = Visibility.values().firstOrNull { it.value == notificationChannel.lockscreenVisibility } + + channels.add(channel) + } + + invoke.resolveObject(channels) + + } else { + invoke.reject("channel not available") + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/notification/android/src/main/java/Notification.kt b/packages/kbot/gui/app/plugins/notification/android/src/main/java/Notification.kt new file mode 100644 index 00000000..bf10f3dc --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/android/src/main/java/Notification.kt @@ -0,0 +1,95 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.notification + +import android.content.ContentResolver +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import app.tauri.annotation.InvokeArg +import app.tauri.plugin.JSArray +import app.tauri.plugin.JSObject +import org.json.JSONException +import org.json.JSONObject + +@InvokeArg +class Notification { + var id: Int = 0 + var title: String? = null + var body: String? = null + var largeBody: String? = null + var summary: String? = null + var sound: String? = null + var icon: String? = null + var largeIcon: String? = null + var iconColor: String? = null + var actionTypeId: String? = null + var group: String? = null + var inboxLines: List? = null + var isGroupSummary = false + var isOngoing = false + var isAutoCancel = false + var extra: JSObject? = null + var attachments: List? = null + var schedule: NotificationSchedule? = null + var channelId: String? = null + var sourceJson: String? = null + var visibility: Int? = null + var number: Int? = null + + fun getSound(context: Context, defaultSound: Int): String? { + var soundPath: String? = null + var resId: Int = AssetUtils.RESOURCE_ID_ZERO_VALUE + val name = AssetUtils.getResourceBaseName(sound) + if (name != null) { + resId = AssetUtils.getResourceID(context, name, "raw") + } + if (resId == AssetUtils.RESOURCE_ID_ZERO_VALUE) { + resId = defaultSound + } + if (resId != AssetUtils.RESOURCE_ID_ZERO_VALUE) { + soundPath = + ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/" + resId + } + return soundPath + } + + fun getIconColor(globalColor: String): String { + // use the one defined local before trying for a globally defined color + return iconColor ?: globalColor + } + + fun getSmallIcon(context: Context, defaultIcon: Int): Int { + var resId: Int = AssetUtils.RESOURCE_ID_ZERO_VALUE + if (icon != null) { + resId = AssetUtils.getResourceID(context, icon, "drawable") + } + if (resId == AssetUtils.RESOURCE_ID_ZERO_VALUE) { + resId = defaultIcon + } + return resId + } + + fun getLargeIcon(context: Context): Bitmap? { + if (largeIcon != null) { + val resId: Int = AssetUtils.getResourceID(context, largeIcon, "drawable") + return BitmapFactory.decodeResource(context.resources, resId) + } + return null + } + + companion object { + fun buildNotificationPendingList(notifications: List): List { + val pendingNotifications = mutableListOf() + for (notification in notifications) { + val pendingNotification = PendingNotification(notification.id, notification.title, notification.body, notification.schedule, notification.extra) + pendingNotifications.add(pendingNotification) + } + return pendingNotifications + } + } +} + +class PendingNotification(val id: Int, val title: String?, val body: String?, val schedule: NotificationSchedule?, val extra: JSObject?) \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/notification/android/src/main/java/NotificationAttachment.kt b/packages/kbot/gui/app/plugins/notification/android/src/main/java/NotificationAttachment.kt new file mode 100644 index 00000000..56a13818 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/android/src/main/java/NotificationAttachment.kt @@ -0,0 +1,52 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.notification + +import app.tauri.plugin.JSObject +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject + +class NotificationAttachment { + var id: String? = null + var url: String? = null + var options: JSONObject? = null + + companion object { + fun getAttachments(notification: JSObject): List { + val attachmentsList: MutableList = ArrayList() + var attachments: JSONArray? = null + try { + attachments = notification.getJSONArray("attachments") + } catch (_: Exception) { + } + if (attachments != null) { + for (i in 0 until attachments.length()) { + val newAttachment = NotificationAttachment() + var jsonObject: JSONObject? = null + try { + jsonObject = attachments.getJSONObject(i) + } catch (e: JSONException) { + } + if (jsonObject != null) { + var jsObject: JSObject? = null + try { + jsObject = JSObject.fromJSONObject(jsonObject) + } catch (_: JSONException) { + } + newAttachment.id = jsObject!!.getString("id") + newAttachment.url = jsObject.getString("url") + try { + newAttachment.options = jsObject.getJSONObject("options") + } catch (_: JSONException) { + } + attachmentsList.add(newAttachment) + } + } + } + return attachmentsList + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/notification/android/src/main/java/NotificationPlugin.kt b/packages/kbot/gui/app/plugins/notification/android/src/main/java/NotificationPlugin.kt new file mode 100644 index 00000000..3ead3152 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/android/src/main/java/NotificationPlugin.kt @@ -0,0 +1,282 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.notification + +import android.Manifest +import android.annotation.SuppressLint +import android.app.Activity +import android.app.NotificationManager +import android.content.Context +import android.content.Intent +import android.os.Build +import android.webkit.WebView +import app.tauri.PermissionState +import app.tauri.annotation.Command +import app.tauri.annotation.InvokeArg +import app.tauri.annotation.Permission +import app.tauri.annotation.PermissionCallback +import app.tauri.annotation.TauriPlugin +import app.tauri.plugin.Invoke +import app.tauri.plugin.JSArray +import app.tauri.plugin.JSObject +import app.tauri.plugin.Plugin + +const val LOCAL_NOTIFICATIONS = "permissionState" + +@InvokeArg +class PluginConfig { + var icon: String? = null + var sound: String? = null + var iconColor: String? = null +} + +@InvokeArg +class BatchArgs { + lateinit var notifications: List +} + +@InvokeArg +class CancelArgs { + lateinit var notifications: List +} + +@InvokeArg +class NotificationAction { + lateinit var id: String + var title: String? = null + var input: Boolean? = null +} + +@InvokeArg +class ActionType { + lateinit var id: String + lateinit var actions: List +} + +@InvokeArg +class RegisterActionTypesArgs { + lateinit var types: List +} + +@InvokeArg +class ActiveNotification { + var id: Int = 0 + var tag: String? = null +} + +@InvokeArg +class RemoveActiveArgs { + var notifications: List = listOf() +} + +@TauriPlugin( + permissions = [ + Permission(strings = [Manifest.permission.POST_NOTIFICATIONS], alias = "permissionState") + ] +) +class NotificationPlugin(private val activity: Activity): Plugin(activity) { + private var webView: WebView? = null + private lateinit var manager: TauriNotificationManager + private lateinit var notificationManager: NotificationManager + private lateinit var notificationStorage: NotificationStorage + private var channelManager = ChannelManager(activity) + + companion object { + var instance: NotificationPlugin? = null + + fun triggerNotification(notification: Notification) { + instance?.triggerObject("notification", notification) + } + } + + override fun load(webView: WebView) { + instance = this + + super.load(webView) + this.webView = webView + notificationStorage = NotificationStorage(activity, jsonMapper()) + + val manager = TauriNotificationManager( + notificationStorage, + activity, + activity, + getConfig(PluginConfig::class.java) + ) + manager.createNotificationChannel() + + this.manager = manager + + notificationManager = activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + val intent = activity.intent + intent?.let { + onIntent(it) + } + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + onIntent(intent) + } + + fun onIntent(intent: Intent) { + if (Intent.ACTION_MAIN != intent.action) { + return + } + val dataJson = manager.handleNotificationActionPerformed(intent, notificationStorage) + if (dataJson != null) { + trigger("actionPerformed", dataJson) + } + } + + @Command + fun show(invoke: Invoke) { + val notification = invoke.parseArgs(Notification::class.java) + val id = manager.schedule(notification) + + invoke.resolveObject(id) + } + + @Command + fun batch(invoke: Invoke) { + val args = invoke.parseArgs(BatchArgs::class.java) + + val ids = manager.schedule(args.notifications) + notificationStorage.appendNotifications(args.notifications) + + invoke.resolveObject(ids) + } + + @Command + fun cancel(invoke: Invoke) { + val args = invoke.parseArgs(CancelArgs::class.java) + manager.cancel(args.notifications) + invoke.resolve() + } + + @Command + fun removeActive(invoke: Invoke) { + val args = invoke.parseArgs(RemoveActiveArgs::class.java) + + if (args.notifications.isEmpty()) { + notificationManager.cancelAll() + invoke.resolve() + } else { + for (notification in args.notifications) { + if (notification.tag == null) { + notificationManager.cancel(notification.id) + } else { + notificationManager.cancel(notification.tag, notification.id) + } + } + invoke.resolve() + } + } + + @Command + fun getPending(invoke: Invoke) { + val notifications= notificationStorage.getSavedNotifications() + val result = Notification.buildNotificationPendingList(notifications) + invoke.resolveObject(result) + } + + @Command + fun registerActionTypes(invoke: Invoke) { + val args = invoke.parseArgs(RegisterActionTypesArgs::class.java) + notificationStorage.writeActionGroup(args.types) + invoke.resolve() + } + + @SuppressLint("ObsoleteSdkInt") + @Command + fun getActive(invoke: Invoke) { + val notifications = JSArray() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val activeNotifications = notificationManager.activeNotifications + for (activeNotification in activeNotifications) { + val jsNotification = JSObject() + jsNotification.put("id", activeNotification.id) + jsNotification.put("tag", activeNotification.tag) + val notification = activeNotification.notification + if (notification != null) { + jsNotification.put("title", notification.extras.getCharSequence(android.app.Notification.EXTRA_TITLE)) + jsNotification.put("body", notification.extras.getCharSequence(android.app.Notification.EXTRA_TEXT)) + jsNotification.put("group", notification.group) + jsNotification.put( + "groupSummary", + 0 != notification.flags and android.app.Notification.FLAG_GROUP_SUMMARY + ) + val extras = JSObject() + for (key in notification.extras.keySet()) { + extras.put(key!!, notification.extras.getString(key)) + } + jsNotification.put("data", extras) + } + notifications.put(jsNotification) + } + } + + invoke.resolveObject(notifications) + } + + @Command + fun createChannel(invoke: Invoke) { + channelManager.createChannel(invoke) + } + + @Command + fun deleteChannel(invoke: Invoke) { + channelManager.deleteChannel(invoke) + } + + @Command + fun listChannels(invoke: Invoke) { + channelManager.listChannels(invoke) + } + + @Command + override fun checkPermissions(invoke: Invoke) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + val permissionsResultJSON = JSObject() + permissionsResultJSON.put("permissionState", getPermissionState()) + invoke.resolve(permissionsResultJSON) + } else { + super.checkPermissions(invoke) + } + } + + @Command + override fun requestPermissions(invoke: Invoke) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + permissionState(invoke) + } else { + if (getPermissionState(LOCAL_NOTIFICATIONS) !== PermissionState.GRANTED) { + requestPermissionForAlias(LOCAL_NOTIFICATIONS, invoke, "permissionsCallback") + } + } + } + + @Command + fun permissionState(invoke: Invoke) { + val permissionsResultJSON = JSObject() + permissionsResultJSON.put("permissionState", getPermissionState()) + invoke.resolve(permissionsResultJSON) + } + + @PermissionCallback + private fun permissionsCallback(invoke: Invoke) { + val permissionsResultJSON = JSObject() + permissionsResultJSON.put("permissionState", getPermissionState()) + invoke.resolve(permissionsResultJSON) + } + + private fun getPermissionState(): String { + return if (manager.areNotificationsEnabled()) { + "granted" + } else { + "denied" + } + } +} diff --git a/packages/kbot/gui/app/plugins/notification/android/src/main/java/NotificationSchedule.kt b/packages/kbot/gui/app/plugins/notification/android/src/main/java/NotificationSchedule.kt new file mode 100644 index 00000000..316ff909 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/android/src/main/java/NotificationSchedule.kt @@ -0,0 +1,369 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.notification + +import android.annotation.SuppressLint +import android.text.format.DateUtils +import com.fasterxml.jackson.annotation.JsonFormat +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.SerializerProvider +import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import com.fasterxml.jackson.databind.annotation.JsonSerialize +import com.fasterxml.jackson.databind.deser.std.StdDeserializer +import com.fasterxml.jackson.databind.ser.std.StdSerializer +import java.io.IOException +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Date +import java.util.TimeZone + +const val JS_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + +enum class NotificationInterval { + @JsonProperty("year") + Year, + @JsonProperty("month") + Month, + @JsonProperty("twoWeeks") + TwoWeeks, + @JsonProperty("week") + Week, + @JsonProperty("day") + Day, + @JsonProperty("hour") + Hour, + @JsonProperty("minute") + Minute, + @JsonProperty("second") + Second +} + +fun getIntervalTime(interval: NotificationInterval, count: Int): Long { + return when (interval) { + // This case is just approximation as not all years have the same number of days + NotificationInterval.Year -> count * DateUtils.WEEK_IN_MILLIS * 52 + // This case is just approximation as months have different number of days + NotificationInterval.Month -> count * 30 * DateUtils.DAY_IN_MILLIS + NotificationInterval.TwoWeeks -> count * 2 * DateUtils.WEEK_IN_MILLIS + NotificationInterval.Week -> count * DateUtils.WEEK_IN_MILLIS + NotificationInterval.Day -> count * DateUtils.DAY_IN_MILLIS + NotificationInterval.Hour -> count * DateUtils.HOUR_IN_MILLIS + NotificationInterval.Minute -> count * DateUtils.MINUTE_IN_MILLIS + NotificationInterval.Second -> count * DateUtils.SECOND_IN_MILLIS + } +} + +@JsonDeserialize(using = NotificationScheduleDeserializer::class) +@JsonSerialize(using = NotificationScheduleSerializer::class) +sealed class NotificationSchedule { + // At specific moment of time (with repeating option) + @JsonDeserialize + class At: NotificationSchedule() { + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = JS_DATE_FORMAT) + lateinit var date: Date + var repeating: Boolean = false + var allowWhileIdle: Boolean = false + } + @JsonDeserialize + class Interval: NotificationSchedule() { + lateinit var interval: DateMatch + var allowWhileIdle: Boolean = false + } + @JsonDeserialize + class Every: NotificationSchedule() { + lateinit var interval: NotificationInterval + var count: Int = 0 + var allowWhileIdle: Boolean = false + } + + fun isRemovable(): Boolean { + return when (this) { + is At -> !repeating + else -> false + } + } + + fun allowWhileIdle(): Boolean { + return when (this) { + is At -> allowWhileIdle + is Interval -> allowWhileIdle + is Every -> allowWhileIdle + else -> false + } + } +} + +internal class NotificationScheduleSerializer @JvmOverloads constructor(t: Class? = null) : + StdSerializer(t) { + @SuppressLint("SimpleDateFormat") + @Throws(IOException::class, JsonProcessingException::class) + override fun serialize( + value: NotificationSchedule, jgen: JsonGenerator, provider: SerializerProvider + ) { + jgen.writeStartObject() + when (value) { + is NotificationSchedule.At -> { + jgen.writeObjectFieldStart("at") + + val sdf = SimpleDateFormat(JS_DATE_FORMAT) + sdf.timeZone = TimeZone.getTimeZone("UTC") + jgen.writeStringField("date", sdf.format(value.date)) + jgen.writeBooleanField("repeating", value.repeating) + + jgen.writeEndObject() + } + is NotificationSchedule.Interval -> { + jgen.writeObjectFieldStart("interval") + + jgen.writeObjectField("interval", value.interval) + + jgen.writeEndObject() + } + is NotificationSchedule.Every -> { + jgen.writeObjectFieldStart("every") + + jgen.writeObjectField("interval", value.interval) + jgen.writeNumberField("count", value.count) + + jgen.writeEndObject() + } + } + + jgen.writeEndObject() + } +} + +internal class NotificationScheduleDeserializer: JsonDeserializer() { + override fun deserialize( + jsonParser: JsonParser, + deserializationContext: DeserializationContext + ): NotificationSchedule { + val node: JsonNode = jsonParser.codec.readTree(jsonParser) + node.get("at")?.let { + return jsonParser.codec.treeToValue(it, NotificationSchedule.At::class.java) + } + node.get("interval")?.let { + return jsonParser.codec.treeToValue(it, NotificationSchedule.Interval::class.java) + } + node.get("every")?.let { + return jsonParser.codec.treeToValue(it, NotificationSchedule.Every::class.java) + } + throw Error("unknown schedule kind $node") + } +} + +class DateMatch { + var year: Int? = null + var month: Int? = null + var day: Int? = null + var weekday: Int? = null + var hour: Int? = null + var minute: Int? = null + var second: Int? = null + + // Unit used to save the last used unit for a trigger. + // One of the Calendar constants values + var unit: Int? = -1 + + /** + * Gets a calendar instance pointing to the specified date. + * + * @param date The date to point. + */ + private fun buildCalendar(date: Date): Calendar { + val cal: Calendar = Calendar.getInstance() + cal.time = date + cal.set(Calendar.MILLISECOND, 0) + return cal + } + + /** + * Calculates next trigger date for + * + * @param date base date used to calculate trigger + * @return next trigger timestamp + */ + fun nextTrigger(date: Date): Long { + val current: Calendar = buildCalendar(date) + val next: Calendar = buildNextTriggerTime(date) + return postponeTriggerIfNeeded(current, next) + } + + /** + * Postpone trigger if first schedule matches the past + */ + private fun postponeTriggerIfNeeded(current: Calendar, next: Calendar): Long { + if (next.timeInMillis <= current.timeInMillis && unit != -1) { + var incrementUnit = -1 + if (unit == Calendar.YEAR || unit == Calendar.MONTH) { + incrementUnit = Calendar.YEAR + } else if (unit == Calendar.DAY_OF_MONTH) { + incrementUnit = Calendar.MONTH + } else if (unit == Calendar.DAY_OF_WEEK) { + incrementUnit = Calendar.WEEK_OF_MONTH + } else if (unit == Calendar.HOUR_OF_DAY) { + incrementUnit = Calendar.DAY_OF_MONTH + } else if (unit == Calendar.MINUTE) { + incrementUnit = Calendar.HOUR_OF_DAY + } else if (unit == Calendar.SECOND) { + incrementUnit = Calendar.MINUTE + } + if (incrementUnit != -1) { + next.set(incrementUnit, next.get(incrementUnit) + 1) + } + } + return next.timeInMillis + } + + private fun buildNextTriggerTime(date: Date): Calendar { + val next: Calendar = buildCalendar(date) + if (year != null) { + next.set(Calendar.YEAR, year ?: 0) + if (unit == -1) unit = Calendar.YEAR + } + if (month != null) { + next.set(Calendar.MONTH, month ?: 0) + if (unit == -1) unit = Calendar.MONTH + } + if (day != null) { + next.set(Calendar.DAY_OF_MONTH, day ?: 0) + if (unit == -1) unit = Calendar.DAY_OF_MONTH + } + if (weekday != null) { + next.set(Calendar.DAY_OF_WEEK, weekday ?: 0) + if (unit == -1) unit = Calendar.DAY_OF_WEEK + } + if (hour != null) { + next.set(Calendar.HOUR_OF_DAY, hour ?: 0) + if (unit == -1) unit = Calendar.HOUR_OF_DAY + } + if (minute != null) { + next.set(Calendar.MINUTE, minute ?: 0) + if (unit == -1) unit = Calendar.MINUTE + } + if (second != null) { + next.set(Calendar.SECOND, second ?: 0) + if (unit == -1) unit = Calendar.SECOND + } + return next + } + + override fun toString(): String { + return "DateMatch{" + + "year=" + + year + + ", month=" + + month + + ", day=" + + day + + ", weekday=" + + weekday + + ", hour=" + + hour + + ", minute=" + + minute + + ", second=" + + second + + '}' + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || javaClass != other.javaClass) return false + val dateMatch = other as DateMatch + if (if (year != null) year != dateMatch.year else dateMatch.year != null) return false + if (if (month != null) month != dateMatch.month else dateMatch.month != null) return false + if (if (day != null) day != dateMatch.day else dateMatch.day != null) return false + if (if (weekday != null) weekday != dateMatch.weekday else dateMatch.weekday != null) return false + if (if (hour != null) hour != dateMatch.hour else dateMatch.hour != null) return false + if (if (minute != null) minute != dateMatch.minute else dateMatch.minute != null) return false + return if (second != null) second == dateMatch.second else dateMatch.second == null + } + + override fun hashCode(): Int { + var result = if (year != null) year.hashCode() else 0 + result = 31 * result + if (month != null) month.hashCode() else 0 + result = 31 * result + if (day != null) day.hashCode() else 0 + result = 31 * result + if (weekday != null) weekday.hashCode() else 0 + result = 31 * result + if (hour != null) hour.hashCode() else 0 + result = 31 * result + if (minute != null) minute.hashCode() else 0 + result += 31 + if (second != null) second.hashCode() else 0 + return result + } + + /** + * Transform DateMatch object to CronString + * + * @return + */ + fun toMatchString(): String { + val matchString = year.toString() + + separator + + month + + separator + + day + + separator + + weekday + + separator + + hour + + separator + + minute + + separator + + second + + separator + + unit + return matchString.replace("null", "*") + } + + companion object { + private const val separator = " " + + /** + * Create DateMatch object from stored string + * + * @param matchString + * @return + */ + fun fromMatchString(matchString: String): DateMatch { + val date = DateMatch() + val split = matchString.split(separator.toRegex()).dropLastWhile { it.isEmpty() } + .toTypedArray() + if (split.size == 7) { + date.year = getValueFromCronElement(split[0]) + date.month = getValueFromCronElement(split[1]) + date.day = getValueFromCronElement(split[2]) + date.weekday = getValueFromCronElement(split[3]) + date.hour = getValueFromCronElement(split[4]) + date.minute = getValueFromCronElement(split[5]) + date.unit = getValueFromCronElement(split[6]) + } + if (split.size == 8) { + date.year = getValueFromCronElement(split[0]) + date.month = getValueFromCronElement(split[1]) + date.day = getValueFromCronElement(split[2]) + date.weekday = getValueFromCronElement(split[3]) + date.hour = getValueFromCronElement(split[4]) + date.minute = getValueFromCronElement(split[5]) + date.second = getValueFromCronElement(split[6]) + date.unit = getValueFromCronElement(split[7]) + } + return date + } + + private fun getValueFromCronElement(token: String): Int? { + return try { + token.toInt() + } catch (e: NumberFormatException) { + null + } + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/notification/android/src/main/java/NotificationStorage.kt b/packages/kbot/gui/app/plugins/notification/android/src/main/java/NotificationStorage.kt new file mode 100644 index 00000000..bceb985d --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/android/src/main/java/NotificationStorage.kt @@ -0,0 +1,113 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.notification + +import android.content.Context +import android.content.SharedPreferences +import com.fasterxml.jackson.databind.ObjectMapper +import org.json.JSONException +import java.lang.Exception + +// Key for private preferences +private const val NOTIFICATION_STORE_ID = "NOTIFICATION_STORE" +// Key used to save action types +private const val ACTION_TYPES_ID = "ACTION_TYPE_STORE" + +class NotificationStorage(private val context: Context, private val jsonMapper: ObjectMapper) { + fun appendNotifications(localNotifications: List) { + val storage = getStorage(NOTIFICATION_STORE_ID) + val editor = storage.edit() + for (request in localNotifications) { + if (request.schedule != null) { + val key: String = request.id.toString() + editor.putString(key, request.sourceJson.toString()) + } + } + editor.apply() + } + + fun getSavedNotificationIds(): List { + val storage = getStorage(NOTIFICATION_STORE_ID) + val all = storage.all + return if (all != null) { + ArrayList(all.keys) + } else ArrayList() + } + + fun getSavedNotifications(): List { + val storage = getStorage(NOTIFICATION_STORE_ID) + val all = storage.all + if (all != null) { + val notifications = ArrayList() + for (key in all.keys) { + val notificationString = all[key] as String? + try { + val notification = jsonMapper.readValue(notificationString, Notification::class.java) + notifications.add(notification) + } catch (_: Exception) { } + } + return notifications + } + return ArrayList() + } + + fun getSavedNotification(key: String): Notification? { + val storage = getStorage(NOTIFICATION_STORE_ID) + val notificationString = try { + storage.getString(key, null) + } catch (ex: ClassCastException) { + return null + } ?: return null + + return try { + jsonMapper.readValue(notificationString, Notification::class.java) + } catch (ex: JSONException) { + null + } + } + + fun deleteNotification(id: String?) { + val editor = getStorage(NOTIFICATION_STORE_ID).edit() + editor.remove(id) + editor.apply() + } + + private fun getStorage(key: String): SharedPreferences { + return context.getSharedPreferences(key, Context.MODE_PRIVATE) + } + + fun writeActionGroup(actions: List) { + for (type in actions) { + val i = type.id + val editor = getStorage(ACTION_TYPES_ID + type.id).edit() + editor.clear() + editor.putInt("count", type.actions.size) + for (action in type.actions) { + editor.putString("id$i", action.id) + editor.putString("title$i", action.title) + editor.putBoolean("input$i", action.input ?: false) + } + editor.apply() + } + } + + fun getActionGroup(forId: String): Array { + val storage = getStorage(ACTION_TYPES_ID + forId) + val count = storage.getInt("count", 0) + val actions: Array = arrayOfNulls(count) + for (i in 0 until count) { + val id = storage.getString("id$i", "") + val title = storage.getString("title$i", "") + val input = storage.getBoolean("input$i", false) + + val action = NotificationAction() + action.id = id ?: "" + action.title = title + action.input = input + actions[i] = action + } + return actions + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/notification/android/src/main/java/TauriNotificationManager.kt b/packages/kbot/gui/app/plugins/notification/android/src/main/java/TauriNotificationManager.kt new file mode 100644 index 00000000..a8912739 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/android/src/main/java/TauriNotificationManager.kt @@ -0,0 +1,581 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.notification + +import android.annotation.SuppressLint +import android.app.Activity +import android.app.AlarmManager +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.ContentResolver +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.media.AudioAttributes +import android.net.Uri +import android.os.Build +import android.os.Build.VERSION.SDK_INT +import android.os.UserManager +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.core.app.RemoteInput +import app.tauri.Logger +import app.tauri.plugin.JSObject +import app.tauri.plugin.PluginManager +import com.fasterxml.jackson.databind.ObjectMapper +import org.json.JSONException +import org.json.JSONObject +import java.text.SimpleDateFormat +import java.util.Date + +// Action constants +const val NOTIFICATION_INTENT_KEY = "NotificationId" +const val NOTIFICATION_OBJ_INTENT_KEY = "LocalNotficationObject" +const val ACTION_INTENT_KEY = "NotificationUserAction" +const val NOTIFICATION_IS_REMOVABLE_KEY = "NotificationRepeating" +const val REMOTE_INPUT_KEY = "NotificationRemoteInput" +const val DEFAULT_NOTIFICATION_CHANNEL_ID = "default" +const val DEFAULT_PRESS_ACTION = "tap" + +class TauriNotificationManager( + private val storage: NotificationStorage, + private val activity: Activity?, + private val context: Context, + private val config: PluginConfig? +) { + private var defaultSoundID: Int = AssetUtils.RESOURCE_ID_ZERO_VALUE + private var defaultSmallIconID: Int = AssetUtils.RESOURCE_ID_ZERO_VALUE + + fun handleNotificationActionPerformed( + data: Intent, + notificationStorage: NotificationStorage + ): JSObject? { + Logger.debug(Logger.tags("Notification"), "Notification received: " + data.dataString) + val notificationId = + data.getIntExtra(NOTIFICATION_INTENT_KEY, Int.MIN_VALUE) + if (notificationId == Int.MIN_VALUE) { + Logger.debug(Logger.tags("Notification"), "Activity started without notification attached") + return null + } + val isRemovable = + data.getBooleanExtra(NOTIFICATION_IS_REMOVABLE_KEY, true) + if (isRemovable) { + notificationStorage.deleteNotification(notificationId.toString()) + } + val dataJson = JSObject() + val results = RemoteInput.getResultsFromIntent(data) + val input = results?.getCharSequence(REMOTE_INPUT_KEY) + dataJson.put("inputValue", input?.toString()) + val menuAction = data.getStringExtra(ACTION_INTENT_KEY) + dismissVisibleNotification(notificationId) + dataJson.put("actionId", menuAction) + var request: JSONObject? = null + try { + val notificationJsonString = + data.getStringExtra(NOTIFICATION_OBJ_INTENT_KEY) + if (notificationJsonString != null) { + request = JSObject(notificationJsonString) + } + } catch (_: JSONException) { + } + dataJson.put("notification", request) + return dataJson + } + + /** + * Create notification channel + */ + fun createNotificationChannel() { + // Create the NotificationChannel, but only on API 26+ because + // the NotificationChannel class is new and not in the support library + if (SDK_INT >= Build.VERSION_CODES.O) { + val name: CharSequence = "Default" + val description = "Default" + val importance = NotificationManager.IMPORTANCE_DEFAULT + val channel = NotificationChannel(DEFAULT_NOTIFICATION_CHANNEL_ID, name, importance) + channel.description = description + val audioAttributes = AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_ALARM) + .build() + val soundUri = getDefaultSoundUrl(context) + if (soundUri != null) { + channel.setSound(soundUri, audioAttributes) + } + // Register the channel with the system; you can't change the importance + // or other notification behaviors after this + val notificationManager = context.getSystemService( + NotificationManager::class.java + ) + notificationManager.createNotificationChannel(channel) + } + } + + private fun trigger(notificationManager: NotificationManagerCompat, notification: Notification): Int { + dismissVisibleNotification(notification.id) + cancelTimerForNotification(notification.id) + buildNotification(notificationManager, notification) + + return notification.id + } + + fun schedule(notification: Notification): Int { + val notificationManager = NotificationManagerCompat.from(context) + return trigger(notificationManager, notification) + } + + fun schedule(notifications: List): List { + val ids = mutableListOf() + val notificationManager = NotificationManagerCompat.from(context) + + for (notification in notifications) { + val id = trigger(notificationManager, notification) + ids.add(id) + } + + return ids + } + + // TODO Progressbar support + // TODO System categories (DO_NOT_DISTURB etc.) + // TODO use NotificationCompat.MessagingStyle for latest API + // TODO expandable notification NotificationCompat.MessagingStyle + // TODO media style notification support NotificationCompat.MediaStyle + @SuppressLint("MissingPermission") + private fun buildNotification( + notificationManager: NotificationManagerCompat, + notification: Notification, + ) { + val channelId = notification.channelId ?: DEFAULT_NOTIFICATION_CHANNEL_ID + val mBuilder = NotificationCompat.Builder( + context, channelId + ) + .setContentTitle(notification.title) + .setContentText(notification.body) + .setAutoCancel(notification.isAutoCancel) + .setOngoing(notification.isOngoing) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setGroupSummary(notification.isGroupSummary) + if (notification.largeBody != null) { + // support multiline text + mBuilder.setStyle( + NotificationCompat.BigTextStyle() + .bigText(notification.largeBody) + .setSummaryText(notification.summary) + ) + } else if (notification.inboxLines != null) { + val inboxStyle = NotificationCompat.InboxStyle() + for (line in notification.inboxLines ?: listOf()) { + inboxStyle.addLine(line) + } + inboxStyle.setBigContentTitle(notification.title) + inboxStyle.setSummaryText(notification.summary) + mBuilder.setStyle(inboxStyle) + } + val sound = notification.getSound(context, getDefaultSound(context)) + if (sound != null) { + val soundUri = Uri.parse(sound) + // Grant permission to use sound + context.grantUriPermission( + "com.android.systemui", + soundUri, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + mBuilder.setSound(soundUri) + mBuilder.setDefaults(android.app.Notification.DEFAULT_VIBRATE or android.app.Notification.DEFAULT_LIGHTS) + } else { + mBuilder.setDefaults(android.app.Notification.DEFAULT_ALL) + } + val group = notification.group + if (group != null) { + mBuilder.setGroup(group) + if (notification.isGroupSummary) { + mBuilder.setSubText(notification.summary) + } + } + mBuilder.setVisibility(notification.visibility ?: NotificationCompat.VISIBILITY_PRIVATE) + mBuilder.setOnlyAlertOnce(true) + mBuilder.setSmallIcon(notification.getSmallIcon(context, getDefaultSmallIcon(context))) + mBuilder.setLargeIcon(notification.getLargeIcon(context)) + val iconColor = notification.getIconColor(config?.iconColor ?: "") + if (iconColor.isNotEmpty()) { + try { + mBuilder.color = Color.parseColor(iconColor) + } catch (ex: IllegalArgumentException) { + throw Exception("Invalid color provided. Must be a hex string (ex: #ff0000") + } + } + createActionIntents(notification, mBuilder) + // notificationId is a unique int for each notification that you must define + val buildNotification = mBuilder.build() + if (notification.schedule != null) { + triggerScheduledNotification(buildNotification, notification) + } else { + notificationManager.notify(notification.id, buildNotification) + try { + NotificationPlugin.triggerNotification(notification) + } catch (_: JSONException) { + } + } + } + + // Create intents for open/dismiss actions + private fun createActionIntents( + notification: Notification, + mBuilder: NotificationCompat.Builder + ) { + // Open intent + val intent = buildIntent(notification, DEFAULT_PRESS_ACTION) + var flags = PendingIntent.FLAG_CANCEL_CURRENT + if (SDK_INT >= Build.VERSION_CODES.S) { + flags = flags or PendingIntent.FLAG_MUTABLE + } + val pendingIntent = PendingIntent.getActivity(context, notification.id, intent, flags) + mBuilder.setContentIntent(pendingIntent) + + // Build action types + val actionTypeId = notification.actionTypeId + if (actionTypeId != null) { + val actionGroup = storage.getActionGroup(actionTypeId) + for (notificationAction in actionGroup) { + // TODO Add custom icons to actions + val actionIntent = buildIntent(notification, notificationAction!!.id) + val actionPendingIntent = PendingIntent.getActivity( + context, + (notification.id) + notificationAction.id.hashCode(), + actionIntent, + flags + ) + val actionBuilder: NotificationCompat.Action.Builder = NotificationCompat.Action.Builder( + R.drawable.ic_transparent, + notificationAction.title, + actionPendingIntent + ) + if (notificationAction.input == true) { + val remoteInput = RemoteInput.Builder(REMOTE_INPUT_KEY).setLabel( + notificationAction.title + ).build() + actionBuilder.addRemoteInput(remoteInput) + } + mBuilder.addAction(actionBuilder.build()) + } + } + + // Dismiss intent + val dissmissIntent = Intent( + context, + NotificationDismissReceiver::class.java + ) + dissmissIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + dissmissIntent.putExtra(NOTIFICATION_INTENT_KEY, notification.id) + dissmissIntent.putExtra(ACTION_INTENT_KEY, "dismiss") + val schedule = notification.schedule + dissmissIntent.putExtra( + NOTIFICATION_IS_REMOVABLE_KEY, + schedule == null || schedule.isRemovable() + ) + flags = 0 + if (SDK_INT >= Build.VERSION_CODES.S) { + flags = PendingIntent.FLAG_MUTABLE + } + val deleteIntent = + PendingIntent.getBroadcast(context, notification.id, dissmissIntent, flags) + mBuilder.setDeleteIntent(deleteIntent) + } + + private fun buildIntent(notification: Notification, action: String?): Intent { + val intent = if (activity != null) { + Intent(context, activity.javaClass) + } else { + val packageName = context.packageName + context.packageManager.getLaunchIntentForPackage(packageName)!! + } + intent.action = Intent.ACTION_MAIN + intent.addCategory(Intent.CATEGORY_LAUNCHER) + intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP + intent.putExtra(NOTIFICATION_INTENT_KEY, notification.id) + intent.putExtra(ACTION_INTENT_KEY, action) + intent.putExtra(NOTIFICATION_OBJ_INTENT_KEY, notification.sourceJson) + val schedule = notification.schedule + intent.putExtra(NOTIFICATION_IS_REMOVABLE_KEY, schedule == null || schedule.isRemovable()) + return intent + } + + /** + * Build a notification trigger, such as triggering each N seconds, or + * on a certain date "shape" (such as every first of the month) + */ + // TODO support different AlarmManager.RTC modes depending on priority + @SuppressLint("SimpleDateFormat") + private fun triggerScheduledNotification(notification: android.app.Notification, request: Notification) { + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + val schedule = request.schedule + val notificationIntent = Intent( + context, + TimedNotificationPublisher::class.java + ) + notificationIntent.putExtra(NOTIFICATION_INTENT_KEY, request.id) + notificationIntent.putExtra(TimedNotificationPublisher.NOTIFICATION_KEY, notification) + var flags = PendingIntent.FLAG_CANCEL_CURRENT + if (SDK_INT >= Build.VERSION_CODES.S) { + flags = flags or PendingIntent.FLAG_MUTABLE + } + var pendingIntent = + PendingIntent.getBroadcast(context, request.id, notificationIntent, flags) + + when (schedule) { + is NotificationSchedule.At -> { + if (schedule.date.time < Date().time) { + Logger.error(Logger.tags("Notification"), "Scheduled time must be *after* current time", null) + return + } + if (schedule.repeating) { + val interval: Long = schedule.date.time - Date().time + alarmManager.setRepeating(AlarmManager.RTC, schedule.date.time, interval, pendingIntent) + } else { + setExactIfPossible(alarmManager, schedule, schedule.date.time, pendingIntent) + } + } + is NotificationSchedule.Interval -> { + val trigger = schedule.interval.nextTrigger(Date()) + notificationIntent.putExtra(TimedNotificationPublisher.CRON_KEY, schedule.interval.toMatchString()) + pendingIntent = + PendingIntent.getBroadcast(context, request.id, notificationIntent, flags) + setExactIfPossible(alarmManager, schedule, trigger, pendingIntent) + val sdf = SimpleDateFormat("yyyy/MM/dd HH:mm:ss") + Logger.debug( + Logger.tags("Notification"), + "notification " + request.id + " will next fire at " + sdf.format(Date(trigger)) + ) + } + is NotificationSchedule.Every -> { + val everyInterval = getIntervalTime(schedule.interval, schedule.count) + val startTime: Long = Date().time + everyInterval + alarmManager.setRepeating(AlarmManager.RTC, startTime, everyInterval, pendingIntent) + } + else -> {} + } + } + + @SuppressLint("ObsoleteSdkInt", "MissingPermission") + private fun setExactIfPossible( + alarmManager: AlarmManager, + schedule: NotificationSchedule, + trigger: Long, + pendingIntent: PendingIntent + ) { + if (SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) { + if (SDK_INT >= Build.VERSION_CODES.M && schedule.allowWhileIdle()) { + alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, trigger, pendingIntent) + } else { + alarmManager[AlarmManager.RTC, trigger] = pendingIntent + } + } else { + if (SDK_INT >= Build.VERSION_CODES.M && schedule.allowWhileIdle()) { + alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, trigger, pendingIntent) + } else { + alarmManager.setExact(AlarmManager.RTC, trigger, pendingIntent) + } + } + } + + fun cancel(notifications: List) { + for (id in notifications) { + dismissVisibleNotification(id) + cancelTimerForNotification(id) + storage.deleteNotification(id.toString()) + } + } + + private fun cancelTimerForNotification(notificationId: Int) { + val intent = Intent(context, TimedNotificationPublisher::class.java) + var flags = 0 + if (SDK_INT >= Build.VERSION_CODES.S) { + flags = PendingIntent.FLAG_MUTABLE + } + val pi = PendingIntent.getBroadcast(context, notificationId, intent, flags) + if (pi != null) { + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + alarmManager.cancel(pi) + } + } + + private fun dismissVisibleNotification(notificationId: Int) { + val notificationManager = NotificationManagerCompat.from( + context + ) + notificationManager.cancel(notificationId) + } + + fun areNotificationsEnabled(): Boolean { + val notificationManager = NotificationManagerCompat.from(context) + return notificationManager.areNotificationsEnabled() + } + + private fun getDefaultSoundUrl(context: Context): Uri? { + val soundId = getDefaultSound(context) + return if (soundId != AssetUtils.RESOURCE_ID_ZERO_VALUE) { + Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/" + soundId) + } else null + } + + private fun getDefaultSound(context: Context): Int { + if (defaultSoundID != AssetUtils.RESOURCE_ID_ZERO_VALUE) return defaultSoundID + var resId: Int = AssetUtils.RESOURCE_ID_ZERO_VALUE + val soundConfigResourceName = AssetUtils.getResourceBaseName(config?.sound) + if (soundConfigResourceName != null) { + resId = AssetUtils.getResourceID(context, soundConfigResourceName, "raw") + } + defaultSoundID = resId + return resId + } + + private fun getDefaultSmallIcon(context: Context): Int { + if (defaultSmallIconID != AssetUtils.RESOURCE_ID_ZERO_VALUE) return defaultSmallIconID + var resId: Int = AssetUtils.RESOURCE_ID_ZERO_VALUE + val smallIconConfigResourceName = AssetUtils.getResourceBaseName(config?.icon) + if (smallIconConfigResourceName != null) { + resId = AssetUtils.getResourceID(context, smallIconConfigResourceName, "drawable") + } + if (resId == AssetUtils.RESOURCE_ID_ZERO_VALUE) { + resId = android.R.drawable.ic_dialog_info + } + defaultSmallIconID = resId + return resId + } +} + +class NotificationDismissReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val intExtra = + intent.getIntExtra(NOTIFICATION_INTENT_KEY, Int.MIN_VALUE) + if (intExtra == Int.MIN_VALUE) { + Logger.error(Logger.tags("Notification"), "Invalid notification dismiss operation", null) + return + } + val isRemovable = + intent.getBooleanExtra(NOTIFICATION_IS_REMOVABLE_KEY, true) + if (isRemovable) { + val notificationStorage = NotificationStorage(context, ObjectMapper()) + notificationStorage.deleteNotification(intExtra.toString()) + } + } +} + +class TimedNotificationPublisher : BroadcastReceiver() { + /** + * Restore and present notification + */ + override fun onReceive(context: Context, intent: Intent) { + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val notification = if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra( + NOTIFICATION_KEY, + android.app.Notification::class.java + ) + } else { + getParcelableExtraLegacy(intent, NOTIFICATION_KEY) + } + notification?.`when` = System.currentTimeMillis() + val id = intent.getIntExtra(NOTIFICATION_INTENT_KEY, Int.MIN_VALUE) + if (id == Int.MIN_VALUE) { + Logger.error(Logger.tags("Notification"), "No valid id supplied", null) + } + val storage = NotificationStorage(context, ObjectMapper()) + + val savedNotification = storage.getSavedNotification(id.toString()) + if (savedNotification != null) { + NotificationPlugin.triggerNotification(savedNotification) + } + + notificationManager.notify(id, notification) + if (!rescheduleNotificationIfNeeded(context, intent, id)) { + storage.deleteNotification(id.toString()) + } + } + + @Suppress("DEPRECATION") + private fun getParcelableExtraLegacy(intent: Intent, string: String): android.app.Notification? { + return intent.getParcelableExtra(string) + } + + @SuppressLint("MissingPermission", "SimpleDateFormat") + private fun rescheduleNotificationIfNeeded(context: Context, intent: Intent, id: Int): Boolean { + val dateString = intent.getStringExtra(CRON_KEY) + if (dateString != null) { + val date = DateMatch.fromMatchString(dateString) + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + val trigger = date.nextTrigger(Date()) + val clone = intent.clone() as Intent + var flags = PendingIntent.FLAG_CANCEL_CURRENT + if (SDK_INT >= Build.VERSION_CODES.S) { + flags = flags or PendingIntent.FLAG_MUTABLE + } + val pendingIntent = PendingIntent.getBroadcast(context, id, clone, flags) + if (SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) { + alarmManager[AlarmManager.RTC, trigger] = pendingIntent + } else { + alarmManager.setExact(AlarmManager.RTC, trigger, pendingIntent) + } + val sdf = SimpleDateFormat("yyyy/MM/dd HH:mm:ss") + Logger.debug( + Logger.tags("Notification"), + "notification " + id + " will next fire at " + sdf.format(Date(trigger)) + ) + return true + } + return false + } + + companion object { + var NOTIFICATION_KEY = "NotificationPublisher.notification" + var CRON_KEY = "NotificationPublisher.cron" + } +} + +class LocalNotificationRestoreReceiver : BroadcastReceiver() { + @SuppressLint("ObsoleteSdkInt") + override fun onReceive(context: Context, intent: Intent) { + if (SDK_INT >= Build.VERSION_CODES.N) { + val um = context.getSystemService( + UserManager::class.java + ) + if (um == null || !um.isUserUnlocked) return + } + val storage = NotificationStorage(context, ObjectMapper()) + val ids = storage.getSavedNotificationIds() + val notifications = mutableListOf() + val updatedNotifications = mutableListOf() + for (id in ids) { + val notification = storage.getSavedNotification(id) ?: continue + val schedule = notification.schedule + if (schedule != null && schedule is NotificationSchedule.At) { + val at: Date = schedule.date + if (at.before(Date())) { + // modify the scheduled date in order to show notifications that would have been delivered while device was off. + val newDateTime = Date().time + 15 * 1000 + schedule.date = Date(newDateTime) + updatedNotifications.add(notification) + } + } + notifications.add(notification) + } + if (updatedNotifications.size > 0) { + storage.appendNotifications(updatedNotifications) + } + + var config: PluginConfig? = null + try { + config = PluginManager.loadConfig(context, "notification", PluginConfig::class.java) + } catch (ex: Exception) { + ex.printStackTrace() + } + val notificationManager = TauriNotificationManager(storage, null, context, config) + notificationManager.schedule(notifications) + } +} diff --git a/packages/kbot/gui/app/plugins/notification/android/src/main/res/drawable/ic_transparent.xml b/packages/kbot/gui/app/plugins/notification/android/src/main/res/drawable/ic_transparent.xml new file mode 100644 index 00000000..fc1779e2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/android/src/main/res/drawable/ic_transparent.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/packages/kbot/gui/app/plugins/notification/android/src/test/java/ExampleUnitTest.kt b/packages/kbot/gui/app/plugins/notification/android/src/test/java/ExampleUnitTest.kt new file mode 100644 index 00000000..134a27d4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/android/src/test/java/ExampleUnitTest.kt @@ -0,0 +1,21 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.notification + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/packages/kbot/gui/app/plugins/notification/api-iife.js b/packages/kbot/gui/app/plugins/notification/api-iife.js new file mode 100644 index 00000000..68cf16b4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_NOTIFICATION__=function(i){"use strict";function n(i,n,t,e){if("function"==typeof n?i!==n||!e:!n.has(i))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===t?e:"a"===t?e.call(i):e?e.value:n.get(i)}function t(i,n,t,e,a){if("function"==typeof n||!n.has(i))throw new TypeError("Cannot write private member to an object whose class did not declare it");return n.set(i,t),t}var e,a,o,r;"function"==typeof SuppressedError&&SuppressedError;const s="__TAURI_TO_IPC_KEY__";class c{constructor(i){e.set(this,void 0),a.set(this,0),o.set(this,[]),r.set(this,void 0),t(this,e,i||(()=>{})),this.id=function(i,n=!1){return window.__TAURI_INTERNALS__.transformCallback(i,n)}((i=>{const s=i.index;if("end"in i)return void(s==n(this,a,"f")?this.cleanupCallback():t(this,r,s));const c=i.message;if(s==n(this,a,"f")){for(n(this,e,"f").call(this,c),t(this,a,n(this,a,"f")+1);n(this,a,"f")in n(this,o,"f");){const i=n(this,o,"f")[n(this,a,"f")];n(this,e,"f").call(this,i),delete n(this,o,"f")[n(this,a,"f")],t(this,a,n(this,a,"f")+1)}n(this,a,"f")===n(this,r,"f")&&this.cleanupCallback()}else n(this,o,"f")[s]=c}))}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(i){t(this,e,i)}get onmessage(){return n(this,e,"f")}[(e=new WeakMap,a=new WeakMap,o=new WeakMap,r=new WeakMap,s)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[s]()}}class l{constructor(i,n,t){this.plugin=i,this.event=n,this.channelId=t}async unregister(){return f(`plugin:${this.plugin}|remove_listener`,{event:this.event,channelId:this.channelId})}}async function u(i,n,t){const e=new c(t);return f(`plugin:${i}|registerListener`,{event:n,handler:e}).then((()=>new l(i,n,e.id)))}async function f(i,n={},t){return window.__TAURI_INTERNALS__.invoke(i,n,t)}var h,d,w;i.ScheduleEvery=void 0,(h=i.ScheduleEvery||(i.ScheduleEvery={})).Year="year",h.Month="month",h.TwoWeeks="twoWeeks",h.Week="week",h.Day="day",h.Hour="hour",h.Minute="minute",h.Second="second";return i.Importance=void 0,(d=i.Importance||(i.Importance={}))[d.None=0]="None",d[d.Min=1]="Min",d[d.Low=2]="Low",d[d.Default=3]="Default",d[d.High=4]="High",i.Visibility=void 0,(w=i.Visibility||(i.Visibility={}))[w.Secret=-1]="Secret",w[w.Private=0]="Private",w[w.Public=1]="Public",i.Schedule=class{static at(i,n=!1,t=!1){return{at:{date:i,repeating:n,allowWhileIdle:t},interval:void 0,every:void 0}}static interval(i,n=!1){return{at:void 0,interval:{interval:i,allowWhileIdle:n},every:void 0}}static every(i,n,t=!1){return{at:void 0,interval:void 0,every:{interval:i,count:n,allowWhileIdle:t}}}},i.active=async function(){return await f("plugin:notification|get_active")},i.cancel=async function(i){await f("plugin:notification|cancel",{notifications:i})},i.cancelAll=async function(){await f("plugin:notification|cancel")},i.channels=async function(){return await f("plugin:notification|listChannels")},i.createChannel=async function(i){await f("plugin:notification|create_channel",{...i})},i.isPermissionGranted=async function(){return"default"!==window.Notification.permission?await Promise.resolve("granted"===window.Notification.permission):await f("plugin:notification|is_permission_granted")},i.onAction=async function(i){return await u("notification","actionPerformed",i)},i.onNotificationReceived=async function(i){return await u("notification","notification",i)},i.pending=async function(){return await f("plugin:notification|get_pending")},i.registerActionTypes=async function(i){await f("plugin:notification|register_action_types",{types:i})},i.removeActive=async function(i){await f("plugin:notification|remove_active",{notifications:i})},i.removeAllActive=async function(){await f("plugin:notification|remove_active")},i.removeChannel=async function(i){await f("plugin:notification|delete_channel",{id:i})},i.requestPermission=async function(){return await window.Notification.requestPermission()},i.sendNotification=function(i){"string"==typeof i?new window.Notification(i):new window.Notification(i.title,i)},i}({});Object.defineProperty(window.__TAURI__,"notification",{value:__TAURI_PLUGIN_NOTIFICATION__})} diff --git a/packages/kbot/gui/app/plugins/notification/banner.png b/packages/kbot/gui/app/plugins/notification/banner.png new file mode 100644 index 00000000..f6460f3d Binary files /dev/null and b/packages/kbot/gui/app/plugins/notification/banner.png differ diff --git a/packages/kbot/gui/app/plugins/notification/build.rs b/packages/kbot/gui/app/plugins/notification/build.rs new file mode 100644 index 00000000..4b24c755 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/build.rs @@ -0,0 +1,35 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +const COMMANDS: &[&str] = &[ + "notify", + "request_permission", + "is_permission_granted", + "register_action_types", + "register_listener", + "cancel", + "get_pending", + "remove_active", + "get_active", + "check_permissions", + "show", + "batch", + "list_channels", + "delete_channel", + "create_channel", + "permission_state", +]; + +fn main() { + let result = tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .android_path("android") + .ios_path("ios") + .try_build(); + + // when building documentation for Android the plugin build result is always Err() and is irrelevant to the crate documentation build + if !(cfg!(docsrs) && std::env::var("TARGET").unwrap().contains("android")) { + result.unwrap(); + } +} diff --git a/packages/kbot/gui/app/plugins/notification/guest-js/index.ts b/packages/kbot/gui/app/plugins/notification/guest-js/index.ts new file mode 100644 index 00000000..685c60c2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/guest-js/index.ts @@ -0,0 +1,606 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/** + * Send toast notifications (brief auto-expiring OS window element) to your user. + * Can also be used with the Notification Web API. + * + * @module + */ + +import { + invoke, + type PluginListener, + addPluginListener +} from '@tauri-apps/api/core' + +export type { PermissionState } from '@tauri-apps/api/core' + +/** + * Options to send a notification. + * + * @since 2.0.0 + */ +interface Options { + /** + * The notification identifier to reference this object later. Must be a 32-bit integer. + */ + id?: number + /** + * Identifier of the {@link Channel} that deliveres this notification. + * + * If the channel does not exist, the notification won't fire. + * Make sure the channel exists with {@link listChannels} and {@link createChannel}. + */ + channelId?: string + /** + * Notification title. + */ + title: string + /** + * Optional notification body. + * */ + body?: string + /** + * Schedule this notification to fire on a later time or a fixed interval. + */ + schedule?: Schedule + /** + * Multiline text. + * Changes the notification style to big text. + * Cannot be used with `inboxLines`. + */ + largeBody?: string + /** + * Detail text for the notification with `largeBody`, `inboxLines` or `groupSummary`. + */ + summary?: string + /** + * Defines an action type for this notification. + */ + actionTypeId?: string + /** + * Identifier used to group multiple notifications. + * + * https://developer.apple.com/documentation/usernotifications/unmutablenotificationcontent/1649872-threadidentifier + */ + group?: string + /** + * Instructs the system that this notification is the summary of a group on Android. + */ + groupSummary?: boolean + /** + * The sound resource name or file path for the notification. + * + * Platform specific behavior: + * - On macOS: use system sounds (e.g., "Ping", "Blow") or sound files in the app bundle + * - On Linux: use XDG theme sounds (e.g., "message-new-instant") or file paths + * - On Windows: use file paths to sound files (.wav format) + * - On Mobile: use resource names + */ + sound?: string + /** + * List of lines to add to the notification. + * Changes the notification style to inbox. + * Cannot be used with `largeBody`. + * + * Only supports up to 5 lines. + */ + inboxLines?: string[] + /** + * Notification icon. + * + * On Android the icon must be placed in the app's `res/drawable` folder. + */ + icon?: string + /** + * Notification large icon (Android). + * + * The icon must be placed in the app's `res/drawable` folder. + */ + largeIcon?: string + /** + * Icon color on Android. + */ + iconColor?: string + /** + * Notification attachments. + */ + attachments?: Attachment[] + /** + * Extra payload to store in the notification. + */ + extra?: Record + /** + * If true, the notification cannot be dismissed by the user on Android. + * + * An application service must manage the dismissal of the notification. + * It is typically used to indicate a background task that is pending (e.g. a file download) + * or the user is engaged with (e.g. playing music). + */ + ongoing?: boolean + /** + * Automatically cancel the notification when the user clicks on it. + */ + autoCancel?: boolean + /** + * Changes the notification presentation to be silent on iOS (no badge, no sound, not listed). + */ + silent?: boolean + /** + * Notification visibility. + */ + visibility?: Visibility + /** + * Sets the number of items this notification represents on Android. + */ + number?: number +} + +interface ScheduleInterval { + year?: number + month?: number + day?: number + /** + * 1 - Sunday + * 2 - Monday + * 3 - Tuesday + * 4 - Wednesday + * 5 - Thursday + * 6 - Friday + * 7 - Saturday + */ + weekday?: number + hour?: number + minute?: number + second?: number +} + +enum ScheduleEvery { + Year = 'year', + Month = 'month', + TwoWeeks = 'twoWeeks', + Week = 'week', + Day = 'day', + Hour = 'hour', + Minute = 'minute', + /** + * Not supported on iOS. + */ + Second = 'second' +} + +class Schedule { + at: + | { + date: Date + repeating: boolean + allowWhileIdle: boolean + } + | undefined + + interval: + | { + interval: ScheduleInterval + allowWhileIdle: boolean + } + | undefined + + every: + | { + interval: ScheduleEvery + count: number + allowWhileIdle: boolean + } + | undefined + + static at(date: Date, repeating = false, allowWhileIdle = false): Schedule { + return { + at: { date, repeating, allowWhileIdle }, + interval: undefined, + every: undefined + } + } + + static interval( + interval: ScheduleInterval, + allowWhileIdle = false + ): Schedule { + return { + at: undefined, + interval: { interval, allowWhileIdle }, + every: undefined + } + } + + static every( + kind: ScheduleEvery, + count: number, + allowWhileIdle = false + ): Schedule { + return { + at: undefined, + interval: undefined, + every: { interval: kind, count, allowWhileIdle } + } + } +} + +/** + * Attachment of a notification. + */ +interface Attachment { + /** Attachment identifier. */ + id: string + /** Attachment URL. Accepts the `asset` and `file` protocols. */ + url: string +} + +interface Action { + id: string + title: string + requiresAuthentication?: boolean + foreground?: boolean + destructive?: boolean + input?: boolean + inputButtonTitle?: string + inputPlaceholder?: string +} + +interface ActionType { + /** + * The identifier of this action type + */ + id: string + /** + * The list of associated actions + */ + actions: Action[] + hiddenPreviewsBodyPlaceholder?: string + customDismissAction?: boolean + allowInCarPlay?: boolean + hiddenPreviewsShowTitle?: boolean + hiddenPreviewsShowSubtitle?: boolean +} + +interface PendingNotification { + id: number + title?: string + body?: string + schedule: Schedule +} + +interface ActiveNotification { + id: number + tag?: string + title?: string + body?: string + group?: string + groupSummary: boolean + data: Record + extra: Record + attachments: Attachment[] + actionTypeId?: string + schedule?: Schedule + sound?: string +} + +enum Importance { + None = 0, + Min, + Low, + Default, + High +} + +enum Visibility { + Secret = -1, + Private, + Public +} + +interface Channel { + id: string + name: string + description?: string + sound?: string + lights?: boolean + lightColor?: string + vibration?: boolean + importance?: Importance + visibility?: Visibility +} + +/** + * Checks if the permission to send notifications is granted. + * @example + * ```typescript + * import { isPermissionGranted } from '@tauri-apps/plugin-notification'; + * const permissionGranted = await isPermissionGranted(); + * ``` + * + * @since 2.0.0 + */ +async function isPermissionGranted(): Promise { + if (window.Notification.permission !== 'default') { + return await Promise.resolve(window.Notification.permission === 'granted') + } + return await invoke('plugin:notification|is_permission_granted') +} + +/** + * Requests the permission to send notifications. + * @example + * ```typescript + * import { isPermissionGranted, requestPermission } from '@tauri-apps/plugin-notification'; + * let permissionGranted = await isPermissionGranted(); + * if (!permissionGranted) { + * const permission = await requestPermission(); + * permissionGranted = permission === 'granted'; + * } + * ``` + * + * @returns A promise resolving to whether the user granted the permission or not. + * + * @since 2.0.0 + */ +async function requestPermission(): Promise { + return await window.Notification.requestPermission() +} + +/** + * Sends a notification to the user. + * @example + * ```typescript + * import { isPermissionGranted, requestPermission, sendNotification } from '@tauri-apps/plugin-notification'; + * let permissionGranted = await isPermissionGranted(); + * if (!permissionGranted) { + * const permission = await requestPermission(); + * permissionGranted = permission === 'granted'; + * } + * if (permissionGranted) { + * sendNotification('Tauri is awesome!'); + * sendNotification({ title: 'TAURI', body: 'Tauri is awesome!' }); + * } + * ``` + * + * @since 2.0.0 + */ +function sendNotification(options: Options | string): void { + if (typeof options === 'string') { + new window.Notification(options) + } else { + new window.Notification(options.title, options) + } +} + +/** + * Register actions that are performed when the user clicks on the notification. + * + * @example + * ```typescript + * import { registerActionTypes } from '@tauri-apps/plugin-notification'; + * await registerActionTypes([{ + * id: 'tauri', + * actions: [{ + * id: 'my-action', + * title: 'Settings' + * }] + * }]) + * ``` + * + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.0.0 + */ +async function registerActionTypes(types: ActionType[]): Promise { + await invoke('plugin:notification|register_action_types', { types }) +} + +/** + * Retrieves the list of pending notifications. + * + * @example + * ```typescript + * import { pending } from '@tauri-apps/plugin-notification'; + * const pendingNotifications = await pending(); + * ``` + * + * @returns A promise resolving to the list of pending notifications. + * + * @since 2.0.0 + */ +async function pending(): Promise { + return await invoke('plugin:notification|get_pending') +} + +/** + * Cancels the pending notifications with the given list of identifiers. + * + * @example + * ```typescript + * import { cancel } from '@tauri-apps/plugin-notification'; + * await cancel([-34234, 23432, 4311]); + * ``` + * + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.0.0 + */ +async function cancel(notifications: number[]): Promise { + await invoke('plugin:notification|cancel', { notifications }) +} + +/** + * Cancels all pending notifications. + * + * @example + * ```typescript + * import { cancelAll } from '@tauri-apps/plugin-notification'; + * await cancelAll(); + * ``` + * + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.0.0 + */ +async function cancelAll(): Promise { + await invoke('plugin:notification|cancel') +} + +/** + * Retrieves the list of active notifications. + * + * @example + * ```typescript + * import { active } from '@tauri-apps/plugin-notification'; + * const activeNotifications = await active(); + * ``` + * + * @returns A promise resolving to the list of active notifications. + * + * @since 2.0.0 + */ +async function active(): Promise { + return await invoke('plugin:notification|get_active') +} + +/** + * Removes the active notifications with the given list of identifiers. + * + * @example + * ```typescript + * import { cancel } from '@tauri-apps/plugin-notification'; + * await cancel([-34234, 23432, 4311]) + * ``` + * + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.0.0 + */ +async function removeActive( + notifications: Array<{ id: number; tag?: string }> +): Promise { + await invoke('plugin:notification|remove_active', { notifications }) +} + +/** + * Removes all active notifications. + * + * @example + * ```typescript + * import { removeAllActive } from '@tauri-apps/plugin-notification'; + * await removeAllActive() + * ``` + * + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.0.0 + */ +async function removeAllActive(): Promise { + await invoke('plugin:notification|remove_active') +} + +/** + * Creates a notification channel. + * + * @example + * ```typescript + * import { createChannel, Importance, Visibility } from '@tauri-apps/plugin-notification'; + * await createChannel({ + * id: 'new-messages', + * name: 'New Messages', + * lights: true, + * vibration: true, + * importance: Importance.Default, + * visibility: Visibility.Private + * }); + * ``` + * + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.0.0 + */ +async function createChannel(channel: Channel): Promise { + await invoke('plugin:notification|create_channel', { ...channel }) +} + +/** + * Removes the channel with the given identifier. + * + * @example + * ```typescript + * import { removeChannel } from '@tauri-apps/plugin-notification'; + * await removeChannel(); + * ``` + * + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.0.0 + */ +async function removeChannel(id: string): Promise { + await invoke('plugin:notification|delete_channel', { id }) +} + +/** + * Retrieves the list of notification channels. + * + * @example + * ```typescript + * import { channels } from '@tauri-apps/plugin-notification'; + * const notificationChannels = await channels(); + * ``` + * + * @returns A promise resolving to the list of notification channels. + * + * @since 2.0.0 + */ +async function channels(): Promise { + return await invoke('plugin:notification|listChannels') +} + +async function onNotificationReceived( + cb: (notification: Options) => void +): Promise { + return await addPluginListener('notification', 'notification', cb) +} + +async function onAction( + cb: (notification: Options) => void +): Promise { + return await addPluginListener('notification', 'actionPerformed', cb) +} + +export type { + Attachment, + Options, + Action, + ActionType, + PendingNotification, + ActiveNotification, + Channel, + ScheduleInterval +} + +export { + Importance, + Visibility, + sendNotification, + requestPermission, + isPermissionGranted, + registerActionTypes, + pending, + cancel, + cancelAll, + active, + removeActive, + removeAllActive, + createChannel, + removeChannel, + channels, + onNotificationReceived, + onAction, + Schedule, + ScheduleEvery +} diff --git a/packages/kbot/gui/app/plugins/notification/guest-js/init.ts b/packages/kbot/gui/app/plugins/notification/guest-js/init.ts new file mode 100644 index 00000000..42d65fd9 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/guest-js/init.ts @@ -0,0 +1,90 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { invoke } from '@tauri-apps/api/core' +import type { PermissionState } from '@tauri-apps/api/core' +import type { Options } from './index' +;(function () { + let permissionSettable = false + let permissionValue = 'default' + + async function isPermissionGranted(): Promise { + // @ts-expect-error __TEMPLATE_windows__ will be replaced in rust before it's injected. + if (window.Notification.permission !== 'default' || __TEMPLATE_windows__) { + return await Promise.resolve(window.Notification.permission === 'granted') + } + return await invoke('plugin:notification|is_permission_granted') + } + + function setNotificationPermission(value: NotificationPermission): void { + permissionSettable = true + // @ts-expect-error we can actually set this value on the webview + window.Notification.permission = value + permissionSettable = false + } + + async function requestPermission(): Promise { + return await invoke( + 'plugin:notification|request_permission' + ).then((permission) => { + setNotificationPermission( + permission === 'prompt' || permission === 'prompt-with-rationale' + ? 'default' + : permission + ) + return permission + }) + } + + async function sendNotification(options: string | Options): Promise { + if (typeof options === 'object') { + Object.freeze(options) + } + + await invoke('plugin:notification|notify', { + options: + typeof options === 'string' + ? { + title: options + } + : options + }) + } + + // @ts-expect-error unfortunately we can't implement the whole type, so we overwrite it with our own version + window.Notification = function (title, options) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const opts = options || {} + void sendNotification( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + Object.assign(opts, { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + title + }) + ) + } + + // @ts-expect-error tauri does not have sync IPC :( + window.Notification.requestPermission = requestPermission + + Object.defineProperty(window.Notification, 'permission', { + enumerable: true, + get: () => permissionValue, + set: (v) => { + if (!permissionSettable) { + throw new Error('Readonly property') + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + permissionValue = v + } + }) + + void isPermissionGranted().then(function (response) { + if (response === null) { + setNotificationPermission('default') + } else { + setNotificationPermission(response ? 'granted' : 'denied') + } + }) +})() diff --git a/packages/kbot/gui/app/plugins/notification/ios/.gitignore b/packages/kbot/gui/app/plugins/notification/ios/.gitignore new file mode 100644 index 00000000..5922fdaa --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/ios/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Package.resolved diff --git a/packages/kbot/gui/app/plugins/notification/ios/Package.swift b/packages/kbot/gui/app/plugins/notification/ios/Package.swift new file mode 100644 index 00000000..bbd6df9e --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/ios/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version:5.5 +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import PackageDescription + +let package = Package( + name: "tauri-plugin-notification", + platforms: [ + .macOS(.v10_13), + .iOS(.v13), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "tauri-plugin-notification", + type: .static, + targets: ["tauri-plugin-notification"]) + ], + dependencies: [ + .package(name: "Tauri", path: "../.tauri/tauri-api") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "tauri-plugin-notification", + dependencies: [ + .byName(name: "Tauri") + ], + path: "Sources") + ] +) diff --git a/packages/kbot/gui/app/plugins/notification/ios/README.md b/packages/kbot/gui/app/plugins/notification/ios/README.md new file mode 100644 index 00000000..f4900bdd --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/ios/README.md @@ -0,0 +1,3 @@ +# Tauri Plugin {{ plugin_name_original }} + +A description of this package. diff --git a/packages/kbot/gui/app/plugins/notification/ios/Sources/Notification.swift b/packages/kbot/gui/app/plugins/notification/ios/Sources/Notification.swift new file mode 100644 index 00000000..259399b8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/ios/Sources/Notification.swift @@ -0,0 +1,286 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Tauri +import UserNotifications + +enum NotificationError: LocalizedError { + case triggerRepeatIntervalTooShort + case attachmentFileNotFound(path: String) + case attachmentUnableToCreate(String) + case pastScheduledTime + case invalidDate(String) + + var errorDescription: String? { + switch self { + case .triggerRepeatIntervalTooShort: + return "Schedule interval too short, must be a least 1 minute" + case .attachmentFileNotFound(let path): + return "Unable to find file \(path) for attachment" + case .attachmentUnableToCreate(let error): + return "Failed to create attachment: \(error)" + case .pastScheduledTime: + return "Scheduled time must be *after* current time" + case .invalidDate(let date): + return "Could not parse date \(date)" + } + } +} + +func makeNotificationContent(_ notification: Notification) throws -> UNNotificationContent { + let content = UNMutableNotificationContent() + content.title = NSString.localizedUserNotificationString( + forKey: notification.title, arguments: nil) + if let body = notification.body { + content.body = NSString.localizedUserNotificationString( + forKey: body, + arguments: nil) + } + + var userInfo: [String: Any] = [:] + + if let extra = notification.extra { + userInfo["__EXTRA__"] = extra + } + + if let schedule = notification.schedule { + userInfo["__SCHEDULE__"] = scheduleToDictionary(schedule) + } + + content.userInfo = userInfo + + if let actionTypeId = notification.actionTypeId { + content.categoryIdentifier = actionTypeId + } + + if let threadIdentifier = notification.group { + content.threadIdentifier = threadIdentifier + } + + if let summaryArgument = notification.summary { + content.summaryArgument = summaryArgument + } + + if let sound = notification.sound { + content.sound = UNNotificationSound(named: UNNotificationSoundName(sound)) + } + + if let attachments = notification.attachments { + content.attachments = try makeAttachments(attachments) + } + + return content +} + +func scheduleToDictionary(_ schedule: NotificationSchedule) -> [String: Any] { + switch schedule { + case .at(let date, let repeating): + return [ + "type": "at", + "date": date, + "repeating": repeating + ] + case .interval(let interval): + return [ + "type": "interval", + "interval": scheduleIntervalToDictionary(interval) + ] + case .every(let interval, let count): + return [ + "type": "every", + "interval": interval.rawValue, + "count": count + ] + } +} + +func scheduleIntervalToDictionary(_ interval: ScheduleInterval) -> [String: Any] { + var dict: [String: Any] = [:] + + if let year = interval.year { + dict["year"] = year + } + if let month = interval.month { + dict["month"] = month + } + if let day = interval.day { + dict["day"] = day + } + if let weekday = interval.weekday { + dict["weekday"] = weekday + } + if let hour = interval.hour { + dict["hour"] = hour + } + if let minute = interval.minute { + dict["minute"] = minute + } + if let second = interval.second { + dict["second"] = second + } + + return dict +} + +func makeAttachments(_ attachments: [NotificationAttachment]) throws -> [UNNotificationAttachment] { + var createdAttachments = [UNNotificationAttachment]() + + for attachment in attachments { + + guard let urlObject = makeAttachmentUrl(attachment.url) else { + throw NotificationError.attachmentFileNotFound(path: attachment.url) + } + + let options = attachment.options != nil ? makeAttachmentOptions(attachment.options!) : nil + + do { + let newAttachment = try UNNotificationAttachment( + identifier: attachment.id, url: urlObject, options: options) + createdAttachments.append(newAttachment) + } catch { + throw NotificationError.attachmentUnableToCreate(error.localizedDescription) + } + } + + return createdAttachments +} + +func makeAttachmentUrl(_ path: String) -> URL? { + return URL(string: path) +} + +func makeAttachmentOptions(_ options: NotificationAttachmentOptions) -> [AnyHashable: Any] { + var opts: [AnyHashable: Any] = [:] + + if let value = options.iosUNNotificationAttachmentOptionsTypeHintKey { + opts[UNNotificationAttachmentOptionsTypeHintKey] = value + } + if let value = options.iosUNNotificationAttachmentOptionsThumbnailHiddenKey { + opts[UNNotificationAttachmentOptionsThumbnailHiddenKey] = value + } + if let value = options.iosUNNotificationAttachmentOptionsThumbnailClippingRectKey { + opts[UNNotificationAttachmentOptionsThumbnailClippingRectKey] = value + } + if let value = options + .iosUNNotificationAttachmentOptionsThumbnailTimeKey + + { + opts[UNNotificationAttachmentOptionsThumbnailTimeKey] = value + } + return opts +} + +func handleScheduledNotification(_ schedule: NotificationSchedule) throws + -> UNNotificationTrigger? +{ + switch schedule { + case .at(let date, let repeating): + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: "en_US_POSIX") + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + + if let at = dateFormatter.date(from: date) { + let dateInfo = Calendar.current.dateComponents(in: TimeZone.current, from: at) + + if dateInfo.date! < Date() { + throw NotificationError.pastScheduledTime + } + + let dateInterval = DateInterval(start: Date(), end: dateInfo.date!) + + // Notifications that repeat have to be at least a minute between each other + if repeating && dateInterval.duration < 60 { + throw NotificationError.triggerRepeatIntervalTooShort + } + + return UNTimeIntervalNotificationTrigger( + timeInterval: dateInterval.duration, repeats: repeating) + + } else { + throw NotificationError.invalidDate(date) + } + case .interval(let interval): + let dateComponents = getDateComponents(interval) + return UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true) + case .every(let interval, let count): + if let repeatDateInterval = getRepeatDateInterval(interval, count) { + // Notifications that repeat have to be at least a minute between each other + if repeatDateInterval.duration < 60 { + throw NotificationError.triggerRepeatIntervalTooShort + } + + return UNTimeIntervalNotificationTrigger( + timeInterval: repeatDateInterval.duration, repeats: true) + } + } + + return nil +} + +/// Given our schedule format, return a DateComponents object +/// that only contains the components passed in. + +func getDateComponents(_ at: ScheduleInterval) -> DateComponents { + // var dateInfo = Calendar.current.dateComponents(in: TimeZone.current, from: Date()) + // dateInfo.calendar = Calendar.current + var dateInfo = DateComponents() + + if let year = at.year { + dateInfo.year = year + } + if let month = at.month { + dateInfo.month = month + } + if let day = at.day { + dateInfo.day = day + } + if let hour = at.hour { + dateInfo.hour = hour + } + if let minute = at.minute { + dateInfo.minute = minute + } + if let second = at.second { + dateInfo.second = second + } + if let weekday = at.weekday { + dateInfo.weekday = weekday + } + return dateInfo +} + +/// Compute the difference between the string representation of a date +/// interval and today. For example, if every is "month", then we +/// return the interval between today and a month from today. + +func getRepeatDateInterval(_ every: ScheduleEveryKind, _ count: Int) -> DateInterval? { + let cal = Calendar.current + let now = Date() + switch every { + case .year: + let newDate = cal.date(byAdding: .year, value: count, to: now)! + return DateInterval(start: now, end: newDate) + case .month: + let newDate = cal.date(byAdding: .month, value: count, to: now)! + return DateInterval(start: now, end: newDate) + case .twoWeeks: + let newDate = cal.date(byAdding: .weekOfYear, value: 2 * count, to: now)! + return DateInterval(start: now, end: newDate) + case .week: + let newDate = cal.date(byAdding: .weekOfYear, value: count, to: now)! + return DateInterval(start: now, end: newDate) + case .day: + let newDate = cal.date(byAdding: .day, value: count, to: now)! + return DateInterval(start: now, end: newDate) + case .hour: + let newDate = cal.date(byAdding: .hour, value: count, to: now)! + return DateInterval(start: now, end: newDate) + case .minute: + let newDate = cal.date(byAdding: .minute, value: count, to: now)! + return DateInterval(start: now, end: newDate) + case .second: + let newDate = cal.date(byAdding: .second, value: count, to: now)! + return DateInterval(start: now, end: newDate) + } +} diff --git a/packages/kbot/gui/app/plugins/notification/ios/Sources/NotificationCategory.swift b/packages/kbot/gui/app/plugins/notification/ios/Sources/NotificationCategory.swift new file mode 100644 index 00000000..f796e03a --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/ios/Sources/NotificationCategory.swift @@ -0,0 +1,97 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Tauri +import UserNotifications + +internal func makeCategories(_ actionTypes: [ActionType]) { + var createdCategories = [UNNotificationCategory]() + + let generalCategory = UNNotificationCategory( + identifier: "GENERAL", + actions: [], + intentIdentifiers: [], + options: .customDismissAction) + + createdCategories.append(generalCategory) + for type in actionTypes { + let newActions = makeActions(type.actions) + + // Create the custom actions for the TIMER_EXPIRED category. + var newCategory: UNNotificationCategory? + + newCategory = UNNotificationCategory( + identifier: type.id, + actions: newActions, + intentIdentifiers: [], + hiddenPreviewsBodyPlaceholder: type.hiddenBodyPlaceholder ?? "", + options: makeCategoryOptions(type)) + + createdCategories.append(newCategory!) + } + + let center = UNUserNotificationCenter.current() + center.setNotificationCategories(Set(createdCategories)) +} + +func makeActions(_ actions: [Action]) -> [UNNotificationAction] { + var createdActions = [UNNotificationAction]() + + for action in actions { + var newAction: UNNotificationAction + if action.input ?? false { + if action.inputButtonTitle != nil { + newAction = UNTextInputNotificationAction( + identifier: action.id, + title: action.title, + options: makeActionOptions(action), + textInputButtonTitle: action.inputButtonTitle ?? "", + textInputPlaceholder: action.inputPlaceholder ?? "") + } else { + newAction = UNTextInputNotificationAction( + identifier: action.id, title: action.title, options: makeActionOptions(action)) + } + } else { + // Create the custom actions for the TIMER_EXPIRED category. + newAction = UNNotificationAction( + identifier: action.id, + title: action.title, + options: makeActionOptions(action)) + } + createdActions.append(newAction) + } + + return createdActions +} + +func makeActionOptions(_ action: Action) -> UNNotificationActionOptions { + if action.foreground ?? false { + return .foreground + } + if action.destructive ?? false { + return .destructive + } + if action.requiresAuthentication ?? false { + return .authenticationRequired + } + return UNNotificationActionOptions(rawValue: 0) +} + +func makeCategoryOptions(_ type: ActionType) -> UNNotificationCategoryOptions { + if type.customDismissAction ?? false { + return .customDismissAction + } + if type.allowInCarPlay ?? false { + return .allowInCarPlay + } + + if type.hiddenPreviewsShowTitle ?? false { + return .hiddenPreviewsShowTitle + } + if type.hiddenPreviewsShowSubtitle ?? false { + return .hiddenPreviewsShowSubtitle + } + + return UNNotificationCategoryOptions(rawValue: 0) +} diff --git a/packages/kbot/gui/app/plugins/notification/ios/Sources/NotificationHandler.swift b/packages/kbot/gui/app/plugins/notification/ios/Sources/NotificationHandler.swift new file mode 100644 index 00000000..1bf134b6 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/ios/Sources/NotificationHandler.swift @@ -0,0 +1,118 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Tauri +import UserNotifications + +public class NotificationHandler: NSObject, NotificationHandlerProtocol { + + public weak var plugin: Plugin? + + private var notificationsMap = [String: Notification]() + + internal func saveNotification(_ key: String, _ notification: Notification) { + notificationsMap.updateValue(notification, forKey: key) + } + + public func requestPermissions(with completion: ((Bool, Error?) -> Void)? = nil) { + let center = UNUserNotificationCenter.current() + center.requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in + completion?(granted, error) + } + } + + public func checkPermissions(with completion: ((UNAuthorizationStatus) -> Void)? = nil) { + let center = UNUserNotificationCenter.current() + center.getNotificationSettings { settings in + completion?(settings.authorizationStatus) + } + } + + public func willPresent(notification: UNNotification) -> UNNotificationPresentationOptions { + let notificationData = toActiveNotification(notification.request) + try? self.plugin?.trigger("notification", data: notificationData) + + if let options = notificationsMap[notification.request.identifier] { + if options.silent ?? false { + return UNNotificationPresentationOptions.init(rawValue: 0) + } + } + + return [ + .badge, + .sound, + .alert, + ] + } + + public func didReceive(response: UNNotificationResponse) { + let originalNotificationRequest = response.notification.request + let actionId = response.actionIdentifier + + var actionIdValue: String + // We turn the two default actions (open/dismiss) into generic strings + if actionId == UNNotificationDefaultActionIdentifier { + actionIdValue = "tap" + } else if actionId == UNNotificationDismissActionIdentifier { + actionIdValue = "dismiss" + } else { + actionIdValue = actionId + } + + var inputValue: String? = nil + // If the type of action was for an input type, get the value + if let inputType = response as? UNTextInputNotificationResponse { + inputValue = inputType.userText + } + + try? self.plugin?.trigger( + "actionPerformed", + data: ReceivedNotification( + actionId: actionIdValue, + inputValue: inputValue, + notification: toActiveNotification(originalNotificationRequest) + )) + } + + func toActiveNotification(_ request: UNNotificationRequest) -> ActiveNotification { + let notificationRequest = notificationsMap[request.identifier]! + return ActiveNotification( + id: Int(request.identifier) ?? -1, + title: request.content.title, + body: request.content.body, + sound: notificationRequest.sound ?? "", + actionTypeId: request.content.categoryIdentifier, + attachments: notificationRequest.attachments + ) + } + + func toPendingNotification(_ request: UNNotificationRequest) -> PendingNotification { + return PendingNotification( + id: Int(request.identifier) ?? -1, + title: request.content.title, + body: request.content.body + ) + } +} + +struct PendingNotification: Encodable { + let id: Int + let title: String + let body: String +} + +struct ActiveNotification: Encodable { + let id: Int + let title: String + let body: String + let sound: String + let actionTypeId: String + let attachments: [NotificationAttachment]? +} + +struct ReceivedNotification: Encodable { + let actionId: String + let inputValue: String? + let notification: ActiveNotification +} diff --git a/packages/kbot/gui/app/plugins/notification/ios/Sources/NotificationManager.swift b/packages/kbot/gui/app/plugins/notification/ios/Sources/NotificationManager.swift new file mode 100644 index 00000000..81a585bf --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/ios/Sources/NotificationManager.swift @@ -0,0 +1,47 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation +import UserNotifications + +@objc public protocol NotificationHandlerProtocol { + func willPresent(notification: UNNotification) -> UNNotificationPresentationOptions + func didReceive(response: UNNotificationResponse) +} + +@objc public class NotificationManager: NSObject, UNUserNotificationCenterDelegate { + public weak var notificationHandler: NotificationHandlerProtocol? + + override init() { + super.init() + let center = UNUserNotificationCenter.current() + center.delegate = self + } + + public func userNotificationCenter( + _ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void + ) { + var presentationOptions: UNNotificationPresentationOptions? = nil + + if notification.request.trigger?.isKind(of: UNPushNotificationTrigger.self) != true { + presentationOptions = notificationHandler?.willPresent(notification: notification) + } + + completionHandler(presentationOptions ?? []) + } + + public func userNotificationCenter( + _ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void + ) { + if response.notification.request.trigger?.isKind(of: UNPushNotificationTrigger.self) != true { + notificationHandler?.didReceive(response: response) + } + + completionHandler() + } +} diff --git a/packages/kbot/gui/app/plugins/notification/ios/Sources/NotificationPlugin.swift b/packages/kbot/gui/app/plugins/notification/ios/Sources/NotificationPlugin.swift new file mode 100644 index 00000000..6d8391bc --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/ios/Sources/NotificationPlugin.swift @@ -0,0 +1,285 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import SwiftRs +import Tauri +import UIKit +import UserNotifications +import WebKit + +enum ShowNotificationError: LocalizedError { + case make(Error) + case create(Error) + + var errorDescription: String? { + switch self { + case .make(let error): + return "Unable to make notification: \(error)" + case .create(let error): + return "Unable to create notification: \(error)" + } + } +} + +enum ScheduleEveryKind: String, Decodable { + case year + case month + case twoWeeks + case week + case day + case hour + case minute + case second +} + +struct ScheduleInterval: Decodable { + var year: Int? + var month: Int? + var day: Int? + var weekday: Int? + var hour: Int? + var minute: Int? + var second: Int? +} + +enum NotificationSchedule: Decodable { + case at(date: String, repeating: Bool) + case interval(interval: ScheduleInterval) + case every(interval: ScheduleEveryKind, count: Int) +} + +struct NotificationAttachmentOptions: Codable { + let iosUNNotificationAttachmentOptionsTypeHintKey: String? + let iosUNNotificationAttachmentOptionsThumbnailHiddenKey: String? + let iosUNNotificationAttachmentOptionsThumbnailClippingRectKey: String? + let iosUNNotificationAttachmentOptionsThumbnailTimeKey: String? +} + +struct NotificationAttachment: Codable { + let id: String + let url: String + let options: NotificationAttachmentOptions? +} + +struct Notification: Decodable { + let id: Int + var title: String + var body: String? + var extra: [String: String]? + var schedule: NotificationSchedule? + var attachments: [NotificationAttachment]? + var sound: String? + var group: String? + var actionTypeId: String? + var summary: String? + var silent: Bool? +} + +struct RemoveActiveNotification: Decodable { + let id: Int +} + +struct RemoveActiveArgs: Decodable { + let notifications: [RemoveActiveNotification] +} + +func showNotification(invoke: Invoke, notification: Notification) + throws -> UNNotificationRequest +{ + var content: UNNotificationContent + do { + content = try makeNotificationContent(notification) + } catch { + throw ShowNotificationError.make(error) + } + + var trigger: UNNotificationTrigger? + + do { + if let schedule = notification.schedule { + try trigger = handleScheduledNotification(schedule) + } + } catch { + throw ShowNotificationError.create(error) + } + + // Schedule the request. + let request = UNNotificationRequest( + identifier: "\(notification.id)", content: content, trigger: trigger + ) + + let center = UNUserNotificationCenter.current() + center.add(request) { (error: Error?) in + if let theError = error { + invoke.reject(theError.localizedDescription) + } + } + + return request +} + +struct CancelArgs: Decodable { + let notifications: [Int] +} + +struct Action: Decodable { + let id: String + let title: String + var requiresAuthentication: Bool? + var foreground: Bool? + var destructive: Bool? + var input: Bool? + var inputButtonTitle: String? + var inputPlaceholder: String? +} + +struct ActionType: Decodable { + let id: String + let actions: [Action] + var hiddenPreviewsBodyPlaceholder: String? + var customDismissAction: Bool? + var allowInCarPlay: Bool? + var hiddenPreviewsShowTitle: Bool? + var hiddenPreviewsShowSubtitle: Bool? + var hiddenBodyPlaceholder: String? +} + +struct RegisterActionTypesArgs: Decodable { + let types: [ActionType] +} + +struct BatchArgs: Decodable { + let notifications: [Notification] +} + +class NotificationPlugin: Plugin { + let notificationHandler = NotificationHandler() + let notificationManager = NotificationManager() + + override init() { + super.init() + notificationManager.notificationHandler = notificationHandler + notificationHandler.plugin = self + } + + @objc public func show(_ invoke: Invoke) throws { + let notification = try invoke.parseArgs(Notification.self) + + let request = try showNotification(invoke: invoke, notification: notification) + notificationHandler.saveNotification(request.identifier, notification) + invoke.resolve(Int(request.identifier) ?? -1) + } + + @objc public func batch(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(BatchArgs.self) + var ids = [Int]() + + for notification in args.notifications { + let request = try showNotification(invoke: invoke, notification: notification) + notificationHandler.saveNotification(request.identifier, notification) + ids.append(Int(request.identifier) ?? -1) + } + + invoke.resolve(ids) + } + + @objc public override func requestPermissions(_ invoke: Invoke) { + notificationHandler.requestPermissions { granted, error in + guard error == nil else { + invoke.reject(error!.localizedDescription) + return + } + invoke.resolve(["permissionState": granted ? "granted" : "denied"]) + } + } + + @objc public override func checkPermissions(_ invoke: Invoke) { + notificationHandler.checkPermissions { status in + let permission: String + + switch status { + case .authorized, .ephemeral, .provisional: + permission = "granted" + case .denied: + permission = "denied" + case .notDetermined: + permission = "prompt" + @unknown default: + permission = "prompt" + } + + invoke.resolve(["permissionState": permission]) + } + } + + @objc func cancel(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(CancelArgs.self) + + UNUserNotificationCenter.current().removePendingNotificationRequests( + withIdentifiers: args.notifications.map { String($0) } + ) + invoke.resolve() + } + + @objc func getPending(_ invoke: Invoke) { + UNUserNotificationCenter.current().getPendingNotificationRequests(completionHandler: { + (notifications) in + let ret = notifications.compactMap({ [weak self] (notification) -> PendingNotification? in + return self?.notificationHandler.toPendingNotification(notification) + }) + + invoke.resolve(ret) + }) + } + + @objc func registerActionTypes(_ invoke: Invoke) throws { + let args = try invoke.parseArgs(RegisterActionTypesArgs.self) + makeCategories(args.types) + invoke.resolve() + } + + @objc func removeActive(_ invoke: Invoke) { + do { + let args = try invoke.parseArgs(RemoveActiveArgs.self) + UNUserNotificationCenter.current().removeDeliveredNotifications( + withIdentifiers: args.notifications.map { String($0.id) }) + invoke.resolve() + } catch { + UNUserNotificationCenter.current().removeAllDeliveredNotifications() + DispatchQueue.main.async(execute: { + UIApplication.shared.applicationIconBadgeNumber = 0 + }) + invoke.resolve() + } + } + + @objc func getActive(_ invoke: Invoke) { + UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { + (notifications) in + let ret = notifications.map({ (notification) -> ActiveNotification in + return self.notificationHandler.toActiveNotification( + notification.request) + }) + invoke.resolve(ret) + }) + } + + @objc func createChannel(_ invoke: Invoke) { + invoke.reject("not implemented") + } + + @objc func deleteChannel(_ invoke: Invoke) { + invoke.reject("not implemented") + } + + @objc func listChannels(_ invoke: Invoke) { + invoke.reject("not implemented") + } + +} + +@_cdecl("init_plugin_notification") +func initPlugin() -> Plugin { + return NotificationPlugin() +} diff --git a/packages/kbot/gui/app/plugins/notification/ios/Tests/PluginTests/PluginTests.swift b/packages/kbot/gui/app/plugins/notification/ios/Tests/PluginTests/PluginTests.swift new file mode 100644 index 00000000..99992ce4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/ios/Tests/PluginTests/PluginTests.swift @@ -0,0 +1,12 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import XCTest +@testable import ExamplePlugin + +final class ExamplePluginTests: XCTestCase { + func testExample() throws { + let plugin = ExamplePlugin() + } +} diff --git a/packages/kbot/gui/app/plugins/notification/package.json b/packages/kbot/gui/app/plugins/notification/package.json new file mode 100644 index 00000000..52a7ac60 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/package.json @@ -0,0 +1,29 @@ +{ + "name": "@tauri-apps/plugin-notification", + "version": "2.3.1", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/batch.toml b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/batch.toml new file mode 100644 index 00000000..c52cc16d --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/batch.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-batch" +description = "Enables the batch command without any pre-configured scope." +commands.allow = ["batch"] + +[[permission]] +identifier = "deny-batch" +description = "Denies the batch command without any pre-configured scope." +commands.deny = ["batch"] diff --git a/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/cancel.toml b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/cancel.toml new file mode 100644 index 00000000..91efeaa0 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/cancel.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-cancel" +description = "Enables the cancel command without any pre-configured scope." +commands.allow = ["cancel"] + +[[permission]] +identifier = "deny-cancel" +description = "Denies the cancel command without any pre-configured scope." +commands.deny = ["cancel"] diff --git a/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/check_permissions.toml b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/check_permissions.toml new file mode 100644 index 00000000..f5af08b1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/check_permissions.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-check-permissions" +description = "Enables the check_permissions command without any pre-configured scope." +commands.allow = ["check_permissions"] + +[[permission]] +identifier = "deny-check-permissions" +description = "Denies the check_permissions command without any pre-configured scope." +commands.deny = ["check_permissions"] diff --git a/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/create_channel.toml b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/create_channel.toml new file mode 100644 index 00000000..2c931474 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/create_channel.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-create-channel" +description = "Enables the create_channel command without any pre-configured scope." +commands.allow = ["create_channel"] + +[[permission]] +identifier = "deny-create-channel" +description = "Denies the create_channel command without any pre-configured scope." +commands.deny = ["create_channel"] diff --git a/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/delete_channel.toml b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/delete_channel.toml new file mode 100644 index 00000000..0adaf2bb --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/delete_channel.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-delete-channel" +description = "Enables the delete_channel command without any pre-configured scope." +commands.allow = ["delete_channel"] + +[[permission]] +identifier = "deny-delete-channel" +description = "Denies the delete_channel command without any pre-configured scope." +commands.deny = ["delete_channel"] diff --git a/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/get_active.toml b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/get_active.toml new file mode 100644 index 00000000..b841eb85 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/get_active.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-get-active" +description = "Enables the get_active command without any pre-configured scope." +commands.allow = ["get_active"] + +[[permission]] +identifier = "deny-get-active" +description = "Denies the get_active command without any pre-configured scope." +commands.deny = ["get_active"] diff --git a/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/get_pending.toml b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/get_pending.toml new file mode 100644 index 00000000..f3bae7a8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/get_pending.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-get-pending" +description = "Enables the get_pending command without any pre-configured scope." +commands.allow = ["get_pending"] + +[[permission]] +identifier = "deny-get-pending" +description = "Denies the get_pending command without any pre-configured scope." +commands.deny = ["get_pending"] diff --git a/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/is_permission_granted.toml b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/is_permission_granted.toml new file mode 100644 index 00000000..5faa73f3 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/is_permission_granted.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-is-permission-granted" +description = "Enables the is_permission_granted command without any pre-configured scope." +commands.allow = ["is_permission_granted"] + +[[permission]] +identifier = "deny-is-permission-granted" +description = "Denies the is_permission_granted command without any pre-configured scope." +commands.deny = ["is_permission_granted"] diff --git a/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/list_channels.toml b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/list_channels.toml new file mode 100644 index 00000000..cb20cd57 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/list_channels.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-list-channels" +description = "Enables the list_channels command without any pre-configured scope." +commands.allow = ["list_channels"] + +[[permission]] +identifier = "deny-list-channels" +description = "Denies the list_channels command without any pre-configured scope." +commands.deny = ["list_channels"] diff --git a/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/notify.toml b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/notify.toml new file mode 100644 index 00000000..7d6d46c2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/notify.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-notify" +description = "Enables the notify command without any pre-configured scope." +commands.allow = ["notify"] + +[[permission]] +identifier = "deny-notify" +description = "Denies the notify command without any pre-configured scope." +commands.deny = ["notify"] diff --git a/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/permission_state.toml b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/permission_state.toml new file mode 100644 index 00000000..dddcd86f --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/permission_state.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-permission-state" +description = "Enables the permission_state command without any pre-configured scope." +commands.allow = ["permission_state"] + +[[permission]] +identifier = "deny-permission-state" +description = "Denies the permission_state command without any pre-configured scope." +commands.deny = ["permission_state"] diff --git a/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/register_action_types.toml b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/register_action_types.toml new file mode 100644 index 00000000..cb5aa89f --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/register_action_types.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-register-action-types" +description = "Enables the register_action_types command without any pre-configured scope." +commands.allow = ["register_action_types"] + +[[permission]] +identifier = "deny-register-action-types" +description = "Denies the register_action_types command without any pre-configured scope." +commands.deny = ["register_action_types"] diff --git a/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/register_listener.toml b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/register_listener.toml new file mode 100644 index 00000000..48363c0d --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/register_listener.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-register-listener" +description = "Enables the register_listener command without any pre-configured scope." +commands.allow = ["register_listener"] + +[[permission]] +identifier = "deny-register-listener" +description = "Denies the register_listener command without any pre-configured scope." +commands.deny = ["register_listener"] diff --git a/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/remove_active.toml b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/remove_active.toml new file mode 100644 index 00000000..9ad2add1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/remove_active.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-remove-active" +description = "Enables the remove_active command without any pre-configured scope." +commands.allow = ["remove_active"] + +[[permission]] +identifier = "deny-remove-active" +description = "Denies the remove_active command without any pre-configured scope." +commands.deny = ["remove_active"] diff --git a/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/request_permission.toml b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/request_permission.toml new file mode 100644 index 00000000..0b410d6a --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/request_permission.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-request-permission" +description = "Enables the request_permission command without any pre-configured scope." +commands.allow = ["request_permission"] + +[[permission]] +identifier = "deny-request-permission" +description = "Denies the request_permission command without any pre-configured scope." +commands.deny = ["request_permission"] diff --git a/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/show.toml b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/show.toml new file mode 100644 index 00000000..3d4cbf38 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/commands/show.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-show" +description = "Enables the show command without any pre-configured scope." +commands.allow = ["show"] + +[[permission]] +identifier = "deny-show" +description = "Denies the show command without any pre-configured scope." +commands.deny = ["show"] diff --git a/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/reference.md new file mode 100644 index 00000000..b0652545 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/autogenerated/reference.md @@ -0,0 +1,453 @@ +## Default Permission + +This permission set configures which +notification features are by default exposed. + +#### Granted Permissions + +It allows all notification related features. + +#### This default permission set includes the following: + +- `allow-is-permission-granted` +- `allow-request-permission` +- `allow-notify` +- `allow-register-action-types` +- `allow-register-listener` +- `allow-cancel` +- `allow-get-pending` +- `allow-remove-active` +- `allow-get-active` +- `allow-check-permissions` +- `allow-show` +- `allow-batch` +- `allow-list-channels` +- `allow-delete-channel` +- `allow-create-channel` +- `allow-permission-state` + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`notification:allow-batch` + + + +Enables the batch command without any pre-configured scope. + +
+ +`notification:deny-batch` + + + +Denies the batch command without any pre-configured scope. + +
+ +`notification:allow-cancel` + + + +Enables the cancel command without any pre-configured scope. + +
+ +`notification:deny-cancel` + + + +Denies the cancel command without any pre-configured scope. + +
+ +`notification:allow-check-permissions` + + + +Enables the check_permissions command without any pre-configured scope. + +
+ +`notification:deny-check-permissions` + + + +Denies the check_permissions command without any pre-configured scope. + +
+ +`notification:allow-create-channel` + + + +Enables the create_channel command without any pre-configured scope. + +
+ +`notification:deny-create-channel` + + + +Denies the create_channel command without any pre-configured scope. + +
+ +`notification:allow-delete-channel` + + + +Enables the delete_channel command without any pre-configured scope. + +
+ +`notification:deny-delete-channel` + + + +Denies the delete_channel command without any pre-configured scope. + +
+ +`notification:allow-get-active` + + + +Enables the get_active command without any pre-configured scope. + +
+ +`notification:deny-get-active` + + + +Denies the get_active command without any pre-configured scope. + +
+ +`notification:allow-get-pending` + + + +Enables the get_pending command without any pre-configured scope. + +
+ +`notification:deny-get-pending` + + + +Denies the get_pending command without any pre-configured scope. + +
+ +`notification:allow-is-permission-granted` + + + +Enables the is_permission_granted command without any pre-configured scope. + +
+ +`notification:deny-is-permission-granted` + + + +Denies the is_permission_granted command without any pre-configured scope. + +
+ +`notification:allow-list-channels` + + + +Enables the list_channels command without any pre-configured scope. + +
+ +`notification:deny-list-channels` + + + +Denies the list_channels command without any pre-configured scope. + +
+ +`notification:allow-notify` + + + +Enables the notify command without any pre-configured scope. + +
+ +`notification:deny-notify` + + + +Denies the notify command without any pre-configured scope. + +
+ +`notification:allow-permission-state` + + + +Enables the permission_state command without any pre-configured scope. + +
+ +`notification:deny-permission-state` + + + +Denies the permission_state command without any pre-configured scope. + +
+ +`notification:allow-register-action-types` + + + +Enables the register_action_types command without any pre-configured scope. + +
+ +`notification:deny-register-action-types` + + + +Denies the register_action_types command without any pre-configured scope. + +
+ +`notification:allow-register-listener` + + + +Enables the register_listener command without any pre-configured scope. + +
+ +`notification:deny-register-listener` + + + +Denies the register_listener command without any pre-configured scope. + +
+ +`notification:allow-remove-active` + + + +Enables the remove_active command without any pre-configured scope. + +
+ +`notification:deny-remove-active` + + + +Denies the remove_active command without any pre-configured scope. + +
+ +`notification:allow-request-permission` + + + +Enables the request_permission command without any pre-configured scope. + +
+ +`notification:deny-request-permission` + + + +Denies the request_permission command without any pre-configured scope. + +
+ +`notification:allow-show` + + + +Enables the show command without any pre-configured scope. + +
+ +`notification:deny-show` + + + +Denies the show command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/notification/permissions/default.toml b/packages/kbot/gui/app/plugins/notification/permissions/default.toml new file mode 100644 index 00000000..00b4e1d0 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/default.toml @@ -0,0 +1,30 @@ +"$schema" = "schemas/schema.json" +[default] +description = """ +This permission set configures which +notification features are by default exposed. + +#### Granted Permissions + +It allows all notification related features. + +""" + +permissions = [ + "allow-is-permission-granted", + "allow-request-permission", + "allow-notify", + "allow-register-action-types", + "allow-register-listener", + "allow-cancel", + "allow-get-pending", + "allow-remove-active", + "allow-get-active", + "allow-check-permissions", + "allow-show", + "allow-batch", + "allow-list-channels", + "allow-delete-channel", + "allow-create-channel", + "allow-permission-state", +] diff --git a/packages/kbot/gui/app/plugins/notification/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/notification/permissions/schemas/schema.json new file mode 100644 index 00000000..26703a8a --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/permissions/schemas/schema.json @@ -0,0 +1,498 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the batch command without any pre-configured scope.", + "type": "string", + "const": "allow-batch", + "markdownDescription": "Enables the batch command without any pre-configured scope." + }, + { + "description": "Denies the batch command without any pre-configured scope.", + "type": "string", + "const": "deny-batch", + "markdownDescription": "Denies the batch command without any pre-configured scope." + }, + { + "description": "Enables the cancel command without any pre-configured scope.", + "type": "string", + "const": "allow-cancel", + "markdownDescription": "Enables the cancel command without any pre-configured scope." + }, + { + "description": "Denies the cancel command without any pre-configured scope.", + "type": "string", + "const": "deny-cancel", + "markdownDescription": "Denies the cancel command without any pre-configured scope." + }, + { + "description": "Enables the check_permissions command without any pre-configured scope.", + "type": "string", + "const": "allow-check-permissions", + "markdownDescription": "Enables the check_permissions command without any pre-configured scope." + }, + { + "description": "Denies the check_permissions command without any pre-configured scope.", + "type": "string", + "const": "deny-check-permissions", + "markdownDescription": "Denies the check_permissions command without any pre-configured scope." + }, + { + "description": "Enables the create_channel command without any pre-configured scope.", + "type": "string", + "const": "allow-create-channel", + "markdownDescription": "Enables the create_channel command without any pre-configured scope." + }, + { + "description": "Denies the create_channel command without any pre-configured scope.", + "type": "string", + "const": "deny-create-channel", + "markdownDescription": "Denies the create_channel command without any pre-configured scope." + }, + { + "description": "Enables the delete_channel command without any pre-configured scope.", + "type": "string", + "const": "allow-delete-channel", + "markdownDescription": "Enables the delete_channel command without any pre-configured scope." + }, + { + "description": "Denies the delete_channel command without any pre-configured scope.", + "type": "string", + "const": "deny-delete-channel", + "markdownDescription": "Denies the delete_channel command without any pre-configured scope." + }, + { + "description": "Enables the get_active command without any pre-configured scope.", + "type": "string", + "const": "allow-get-active", + "markdownDescription": "Enables the get_active command without any pre-configured scope." + }, + { + "description": "Denies the get_active command without any pre-configured scope.", + "type": "string", + "const": "deny-get-active", + "markdownDescription": "Denies the get_active command without any pre-configured scope." + }, + { + "description": "Enables the get_pending command without any pre-configured scope.", + "type": "string", + "const": "allow-get-pending", + "markdownDescription": "Enables the get_pending command without any pre-configured scope." + }, + { + "description": "Denies the get_pending command without any pre-configured scope.", + "type": "string", + "const": "deny-get-pending", + "markdownDescription": "Denies the get_pending command without any pre-configured scope." + }, + { + "description": "Enables the is_permission_granted command without any pre-configured scope.", + "type": "string", + "const": "allow-is-permission-granted", + "markdownDescription": "Enables the is_permission_granted command without any pre-configured scope." + }, + { + "description": "Denies the is_permission_granted command without any pre-configured scope.", + "type": "string", + "const": "deny-is-permission-granted", + "markdownDescription": "Denies the is_permission_granted command without any pre-configured scope." + }, + { + "description": "Enables the list_channels command without any pre-configured scope.", + "type": "string", + "const": "allow-list-channels", + "markdownDescription": "Enables the list_channels command without any pre-configured scope." + }, + { + "description": "Denies the list_channels command without any pre-configured scope.", + "type": "string", + "const": "deny-list-channels", + "markdownDescription": "Denies the list_channels command without any pre-configured scope." + }, + { + "description": "Enables the notify command without any pre-configured scope.", + "type": "string", + "const": "allow-notify", + "markdownDescription": "Enables the notify command without any pre-configured scope." + }, + { + "description": "Denies the notify command without any pre-configured scope.", + "type": "string", + "const": "deny-notify", + "markdownDescription": "Denies the notify command without any pre-configured scope." + }, + { + "description": "Enables the permission_state command without any pre-configured scope.", + "type": "string", + "const": "allow-permission-state", + "markdownDescription": "Enables the permission_state command without any pre-configured scope." + }, + { + "description": "Denies the permission_state command without any pre-configured scope.", + "type": "string", + "const": "deny-permission-state", + "markdownDescription": "Denies the permission_state command without any pre-configured scope." + }, + { + "description": "Enables the register_action_types command without any pre-configured scope.", + "type": "string", + "const": "allow-register-action-types", + "markdownDescription": "Enables the register_action_types command without any pre-configured scope." + }, + { + "description": "Denies the register_action_types command without any pre-configured scope.", + "type": "string", + "const": "deny-register-action-types", + "markdownDescription": "Denies the register_action_types command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_active command without any pre-configured scope.", + "type": "string", + "const": "allow-remove-active", + "markdownDescription": "Enables the remove_active command without any pre-configured scope." + }, + { + "description": "Denies the remove_active command without any pre-configured scope.", + "type": "string", + "const": "deny-remove-active", + "markdownDescription": "Denies the remove_active command without any pre-configured scope." + }, + { + "description": "Enables the request_permission command without any pre-configured scope.", + "type": "string", + "const": "allow-request-permission", + "markdownDescription": "Enables the request_permission command without any pre-configured scope." + }, + { + "description": "Denies the request_permission command without any pre-configured scope.", + "type": "string", + "const": "deny-request-permission", + "markdownDescription": "Denies the request_permission command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`", + "type": "string", + "const": "default", + "markdownDescription": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/notification/rollup.config.js b/packages/kbot/gui/app/plugins/notification/rollup.config.js new file mode 100644 index 00000000..a7dbd4f6 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/rollup.config.js @@ -0,0 +1,22 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' +import { nodeResolve } from '@rollup/plugin-node-resolve' +import typescript from '@rollup/plugin-typescript' +import terser from '@rollup/plugin-terser' + +export default createConfig({ + additionalConfigs: { + input: 'guest-js/init.ts', + output: { + file: 'src/init-iife.js', + format: 'iife' + }, + plugins: [typescript(), terser(), nodeResolve()], + onwarn: (warning) => { + throw Object.assign(new Error(), warning) + } + } +}) diff --git a/packages/kbot/gui/app/plugins/notification/src/commands.rs b/packages/kbot/gui/app/plugins/notification/src/commands.rs new file mode 100644 index 00000000..99b96c5b --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/src/commands.rs @@ -0,0 +1,39 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tauri::{command, plugin::PermissionState, AppHandle, Runtime, State}; + +use crate::{Notification, NotificationData, Result}; + +#[command] +pub(crate) async fn is_permission_granted( + _app: AppHandle, + notification: State<'_, Notification>, +) -> Result> { + let state = notification.permission_state()?; + match state { + PermissionState::Granted => Ok(Some(true)), + PermissionState::Denied => Ok(Some(false)), + PermissionState::Prompt | PermissionState::PromptWithRationale => Ok(None), + } +} + +#[command] +pub(crate) async fn request_permission( + _app: AppHandle, + notification: State<'_, Notification>, +) -> Result { + notification.request_permission() +} + +#[command] +pub(crate) async fn notify( + _app: AppHandle, + notification: State<'_, Notification>, + options: NotificationData, +) -> Result<()> { + let mut builder = notification.builder(); + builder.data = options; + builder.show() +} diff --git a/packages/kbot/gui/app/plugins/notification/src/desktop.rs b/packages/kbot/gui/app/plugins/notification/src/desktop.rs new file mode 100644 index 00000000..4ceb8308 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/src/desktop.rs @@ -0,0 +1,289 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::de::DeserializeOwned; +use tauri::{ + plugin::{PermissionState, PluginApi}, + AppHandle, Runtime, +}; + +use crate::NotificationBuilder; + +pub fn init( + app: &AppHandle, + _api: PluginApi, +) -> crate::Result> { + Ok(Notification(app.clone())) +} + +/// Access to the notification APIs. +/// +/// You can get an instance of this type via [`NotificationExt`](crate::NotificationExt) +pub struct Notification(AppHandle); + +impl crate::NotificationBuilder { + pub fn show(self) -> crate::Result<()> { + let mut notification = imp::Notification::new(self.app.config().identifier.clone()); + + if let Some(title) = self + .data + .title + .or_else(|| self.app.config().product_name.clone()) + { + notification = notification.title(title); + } + if let Some(body) = self.data.body { + notification = notification.body(body); + } + if let Some(icon) = self.data.icon { + notification = notification.icon(icon); + } + if let Some(sound) = self.data.sound { + notification = notification.sound(sound); + } + #[cfg(feature = "windows7-compat")] + { + notification.notify(&self.app)?; + } + #[cfg(not(feature = "windows7-compat"))] + notification.show()?; + + Ok(()) + } +} + +impl Notification { + pub fn builder(&self) -> NotificationBuilder { + NotificationBuilder::new(self.0.clone()) + } + + pub fn request_permission(&self) -> crate::Result { + Ok(PermissionState::Granted) + } + + pub fn permission_state(&self) -> crate::Result { + Ok(PermissionState::Granted) + } +} + +mod imp { + //! Types and functions related to desktop notifications. + + #[cfg(windows)] + use std::path::MAIN_SEPARATOR as SEP; + + /// The desktop notification definition. + /// + /// Allows you to construct a Notification data and send it. + /// + /// # Examples + /// ```rust,no_run + /// use tauri_plugin_notification::NotificationExt; + /// // first we build the application to access the Tauri configuration + /// let app = tauri::Builder::default() + /// // on an actual app, remove the string argument + /// .build(tauri::generate_context!("test/tauri.conf.json")) + /// .expect("error while building tauri application"); + /// + /// // shows a notification with the given title and body + /// app.notification() + /// .builder() + /// .title("New message") + /// .body("You've got a new message.") + /// .show(); + /// + /// // run the app + /// app.run(|_app_handle, _event| {}); + /// ``` + #[allow(dead_code)] + #[derive(Debug, Default)] + pub struct Notification { + /// The notification body. + body: Option, + /// The notification title. + title: Option, + /// The notification icon. + icon: Option, + /// The notification sound. + sound: Option, + /// The notification identifier + identifier: String, + } + + impl Notification { + /// Initializes a instance of a Notification. + pub fn new(identifier: impl Into) -> Self { + Self { + identifier: identifier.into(), + ..Default::default() + } + } + + /// Sets the notification body. + #[must_use] + pub fn body(mut self, body: impl Into) -> Self { + self.body = Some(body.into()); + self + } + + /// Sets the notification title. + #[must_use] + pub fn title(mut self, title: impl Into) -> Self { + self.title = Some(title.into()); + self + } + + /// Sets the notification icon. + #[must_use] + pub fn icon(mut self, icon: impl Into) -> Self { + self.icon = Some(icon.into()); + self + } + + /// Sets the notification sound file. + #[must_use] + pub fn sound(mut self, sound: impl Into) -> Self { + self.sound = Some(sound.into()); + self + } + + /// Shows the notification. + /// + /// # Examples + /// + /// ```no_run + /// use tauri_plugin_notification::NotificationExt; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// app.notification() + /// .builder() + /// .title("Tauri") + /// .body("Tauri is awesome!") + /// .show() + /// .unwrap(); + /// Ok(()) + /// }) + /// .run(tauri::generate_context!("test/tauri.conf.json")) + /// .expect("error while running tauri application"); + /// ``` + /// + /// ## Platform-specific + /// + /// - **Windows**: Not supported on Windows 7. If your app targets it, enable the `windows7-compat` feature and use [`Self::notify`]. + #[cfg_attr( + all(not(docsrs), feature = "windows7-compat"), + deprecated = "This function does not work on Windows 7. Use `Self::notify` instead." + )] + pub fn show(self) -> crate::Result<()> { + let mut notification = notify_rust::Notification::new(); + if let Some(body) = self.body { + notification.body(&body); + } + if let Some(title) = self.title { + notification.summary(&title); + } + if let Some(icon) = self.icon { + notification.icon(&icon); + } else { + notification.auto_icon(); + } + if let Some(sound) = self.sound { + notification.sound_name(&sound); + } + #[cfg(windows)] + { + let exe = tauri::utils::platform::current_exe()?; + let exe_dir = exe.parent().expect("failed to get exe directory"); + let curr_dir = exe_dir.display().to_string(); + // set the notification's System.AppUserModel.ID only when running the installed app + if !(curr_dir.ends_with(format!("{SEP}target{SEP}debug").as_str()) + || curr_dir.ends_with(format!("{SEP}target{SEP}release").as_str())) + { + notification.app_id(&self.identifier); + } + } + #[cfg(target_os = "macos")] + { + let _ = notify_rust::set_application(if tauri::is_dev() { + "com.apple.Terminal" + } else { + &self.identifier + }); + } + + tauri::async_runtime::spawn(async move { + let _ = notification.show(); + }); + + Ok(()) + } + + /// Shows the notification. This API is similar to [`Self::show`], but it also works on Windows 7. + /// + /// # Examples + /// + /// ```no_run + /// use tauri_plugin_notification::NotificationExt; + /// + /// tauri::Builder::default() + /// .setup(move |app| { + /// app.notification().builder() + /// .title("Tauri") + /// .body("Tauri is awesome!") + /// .show() + /// .unwrap(); + /// Ok(()) + /// }) + /// .run(tauri::generate_context!("test/tauri.conf.json")) + /// .expect("error while running tauri application"); + /// ``` + #[cfg(feature = "windows7-compat")] + #[cfg_attr(docsrs, doc(cfg(feature = "windows7-compat")))] + #[allow(unused_variables)] + pub fn notify(self, app: &tauri::AppHandle) -> crate::Result<()> { + #[cfg(windows)] + { + fn is_windows_7() -> bool { + let v = windows_version::OsVersion::current(); + // windows 7 is 6.1 + v.major == 6 && v.minor == 1 + } + + if is_windows_7() { + self.notify_win7(app) + } else { + #[allow(deprecated)] + self.show() + } + } + #[cfg(not(windows))] + { + #[allow(deprecated)] + self.show() + } + } + + /// Shows the notification on Windows 7. + #[cfg(all(windows, feature = "windows7-compat"))] + fn notify_win7(self, app: &tauri::AppHandle) -> crate::Result<()> { + let app_ = app.clone(); + let _ = app.clone().run_on_main_thread(move || { + let mut notification = win7_notifications::Notification::new(); + if let Some(body) = self.body { + notification.body(&body); + } + if let Some(title) = self.title { + notification.summary(&title); + } + if let Some(icon) = app_.default_window_icon() { + notification.icon(icon.rgba().to_vec(), icon.width(), icon.height()); + } + let _ = notification.show(); + }); + + Ok(()) + } + } +} diff --git a/packages/kbot/gui/app/plugins/notification/src/error.rs b/packages/kbot/gui/app/plugins/notification/src/error.rs new file mode 100644 index 00000000..339e763b --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/src/error.rs @@ -0,0 +1,25 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{ser::Serializer, Serialize}; + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/packages/kbot/gui/app/plugins/notification/src/init-iife.js b/packages/kbot/gui/app/plugins/notification/src/init-iife.js new file mode 100644 index 00000000..30bff97d --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/src/init-iife.js @@ -0,0 +1 @@ +!function(){"use strict";async function i(i,n={},t){return window.__TAURI_INTERNALS__.invoke(i,n,t)}"function"==typeof SuppressedError&&SuppressedError,function(){let n=!1,t="default";function o(i){n=!0,window.Notification.permission=i,n=!1}window.Notification=function(n,t){const o=t||{};!async function(n){"object"==typeof n&&Object.freeze(n),await i("plugin:notification|notify",{options:"string"==typeof n?{title:n}:n})}(Object.assign(o,{title:n}))},window.Notification.requestPermission=async function(){return await i("plugin:notification|request_permission").then((i=>(o("prompt"===i||"prompt-with-rationale"===i?"default":i),i)))},Object.defineProperty(window.Notification,"permission",{enumerable:!0,get:()=>t,set:i=>{if(!n)throw new Error("Readonly property");t=i}}),async function(){return"default"!==window.Notification.permission||__TEMPLATE_windows__?await Promise.resolve("granted"===window.Notification.permission):await i("plugin:notification|is_permission_granted")}().then((function(i){o(null===i?"default":i?"granted":"denied")}))}()}(); diff --git a/packages/kbot/gui/app/plugins/notification/src/lib.rs b/packages/kbot/gui/app/plugins/notification/src/lib.rs new file mode 100644 index 00000000..8b79c873 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/src/lib.rs @@ -0,0 +1,242 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Send message notifications (brief auto-expiring OS window element) to your user. Can also be used with the Notification Web API. + +#![doc( + html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", + html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" +)] + +use serde::Serialize; +#[cfg(mobile)] +use tauri::plugin::PluginHandle; +#[cfg(desktop)] +use tauri::AppHandle; +use tauri::{ + plugin::{Builder, TauriPlugin}, + Manager, Runtime, +}; + +pub use models::*; +pub use tauri::plugin::PermissionState; + +#[cfg(desktop)] +mod desktop; +#[cfg(mobile)] +mod mobile; + +mod commands; +mod error; +mod models; + +pub use error::{Error, Result}; + +#[cfg(desktop)] +pub use desktop::Notification; +#[cfg(mobile)] +pub use mobile::Notification; + +/// The notification builder. +#[derive(Debug)] +pub struct NotificationBuilder { + #[cfg(desktop)] + app: AppHandle, + #[cfg(mobile)] + handle: PluginHandle, + pub(crate) data: NotificationData, +} + +impl NotificationBuilder { + #[cfg(desktop)] + fn new(app: AppHandle) -> Self { + Self { + app, + data: Default::default(), + } + } + + #[cfg(mobile)] + fn new(handle: PluginHandle) -> Self { + Self { + handle, + data: Default::default(), + } + } + + /// Sets the notification identifier. + pub fn id(mut self, id: i32) -> Self { + self.data.id = id; + self + } + + /// Identifier of the {@link Channel} that deliveres this notification. + /// + /// If the channel does not exist, the notification won't fire. + /// Make sure the channel exists with {@link listChannels} and {@link createChannel}. + pub fn channel_id(mut self, id: impl Into) -> Self { + self.data.channel_id.replace(id.into()); + self + } + + /// Sets the notification title. + pub fn title(mut self, title: impl Into) -> Self { + self.data.title.replace(title.into()); + self + } + + /// Sets the notification body. + pub fn body(mut self, body: impl Into) -> Self { + self.data.body.replace(body.into()); + self + } + + /// Schedule this notification to fire on a later time or a fixed interval. + pub fn schedule(mut self, schedule: Schedule) -> Self { + self.data.schedule.replace(schedule); + self + } + + /// Multiline text. + /// Changes the notification style to big text. + /// Cannot be used with `inboxLines`. + pub fn large_body(mut self, large_body: impl Into) -> Self { + self.data.large_body.replace(large_body.into()); + self + } + + /// Detail text for the notification with `largeBody`, `inboxLines` or `groupSummary`. + pub fn summary(mut self, summary: impl Into) -> Self { + self.data.summary.replace(summary.into()); + self + } + + /// Defines an action type for this notification. + pub fn action_type_id(mut self, action_type_id: impl Into) -> Self { + self.data.action_type_id.replace(action_type_id.into()); + self + } + + /// Identifier used to group multiple notifications. + /// + /// + pub fn group(mut self, group: impl Into) -> Self { + self.data.group.replace(group.into()); + self + } + + /// Instructs the system that this notification is the summary of a group on Android. + pub fn group_summary(mut self) -> Self { + self.data.group_summary = true; + self + } + + /// The sound resource name for the notification. + pub fn sound(mut self, sound: impl Into) -> Self { + self.data.sound.replace(sound.into()); + self + } + + /// Append an inbox line to the notification. + /// Changes the notification style to inbox. + /// Cannot be used with `largeBody`. + /// + /// Only supports up to 5 lines. + pub fn inbox_line(mut self, line: impl Into) -> Self { + self.data.inbox_lines.push(line.into()); + self + } + + /// Notification icon. + /// + /// On Android the icon must be placed in the app's `res/drawable` folder. + pub fn icon(mut self, icon: impl Into) -> Self { + self.data.icon.replace(icon.into()); + self + } + + /// Notification large icon (Android). + /// + /// The icon must be placed in the app's `res/drawable` folder. + pub fn large_icon(mut self, large_icon: impl Into) -> Self { + self.data.large_icon.replace(large_icon.into()); + self + } + + /// Icon color on Android. + pub fn icon_color(mut self, icon_color: impl Into) -> Self { + self.data.icon_color.replace(icon_color.into()); + self + } + + /// Append an attachment to the notification. + pub fn attachment(mut self, attachment: Attachment) -> Self { + self.data.attachments.push(attachment); + self + } + + /// Adds an extra payload to store in the notification. + pub fn extra(mut self, key: impl Into, value: impl Serialize) -> Self { + self.data + .extra + .insert(key.into(), serde_json::to_value(value).unwrap()); + self + } + + /// If true, the notification cannot be dismissed by the user on Android. + /// + /// An application service must manage the dismissal of the notification. + /// It is typically used to indicate a background task that is pending (e.g. a file download) + /// or the user is engaged with (e.g. playing music). + pub fn ongoing(mut self) -> Self { + self.data.ongoing = true; + self + } + + /// Automatically cancel the notification when the user clicks on it. + pub fn auto_cancel(mut self) -> Self { + self.data.auto_cancel = true; + self + } + + /// Changes the notification presentation to be silent on iOS (no badge, no sound, not listed). + pub fn silent(mut self) -> Self { + self.data.silent = true; + self + } +} + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the notification APIs. +pub trait NotificationExt { + fn notification(&self) -> &Notification; +} + +impl> crate::NotificationExt for T { + fn notification(&self) -> &Notification { + self.state::>().inner() + } +} + +/// Initializes the plugin. +pub fn init() -> TauriPlugin { + Builder::new("notification") + .invoke_handler(tauri::generate_handler![ + commands::notify, + commands::request_permission, + commands::is_permission_granted + ]) + .js_init_script(include_str!("init-iife.js").replace( + "__TEMPLATE_windows__", + if cfg!(windows) { "true" } else { "false" }, + )) + .setup(|app, api| { + #[cfg(mobile)] + let notification = mobile::init(app, api)?; + #[cfg(desktop)] + let notification = desktop::init(app, api)?; + app.manage(notification); + Ok(()) + }) + .build() +} diff --git a/packages/kbot/gui/app/plugins/notification/src/mobile.rs b/packages/kbot/gui/app/plugins/notification/src/mobile.rs new file mode 100644 index 00000000..edfef728 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/src/mobile.rs @@ -0,0 +1,150 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{de::DeserializeOwned, Deserialize}; +use tauri::{ + plugin::{PermissionState, PluginApi, PluginHandle}, + AppHandle, Runtime, +}; + +use crate::models::*; + +use std::collections::HashMap; + +#[cfg(target_os = "android")] +const PLUGIN_IDENTIFIER: &str = "app.tauri.notification"; + +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_notification); + +// initializes the Kotlin or Swift plugin classes +pub fn init( + _app: &AppHandle, + api: PluginApi, +) -> crate::Result> { + #[cfg(target_os = "android")] + let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "NotificationPlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_notification)?; + Ok(Notification(handle)) +} + +impl crate::NotificationBuilder { + pub fn show(self) -> crate::Result<()> { + self.handle + .run_mobile_plugin::("show", self.data) + .map(|_| ()) + .map_err(Into::into) + } +} + +/// Access to the notification APIs. +/// +/// You can get an instance of this type via [`NotificationExt`](crate::NotificationExt) +pub struct Notification(PluginHandle); + +impl Notification { + pub fn builder(&self) -> crate::NotificationBuilder { + crate::NotificationBuilder::new(self.0.clone()) + } + + pub fn request_permission(&self) -> crate::Result { + self.0 + .run_mobile_plugin::("requestPermissions", ()) + .map(|r| r.permission_state) + .map_err(Into::into) + } + + pub fn permission_state(&self) -> crate::Result { + self.0 + .run_mobile_plugin::("checkPermissions", ()) + .map(|r| r.permission_state) + .map_err(Into::into) + } + + pub fn register_action_types(&self, types: Vec) -> crate::Result<()> { + let mut args = HashMap::new(); + args.insert("types", types); + self.0 + .run_mobile_plugin("registerActionTypes", args) + .map_err(Into::into) + } + + pub fn remove_active(&self, notifications: Vec) -> crate::Result<()> { + let mut args = HashMap::new(); + args.insert( + "notifications", + notifications + .into_iter() + .map(|id| { + let mut notification = HashMap::new(); + notification.insert("id", id); + notification + }) + .collect::>>(), + ); + self.0 + .run_mobile_plugin("removeActive", args) + .map_err(Into::into) + } + + pub fn active(&self) -> crate::Result> { + self.0 + .run_mobile_plugin("getActive", ()) + .map_err(Into::into) + } + + pub fn remove_all_active(&self) -> crate::Result<()> { + self.0 + .run_mobile_plugin("removeActive", ()) + .map_err(Into::into) + } + + pub fn pending(&self) -> crate::Result> { + self.0 + .run_mobile_plugin("getPending", ()) + .map_err(Into::into) + } + + /// Cancel pending notifications. + pub fn cancel(&self, notifications: Vec) -> crate::Result<()> { + let mut args = HashMap::new(); + args.insert("notifications", notifications); + self.0.run_mobile_plugin("cancel", args).map_err(Into::into) + } + + /// Cancel all pending notifications. + pub fn cancel_all(&self) -> crate::Result<()> { + self.0.run_mobile_plugin("cancel", ()).map_err(Into::into) + } + + #[cfg(target_os = "android")] + pub fn create_channel(&self, channel: Channel) -> crate::Result<()> { + self.0 + .run_mobile_plugin("createChannel", channel) + .map_err(Into::into) + } + + #[cfg(target_os = "android")] + pub fn delete_channel(&self, id: impl Into) -> crate::Result<()> { + let mut args = HashMap::new(); + args.insert("id", id.into()); + self.0 + .run_mobile_plugin("deleteChannel", args) + .map_err(Into::into) + } + + #[cfg(target_os = "android")] + pub fn list_channels(&self) -> crate::Result> { + self.0 + .run_mobile_plugin("listChannels", ()) + .map_err(Into::into) + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct PermissionResponse { + permission_state: PermissionState, +} diff --git a/packages/kbot/gui/app/plugins/notification/src/models.rs b/packages/kbot/gui/app/plugins/notification/src/models.rs new file mode 100644 index 00000000..02134e4d --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/src/models.rs @@ -0,0 +1,478 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{collections::HashMap, fmt::Display}; + +use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer}; + +use url::Url; + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Attachment { + id: String, + url: Url, +} + +impl Attachment { + pub fn new(id: impl Into, url: Url) -> Self { + Self { id: id.into(), url } + } +} + +#[derive(Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScheduleInterval { + pub year: Option, + pub month: Option, + pub day: Option, + pub weekday: Option, + pub hour: Option, + pub minute: Option, + pub second: Option, +} + +#[derive(Debug)] +pub enum ScheduleEvery { + Year, + Month, + TwoWeeks, + Week, + Day, + Hour, + Minute, + Second, +} + +impl Display for ScheduleEvery { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Year => "year", + Self::Month => "month", + Self::TwoWeeks => "twoWeeks", + Self::Week => "week", + Self::Day => "day", + Self::Hour => "hour", + Self::Minute => "minute", + Self::Second => "second", + } + ) + } +} + +impl Serialize for ScheduleEvery { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} + +impl<'de> Deserialize<'de> for ScheduleEvery { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + match s.to_lowercase().as_str() { + "year" => Ok(Self::Year), + "month" => Ok(Self::Month), + "twoweeks" => Ok(Self::TwoWeeks), + "week" => Ok(Self::Week), + "day" => Ok(Self::Day), + "hour" => Ok(Self::Hour), + "minute" => Ok(Self::Minute), + "second" => Ok(Self::Second), + _ => Err(DeError::custom(format!("unknown every kind '{s}'"))), + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum Schedule { + #[serde(rename_all = "camelCase")] + At { + #[serde( + serialize_with = "iso8601::serialize", + deserialize_with = "time::serde::iso8601::deserialize" + )] + date: time::OffsetDateTime, + #[serde(default)] + repeating: bool, + #[serde(default)] + allow_while_idle: bool, + }, + #[serde(rename_all = "camelCase")] + Interval { + interval: ScheduleInterval, + #[serde(default)] + allow_while_idle: bool, + }, + #[serde(rename_all = "camelCase")] + Every { + interval: ScheduleEvery, + count: u8, + #[serde(default)] + allow_while_idle: bool, + }, +} + +// custom ISO-8601 serialization that does not use 6 digits for years. +mod iso8601 { + use serde::{ser::Error as _, Serialize, Serializer}; + use time::{ + format_description::well_known::iso8601::{Config, EncodedConfig}, + format_description::well_known::Iso8601, + OffsetDateTime, + }; + + const SERDE_CONFIG: EncodedConfig = Config::DEFAULT.encode(); + + pub fn serialize( + datetime: &OffsetDateTime, + serializer: S, + ) -> Result { + datetime + .format(&Iso8601::) + .map_err(S::Error::custom)? + .serialize(serializer) + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NotificationData { + #[serde(default = "default_id")] + pub(crate) id: i32, + pub(crate) channel_id: Option, + pub(crate) title: Option, + pub(crate) body: Option, + pub(crate) schedule: Option, + pub(crate) large_body: Option, + pub(crate) summary: Option, + pub(crate) action_type_id: Option, + pub(crate) group: Option, + #[serde(default)] + pub(crate) group_summary: bool, + pub(crate) sound: Option, + #[serde(default)] + pub(crate) inbox_lines: Vec, + pub(crate) icon: Option, + pub(crate) large_icon: Option, + pub(crate) icon_color: Option, + #[serde(default)] + pub(crate) attachments: Vec, + #[serde(default)] + pub(crate) extra: HashMap, + #[serde(default)] + pub(crate) ongoing: bool, + #[serde(default)] + pub(crate) auto_cancel: bool, + #[serde(default)] + pub(crate) silent: bool, +} + +fn default_id() -> i32 { + rand::random() +} + +impl Default for NotificationData { + fn default() -> Self { + Self { + id: default_id(), + channel_id: None, + title: None, + body: None, + schedule: None, + large_body: None, + summary: None, + action_type_id: None, + group: None, + group_summary: false, + sound: None, + inbox_lines: Vec::new(), + icon: None, + large_icon: None, + icon_color: None, + attachments: Vec::new(), + extra: Default::default(), + ongoing: false, + auto_cancel: false, + silent: false, + } + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PendingNotification { + id: i32, + title: Option, + body: Option, + schedule: Schedule, +} + +impl PendingNotification { + pub fn id(&self) -> i32 { + self.id + } + + pub fn title(&self) -> Option<&str> { + self.title.as_deref() + } + + pub fn body(&self) -> Option<&str> { + self.body.as_deref() + } + + pub fn schedule(&self) -> &Schedule { + &self.schedule + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ActiveNotification { + id: i32, + tag: Option, + title: Option, + body: Option, + group: Option, + #[serde(default)] + group_summary: bool, + #[serde(default)] + data: HashMap, + #[serde(default)] + extra: HashMap, + #[serde(default)] + attachments: Vec, + action_type_id: Option, + schedule: Option, + sound: Option, +} + +impl ActiveNotification { + pub fn id(&self) -> i32 { + self.id + } + + pub fn tag(&self) -> Option<&str> { + self.tag.as_deref() + } + + pub fn title(&self) -> Option<&str> { + self.title.as_deref() + } + + pub fn body(&self) -> Option<&str> { + self.body.as_deref() + } + + pub fn group(&self) -> Option<&str> { + self.group.as_deref() + } + + pub fn group_summary(&self) -> bool { + self.group_summary + } + + pub fn data(&self) -> &HashMap { + &self.data + } + + pub fn extra(&self) -> &HashMap { + &self.extra + } + + pub fn attachments(&self) -> &[Attachment] { + &self.attachments + } + + pub fn action_type_id(&self) -> Option<&str> { + self.action_type_id.as_deref() + } + + pub fn schedule(&self) -> Option<&Schedule> { + self.schedule.as_ref() + } + + pub fn sound(&self) -> Option<&str> { + self.sound.as_deref() + } +} + +#[cfg(mobile)] +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ActionType { + id: String, + actions: Vec, + hidden_previews_body_placeholder: Option, + custom_dismiss_action: bool, + allow_in_car_play: bool, + hidden_previews_show_title: bool, + hidden_previews_show_subtitle: bool, +} + +#[cfg(mobile)] +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Action { + id: String, + title: String, + requires_authentication: bool, + foreground: bool, + destructive: bool, + input: bool, + input_button_title: Option, + input_placeholder: Option, +} + +#[cfg(target_os = "android")] +pub use android::*; + +#[cfg(target_os = "android")] +mod android { + use serde::{Deserialize, Serialize}; + use serde_repr::{Deserialize_repr, Serialize_repr}; + + #[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)] + #[repr(u8)] + pub enum Importance { + None = 0, + Min = 1, + Low = 2, + Default = 3, + High = 4, + } + + impl Default for Importance { + fn default() -> Self { + Self::Default + } + } + + #[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)] + #[repr(i8)] + pub enum Visibility { + Secret = -1, + Private = 0, + Public = 1, + } + + #[derive(Debug, Serialize, Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct Channel { + id: String, + name: String, + description: Option, + sound: Option, + lights: bool, + light_color: Option, + vibration: bool, + importance: Importance, + visibility: Option, + } + + #[derive(Debug)] + pub struct ChannelBuilder(Channel); + + impl Channel { + pub fn builder(id: impl Into, name: impl Into) -> ChannelBuilder { + ChannelBuilder(Self { + id: id.into(), + name: name.into(), + description: None, + sound: None, + lights: false, + light_color: None, + vibration: false, + importance: Default::default(), + visibility: None, + }) + } + + pub fn id(&self) -> &str { + &self.id + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn description(&self) -> Option<&str> { + self.description.as_deref() + } + + pub fn sound(&self) -> Option<&str> { + self.sound.as_deref() + } + + pub fn lights(&self) -> bool { + self.lights + } + + pub fn light_color(&self) -> Option<&str> { + self.light_color.as_deref() + } + + pub fn vibration(&self) -> bool { + self.vibration + } + + pub fn importance(&self) -> Importance { + self.importance + } + + pub fn visibility(&self) -> Option { + self.visibility + } + } + + impl ChannelBuilder { + pub fn description(mut self, description: impl Into) -> Self { + self.0.description.replace(description.into()); + self + } + + pub fn sound(mut self, sound: impl Into) -> Self { + self.0.sound.replace(sound.into()); + self + } + + pub fn lights(mut self, lights: bool) -> Self { + self.0.lights = lights; + self + } + + pub fn light_color(mut self, color: impl Into) -> Self { + self.0.light_color.replace(color.into()); + self + } + + pub fn vibration(mut self, vibration: bool) -> Self { + self.0.vibration = vibration; + self + } + + pub fn importance(mut self, importance: Importance) -> Self { + self.0.importance = importance; + self + } + + pub fn visibility(mut self, visibility: Visibility) -> Self { + self.0.visibility.replace(visibility); + self + } + + pub fn build(self) -> Channel { + self.0 + } + } +} diff --git a/packages/kbot/gui/app/plugins/notification/test/tauri.conf.json b/packages/kbot/gui/app/plugins/notification/test/tauri.conf.json new file mode 100644 index 00000000..4d0ae021 --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/test/tauri.conf.json @@ -0,0 +1,21 @@ +{ + "identifier": "app.tauri.example", + "build": { + "frontendDist": ".", + "devUrl": "http://localhost:4000" + }, + "app": { + "windows": [ + { + "title": "Tauri App" + } + ], + "security": { + "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: http://tauri.localhost 'unsafe-eval' 'unsafe-inline' 'self'" + } + }, + "bundle": { + "active": true, + "icon": ["../../../examples/api/src-tauri/icons/icon.png"] + } +} diff --git a/packages/kbot/gui/app/plugins/notification/tsconfig.json b/packages/kbot/gui/app/plugins/notification/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/notification/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/opener/CHANGELOG.md b/packages/kbot/gui/app/plugins/opener/CHANGELOG.md new file mode 100644 index 00000000..94c5c1bc --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/CHANGELOG.md @@ -0,0 +1,59 @@ +# Changelog + +## \[2.5.0] + +### enhance + +- [`b8056f48`](https://github.com/tauri-apps/plugins-workspace/commit/b8056f484c7144af095d4d6ded1e8adbb9b8a865) ([#2897](https://github.com/tauri-apps/plugins-workspace/pull/2897) by [@petersamokhin](https://github.com/tauri-apps/plugins-workspace/../../petersamokhin)) Allow reveal multiple items in the file explorer. + +## \[2.4.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.3.1] + +### feat + +- [`2aec8ff4`](https://github.com/tauri-apps/plugins-workspace/commit/2aec8ff4c41d178ea9804f7b6eff343c726be015) ([#2803](https://github.com/tauri-apps/plugins-workspace/pull/2803) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Add `inAppBrowser` option to open URLs in an in-app browser on Android and iOS. + +## \[2.3.0] + +- [`ce9888a2`](https://github.com/tauri-apps/plugins-workspace/commit/ce9888a2d4c9b449bd2a306f0fa6c76507fd46d3) ([#2762](https://github.com/tauri-apps/plugins-workspace/pull/2762)) Similar to the `fs` plugin the `opener` plugin now supports a `requireLiteralLeadingDot` configuration in `tauri.conf.json`. + +## \[2.2.7] + +- [`6c9e08dc`](https://github.com/tauri-apps/plugins-workspace/commit/6c9e08dccb3ac99fccfce586fa2b69717ba81b52) ([#2695](https://github.com/tauri-apps/plugins-workspace/pull/2695) by [@ShaunSHamilton](https://github.com/tauri-apps/plugins-workspace/../../ShaunSHamilton)) Adjust `open_url` url type to allow `URL` +- [`dde6f3c3`](https://github.com/tauri-apps/plugins-workspace/commit/dde6f3c31c1b79942abb556be31757dc583f509e) ([#2549](https://github.com/tauri-apps/plugins-workspace/pull/2549) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update `windows` crate to 0.61 to align with Tauri 2.5 + +## \[2.2.6] + +- [`1a984659`](https://github.com/tauri-apps/plugins-workspace/commit/1a9846599b6a71faf330845847a30f6bf9735898) ([#2469](https://github.com/tauri-apps/plugins-workspace/pull/2469) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Update `objc2` crate to 0.6. No user facing changes. +- [`71f95c9f`](https://github.com/tauri-apps/plugins-workspace/commit/71f95c9f05b29cf1be586849614c0b007757c15d) ([#2445](https://github.com/tauri-apps/plugins-workspace/pull/2445) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated `windows` crate to 0.60 to match Tauri 2.3.0. No user facing changes. + +## \[2.2.5] + +- [`5b821181`](https://github.com/tauri-apps/plugins-workspace/commit/5b8211815825ddae2dcc0c00520e0cfdff002763) ([#2332](https://github.com/tauri-apps/plugins-workspace/pull/2332) by [@betamos](https://github.com/tauri-apps/plugins-workspace/../../betamos)) Fix broken JS commands `opener.openPath` and `opener.openUrl` on mobile. + +## \[2.2.4] + +- [`da5c59e2`](https://github.com/tauri-apps/plugins-workspace/commit/da5c59e2fe879d177e3cfd52fcacce85440423cb) ([#2271](https://github.com/tauri-apps/plugins-workspace/pull/2271) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated `zbus` dependency to version 5. No API changes. + +## \[2.2.3] + +- [`a9ac1e3c`](https://github.com/tauri-apps/plugins-workspace/commit/a9ac1e3c939cec4338a9422ef02323c1d4dde6cd) ([#2253](https://github.com/tauri-apps/plugins-workspace/pull/2253) by [@betamos](https://github.com/tauri-apps/plugins-workspace/../../betamos)) Return an error in `open_path` if the file does not exist when opening with default application. + +## \[2.2.2] + +- [`ee0f65de`](https://github.com/tauri-apps/plugins-workspace/commit/ee0f65de5c645c244c5f0b638e0e0aab687cb9bf) ([#2207](https://github.com/tauri-apps/plugins-workspace/pull/2207) by [@universalappfactory](https://github.com/tauri-apps/plugins-workspace/../../universalappfactory)) Fixed OpenerPlugin packagename for android + +## \[2.2.1] + +- [`18dffc9d`](https://github.com/tauri-apps/plugins-workspace/commit/18dffc9dfecaf0c900e233e041d9ca36c92834b5) ([#2189](https://github.com/tauri-apps/plugins-workspace/pull/2189) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Fix usage on iOS. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.0] + +- [`383e636a`](https://github.com/tauri-apps/plugins-workspace/commit/383e636a8e595aec1300999a8aeb7d9bf8c14632) ([#2019](https://github.com/tauri-apps/plugins-workspace/pull/2019) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Initial Release diff --git a/packages/kbot/gui/app/plugins/opener/Cargo.toml b/packages/kbot/gui/app/plugins/opener/Cargo.toml new file mode 100644 index 00000000..bccbcec7 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/Cargo.toml @@ -0,0 +1,65 @@ +[package] +name = "tauri-plugin-opener" +version = "2.5.0" +description = "Open files and URLs using their default application." +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-opener" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] + +# Platforms supported by the plugin +# Support levels are "full", "partial", "none", "unknown" +# Details of the support level are left to plugin maintainer +[package.metadata.platforms.support] +windows = { level = "full", notes = "" } +linux = { level = "full", notes = "" } +macos = { level = "full", notes = "" } +android = { level = "partial", notes = "Only allows to open URLs via `open`" } +ios = { level = "partial", notes = "Only allows to open URLs via `open`" } + + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } +schemars = { workspace = true } +serde = { workspace = true } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +thiserror = { workspace = true } +open = { version = "5", features = ["shellexecute-on-windows"] } +glob = { workspace = true } +dunce = { workspace = true } + +[target."cfg(windows)".dependencies.windows] +version = "0.61" +features = [ + "Win32_Foundation", + "Win32_UI_Shell_Common", + "Win32_UI_WindowsAndMessaging", + "Win32_System_Com", + "Win32_System_Registry", +] + +[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"netbsd\", target_os = \"openbsd\"))".dependencies] +zbus = { workspace = true } +url = { workspace = true } + +[target."cfg(target_os = \"macos\")".dependencies.objc2-app-kit] +version = "0.3" +default-features = false +features = ["std", "NSWorkspace"] + +[target."cfg(target_os = \"macos\")".dependencies.objc2-foundation] +version = "0.3" +default-features = false +features = ["std", "NSURL", "NSArray", "NSString"] + +[target.'cfg(target_os = "ios")'.dependencies] +tauri = { workspace = true, features = ["wry"] } diff --git a/packages/kbot/gui/app/plugins/opener/LICENSE.spdx b/packages/kbot/gui/app/plugins/opener/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/opener/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/opener/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/opener/LICENSE_MIT b/packages/kbot/gui/app/plugins/opener/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/opener/README.md b/packages/kbot/gui/app/plugins/opener/README.md new file mode 100644 index 00000000..bcb9265c --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/README.md @@ -0,0 +1,143 @@ +![opener](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/opener/banner.png) + + + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | ? | +| iOS | ? | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-opener = "2.0.0" +# alternatively with Git: +tauri-plugin-opener = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + + + +```sh +pnpm add @tauri-apps/plugin-opener +# or +npm add @tauri-apps/plugin-opener +# or +yarn add @tauri-apps/plugin-opener +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_opener::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { openUrl, openPath, revealItemInDir } from '@tauri-apps/plugin-opener' + +// Opens the URL in the default browser +await openUrl('https://example.com') +// Or with a specific browser/app +await openUrl('https://example.com', 'firefox') + +// Opens the path with the system's default app +await openPath('/path/to/file') +// Or with a specific app +await openPath('/path/to/file', 'firefox') + +// Reveal a path with the system's default explorer +await revealItemInDir('/path/to/file') + +// Reveal multiple paths with the system's default explorer +// Note: will be renamed to `revealItemsInDir` in the next major version +await revealItemInDir(['/path/to/file', '/path/to/another/file']) +``` + +### Usage from Rust + +You can also use those APIs from Rust: + +```rust +use tauri_plugin_opener::OpenerExt; + +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_opener::init()) + .setup(|app| { + let opener = app.opener(); + + // Opens the URL in the default browser + opener.open_url("https://example.com", None::<&str>)?; + // Or with a specific browser/app + opener.open_url("https://example.com", Some("firefox"))?; + + // Opens the path with the system's default app + opener.open_path("/path/to/file", None::<&str>)?; + // Or with a specific app + opener.open_path("/path/to/file", Some("firefox"))?; + + // Reveal a path with the system's default explorer + opener.reveal_item_in_dir("/path/to/file")?; + + // Reveal multiple paths with the system's default explorer + opener.reveal_items_in_dir(["/path/to/file"])?; + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/opener/SECURITY.md b/packages/kbot/gui/app/plugins/opener/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/opener/android/.gitignore b/packages/kbot/gui/app/plugins/opener/android/.gitignore new file mode 100644 index 00000000..c0f21ec2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/android/.gitignore @@ -0,0 +1,2 @@ +/build +/.tauri diff --git a/packages/kbot/gui/app/plugins/opener/android/build.gradle.kts b/packages/kbot/gui/app/plugins/opener/android/build.gradle.kts new file mode 100644 index 00000000..49ddbe22 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/android/build.gradle.kts @@ -0,0 +1,40 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "app.tauri.opener" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation("androidx.core:core-ktx:1.9.0") + implementation("com.fasterxml.jackson.core:jackson-databind:2.15.3") + implementation("androidx.browser:browser:1.8.0") + implementation(project(":tauri-android")) +} diff --git a/packages/kbot/gui/app/plugins/opener/android/proguard-rules.pro b/packages/kbot/gui/app/plugins/opener/android/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/opener/android/settings.gradle b/packages/kbot/gui/app/plugins/opener/android/settings.gradle new file mode 100644 index 00000000..14a752e4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/android/settings.gradle @@ -0,0 +1,2 @@ +include ':tauri-android' +project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/packages/kbot/gui/app/plugins/opener/android/src/main/AndroidManifest.xml b/packages/kbot/gui/app/plugins/opener/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..9a40236b --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/packages/kbot/gui/app/plugins/opener/android/src/main/java/OpenerPlugin.kt b/packages/kbot/gui/app/plugins/opener/android/src/main/java/OpenerPlugin.kt new file mode 100644 index 00000000..a65a2f52 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/android/src/main/java/OpenerPlugin.kt @@ -0,0 +1,44 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.opener + +import android.app.Activity +import android.content.Intent +import androidx.browser.customtabs.CustomTabsIntent +import app.tauri.annotation.Command +import app.tauri.annotation.TauriPlugin +import app.tauri.plugin.Invoke +import app.tauri.plugin.Plugin +import androidx.core.net.toUri +import app.tauri.annotation.InvokeArg + +@InvokeArg +class OpenArgs { + lateinit var url: String + var with: String? = null +} + +@TauriPlugin +class OpenerPlugin(private val activity: Activity) : Plugin(activity) { + @Command + fun open(invoke: Invoke) { + try { + val args = invoke.parseArgs(OpenArgs::class.java) + + if (args.with == "inAppBrowser") { + val builder = CustomTabsIntent.Builder() + val intent = builder.build() + intent.launchUrl(activity, args.url.toUri()) + } else { + val intent = Intent(Intent.ACTION_VIEW, args.url.toUri()) + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + activity.applicationContext?.startActivity(intent) + } + invoke.resolve() + } catch (ex: Exception) { + invoke.reject(ex.message) + } + } +} diff --git a/packages/kbot/gui/app/plugins/opener/api-iife.js b/packages/kbot/gui/app/plugins/opener/api-iife.js new file mode 100644 index 00000000..dd976e57 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_OPENER__=function(n){"use strict";async function e(n,e={},r){return window.__TAURI_INTERNALS__.invoke(n,e,r)}return"function"==typeof SuppressedError&&SuppressedError,n.openPath=async function(n,r){await e("plugin:opener|open_path",{path:n,with:r})},n.openUrl=async function(n,r){await e("plugin:opener|open_url",{url:n,with:r})},n.revealItemInDir=async function(n){return e("plugin:opener|reveal_item_in_dir",{paths:"string"==typeof n?[n]:n})},n}({});Object.defineProperty(window.__TAURI__,"opener",{value:__TAURI_PLUGIN_OPENER__})} diff --git a/packages/kbot/gui/app/plugins/opener/build.rs b/packages/kbot/gui/app/plugins/opener/build.rs new file mode 100644 index 00000000..fbad4d3a --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/build.rs @@ -0,0 +1,136 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::PathBuf; + +#[path = "src/scope_entry.rs"] +#[allow(dead_code)] +mod scope; + +/// Opener scope application. +#[derive(schemars::JsonSchema)] +#[serde(untagged)] +#[allow(unused)] +enum Application { + /// Open in default application. + Default, + /// If true, allow open with any application. + Enable(bool), + /// Allow specific application to open with. + App(String), +} + +impl Default for Application { + fn default() -> Self { + Self::Default + } +} + +/// Opener scope entry. +#[derive(schemars::JsonSchema)] +#[serde(untagged)] +#[allow(unused)] +enum OpenerScopeEntry { + Url { + /// A URL that can be opened by the webview when using the Opener APIs. + /// + /// Wildcards can be used following the UNIX glob pattern. + /// + /// Examples: + /// + /// - "https://*" : allows all HTTPS origin + /// + /// - "https://*.github.com/tauri-apps/tauri": allows any subdomain of "github.com" with the "tauri-apps/api" path + /// + /// - "https://myapi.service.com/users/*": allows access to any URLs that begins with "https://myapi.service.com/users/" + url: String, + /// An application to open this url with, for example: firefox. + #[serde(default)] + app: Application, + }, + Path { + /// A path that can be opened by the webview when using the Opener APIs. + /// + /// The pattern can start with a variable that resolves to a system base directory. + /// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, + /// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, + /// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, + /// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`. + path: PathBuf, + /// An application to open this path with, for example: xdg-open. + #[serde(default)] + app: Application, + }, +} + +// Ensure `OpenerScopeEntry` and `scope::EntryRaw` is kept in sync +fn _f() { + match (scope::EntryRaw::Url { + url: String::new(), + app: scope::Application::Enable(true), + }) { + scope::EntryRaw::Url { url, app } => OpenerScopeEntry::Url { + url, + app: match app { + scope::Application::Enable(p) => Application::Enable(p), + scope::Application::App(p) => Application::App(p), + scope::Application::Default => Application::Default, + }, + }, + scope::EntryRaw::Path { path, app } => OpenerScopeEntry::Path { + path, + app: match app { + scope::Application::Enable(p) => Application::Enable(p), + scope::Application::App(p) => Application::App(p), + scope::Application::Default => Application::Default, + }, + }, + }; + match (OpenerScopeEntry::Url { + url: String::new(), + app: Application::Enable(true), + }) { + OpenerScopeEntry::Url { url, app } => scope::EntryRaw::Url { + url, + app: match app { + Application::Enable(p) => scope::Application::Enable(p), + Application::App(p) => scope::Application::App(p), + Application::Default => scope::Application::Default, + }, + }, + OpenerScopeEntry::Path { path, app } => scope::EntryRaw::Path { + path, + app: match app { + Application::Enable(p) => scope::Application::Enable(p), + Application::App(p) => scope::Application::App(p), + Application::Default => scope::Application::Default, + }, + }, + }; +} + +const COMMANDS: &[&str] = &["open_url", "open_path", "reveal_item_in_dir"]; + +fn main() { + tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .android_path("android") + .ios_path("ios") + .global_scope_schema(schemars::schema_for!(OpenerScopeEntry)) + .build(); + + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); + let mobile = target_os == "ios" || target_os == "android"; + alias("desktop", !mobile); + alias("mobile", mobile); +} + +// creates a cfg alias if `has_feature` is true. +// `alias` must be a snake case string. +fn alias(alias: &str, has_feature: bool) { + println!("cargo:rustc-check-cfg=cfg({alias})"); + if has_feature { + println!("cargo:rustc-cfg={alias}"); + } +} diff --git a/packages/kbot/gui/app/plugins/opener/guest-js/index.ts b/packages/kbot/gui/app/plugins/opener/guest-js/index.ts new file mode 100644 index 00000000..6b40da19 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/guest-js/index.ts @@ -0,0 +1,99 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/** + * Open files and URLs using their default application. + * + * ## Security + * + * This API has a scope configuration that forces you to restrict the files and urls to be opened. + * + * ### Restricting access to the {@link open | `open`} API + * + * On the configuration object, `open: true` means that the {@link open} API can be used with any URL, + * as the argument is validated with the `^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+` regex. + * You can change that regex by changing the boolean value to a string, e.g. `open: ^https://github.com/`. + * + * @module + */ + +import { invoke } from '@tauri-apps/api/core' + +/** + * Opens a url with the system's default app, or the one specified with {@linkcode openWith}. + * + * @example + * ```typescript + * import { openUrl } from '@tauri-apps/plugin-opener'; + * + * // opens the given URL on the default browser: + * await openUrl('https://github.com/tauri-apps/tauri'); + * // opens the given URL using `firefox`: + * await openUrl('https://github.com/tauri-apps/tauri', 'firefox'); + * ``` + * + * @param url The URL to open. + * @param openWith The app to open the URL with. If not specified, defaults to the system default application for the specified url type. + * On mobile, `openWith` can be provided as `inAppBrowser` to open the URL in an in-app browser. Otherwise, it will open the URL in the system default browser. + * + * @since 2.0.0 + */ +export async function openUrl( + url: string | URL, + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents + openWith?: 'inAppBrowser' | string +): Promise { + await invoke('plugin:opener|open_url', { + url, + with: openWith + }) +} + +/** + * Opens a path with the system's default app, or the one specified with {@linkcode openWith}. + * + * @example + * ```typescript + * import { openPath } from '@tauri-apps/plugin-opener'; + * + * // opens a file using the default program: + * await openPath('/path/to/file'); + * // opens a file using `vlc` command on Windows. + * await openPath('C:/path/to/file', 'vlc'); + * ``` + * + * @param path The path to open. + * @param openWith The app to open the path with. If not specified, defaults to the system default application for the specified path type. + * + * @since 2.0.0 + */ +export async function openPath(path: string, openWith?: string): Promise { + await invoke('plugin:opener|open_path', { + path, + with: openWith + }) +} + +/** + * Reveal a path with the system's default explorer. + * + * #### Platform-specific: + * + * - **Android / iOS:** Unsupported. + * + * @example + * ```typescript + * import { revealItemInDir } from '@tauri-apps/plugin-opener'; + * await revealItemInDir('/path/to/file'); + * await revealItemInDir([ '/path/to/file', '/path/to/another/file' ]); + * ``` + * + * @param path The path to reveal. + * + * @since 2.0.0 + */ +export async function revealItemInDir(path: string | string[]): Promise { + const paths = typeof path === 'string' ? [path] : path + return invoke('plugin:opener|reveal_item_in_dir', { paths }) +} diff --git a/packages/kbot/gui/app/plugins/opener/guest-js/init.ts b/packages/kbot/gui/app/plugins/opener/guest-js/init.ts new file mode 100644 index 00000000..6f81141a --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/guest-js/init.ts @@ -0,0 +1,61 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { invoke } from '@tauri-apps/api/core' + +// open links with the API +window.addEventListener('click', function (evt) { + // return early if + if ( + // event was prevented + evt.defaultPrevented + // or not a left click + || evt.button !== 0 + // or meta key pressed + || evt.metaKey + // or al key pressed + || evt.altKey + ) + return + + const a = evt + .composedPath() + .find((el) => el instanceof Node && el.nodeName.toUpperCase() === 'A') as + | HTMLAnchorElement + | undefined + + // return early if + if ( + // not tirggered from element + !a + // or doesn't have a href + || !a.href + // or not supposed to be open in a new tab + || !( + a.target === '_blank' + // or ctrl key pressed + || evt.ctrlKey + // or shift key pressed + || evt.shiftKey + ) + ) + return + + const url = new URL(a.href) + + // return early if + if ( + // same origin (internal navigation) + url.origin === window.location.origin + // not default protocols + || ['http:', 'https:', 'mailto:', 'tel:'].every((p) => url.protocol !== p) + ) + return + + evt.preventDefault() + + void invoke('plugin:opener|open_url', { + url + }) +}) diff --git a/packages/kbot/gui/app/plugins/opener/ios/Package.resolved b/packages/kbot/gui/app/plugins/opener/ios/Package.resolved new file mode 100644 index 00000000..5f998e0e --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/ios/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "SwiftRs", + "repositoryURL": "https://github.com/Brendonovich/swift-rs", + "state": { + "branch": null, + "revision": "b5ed223fcdab165bc21219c1925dc1e77e2bef5e", + "version": "1.0.6" + } + } + ] + }, + "version": 1 +} diff --git a/packages/kbot/gui/app/plugins/opener/ios/Package.swift b/packages/kbot/gui/app/plugins/opener/ios/Package.swift new file mode 100644 index 00000000..8d06936e --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/ios/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version:5.3 +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import PackageDescription + +let package = Package( + name: "tauri-plugin-opener", + platforms: [ + .macOS(.v10_13), + .iOS(.v13), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "tauri-plugin-opener", + type: .static, + targets: ["tauri-plugin-opener"]) + ], + dependencies: [ + .package(name: "Tauri", path: "../.tauri/tauri-api") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "tauri-plugin-opener", + dependencies: [ + .byName(name: "Tauri") + ], + path: "Sources") + ] +) diff --git a/packages/kbot/gui/app/plugins/opener/ios/Sources/OpenerPlugin.swift b/packages/kbot/gui/app/plugins/opener/ios/Sources/OpenerPlugin.swift new file mode 100644 index 00000000..2311a3b1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/ios/Sources/OpenerPlugin.swift @@ -0,0 +1,46 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation +import SafariServices +import SwiftRs +import Tauri +import UIKit +import WebKit + +struct OpenArgs: Decodable { + let url: String + let with: String? +} + +class OpenerPlugin: Plugin { + @objc public func open(_ invoke: Invoke) throws { + do { + let args = try invoke.parseArgs(OpenArgs.self) + if let url = URL(string: args.url) { + if args.with == "inAppBrowser" { + DispatchQueue.main.async { + let safariVC = SFSafariViewController(url: url) + self.manager.viewController?.present(safariVC, animated: true) + } + } else { + if #available(iOS 10, *) { + UIApplication.shared.open(url, options: [:]) + } else { + UIApplication.shared.openURL(url) + } + } + + } + invoke.resolve() + } catch { + invoke.reject(error.localizedDescription) + } + } +} + +@_cdecl("init_plugin_opener") +func initPlugin() -> Plugin { + return OpenerPlugin() +} diff --git a/packages/kbot/gui/app/plugins/opener/package.json b/packages/kbot/gui/app/plugins/opener/package.json new file mode 100644 index 00000000..b979726d --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/package.json @@ -0,0 +1,30 @@ +{ + "name": "@tauri-apps/plugin-opener", + "version": "2.5.0", + "description": "Open files and URLs using their default application.", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/opener/permissions/allow-default-urls.toml b/packages/kbot/gui/app/plugins/opener/permissions/allow-default-urls.toml new file mode 100644 index 00000000..93495b89 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/permissions/allow-default-urls.toml @@ -0,0 +1,17 @@ +"$schema" = "schemas/schema.json" + +[[permission]] +identifier = "allow-default-urls" +description = "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application." + +[[permission.scope.allow]] +url = "mailto:*" + +[[permission.scope.allow]] +url = "tel:*" + +[[permission.scope.allow]] +url = "http://*" + +[[permission.scope.allow]] +url = "https://*" diff --git a/packages/kbot/gui/app/plugins/opener/permissions/autogenerated/commands/open_path.toml b/packages/kbot/gui/app/plugins/opener/permissions/autogenerated/commands/open_path.toml new file mode 100644 index 00000000..ae67b939 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/permissions/autogenerated/commands/open_path.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-open-path" +description = "Enables the open_path command without any pre-configured scope." +commands.allow = ["open_path"] + +[[permission]] +identifier = "deny-open-path" +description = "Denies the open_path command without any pre-configured scope." +commands.deny = ["open_path"] diff --git a/packages/kbot/gui/app/plugins/opener/permissions/autogenerated/commands/open_url.toml b/packages/kbot/gui/app/plugins/opener/permissions/autogenerated/commands/open_url.toml new file mode 100644 index 00000000..f1e694b1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/permissions/autogenerated/commands/open_url.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-open-url" +description = "Enables the open_url command without any pre-configured scope." +commands.allow = ["open_url"] + +[[permission]] +identifier = "deny-open-url" +description = "Denies the open_url command without any pre-configured scope." +commands.deny = ["open_url"] diff --git a/packages/kbot/gui/app/plugins/opener/permissions/autogenerated/commands/reveal_item_in_dir.toml b/packages/kbot/gui/app/plugins/opener/permissions/autogenerated/commands/reveal_item_in_dir.toml new file mode 100644 index 00000000..e669620f --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/permissions/autogenerated/commands/reveal_item_in_dir.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-reveal-item-in-dir" +description = "Enables the reveal_item_in_dir command without any pre-configured scope." +commands.allow = ["reveal_item_in_dir"] + +[[permission]] +identifier = "deny-reveal-item-in-dir" +description = "Denies the reveal_item_in_dir command without any pre-configured scope." +commands.deny = ["reveal_item_in_dir"] diff --git a/packages/kbot/gui/app/plugins/opener/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/opener/permissions/autogenerated/reference.md new file mode 100644 index 00000000..6ad6ba1f --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/permissions/autogenerated/reference.md @@ -0,0 +1,111 @@ +## Default Permission + +This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application +as well as reveal file in directories using default file explorer + +#### This default permission set includes the following: + +- `allow-open-url` +- `allow-reveal-item-in-dir` +- `allow-default-urls` + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`opener:allow-default-urls` + + + +This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application. + +
+ +`opener:allow-open-path` + + + +Enables the open_path command without any pre-configured scope. + +
+ +`opener:deny-open-path` + + + +Denies the open_path command without any pre-configured scope. + +
+ +`opener:allow-open-url` + + + +Enables the open_url command without any pre-configured scope. + +
+ +`opener:deny-open-url` + + + +Denies the open_url command without any pre-configured scope. + +
+ +`opener:allow-reveal-item-in-dir` + + + +Enables the reveal_item_in_dir command without any pre-configured scope. + +
+ +`opener:deny-reveal-item-in-dir` + + + +Denies the reveal_item_in_dir command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/opener/permissions/default.toml b/packages/kbot/gui/app/plugins/opener/permissions/default.toml new file mode 100644 index 00000000..846d6e51 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/permissions/default.toml @@ -0,0 +1,10 @@ +"$schema" = "schemas/schema.json" + +[default] +description = """This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application +as well as reveal file in directories using default file explorer""" +permissions = [ + "allow-open-url", + "allow-reveal-item-in-dir", + "allow-default-urls", +] diff --git a/packages/kbot/gui/app/plugins/opener/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/opener/permissions/schemas/schema.json new file mode 100644 index 00000000..78c80ba3 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/permissions/schemas/schema.json @@ -0,0 +1,348 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.", + "type": "string", + "const": "allow-default-urls", + "markdownDescription": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application." + }, + { + "description": "Enables the open_path command without any pre-configured scope.", + "type": "string", + "const": "allow-open-path", + "markdownDescription": "Enables the open_path command without any pre-configured scope." + }, + { + "description": "Denies the open_path command without any pre-configured scope.", + "type": "string", + "const": "deny-open-path", + "markdownDescription": "Denies the open_path command without any pre-configured scope." + }, + { + "description": "Enables the open_url command without any pre-configured scope.", + "type": "string", + "const": "allow-open-url", + "markdownDescription": "Enables the open_url command without any pre-configured scope." + }, + { + "description": "Denies the open_url command without any pre-configured scope.", + "type": "string", + "const": "deny-open-url", + "markdownDescription": "Denies the open_url command without any pre-configured scope." + }, + { + "description": "Enables the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "allow-reveal-item-in-dir", + "markdownDescription": "Enables the reveal_item_in_dir command without any pre-configured scope." + }, + { + "description": "Denies the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "deny-reveal-item-in-dir", + "markdownDescription": "Denies the reveal_item_in_dir command without any pre-configured scope." + }, + { + "description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`", + "type": "string", + "const": "default", + "markdownDescription": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/opener/rollup.config.js b/packages/kbot/gui/app/plugins/opener/rollup.config.js new file mode 100644 index 00000000..a7dbd4f6 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/rollup.config.js @@ -0,0 +1,22 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' +import { nodeResolve } from '@rollup/plugin-node-resolve' +import typescript from '@rollup/plugin-typescript' +import terser from '@rollup/plugin-terser' + +export default createConfig({ + additionalConfigs: { + input: 'guest-js/init.ts', + output: { + file: 'src/init-iife.js', + format: 'iife' + }, + plugins: [typescript(), terser(), nodeResolve()], + onwarn: (warning) => { + throw Object.assign(new Error(), warning) + } + } +}) diff --git a/packages/kbot/gui/app/plugins/opener/src/commands.rs b/packages/kbot/gui/app/plugins/opener/src/commands.rs new file mode 100644 index 00000000..e22b41d4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/src/commands.rs @@ -0,0 +1,76 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::{Path, PathBuf}; + +use tauri::{ + ipc::{CommandScope, GlobalScope}, + AppHandle, Runtime, +}; + +use crate::{scope::Scope, Error, OpenerExt}; + +#[tauri::command] +pub async fn open_url( + app: AppHandle, + command_scope: CommandScope, + global_scope: GlobalScope, + url: String, + with: Option, +) -> crate::Result<()> { + let scope = Scope::new( + &app, + command_scope + .allows() + .iter() + .chain(global_scope.allows()) + .collect(), + command_scope + .denies() + .iter() + .chain(global_scope.denies()) + .collect(), + ); + + if scope.is_url_allowed(&url, with.as_deref()) { + app.opener().open_url(url, with) + } else { + Err(Error::ForbiddenUrl { url, with }) + } +} + +#[tauri::command] +pub async fn open_path( + app: AppHandle, + command_scope: CommandScope, + global_scope: GlobalScope, + path: String, + with: Option, +) -> crate::Result<()> { + let scope = Scope::new( + &app, + command_scope + .allows() + .iter() + .chain(global_scope.allows()) + .collect(), + command_scope + .denies() + .iter() + .chain(global_scope.denies()) + .collect(), + ); + + if scope.is_path_allowed(Path::new(&path), with.as_deref())? { + app.opener().open_path(path, with) + } else { + Err(Error::ForbiddenPath { path, with }) + } +} + +/// TODO: in the next major version, rename to `reveal_items_in_dir` +#[tauri::command] +pub async fn reveal_item_in_dir(paths: Vec) -> crate::Result<()> { + crate::reveal_items_in_dir(&paths) +} diff --git a/packages/kbot/gui/app/plugins/opener/src/config.rs b/packages/kbot/gui/app/plugins/opener/src/config.rs new file mode 100644 index 00000000..db3bae45 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/src/config.rs @@ -0,0 +1,19 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::Deserialize; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct Config { + /// Whether or not paths that contain components that start with a `.` + /// will require that `.` appears literally in the pattern; `*`, `?`, `**`, + /// or `[...]` will not match. This is useful because such files are + /// conventionally considered hidden on Unix systems and it might be + /// desirable to skip them when listing files. + /// + /// Defaults to `true` on Unix systems and `false` on Windows + // dotfiles are not supposed to be exposed by default on unix + pub require_literal_leading_dot: Option, +} diff --git a/packages/kbot/gui/app/plugins/opener/src/error.rs b/packages/kbot/gui/app/plugins/opener/src/error.rs new file mode 100644 index 00000000..c5a4dde3 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/src/error.rs @@ -0,0 +1,57 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::PathBuf; + +use serde::{Serialize, Serializer}; + +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum Error { + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), + #[error(transparent)] + Tauri(#[from] tauri::Error), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + Json(#[from] serde_json::Error), + #[error("unknown program {0}")] + UnknownProgramName(String), + #[error("Not allowed to open path {}{}", .path, .with.as_ref().map(|w| format!(" with {w}")).unwrap_or_default())] + ForbiddenPath { path: String, with: Option }, + #[error("Not allowed to open url {}{}", .url, .with.as_ref().map(|w| format!(" with {w}")).unwrap_or_default())] + ForbiddenUrl { url: String, with: Option }, + #[error("API not supported on the current platform")] + UnsupportedPlatform, + #[error(transparent)] + #[cfg(windows)] + Win32Error(#[from] windows::core::Error), + #[error("Path doesn't have a parent: {0}")] + NoParent(PathBuf), + #[cfg(windows)] + #[error("Failed to convert path '{0}' to ITEMIDLIST")] + FailedToConvertPathToItemIdList(PathBuf), + #[error("Failed to convert path to file:// url")] + FailedToConvertPathToFileUrl, + #[error(transparent)] + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + Zbus(#[from] zbus::Error), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/packages/kbot/gui/app/plugins/opener/src/init-iife.js b/packages/kbot/gui/app/plugins/opener/src/init-iife.js new file mode 100644 index 00000000..51f6f068 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/src/init-iife.js @@ -0,0 +1 @@ +!function(){"use strict";"function"==typeof SuppressedError&&SuppressedError,window.addEventListener("click",(function(e){if(e.defaultPrevented||0!==e.button||e.metaKey||e.altKey)return;const t=e.composedPath().find((e=>e instanceof Node&&"A"===e.nodeName.toUpperCase()));if(!t||!t.href||"_blank"!==t.target&&!e.ctrlKey&&!e.shiftKey)return;const n=new URL(t.href);n.origin===window.location.origin||["http:","https:","mailto:","tel:"].every((e=>n.protocol!==e))||(e.preventDefault(),async function(e,t={},n){window.__TAURI_INTERNALS__.invoke(e,t,n)}("plugin:opener|open_url",{url:n}))}))}(); diff --git a/packages/kbot/gui/app/plugins/opener/src/lib.rs b/packages/kbot/gui/app/plugins/opener/src/lib.rs new file mode 100644 index 00000000..343e9155 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/src/lib.rs @@ -0,0 +1,238 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::Path; + +use tauri::{plugin::TauriPlugin, Manager, Runtime}; + +#[cfg(mobile)] +use tauri::plugin::PluginHandle; +#[cfg(target_os = "android")] +const PLUGIN_IDENTIFIER: &str = "app.tauri.opener"; +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_opener); + +mod commands; +mod config; +mod error; +mod open; +mod reveal_item_in_dir; +mod scope; +mod scope_entry; + +pub use error::Error; +type Result = std::result::Result; + +pub use open::{open_path, open_url}; +pub use reveal_item_in_dir::{reveal_item_in_dir, reveal_items_in_dir}; + +pub struct Opener { + // we use `fn() -> R` to silence the unused generic error + // while keeping this struct `Send + Sync` without requiring `R` to be + #[cfg(not(mobile))] + _marker: std::marker::PhantomData R>, + #[cfg(mobile)] + mobile_plugin_handle: PluginHandle, + require_literal_leading_dot: Option, +} + +impl Opener { + /// Open a url with a default or specific program. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri_plugin_opener::OpenerExt; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// // open the given URL on the system default browser + /// app.opener().open_url("https://github.com/tauri-apps/tauri", None::<&str>)?; + /// Ok(()) + /// }); + /// ``` + /// + /// ## Platform-specific: + /// + /// - **Android / iOS**: Always opens using default program, unless `with` is provided as "inAppBrowser". + #[cfg(desktop)] + pub fn open_url(&self, url: impl Into, with: Option>) -> Result<()> { + crate::open::open(url.into(), with.map(Into::into)) + } + + /// Open a url with a default or specific program. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri_plugin_opener::OpenerExt; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// // open the given URL on the system default browser + /// app.opener().open_url("https://github.com/tauri-apps/tauri", None::<&str>)?; + /// Ok(()) + /// }); + /// ``` + /// + /// ## Platform-specific: + /// + /// - **Android / iOS**: Always opens using default program, unless `with` is provided as "inAppBrowser". + #[cfg(mobile)] + pub fn open_url(&self, url: impl Into, with: Option>) -> Result<()> { + self.mobile_plugin_handle + .run_mobile_plugin( + "open", + serde_json::json!({ "url": url.into(), "with": with.map(Into::into) }), + ) + .map_err(Into::into) + } + + /// Open a path with a default or specific program. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri_plugin_opener::OpenerExt; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// // open the given path on the system default explorer + /// app.opener().open_path("/path/to/file", None::<&str>)?; + /// Ok(()) + /// }); + /// ``` + /// + /// ## Platform-specific: + /// + /// - **Android / iOS**: Always opens using default program. + #[cfg(desktop)] + pub fn open_path( + &self, + path: impl Into, + with: Option>, + ) -> Result<()> { + crate::open::open(path.into(), with.map(Into::into)) + } + + /// Open a path with a default or specific program. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri_plugin_opener::OpenerExt; + /// + /// tauri::Builder::default() + /// .setup(|app| { + /// // open the given path on the system default explorer + /// app.opener().open_path("/path/to/file", None::<&str>)?; + /// Ok(()) + /// }); + /// ``` + /// + /// ## Platform-specific: + /// + /// - **Android / iOS**: Always opens using default program. + #[cfg(mobile)] + pub fn open_path( + &self, + path: impl Into, + _with: Option>, + ) -> Result<()> { + self.mobile_plugin_handle + .run_mobile_plugin("open", path.into()) + .map_err(Into::into) + } + + pub fn reveal_item_in_dir>(&self, p: P) -> Result<()> { + reveal_item_in_dir(p) + } + + pub fn reveal_items_in_dir(&self, paths: I) -> Result<()> + where + I: IntoIterator, + P: AsRef, + { + reveal_items_in_dir(paths) + } +} + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the opener APIs. +pub trait OpenerExt { + fn opener(&self) -> &Opener; +} + +impl> OpenerExt for T { + fn opener(&self) -> &Opener { + self.state::>().inner() + } +} + +/// The opener plugin Builder. +pub struct Builder { + open_js_links_on_click: bool, +} + +impl Default for Builder { + fn default() -> Self { + Self { + open_js_links_on_click: true, + } + } +} + +impl Builder { + /// Create a new opener plugin Builder. + pub fn new() -> Self { + Self::default() + } + + /// Whether the plugin should inject a JS script to open URLs in default browser + /// when clicking on `` elements that has `_blank` target, or when pressing `Ctrl` or `Shift` while clicking it. + /// + /// Enabled by default for `http:`, `https:`, `mailto:`, `tel:` links. + pub fn open_js_links_on_click(mut self, open: bool) -> Self { + self.open_js_links_on_click = open; + self + } + + /// Build and Initializes the plugin. + pub fn build(self) -> TauriPlugin> { + let mut builder = tauri::plugin::Builder::>::new("opener") + .setup(|app, api| { + #[cfg(target_os = "android")] + let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "OpenerPlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_opener)?; + + app.manage(Opener { + #[cfg(not(mobile))] + _marker: std::marker::PhantomData:: R>, + #[cfg(mobile)] + mobile_plugin_handle: handle, + require_literal_leading_dot: api + .config() + .as_ref() + .and_then(|c| c.require_literal_leading_dot), + }); + Ok(()) + }) + .invoke_handler(tauri::generate_handler![ + commands::open_url, + commands::open_path, + commands::reveal_item_in_dir, + ]); + + if self.open_js_links_on_click { + builder = builder.js_init_script(include_str!("init-iife.js").to_string()); + } + + builder.build() + } +} + +/// Initializes the plugin. +pub fn init() -> TauriPlugin> { + Builder::default().build() +} diff --git a/packages/kbot/gui/app/plugins/opener/src/open.rs b/packages/kbot/gui/app/plugins/opener/src/open.rs new file mode 100644 index 00000000..a3d46c50 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/src/open.rs @@ -0,0 +1,61 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Types and functions related to shell. + +use std::{ffi::OsStr, path::Path}; + +pub(crate) fn open, S: AsRef>(path: P, with: Option) -> crate::Result<()> { + match with { + Some(program) => ::open::with_detached(path, program.as_ref()), + None => ::open::that_detached(path), + } + .map_err(Into::into) +} + +/// Opens URL with the program specified in `with`, or system default if `None`. +/// +/// ## Platform-specific: +/// +/// - **Android / iOS**: Always opens using default program. +/// +/// # Examples +/// +/// ```rust,no_run +/// tauri::Builder::default() +/// .setup(|app| { +/// // open the given URL on the system default browser +/// tauri_plugin_opener::open_url("https://github.com/tauri-apps/tauri", None::<&str>)?; +/// Ok(()) +/// }); +/// ``` +pub fn open_url, S: AsRef>(url: P, with: Option) -> crate::Result<()> { + let url = url.as_ref(); + open(url, with) +} + +/// Opens path with the program specified in `with`, or system default if `None`. +/// +/// ## Platform-specific: +/// +/// - **Android / iOS**: Always opens using default program. +/// +/// # Examples +/// +/// ```rust,no_run +/// tauri::Builder::default() +/// .setup(|app| { +/// // open the given URL on the system default explorer +/// tauri_plugin_opener::open_path("/path/to/file", None::<&str>)?; +/// Ok(()) +/// }); +/// ``` +pub fn open_path, S: AsRef>(path: P, with: Option) -> crate::Result<()> { + let path = path.as_ref(); + if with.is_none() { + // Returns an IO error if not exists, and besides `exists()` is a shorthand for `metadata()` + _ = path.metadata()?; + } + open(path, with) +} diff --git a/packages/kbot/gui/app/plugins/opener/src/reveal_item_in_dir.rs b/packages/kbot/gui/app/plugins/opener/src/reveal_item_in_dir.rs new file mode 100644 index 00000000..6112fb8b --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/src/reveal_item_in_dir.rs @@ -0,0 +1,299 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::Path; + +/// Reveal a path the system's default explorer. +/// +/// ## Platform-specific: +/// +/// - **Android / iOS:** Unsupported. +pub fn reveal_item_in_dir>(path: P) -> crate::Result<()> { + let path = dunce::canonicalize(path.as_ref())?; + + #[cfg(any( + windows, + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + return imp::reveal_items_in_dir(&[path]); + + #[cfg(not(any( + windows, + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + )))] + Err(crate::Error::UnsupportedPlatform) +} + +/// Reveal the paths the system's default explorer. +/// +/// ## Platform-specific: +/// +/// - **Android / iOS:** Unsupported. +pub fn reveal_items_in_dir(paths: I) -> crate::Result<()> +where + I: IntoIterator, + P: AsRef, +{ + let mut canonicalized = vec![]; + + for path in paths { + let path = dunce::canonicalize(path.as_ref())?; + canonicalized.push(path); + } + + #[cfg(any( + windows, + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + return imp::reveal_items_in_dir(&canonicalized); + + #[cfg(not(any( + windows, + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + )))] + Err(crate::Error::UnsupportedPlatform) +} + +#[cfg(windows)] +mod imp { + use std::collections::HashMap; + use std::path::{Path, PathBuf}; + + use windows::Win32::UI::Shell::Common::ITEMIDLIST; + use windows::{ + core::{w, HSTRING, PCWSTR}, + Win32::{ + Foundation::ERROR_FILE_NOT_FOUND, + System::Com::CoInitialize, + UI::{ + Shell::{ + ILCreateFromPathW, ILFree, SHOpenFolderAndSelectItems, ShellExecuteExW, + SHELLEXECUTEINFOW, + }, + WindowsAndMessaging::SW_SHOWNORMAL, + }, + }, + }; + + pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> { + if paths.is_empty() { + return Ok(()); + } + + let mut grouped_paths: HashMap<&Path, Vec<&Path>> = HashMap::new(); + for path in paths { + let parent = path + .parent() + .ok_or_else(|| crate::Error::NoParent(path.to_path_buf()))?; + grouped_paths.entry(parent).or_default().push(path); + } + + let _ = unsafe { CoInitialize(None) }; + + for (parent, to_reveals) in grouped_paths { + let parent_item_id_list = OwnedItemIdList::new(parent)?; + let to_reveals_item_id_list = to_reveals + .iter() + .map(|to_reveal| OwnedItemIdList::new(*to_reveal)) + .collect::>>()?; + if let Err(e) = unsafe { + SHOpenFolderAndSelectItems( + parent_item_id_list.item, + Some( + &to_reveals_item_id_list + .iter() + .map(|item| item.item) + .collect::>(), + ), + 0, + ) + } { + // from https://github.com/electron/electron/blob/10d967028af2e72382d16b7e2025d243b9e204ae/shell/common/platform_util_win.cc#L302 + // On some systems, the above call mysteriously fails with "file not + // found" even though the file is there. In these cases, ShellExecute() + // seems to work as a fallback (although it won't select the file). + // + // Note: we only handle the first file here if multiple of are present + if e.code().0 == ERROR_FILE_NOT_FOUND.0 as i32 { + let first_path = to_reveals[0]; + let is_dir = first_path.is_dir(); + let mut info = SHELLEXECUTEINFOW { + cbSize: std::mem::size_of::() as _, + nShow: SW_SHOWNORMAL.0, + lpFile: PCWSTR(parent_item_id_list.hstring.as_ptr()), + lpClass: if is_dir { w!("folder") } else { PCWSTR::null() }, + lpVerb: if is_dir { + w!("explore") + } else { + PCWSTR::null() + }, + ..Default::default() + }; + + unsafe { ShellExecuteExW(&mut info) }?; + } + } + } + + Ok(()) + } + + struct OwnedItemIdList { + hstring: HSTRING, + item: *const ITEMIDLIST, + } + + impl OwnedItemIdList { + fn new(path: &Path) -> crate::Result { + let path_hstring = HSTRING::from(path); + let item_id_list = unsafe { ILCreateFromPathW(&path_hstring) }; + if item_id_list.is_null() { + Err(crate::Error::FailedToConvertPathToItemIdList( + path.to_owned(), + )) + } else { + Ok(Self { + hstring: path_hstring, + item: item_id_list, + }) + } + } + } + + impl Drop for OwnedItemIdList { + fn drop(&mut self) { + if !self.item.is_null() { + unsafe { ILFree(Some(self.item)) }; + } + } + } +} + +#[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] +mod imp { + use super::*; + use std::collections::HashMap; + use std::path::PathBuf; + + pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> { + let connection = zbus::blocking::Connection::session()?; + + reveal_with_filemanager1(paths, &connection).or_else(|e| { + // Fallback to opening the directory of the first item if revealing multiple items fails. + if let Some(first_path) = paths.first() { + reveal_with_open_uri_portal(first_path, &connection) + } else { + Err(e) + } + }) + } + + fn reveal_with_filemanager1( + paths: &[PathBuf], + connection: &zbus::blocking::Connection, + ) -> crate::Result<()> { + let uris: Result, _> = paths + .iter() + .map(|path| { + url::Url::from_file_path(path) + .map_err(|_| crate::Error::FailedToConvertPathToFileUrl) + }) + .collect(); + let uris = uris?; + let uri_strs: Vec<&str> = uris.iter().map(|uri| uri.as_str()).collect(); + + #[zbus::proxy( + interface = "org.freedesktop.FileManager1", + default_service = "org.freedesktop.FileManager1", + default_path = "/org/freedesktop/FileManager1" + )] + trait FileManager1 { + async fn ShowItems(&self, name: Vec<&str>, arg2: &str) -> crate::Result<()>; + } + + let proxy = FileManager1ProxyBlocking::new(connection)?; + + proxy.ShowItems(uri_strs, "") + } + + fn reveal_with_open_uri_portal( + path: &Path, + connection: &zbus::blocking::Connection, + ) -> crate::Result<()> { + let uri = url::Url::from_file_path(path) + .map_err(|_| crate::Error::FailedToConvertPathToFileUrl)?; + + #[zbus::proxy( + interface = "org.freedesktop.portal.Desktop", + default_service = "org.freedesktop.portal.OpenURI", + default_path = "/org/freedesktop/portal/desktop" + )] + trait PortalDesktop { + async fn OpenDirectory( + &self, + arg1: &str, + name: &str, + arg3: HashMap<&str, &str>, + ) -> crate::Result<()>; + } + + let proxy = PortalDesktopProxyBlocking::new(connection)?; + + proxy.OpenDirectory("", uri.as_str(), HashMap::new()) + } +} + +#[cfg(target_os = "macos")] +mod imp { + use objc2_app_kit::NSWorkspace; + use objc2_foundation::{NSArray, NSString, NSURL}; + use std::path::PathBuf; + + pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> { + unsafe { + let mut urls = Vec::new(); + + for path in paths { + let path = path.to_string_lossy(); + let path = NSString::from_str(&path); + let url = NSURL::fileURLWithPath(&path); + + urls.push(url); + } + + let urls = NSArray::from_retained_slice(&urls); + + let workspace = NSWorkspace::new(); + workspace.activateFileViewerSelectingURLs(&urls); + } + + Ok(()) + } +} diff --git a/packages/kbot/gui/app/plugins/opener/src/scope.rs b/packages/kbot/gui/app/plugins/opener/src/scope.rs new file mode 100644 index 00000000..275913d1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/src/scope.rs @@ -0,0 +1,141 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{ + marker::PhantomData, + path::{Path, PathBuf}, + sync::Arc, +}; + +use tauri::{ipc::ScopeObject, utils::acl::Value, AppHandle, Manager, Runtime}; + +use crate::{scope_entry::EntryRaw, Error}; + +pub use crate::scope_entry::Application; + +#[derive(Debug)] +pub enum Entry { + Url { + url: glob::Pattern, + app: Application, + }, + Path { + path: Option, + app: Application, + }, +} + +impl ScopeObject for Entry { + type Error = Error; + + fn deserialize( + app_handle: &AppHandle, + raw: Value, + ) -> std::result::Result { + serde_json::from_value(raw.into()) + .and_then(|raw| { + let entry = match raw { + EntryRaw::Url { url, app } => Entry::Url { + url: glob::Pattern::new(&url) + .map_err(|e| serde::de::Error::custom(e.to_string()))?, + app, + }, + EntryRaw::Path { path, app } => { + let path = match app_handle.path().parse(path) { + Ok(path) => Some(path), + #[cfg(not(target_os = "android"))] + Err(tauri::Error::UnknownPath) => None, + Err(err) => return Err(serde::de::Error::custom(err.to_string())), + }; + + Entry::Path { path, app } + } + }; + + Ok(entry) + }) + .map_err(Into::into) + } +} + +impl Application { + fn matches(&self, a: Option<&str>) -> bool { + match self { + Self::Default => a.is_none(), + Self::Enable(enable) => *enable, + Self::App(program) => Some(program.as_str()) == a, + } + } +} + +impl Entry { + fn path(&self) -> Option { + match self { + Self::Url { .. } => None, + Self::Path { path, .. } => path.clone(), + } + } + + fn matches_url(&self, u: &str, a: Option<&str>) -> bool { + match self { + Self::Url { url, app } => url.matches(u) && app.matches(a), + Self::Path { .. } => false, + } + } + + fn matches_path_program(&self, a: Option<&str>) -> bool { + match self { + Self::Url { .. } => false, + Self::Path { app, .. } => app.matches(a), + } + } +} + +#[derive(Debug)] +pub struct Scope<'a, R: Runtime, M: Manager> { + allowed: Vec<&'a Arc>, + denied: Vec<&'a Arc>, + manager: &'a M, + _marker: PhantomData, +} + +impl<'a, R: Runtime, M: Manager> Scope<'a, R, M> { + pub(crate) fn new( + manager: &'a M, + allowed: Vec<&'a Arc>, + denied: Vec<&'a Arc>, + ) -> Self { + Self { + manager, + allowed, + denied, + _marker: PhantomData, + } + } + + pub fn is_url_allowed(&self, url: &str, with: Option<&str>) -> bool { + let denied = self.denied.iter().any(|e| e.matches_url(url, with)); + if denied { + false + } else { + self.allowed.iter().any(|e| e.matches_url(url, with)) + } + } + + pub fn is_path_allowed(&self, path: &Path, with: Option<&str>) -> crate::Result { + let fs_scope = tauri::fs::Scope::new( + self.manager, + &tauri::utils::config::FsScope::Scope { + allow: self.allowed.iter().filter_map(|e| e.path()).collect(), + deny: self.denied.iter().filter_map(|e| e.path()).collect(), + require_literal_leading_dot: self + .manager + .state::>() + .require_literal_leading_dot, + }, + )?; + + Ok(fs_scope.is_allowed(path) && self.allowed.iter().any(|e| e.matches_path_program(with))) + } +} diff --git a/packages/kbot/gui/app/plugins/opener/src/scope_entry.rs b/packages/kbot/gui/app/plugins/opener/src/scope_entry.rs new file mode 100644 index 00000000..cf9004a2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/src/scope_entry.rs @@ -0,0 +1,36 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::PathBuf; + +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +#[serde(untagged)] +pub enum Application { + Default, + Enable(bool), + App(String), +} + +impl Default for Application { + fn default() -> Self { + Self::Default + } +} + +#[derive(Deserialize)] +#[serde(untagged, rename_all = "camelCase")] +pub(crate) enum EntryRaw { + Url { + url: String, + #[serde(default)] + app: Application, + }, + Path { + path: PathBuf, + #[serde(default)] + app: Application, + }, +} diff --git a/packages/kbot/gui/app/plugins/opener/tsconfig.json b/packages/kbot/gui/app/plugins/opener/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/opener/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/os/CHANGELOG.md b/packages/kbot/gui/app/plugins/os/CHANGELOG.md new file mode 100644 index 00000000..0bce6532 --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/CHANGELOG.md @@ -0,0 +1,120 @@ +# Changelog + +## \[2.3.1] + +- [`d3d290ab`](https://github.com/tauri-apps/plugins-workspace/commit/d3d290ab8a8913981a98e2eb7f2c5d4aba3bc36c) ([#2912](https://github.com/tauri-apps/plugins-workspace/pull/2912) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Unlocked version of `serialize-to-javascript` from `=0.1.1` to `^0.1.1` for compatibility with Tauri's upcoming version `2.8`. + +## \[2.3.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.2.2] + +- [`f634e524`](https://github.com/tauri-apps/plugins-workspace/commit/f634e5248ebe428f8305a59f74c13fc15147fb8e) This is an "empty" release to update the plugins' source files on crates.io and docs.rs. This should fix docs.rs build failures for projects using tauri plugins as dependencies. + +## \[2.2.1] + +- [`a1b3fa27`](https://github.com/tauri-apps/plugins-workspace/commit/a1b3fa27f11022c9b6622b4fab12d93239eb05de) ([#2515](https://github.com/tauri-apps/plugins-workspace/pull/2515) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Re-exported the `Geolocation`, `Haptics`, `Notification`, and `Os` structs so that they show up on docs.rs. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +## \[2.0.0-beta.8] + +- [`99d6ac0f`](https://github.com/tauri-apps/plugins-workspace/commit/99d6ac0f9506a6a4a1aa59c728157190a7441af6) ([#1606](https://github.com/tauri-apps/plugins-workspace/pull/1606) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The JS packages now specify the *minimum* `@tauri-apps/api` version instead of a single exact version. +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.7] + +- [`40ef9a81`](https://github.com/tauri-apps/plugins-workspace/commit/40ef9a818fb03457819c1d72ea84de57fbf868ba) ([#1514](https://github.com/tauri-apps/plugins-workspace/pull/1514) by [@fynntang](https://github.com/tauri-apps/plugins-workspace/../../fynntang)) **Changed:** `platform`, `arch`, `type`, `family`, `version` and `exe_extension` functions in the documentation examples to better reflect that these functions are synchronous. +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.6] + +- [`0959fe37`](https://github.com/tauri-apps/plugins-workspace/commit/0959fe3757250c6dea6247edb20e6ab468f20511) ([#1353](https://github.com/tauri-apps/plugins-workspace/pull/1353) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) **Breaking** Changed `platform`, `arch`, `type`, `family`, `version` and `exe_extension` functions to be sync. +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.5] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.4] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.3] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.3] + +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. + +## \[2.0.0-beta.2] + +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. + +## \[2.0.0-alpha.6] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.13. + +## \[2.0.0-alpha.5] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.12. +- [`fc62ead`](https://github.com/tauri-apps/plugins-workspace/commit/fc62ead56515b64138b8342af1c5ec6071b715fc)([#721](https://github.com/tauri-apps/plugins-workspace/pull/721)) Fix `Uncaught TypeError: Cannot read properties of undefined (reading 'os')` + +## \[2.0.0-alpha.4] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to @tauri-apps/api v2.0.0-alpha.11. + +## \[2.0.0-alpha.3] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update to @tauri-apps/api v2.0.0-alpha.9. + +## \[2.0.0-alpha.2] + +- [`4e2cef9`](https://github.com/tauri-apps/plugins-workspace/commit/4e2cef9b702bbbb9cf4ee17de50791cb21f1b2a4)([#593](https://github.com/tauri-apps/plugins-workspace/pull/593)) Update to alpha.12. + +## \[2.0.0-alpha.2] + +- [`e510f2f`](https://github.com/tauri-apps/plugins-workspace/commit/e510f2fe4c227c107a1faca9386b5ceb326611ed)([#561](https://github.com/tauri-apps/plugins-workspace/pull/561)) Fix `macss -> macos` typo in `OsType` type. + +## \[2.0.0-alpha.1] + +- [`1091d6d`](https://github.com/tauri-apps/plugins-workspace/commit/1091d6d6ac5081f2c7526b0f492ae4f34b306f1d)([#419](https://github.com/tauri-apps/plugins-workspace/pull/419)) The os plugin is recieving a few changes to improve consistency and add new features: + + - Renamed `Kind` enum to `OsType` and `kind()` function to `os_type()`. + - Added `family()`,`exe_extension()`, and `hostname()` functions and their equivalents for JS. + - Removed `tempdir()` function and its equivalent on JS, use `std::env::temp_dir` instead of `temp_dir` from `tauri::path::PathResolver::temp_dir` and `path.tempDir` on JS. + - Modified `platform()` implementation to return `windows` instead of `win32` and `macos` instead of `darwin` to align with Rust's `std::env::consts::OS` + - `EOL` const in JS has been modified into a function `eol()` fix import issues in frameworks like `next.js` +- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11. + +## \[2.0.0-alpha.0] + +- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! diff --git a/packages/kbot/gui/app/plugins/os/Cargo.toml b/packages/kbot/gui/app/plugins/os/Cargo.toml new file mode 100644 index 00000000..09ee3d90 --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "tauri-plugin-os" +version = "2.3.1" +description = "Read information about the operating system." +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-os" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.platforms.support] +windows = { level = "full", notes = "" } +linux = { level = "full", notes = "" } +macos = { level = "full", notes = "" } +android = { level = "full", notes = "" } +ios = { level = "full", notes = "" } + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } +os_info = "3" +sys-locale = "0.3" +gethostname = "1.0" +serialize-to-javascript = "0.1.1" diff --git a/packages/kbot/gui/app/plugins/os/LICENSE.spdx b/packages/kbot/gui/app/plugins/os/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/os/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/os/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/os/LICENSE_MIT b/packages/kbot/gui/app/plugins/os/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/os/README.md b/packages/kbot/gui/app/plugins/os/README.md new file mode 100644 index 00000000..d3f58b26 --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/README.md @@ -0,0 +1,90 @@ +![plugin-os](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/os/banner.png) + +Read information about the operating system. + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | ✓ | +| iOS | ✓ | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-os = "2.0.0" +# alternatively with Git: +tauri-plugin-os = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +```sh +pnpm add @tauri-apps/plugin-os +# or +npm add @tauri-apps/plugin-os +# or +yarn add @tauri-apps/plugin-os +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_os::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { version } from '@tauri-apps/plugin-os' +const osVersion = await version() +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/os/SECURITY.md b/packages/kbot/gui/app/plugins/os/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/os/api-iife.js b/packages/kbot/gui/app/plugins/os/api-iife.js new file mode 100644 index 00000000..2b7924de --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_OS__=function(_){"use strict";async function n(_,n={},o){return window.__TAURI_INTERNALS__.invoke(_,n,o)}return"function"==typeof SuppressedError&&SuppressedError,_.arch=function(){return window.__TAURI_OS_PLUGIN_INTERNALS__.arch},_.eol=function(){return window.__TAURI_OS_PLUGIN_INTERNALS__.eol},_.exeExtension=function(){return window.__TAURI_OS_PLUGIN_INTERNALS__.exe_extension},_.family=function(){return window.__TAURI_OS_PLUGIN_INTERNALS__.family},_.hostname=async function(){return await n("plugin:os|hostname")},_.locale=async function(){return await n("plugin:os|locale")},_.platform=function(){return window.__TAURI_OS_PLUGIN_INTERNALS__.platform},_.type=function(){return window.__TAURI_OS_PLUGIN_INTERNALS__.os_type},_.version=function(){return window.__TAURI_OS_PLUGIN_INTERNALS__.version},_}({});Object.defineProperty(window.__TAURI__,"os",{value:__TAURI_PLUGIN_OS__})} diff --git a/packages/kbot/gui/app/plugins/os/banner.png b/packages/kbot/gui/app/plugins/os/banner.png new file mode 100644 index 00000000..08b4fd8c Binary files /dev/null and b/packages/kbot/gui/app/plugins/os/banner.png differ diff --git a/packages/kbot/gui/app/plugins/os/build.rs b/packages/kbot/gui/app/plugins/os/build.rs new file mode 100644 index 00000000..f108f965 --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/build.rs @@ -0,0 +1,20 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +const COMMANDS: &[&str] = &[ + "platform", + "version", + "os_type", + "family", + "arch", + "exe_extension", + "locale", + "hostname", +]; + +fn main() { + tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .build(); +} diff --git a/packages/kbot/gui/app/plugins/os/guest-js/index.ts b/packages/kbot/gui/app/plugins/os/guest-js/index.ts new file mode 100644 index 00000000..697ae8ed --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/guest-js/index.ts @@ -0,0 +1,196 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/** + * Provides operating system-related utility methods and properties. + * + * @module + */ + +import { invoke } from '@tauri-apps/api/core' + +/** @ignore */ +declare global { + interface Window { + __TAURI_OS_PLUGIN_INTERNALS__: { + eol: string + os_type: OsType + platform: Platform + family: Family + version: string + arch: Arch + exe_extension: string + } + } +} + +type Platform = + | 'linux' + | 'macos' + | 'ios' + | 'freebsd' + | 'dragonfly' + | 'netbsd' + | 'openbsd' + | 'solaris' + | 'android' + | 'windows' + +type OsType = 'linux' | 'windows' | 'macos' | 'ios' | 'android' + +type Arch = + | 'x86' + | 'x86_64' + | 'arm' + | 'aarch64' + | 'mips' + | 'mips64' + | 'powerpc' + | 'powerpc64' + | 'riscv64' + | 's390x' + | 'sparc64' + +/** + * Returns the operating system-specific end-of-line marker. + * - `\n` on POSIX + * - `\r\n` on Windows + * + * @since 2.0.0 + * */ +function eol(): string { + return window.__TAURI_OS_PLUGIN_INTERNALS__.eol +} + +/** + * Returns a string describing the specific operating system in use. + * The value is set at compile time. Possible values are `'linux'`, `'macos'`, `'ios'`, `'freebsd'`, `'dragonfly'`, `'netbsd'`, `'openbsd'`, `'solaris'`, `'android'`, `'windows'` + * + * @example + * ```typescript + * import { platform } from '@tauri-apps/plugin-os'; + * const platformName = platform(); + * ``` + * + * @since 2.0.0 + * + */ +function platform(): Platform { + return window.__TAURI_OS_PLUGIN_INTERNALS__.platform +} + +/** + * Returns the current operating system version. + * @example + * ```typescript + * import { version } from '@tauri-apps/plugin-os'; + * const osVersion = version(); + * ``` + * + * @since 2.0.0 + */ +function version(): string { + return window.__TAURI_OS_PLUGIN_INTERNALS__.version +} + +type Family = 'unix' | 'windows' + +/** + * Returns the current operating system family. Possible values are `'unix'`, `'windows'`. + * @example + * ```typescript + * import { family } from '@tauri-apps/plugin-os'; + * const family = family(); + * ``` + * + * @since 2.0.0 + */ +function family(): Family { + return window.__TAURI_OS_PLUGIN_INTERNALS__.family +} + +/** + * Returns the current operating system type. Returns `'linux'` on Linux, `'macos'` on macOS, `'windows'` on Windows, `'ios'` on iOS and `'android'` on Android. + * @example + * ```typescript + * import { type } from '@tauri-apps/plugin-os'; + * const osType = type(); + * ``` + * + * @since 2.0.0 + */ +function type(): OsType { + return window.__TAURI_OS_PLUGIN_INTERNALS__.os_type +} + +/** + * Returns the current operating system architecture. + * Possible values are `'x86'`, `'x86_64'`, `'arm'`, `'aarch64'`, `'mips'`, `'mips64'`, `'powerpc'`, `'powerpc64'`, `'riscv64'`, `'s390x'`, `'sparc64'`. + * @example + * ```typescript + * import { arch } from '@tauri-apps/plugin-os'; + * const archName = arch(); + * ``` + * + * @since 2.0.0 + */ +function arch(): Arch { + return window.__TAURI_OS_PLUGIN_INTERNALS__.arch +} + +/** + * Returns the file extension, if any, used for executable binaries on this platform. Possible values are `'exe'` and `''` (empty string). + * @example + * ```typescript + * import { exeExtension } from '@tauri-apps/plugin-os'; + * const exeExt = exeExtension(); + * ``` + * + * @since 2.0.0 + */ +function exeExtension(): string { + return window.__TAURI_OS_PLUGIN_INTERNALS__.exe_extension +} + +/** + * Returns a String with a `BCP-47` language tag inside. If the locale couldn’t be obtained, `null` is returned instead. + * @example + * ```typescript + * import { locale } from '@tauri-apps/plugin-os'; + * const locale = await locale(); + * if (locale) { + * // use the locale string here + * } + * ``` + * + * @since 2.0.0 + */ +async function locale(): Promise { + return await invoke('plugin:os|locale') +} + +/** + * Returns the host name of the operating system. + * @example + * ```typescript + * import { hostname } from '@tauri-apps/plugin-os'; + * const hostname = await hostname(); + * ``` + */ +async function hostname(): Promise { + return await invoke('plugin:os|hostname') +} + +export { + eol, + platform, + family, + version, + type, + arch, + locale, + exeExtension, + hostname +} +export type { Platform, OsType, Arch, Family } diff --git a/packages/kbot/gui/app/plugins/os/package.json b/packages/kbot/gui/app/plugins/os/package.json new file mode 100644 index 00000000..4e332a37 --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/package.json @@ -0,0 +1,29 @@ +{ + "name": "@tauri-apps/plugin-os", + "version": "2.3.1", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/arch.toml b/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/arch.toml new file mode 100644 index 00000000..160cce0d --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/arch.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-arch" +description = "Enables the arch command without any pre-configured scope." +commands.allow = ["arch"] + +[[permission]] +identifier = "deny-arch" +description = "Denies the arch command without any pre-configured scope." +commands.deny = ["arch"] diff --git a/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/exe_extension.toml b/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/exe_extension.toml new file mode 100644 index 00000000..aef4d2e6 --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/exe_extension.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-exe-extension" +description = "Enables the exe_extension command without any pre-configured scope." +commands.allow = ["exe_extension"] + +[[permission]] +identifier = "deny-exe-extension" +description = "Denies the exe_extension command without any pre-configured scope." +commands.deny = ["exe_extension"] diff --git a/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/family.toml b/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/family.toml new file mode 100644 index 00000000..4109be39 --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/family.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-family" +description = "Enables the family command without any pre-configured scope." +commands.allow = ["family"] + +[[permission]] +identifier = "deny-family" +description = "Denies the family command without any pre-configured scope." +commands.deny = ["family"] diff --git a/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/hostname.toml b/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/hostname.toml new file mode 100644 index 00000000..c65376cb --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/hostname.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-hostname" +description = "Enables the hostname command without any pre-configured scope." +commands.allow = ["hostname"] + +[[permission]] +identifier = "deny-hostname" +description = "Denies the hostname command without any pre-configured scope." +commands.deny = ["hostname"] diff --git a/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/locale.toml b/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/locale.toml new file mode 100644 index 00000000..2a85e471 --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/locale.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-locale" +description = "Enables the locale command without any pre-configured scope." +commands.allow = ["locale"] + +[[permission]] +identifier = "deny-locale" +description = "Denies the locale command without any pre-configured scope." +commands.deny = ["locale"] diff --git a/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/os_type.toml b/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/os_type.toml new file mode 100644 index 00000000..c91987a3 --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/os_type.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-os-type" +description = "Enables the os_type command without any pre-configured scope." +commands.allow = ["os_type"] + +[[permission]] +identifier = "deny-os-type" +description = "Denies the os_type command without any pre-configured scope." +commands.deny = ["os_type"] diff --git a/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/platform.toml b/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/platform.toml new file mode 100644 index 00000000..49dc08d6 --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/platform.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-platform" +description = "Enables the platform command without any pre-configured scope." +commands.allow = ["platform"] + +[[permission]] +identifier = "deny-platform" +description = "Denies the platform command without any pre-configured scope." +commands.deny = ["platform"] diff --git a/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/version.toml b/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/version.toml new file mode 100644 index 00000000..2d65e325 --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/permissions/autogenerated/commands/version.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-version" +description = "Enables the version command without any pre-configured scope." +commands.allow = ["version"] + +[[permission]] +identifier = "deny-version" +description = "Denies the version command without any pre-configured scope." +commands.deny = ["version"] diff --git a/packages/kbot/gui/app/plugins/os/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/os/permissions/autogenerated/reference.md new file mode 100644 index 00000000..7ac73751 --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/permissions/autogenerated/reference.md @@ -0,0 +1,237 @@ +## Default Permission + +This permission set configures which +operating system information are available +to gather from the frontend. + +#### Granted Permissions + +All information except the host name are available. + +#### This default permission set includes the following: + +- `allow-arch` +- `allow-exe-extension` +- `allow-family` +- `allow-locale` +- `allow-os-type` +- `allow-platform` +- `allow-version` + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`os:allow-arch` + + + +Enables the arch command without any pre-configured scope. + +
+ +`os:deny-arch` + + + +Denies the arch command without any pre-configured scope. + +
+ +`os:allow-exe-extension` + + + +Enables the exe_extension command without any pre-configured scope. + +
+ +`os:deny-exe-extension` + + + +Denies the exe_extension command without any pre-configured scope. + +
+ +`os:allow-family` + + + +Enables the family command without any pre-configured scope. + +
+ +`os:deny-family` + + + +Denies the family command without any pre-configured scope. + +
+ +`os:allow-hostname` + + + +Enables the hostname command without any pre-configured scope. + +
+ +`os:deny-hostname` + + + +Denies the hostname command without any pre-configured scope. + +
+ +`os:allow-locale` + + + +Enables the locale command without any pre-configured scope. + +
+ +`os:deny-locale` + + + +Denies the locale command without any pre-configured scope. + +
+ +`os:allow-os-type` + + + +Enables the os_type command without any pre-configured scope. + +
+ +`os:deny-os-type` + + + +Denies the os_type command without any pre-configured scope. + +
+ +`os:allow-platform` + + + +Enables the platform command without any pre-configured scope. + +
+ +`os:deny-platform` + + + +Denies the platform command without any pre-configured scope. + +
+ +`os:allow-version` + + + +Enables the version command without any pre-configured scope. + +
+ +`os:deny-version` + + + +Denies the version command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/os/permissions/default.toml b/packages/kbot/gui/app/plugins/os/permissions/default.toml new file mode 100644 index 00000000..217b389c --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/permissions/default.toml @@ -0,0 +1,23 @@ +"$schema" = "schemas/schema.json" + +[default] +description = """ +This permission set configures which +operating system information are available +to gather from the frontend. + +#### Granted Permissions + +All information except the host name are available. + +""" + +permissions = [ + "allow-arch", + "allow-exe-extension", + "allow-family", + "allow-locale", + "allow-os-type", + "allow-platform", + "allow-version", +] diff --git a/packages/kbot/gui/app/plugins/os/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/os/permissions/schemas/schema.json new file mode 100644 index 00000000..36680b44 --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/permissions/schemas/schema.json @@ -0,0 +1,402 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the arch command without any pre-configured scope.", + "type": "string", + "const": "allow-arch", + "markdownDescription": "Enables the arch command without any pre-configured scope." + }, + { + "description": "Denies the arch command without any pre-configured scope.", + "type": "string", + "const": "deny-arch", + "markdownDescription": "Denies the arch command without any pre-configured scope." + }, + { + "description": "Enables the exe_extension command without any pre-configured scope.", + "type": "string", + "const": "allow-exe-extension", + "markdownDescription": "Enables the exe_extension command without any pre-configured scope." + }, + { + "description": "Denies the exe_extension command without any pre-configured scope.", + "type": "string", + "const": "deny-exe-extension", + "markdownDescription": "Denies the exe_extension command without any pre-configured scope." + }, + { + "description": "Enables the family command without any pre-configured scope.", + "type": "string", + "const": "allow-family", + "markdownDescription": "Enables the family command without any pre-configured scope." + }, + { + "description": "Denies the family command without any pre-configured scope.", + "type": "string", + "const": "deny-family", + "markdownDescription": "Denies the family command without any pre-configured scope." + }, + { + "description": "Enables the hostname command without any pre-configured scope.", + "type": "string", + "const": "allow-hostname", + "markdownDescription": "Enables the hostname command without any pre-configured scope." + }, + { + "description": "Denies the hostname command without any pre-configured scope.", + "type": "string", + "const": "deny-hostname", + "markdownDescription": "Denies the hostname command without any pre-configured scope." + }, + { + "description": "Enables the locale command without any pre-configured scope.", + "type": "string", + "const": "allow-locale", + "markdownDescription": "Enables the locale command without any pre-configured scope." + }, + { + "description": "Denies the locale command without any pre-configured scope.", + "type": "string", + "const": "deny-locale", + "markdownDescription": "Denies the locale command without any pre-configured scope." + }, + { + "description": "Enables the os_type command without any pre-configured scope.", + "type": "string", + "const": "allow-os-type", + "markdownDescription": "Enables the os_type command without any pre-configured scope." + }, + { + "description": "Denies the os_type command without any pre-configured scope.", + "type": "string", + "const": "deny-os-type", + "markdownDescription": "Denies the os_type command without any pre-configured scope." + }, + { + "description": "Enables the platform command without any pre-configured scope.", + "type": "string", + "const": "allow-platform", + "markdownDescription": "Enables the platform command without any pre-configured scope." + }, + { + "description": "Denies the platform command without any pre-configured scope.", + "type": "string", + "const": "deny-platform", + "markdownDescription": "Denies the platform command without any pre-configured scope." + }, + { + "description": "Enables the version command without any pre-configured scope.", + "type": "string", + "const": "allow-version", + "markdownDescription": "Enables the version command without any pre-configured scope." + }, + { + "description": "Denies the version command without any pre-configured scope.", + "type": "string", + "const": "deny-version", + "markdownDescription": "Denies the version command without any pre-configured scope." + }, + { + "description": "This permission set configures which\noperating system information are available\nto gather from the frontend.\n\n#### Granted Permissions\n\nAll information except the host name are available.\n\n\n#### This default permission set includes:\n\n- `allow-arch`\n- `allow-exe-extension`\n- `allow-family`\n- `allow-locale`\n- `allow-os-type`\n- `allow-platform`\n- `allow-version`", + "type": "string", + "const": "default", + "markdownDescription": "This permission set configures which\noperating system information are available\nto gather from the frontend.\n\n#### Granted Permissions\n\nAll information except the host name are available.\n\n\n#### This default permission set includes:\n\n- `allow-arch`\n- `allow-exe-extension`\n- `allow-family`\n- `allow-locale`\n- `allow-os-type`\n- `allow-platform`\n- `allow-version`" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/os/rollup.config.js b/packages/kbot/gui/app/plugins/os/rollup.config.js new file mode 100644 index 00000000..1f349ec8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/rollup.config.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' + +export default createConfig() diff --git a/packages/kbot/gui/app/plugins/os/src/commands.rs b/packages/kbot/gui/app/plugins/os/src/commands.rs new file mode 100644 index 00000000..b10c7f5d --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/src/commands.rs @@ -0,0 +1,13 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#[tauri::command] +pub fn locale() -> Option { + crate::locale() +} + +#[tauri::command] +pub fn hostname() -> String { + crate::hostname() +} diff --git a/packages/kbot/gui/app/plugins/os/src/error.rs b/packages/kbot/gui/app/plugins/os/src/error.rs new file mode 100644 index 00000000..f5d8816a --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/src/error.rs @@ -0,0 +1,17 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{Serialize, Serializer}; + +#[derive(Debug, thiserror::Error)] +pub enum Error {} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/packages/kbot/gui/app/plugins/os/src/init.js b/packages/kbot/gui/app/plugins/os/src/init.js new file mode 100644 index 00000000..97eeab3a --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/src/init.js @@ -0,0 +1,16 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +// eslint-disable-next-line +Object.defineProperty(window, '__TAURI_OS_PLUGIN_INTERNALS__', { + value: { + eol: __TEMPLATE_eol__, + os_type: __TEMPLATE_os_type__, + platform: __TEMPLATE_platform__, + family: __TEMPLATE_family__, + version: __TEMPLATE_version__, + arch: __TEMPLATE_arch__, + exe_extension: __TEMPLATE_exe_extension__ + } +}) diff --git a/packages/kbot/gui/app/plugins/os/src/lib.rs b/packages/kbot/gui/app/plugins/os/src/lib.rs new file mode 100644 index 00000000..50ab89ee --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/src/lib.rs @@ -0,0 +1,143 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Read information about the operating system. + +#![doc( + html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", + html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" +)] + +use std::fmt::Display; + +pub use os_info::Version; +use serialize_to_javascript::{default_template, DefaultTemplate, Template}; +use tauri::{ + plugin::{Builder, TauriPlugin}, + Runtime, +}; + +mod commands; +mod error; + +pub use error::Error; + +pub enum OsType { + Linux, + Windows, + Macos, + IOS, + Android, +} + +impl Display for OsType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Linux => write!(f, "linux"), + Self::Windows => write!(f, "windows"), + Self::Macos => write!(f, "macos"), + Self::IOS => write!(f, "ios"), + Self::Android => write!(f, "android"), + } + } +} + +/// Returns a string describing the specific operating system in use, see [std::env::consts::OS]. +pub fn platform() -> &'static str { + std::env::consts::OS +} + +/// Returns the current operating system version. +pub fn version() -> Version { + os_info::get().version().clone() +} + +/// Returns the current operating system type. +pub fn type_() -> OsType { + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] + return OsType::Linux; + #[cfg(target_os = "windows")] + return OsType::Windows; + #[cfg(target_os = "macos")] + return OsType::Macos; + #[cfg(target_os = "ios")] + return OsType::IOS; + #[cfg(target_os = "android")] + return OsType::Android; +} + +/// Returns the current operating system family, see [std::env::consts::FAMILY]. +pub fn family() -> &'static str { + std::env::consts::FAMILY +} + +/// Returns the current operating system architecture, see [std::env::consts::ARCH]. +pub fn arch() -> &'static str { + std::env::consts::ARCH +} + +/// Returns the file extension, if any, used for executable binaries on this platform. Example value is `exe`, see [std::env::consts::EXE_EXTENSION]. +pub fn exe_extension() -> &'static str { + std::env::consts::EXE_EXTENSION +} + +/// Returns the current operating system locale with the `BCP-47` language tag. If the locale couldn't be obtained, `None` is returned instead. +pub fn locale() -> Option { + sys_locale::get_locale() +} + +/// Returns the current operating system hostname. +pub fn hostname() -> String { + gethostname::gethostname().to_string_lossy().to_string() +} + +#[derive(Template)] +#[default_template("./init.js")] +struct InitJavascript<'a> { + eol: &'static str, + os_type: String, + platform: &'a str, + family: &'a str, + version: String, + arch: &'a str, + exe_extension: &'a str, +} + +impl InitJavascript<'_> { + fn new() -> Self { + Self { + #[cfg(windows)] + eol: "\r\n", + #[cfg(not(windows))] + eol: "\n", + os_type: crate::type_().to_string(), + platform: crate::platform(), + family: crate::family(), + version: crate::version().to_string(), + arch: crate::arch(), + exe_extension: crate::exe_extension(), + } + } +} + +pub fn init() -> TauriPlugin { + let init_js = InitJavascript::new() + .render_default(&Default::default()) + // this will never fail with the above global_os_api values + .unwrap(); + + Builder::new("os") + .js_init_script(init_js.to_string()) + .invoke_handler(tauri::generate_handler![ + commands::locale, + commands::hostname + ]) + .build() +} diff --git a/packages/kbot/gui/app/plugins/os/tsconfig.json b/packages/kbot/gui/app/plugins/os/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/os/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/persisted-scope/CHANGELOG.md b/packages/kbot/gui/app/plugins/persisted-scope/CHANGELOG.md new file mode 100644 index 00000000..ca40e97a --- /dev/null +++ b/packages/kbot/gui/app/plugins/persisted-scope/CHANGELOG.md @@ -0,0 +1,310 @@ +# Changelog + +## \[2.3.2] + +### Dependencies + +- Upgraded to `fs@2.4.2` + +## \[2.3.1] + +### Dependencies + +- Upgraded to `fs@2.4.1` + +## \[2.3.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +### Dependencies + +- Upgraded to `fs@2.4.0` + +## \[2.2.2] + +### Dependencies + +- Upgraded to `fs@2.3.0` + +## \[2.2.1] + +### Dependencies + +- Upgraded to `fs@2.2.1` + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +### Dependencies + +- Upgraded to `fs@2.2.0` + +## \[2.1.1] + +### Dependencies + +- Upgraded to `fs@2.1.1` + +## \[2.1.0] + +- [`fecfd553`](https://github.com/tauri-apps/plugins-workspace/commit/fecfd5533a6452f054fbcd909021f12b0dce834f) ([#2070](https://github.com/tauri-apps/plugins-workspace/pull/2070) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) **Breaking Change:** Replaced the custom `tauri_plugin_fs::Scope` struct with `tauri::fs::Scope`. + +### Dependencies + +- Upgraded to `fs@2.1.0` + +## \[2.0.3] + +### Dependencies + +- Upgraded to `fs@2.0.3` + +## \[2.0.2] + +### Dependencies + +- Upgraded to `fs@2.0.2` + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +### Dependencies + +- Upgraded to `fs@2.0.1` + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +### Dependencies + +- Upgraded to `fs@2.0.0` + +## \[2.0.0-rc.6] + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.6` + +## \[2.0.0-rc.5] + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.5` + +## \[2.0.0-rc.4] + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.4` + +## \[2.0.0-rc.3] + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.3` + +## \[2.0.0-rc.2] + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.2` + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.1` + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +### Dependencies + +- Upgraded to `fs@2.0.0-rc.0` + +## \[2.0.0-beta.12] + +- [`e847cedc`](https://github.com/tauri-apps/plugins-workspace/commit/e847cedc1f46f3e7a2ad81ea579b620bc5b992d7) ([#1402](https://github.com/tauri-apps/plugins-workspace/pull/1402) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Use no default features on tauri for all plugins so that consumers can use `default-features = false` on tauri, note that this will still enable wry feature on iOS +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.12` + +## \[2.0.0-beta.11] + +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.11` + +## \[2.0.0-beta.10] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.10` + +## \[2.0.0-beta.9] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.9` + +## \[2.0.0-beta.8] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.8` + +## \[2.0.0-beta.7] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.7` + +## \[2.0.0-beta.6] + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.6` + +## \[2.0.0-beta.5] + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.5` + +## \[2.0.0-beta.4] + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.4` + +## \[2.0.0-beta.3] + +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.3` + +## \[2.0.0-beta.2] + +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.2` + +## \[2.0.0-beta.1] + +- [`14f381a`](https://github.com/tauri-apps/plugins-workspace/commit/14f381acf8fe690acecc676922c6f05939b95734) Update MSRV to 1.75. +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.1` + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Add permissions. + +### Dependencies + +- Upgraded to `fs@2.0.0-beta.0` + +## \[2.0.0-alpha.7] + +### Dependencies + +- Upgraded to `fs@2.0.0-alpha.7` + +## \[2.0.0-alpha.6] + +- [`2cf8faa`](https://github.com/tauri-apps/plugins-workspace/commit/2cf8faa3e149af55eb86e5aba8ebfc54210ca703)([#839](https://github.com/tauri-apps/plugins-workspace/pull/839)) Update to tauri@alpha.20. +- [`10b8039`](https://github.com/tauri-apps/plugins-workspace/commit/10b80391fcdef28e26124505053fb3a4c4f85e75)([#825](https://github.com/tauri-apps/plugins-workspace/pull/825)) Use `tauri::scope::fs::Scope` instead of local copy of its implementation. + +### Dependencies + +- Upgraded to `fs@2.0.0-alpha.6` + +## \[2.0.0-alpha.5] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to tauri@alpha.18. + +### Dependencies + +- Upgraded to `fs@2.0.0-alpha.5` + +## \[2.0.0-alpha.4] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to tauri@alpha.17. + +### Dependencies + +- Upgraded to `fs@2.0.0-alpha.4` + +## \[2.0.0-alpha.3] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update MSRV to 1.75. +- [`5de23e7`](https://github.com/tauri-apps/plugins-workspace/commit/5de23e79f9880921b62e4b7a8819bc0dbc833216)([#649](https://github.com/tauri-apps/plugins-workspace/pull/649)) Update to tauri@alpha.15. + +### Dependencies + +- Upgraded to `fs@2.0.0-alpha.3` + +## \[2.0.0-alpha.2] + +- [`4e2cef9`](https://github.com/tauri-apps/plugins-workspace/commit/4e2cef9b702bbbb9cf4ee17de50791cb21f1b2a4)([#593](https://github.com/tauri-apps/plugins-workspace/pull/593)) Update to alpha.12. + +### Dependencies + +- Upgraded to `fs@2.0.0-alpha.2` + +## \[2.0.0-alpha.1] + +- [`d5a7c77`](https://github.com/tauri-apps/plugins-workspace/commit/d5a7c77a8d0e7912a6b07b22ed329004edd6e80b)([#545](https://github.com/tauri-apps/plugins-workspace/pull/545)) Fixes docs.rs build by enabling the `tauri/dox` feature flag. +- [`aba07c2`](https://github.com/tauri-apps/plugins-workspace/commit/aba07c27b887c1cc54026024227cb3f74c91e21a)([#468](https://github.com/tauri-apps/plugins-workspace/pull/468)) Split up fs and asset scopes. **This will reset the asset protocol scope once!** +- [`aba07c2`](https://github.com/tauri-apps/plugins-workspace/commit/aba07c27b887c1cc54026024227cb3f74c91e21a)([#468](https://github.com/tauri-apps/plugins-workspace/pull/468)) Fix usage of directory patterns by removing glob asterisks at the end before allowing/forbidding them. This was causing them to be escaped, and so undesirable paths were allowed/forbidden while polluting the `.persisted-scope` file. +- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11. + +### Dependencies + +- Upgraded to `fs@2.0.0-alpha.1` + +## \[2.0.0-alpha.0] + +- [`ebb2eb2`](https://github.com/tauri-apps/plugins-workspace/commit/ebb2eb2fe2ebfbb70530d16a983d396aa5829aa1)([#274](https://github.com/tauri-apps/plugins-workspace/pull/274)) Recursively unescape saved patterns before allowing/forbidding them. This effectively prevents `.persisted-scope` files from blowing up, which caused Out-Of-Memory issues, while automatically fixing existing broken files seamlessly. +- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! + +## \[0.1.3] + +- Split up fs and asset scopes. **This will reset the asset protocol scope once!** + - [ad30286](https://github.com/tauri-apps/plugins-workspace/commit/ad3028646c96ed213a2f483823ffdc3c17b5fc1e) fix(persisted-scope): separately save asset protocol patterns ([#459](https://github.com/tauri-apps/plugins-workspace/pull/459)) on 2023-07-10 + +## \[0.1.2] + +- Fix usage of directory patterns by removing glob asterisks at the end before allowing/forbidding them. This was causing them to be escaped, and so undesirable paths were allowed/forbidden while polluting the `.persisted_scope` file. + - [9174b80](https://github.com/tauri-apps/plugins-workspace/commit/9174b808dc37154999c119fcc3f31258a9c5a3fb) \[persisted scope] fix: handle recursive directory correctly ([#455](https://github.com/tauri-apps/plugins-workspace/pull/455)) on 2023-06-29 + +## \[0.1.1] + +- The MSRV was raised to 1.64! +- The plugin now recursively unescapes saved patterns before allowing/forbidding them. This effectively prevents `.persisted-scope` files from blowing up, which caused Out-Of-Memory issues, while automatically fixing existing broken files seamlessly. + - [ebb2eb2](https://github.com/tauri-apps/plugins-workspace/commit/ebb2eb2fe2ebfbb70530d16a983d396aa5829aa1) fix(persisted-scope): Prevent out-of-memory issues, fixes [#274](https://github.com/tauri-apps/plugins-workspace/pull/274) ([#328](https://github.com/tauri-apps/plugins-workspace/pull/328)) on 2023-04-26 diff --git a/packages/kbot/gui/app/plugins/persisted-scope/Cargo.toml b/packages/kbot/gui/app/plugins/persisted-scope/Cargo.toml new file mode 100644 index 00000000..5c8f08cd --- /dev/null +++ b/packages/kbot/gui/app/plugins/persisted-scope/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "tauri-plugin-persisted-scope" +version = "2.3.2" +description = "Save filesystem and asset scopes and restore them when the app is reopened." +authors = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.platforms.support] +windows = { level = "full", notes = "" } +linux = { level = "full", notes = "" } +macos = { level = "full", notes = "" } +android = { level = "full", notes = "" } +ios = { level = "full", notes = "" } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } +aho-corasick = "1" +bincode = "1" +tauri-plugin-fs = { path = "../fs", version = "2.4.2" } + +[features] +protocol-asset = ["tauri/protocol-asset"] diff --git a/packages/kbot/gui/app/plugins/persisted-scope/LICENSE.spdx b/packages/kbot/gui/app/plugins/persisted-scope/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/persisted-scope/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/persisted-scope/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/persisted-scope/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/persisted-scope/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/persisted-scope/LICENSE_MIT b/packages/kbot/gui/app/plugins/persisted-scope/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/persisted-scope/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/persisted-scope/README.md b/packages/kbot/gui/app/plugins/persisted-scope/README.md new file mode 100644 index 00000000..c3ec88fe --- /dev/null +++ b/packages/kbot/gui/app/plugins/persisted-scope/README.md @@ -0,0 +1,75 @@ +![plugin-persisted-scope](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/persisted-scope/banner.png) + +Save filesystem and asset scopes and restore them when the app is reopened. + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | ✓ | +| iOS | ✓ | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-persisted-scope = "2.0.0" +# alternatively with Git: +tauri-plugin-persisted-scope = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_persisted_scope::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards the plugin will automatically save and restore filesystem and asset scopes. + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/persisted-scope/SECURITY.md b/packages/kbot/gui/app/plugins/persisted-scope/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/persisted-scope/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/persisted-scope/banner.png b/packages/kbot/gui/app/plugins/persisted-scope/banner.png new file mode 100644 index 00000000..816f0c29 Binary files /dev/null and b/packages/kbot/gui/app/plugins/persisted-scope/banner.png differ diff --git a/packages/kbot/gui/app/plugins/persisted-scope/src/lib.rs b/packages/kbot/gui/app/plugins/persisted-scope/src/lib.rs new file mode 100644 index 00000000..6ced7b24 --- /dev/null +++ b/packages/kbot/gui/app/plugins/persisted-scope/src/lib.rs @@ -0,0 +1,256 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Save filesystem and asset scopes and restore them when the app is reopened. + +#![doc( + html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", + html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" +)] + +use aho_corasick::AhoCorasick; +use serde::{Deserialize, Serialize}; + +use tauri::{ + plugin::{Builder, TauriPlugin}, + Manager, Runtime, +}; +use tauri_plugin_fs::FsExt; + +use std::{ + fs::{create_dir_all, File}, + io::Write, + path::Path, +}; + +// Using 2 separate files so that we don't have to think about write conflicts and not break backwards compat. +const SCOPE_STATE_FILENAME: &str = ".persisted-scope"; +#[cfg(feature = "protocol-asset")] +const ASSET_SCOPE_STATE_FILENAME: &str = ".persisted-scope-asset"; + +// Most of these patterns are just added to try to fix broken files in the wild. +// After a while we can hopefully reduce it to something like [r"[?]", r"[*]", r"\\?\\\?\"] +const PATTERNS: &[&str] = &[ + r"[[]", + r"[]]", + r"[?]", + r"[*]", + r"\?\?", + r"\\?\\?\", + r"\\?\\\?\", +]; +const REPLACE_WITH: &[&str] = &[r"[", r"]", r"?", r"*", r"\?", r"\\?\", r"\\?\"]; + +#[derive(Debug, thiserror::Error)] +enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + Tauri(#[from] tauri::Error), + #[error(transparent)] + Bincode(#[from] Box), +} + +#[derive(Debug, Default, Deserialize, Serialize, Eq, PartialEq, Hash)] +enum TargetType { + #[default] + File, + Directory, + RecursiveDirectory, +} + +#[derive(Debug, Default, Deserialize, Serialize)] +struct Scope { + allowed_paths: Vec, + forbidden_patterns: Vec, +} + +fn fix_pattern(ac: &AhoCorasick, s: &str) -> String { + let s = ac.replace_all(s, REPLACE_WITH); + + if ac.find(&s).is_some() { + return fix_pattern(ac, &s); + } + + s +} + +const RESURSIVE_DIRECTORY_SUFFIX: &str = "**"; +const DIRECTORY_SUFFIX: &str = "*"; + +fn detect_scope_type(scope_state_path: &str) -> TargetType { + if scope_state_path.ends_with(RESURSIVE_DIRECTORY_SUFFIX) { + TargetType::RecursiveDirectory + } else if scope_state_path.ends_with(DIRECTORY_SUFFIX) { + TargetType::Directory + } else { + TargetType::File + } +} + +fn fix_directory(path_str: &str) -> &Path { + let mut path = Path::new(path_str); + + if path.ends_with(DIRECTORY_SUFFIX) || path.ends_with(RESURSIVE_DIRECTORY_SUFFIX) { + path = match path.parent() { + Some(value) => value, + None => return path, + }; + } + + path +} + +fn allow_path(scope: &tauri::fs::Scope, path: &str) { + let target_type = detect_scope_type(path); + + match target_type { + TargetType::File => { + let _ = scope.allow_file(Path::new(path)); + } + TargetType::Directory => { + // We remove the '*' at the end of it, else it will be escaped by the pattern. + let _ = scope.allow_directory(fix_directory(path), false); + } + TargetType::RecursiveDirectory => { + // We remove the '**' at the end of it, else it will be escaped by the pattern. + let _ = scope.allow_directory(fix_directory(path), true); + } + } +} + +fn forbid_path(scope: &tauri::fs::Scope, path: &str) { + let target_type = detect_scope_type(path); + + match target_type { + TargetType::File => { + let _ = scope.forbid_file(Path::new(path)); + } + TargetType::Directory => { + let _ = scope.forbid_directory(fix_directory(path), false); + } + TargetType::RecursiveDirectory => { + let _ = scope.forbid_directory(fix_directory(path), true); + } + } +} + +fn save_scopes(scope: &tauri::fs::Scope, app_dir: &Path, scope_state_path: &Path) { + let scope = Scope { + allowed_paths: scope + .allowed_patterns() + .into_iter() + .map(|p| p.to_string()) + .collect(), + forbidden_patterns: scope + .forbidden_patterns() + .into_iter() + .map(|p| p.to_string()) + .collect(), + }; + + let _ = create_dir_all(app_dir) + .and_then(|_| File::create(scope_state_path)) + .map_err(Error::Io) + .and_then(|mut f| { + f.write_all(&bincode::serialize(&scope).map_err(Error::from)?) + .map_err(Into::into) + }); +} + +pub fn init() -> TauriPlugin { + Builder::new("persisted-scope") + .setup(|app, _api| { + let fs_scope = app.try_fs_scope(); + #[cfg(feature = "protocol-asset")] + let asset_protocol_scope = app.asset_protocol_scope(); + let app = app.clone(); + let app_dir = app.path().app_data_dir(); + + if let Ok(app_dir) = app_dir { + let fs_scope_state_path = app_dir.join(SCOPE_STATE_FILENAME); + #[cfg(feature = "protocol-asset")] + let asset_scope_state_path = app_dir.join(ASSET_SCOPE_STATE_FILENAME); + + if let Some(fs_scope) = &fs_scope { + let _ = fs_scope.forbid_file(&fs_scope_state_path); + } else { + #[cfg(debug_assertions)] + eprintln!("Please make sure to register the `fs` plugin before the `persisted-scope` plugin!"); + } + #[cfg(feature = "protocol-asset")] + let _ = asset_protocol_scope.forbid_file(&asset_scope_state_path); + + // We're trying to fix broken .persisted-scope files seamlessly, so we'll be running this on the values read on the saved file. + // We will still save some semi-broken values because the scope events are quite spammy and we don't want to reduce runtime performance any further. + let ac = AhoCorasick::new(PATTERNS).unwrap(/* This should be impossible to fail since we're using a small static input */); + + if let Some(fs_scope) = &fs_scope { + if fs_scope_state_path.exists() { + let scope: Scope = std::fs::read(&fs_scope_state_path) + .map_err(Error::from) + .and_then(|scope| bincode::deserialize(&scope).map_err(Into::into)) + .unwrap_or_default(); + + for allowed in &scope.allowed_paths { + let allowed = fix_pattern(&ac, allowed); + allow_path(fs_scope, &allowed); + } + for forbidden in &scope.forbidden_patterns { + let forbidden = fix_pattern(&ac, forbidden); + forbid_path(fs_scope, &forbidden); + } + + // Manually save the fixed scopes to disk once. + // This is needed to fix broken .peristed-scope files in case the app doesn't update the scope itself. + save_scopes(fs_scope, &app_dir, &fs_scope_state_path); + } + } + + #[cfg(feature = "protocol-asset")] + if asset_scope_state_path.exists() { + let scope: Scope = std::fs::read(&asset_scope_state_path) + .map_err(Error::from) + .and_then(|scope| bincode::deserialize(&scope).map_err(Into::into)) + .unwrap_or_default(); + + for allowed in &scope.allowed_paths { + let allowed = fix_pattern(&ac, allowed); + allow_path(&asset_protocol_scope, &allowed); + } + for forbidden in &scope.forbidden_patterns { + let forbidden = fix_pattern(&ac, forbidden); + forbid_path(&asset_protocol_scope, &forbidden); + } + + // Manually save the fixed scopes to disk once. + save_scopes(&asset_protocol_scope, &app_dir, &asset_scope_state_path); + } + + #[cfg(feature = "protocol-asset")] + let app_dir_ = app_dir.clone(); + + if let Some(fs_scope) = &fs_scope { + let app_ = app.clone(); + fs_scope.listen(move |event| { + if let tauri::fs::Event::PathAllowed(_) = event { + save_scopes(&app_.fs_scope(), &app_dir, &fs_scope_state_path); + } + }); + } + + #[cfg(feature = "protocol-asset")] + { + let asset_protocol_scope_ = asset_protocol_scope.clone(); + asset_protocol_scope.listen(move |event| { + if let tauri::scope::fs::Event::PathAllowed(_) = event { + save_scopes(&asset_protocol_scope_, &app_dir_, &asset_scope_state_path); + } + }); + } + } + Ok(()) + }) + .build() +} diff --git a/packages/kbot/gui/app/plugins/positioner/CHANGELOG.md b/packages/kbot/gui/app/plugins/positioner/CHANGELOG.md new file mode 100644 index 00000000..28eb3f40 --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/CHANGELOG.md @@ -0,0 +1,171 @@ +# Changelog + +## \[2.3.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.2.1] + +- [`f634e524`](https://github.com/tauri-apps/plugins-workspace/commit/f634e5248ebe428f8305a59f74c13fc15147fb8e) This is an "empty" release to update the plugins' source files on crates.io and docs.rs. This should fix docs.rs build failures for projects using tauri plugins as dependencies. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.1.0] + +- [`4db62635`](https://github.com/tauri-apps/plugins-workspace/commit/4db626354c8e29e37bedcf94787a8dd36ce21c55) ([#2076](https://github.com/tauri-apps/plugins-workspace/pull/2076) by [@jakobwesthoff](https://github.com/tauri-apps/plugins-workspace/../../jakobwesthoff)) Add `moveWindowConstrained` function that is similar to `moveWindow` but constrains the window to the screen dimensions in case of tray icon positions. + +## \[2.0.1] + +- [`3c1f3874`](https://github.com/tauri-apps/plugins-workspace/commit/3c1f3874f4c828637b3aa983cba13c77427faf58) ([#1911](https://github.com/tauri-apps/plugins-workspace/pull/1911) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Added missing permission for `handleIconState` and fixed its event processing logic. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.2] + +- [`2f7e32b5`](https://github.com/tauri-apps/plugins-workspace/commit/2f7e32b5e07454d6c0cf3ab03f8af8da74c4a8a7) ([#1822](https://github.com/tauri-apps/plugins-workspace/pull/1822) by [@jbolda](https://github.com/tauri-apps/plugins-workspace/../../jbolda)) `handleIconState` function for use in JavaScript event handlers. This allows one to update the TrayIcon state through JavaScript and fully create and handle the TrayIcon without requiring Rust (and the side-effect of creating a TrayIcon). + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +## \[2.0.0-beta.8] + +- [`99d6ac0f`](https://github.com/tauri-apps/plugins-workspace/commit/99d6ac0f9506a6a4a1aa59c728157190a7441af6) ([#1606](https://github.com/tauri-apps/plugins-workspace/pull/1606) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The JS packages now specify the *minimum* `@tauri-apps/api` version instead of a single exact version. +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.7] + +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.6] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.5] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.4] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.5] + +- [`d9de5b19`](https://github.com/tauri-apps/plugins-workspace/commit/d9de5b19d1e950c06f0915ae92a862acb266d108)([#1283](https://github.com/tauri-apps/plugins-workspace/pull/1283)) Implement `WindowExt` for `WebviewWindow`. + +## \[2.0.0-beta.3] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.3] + +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. + +## \[2.0.0-beta.2] + +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. + +## \[2.0.0-alpha.5] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.13. + +## \[2.0.0-alpha.4] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.12. + +## \[2.0.0-alpha.3] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to @tauri-apps/api v2.0.0-alpha.11. + +## \[2.0.0-alpha.2] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update to @tauri-apps/api v2.0.0-alpha.9. + +## \[2.0.0-alpha.2] + +- [`4e2cef9`](https://github.com/tauri-apps/plugins-workspace/commit/4e2cef9b702bbbb9cf4ee17de50791cb21f1b2a4)([#593](https://github.com/tauri-apps/plugins-workspace/pull/593)) Update to alpha.12. + +## \[2.0.0-alpha.1] + +- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11. + +## \[2.0.0-alpha.0] + +- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! + +## \[1.0.5] + +- `TrayLeft`, `TrayRight` and `TrayCenter` will now position the window according to the tray position relative to the monitor dimensions to prevent windows being displayed partially off-screen. + - [3d27909](https://github.com/tauri-apps/plugins-workspace/commit/3d279094d44be78cdc5d1de3938f1414e13db6b0) fix(positioner): Prevent tray relative windows from being moved off-screen ([#291](https://github.com/tauri-apps/plugins-workspace/pull/291)) on 2023-09-27 + +## \[0.2.7] + +- Update Tauri to v1.0.0 + - Bumped due to a bump in tauri-plugin-positioner. + - [0bb73eb](https://www.github.com/JonasKruckenberg/tauri-plugin-positioner/commit/0bb73eb20dae87f730c0b5f4cc08e6689e25fdba) Create tauri-v1.md on 2022-06-16 + +## \[0.2.6] + +- Update Tauri to v1.0.0-rc.12 + - Bumped due to a bump in tauri-plugin-positioner. + - [de6d76f](https://www.github.com/JonasKruckenberg/tauri-plugin-positioner/commit/de6d76f3a96a68e88a7ac434d65df4dbc82af122) Create update-tauri.md on 2022-05-25 + +## \[0.2.5] + +- Update deps + - Bumped due to a bump in tauri-plugin-positioner. + - [a8d3f73](https://www.github.com/JonasKruckenberg/tauri-plugin-positioner/commit/a8d3f73b74829ef5d53d4fb028e59d09e8946399) Create chore-update-deps.md on 2022-05-18 + +## \[0.2.4] + +- Update Tauri dependencies + - [2095b6a](https://www.github.com/JonasKruckenberg/tauri-plugin-positioner/commit/2095b6a4a4ab5590add099ddb2b1e8118e3496e4) add dep update changefile on 2022-02-14 + - [53d3a50](https://www.github.com/JonasKruckenberg/tauri-plugin-positioner/commit/53d3a501776f16741124aa961f521b9d7798c878) publish new versions ([#42](https://www.github.com/JonasKruckenberg/tauri-plugin-positioner/pull/42)) on 2022-02-14 + - [9f32726](https://www.github.com/JonasKruckenberg/tauri-plugin-positioner/commit/9f32726ede38bb9b2711f738a2f9fee7f0da2d73) Create update-deps.md on 2022-05-11 + +## \[0.2.3] + +- **Breaking Change**: Uses the new Tauri plugin builder pattern. Use `tauri_plugin_positioner::init()` instead of `tauri_plugin_positioner::Positioner::default()`. + - Bumped due to a bump in tauri-plugin-positioner. + - [14837a8](https://www.github.com/JonasKruckenberg/tauri-plugin-positioner/commit/14837a8d9cecdd6014867d4ef00fb98f21b2249d) refactor: use new builder pattern on 2022-02-26 + - [59874d8](https://www.github.com/JonasKruckenberg/tauri-plugin-positioner/commit/59874d827471dfb889662fadc74fec1f2243b89e) fix typo on 2022-02-26 + +## \[0.2.2] + +- Update README.md + - Bumped due to a bump in tauri-plugin-positioner. + - [92d6c3d](https://www.github.com/JonasKruckenberg/tauri-plugin-positioner/commit/92d6c3dca00a6b3562682804a649c0023831ce2b) Create docs-update-readme.md on 2022-02-17 + +## \[0.2.1] + +- Update `tauri` to `1.0.0-rc.1`, `serde` to `1.0.136` and `serde_json` to `1.0.79`. + - [2095b6a](https://www.github.com/JonasKruckenberg/tauri-plugin-positioner/commit/2095b6a4a4ab5590add099ddb2b1e8118e3496e4) add dep update changefile on 2022-02-14 + +## \[0.2.0] + +- Add SystemTray relative positions. + - [765b3ed](https://www.github.com/JonasKruckenberg/tauri-plugin-positioner/commit/765b3ed90056d85ae88b0852b7107ff2b84a6c3a) Create feat-tray-pos.md on 2022-01-19 + +## \[0.1.0] + +- Update package/crate metadata diff --git a/packages/kbot/gui/app/plugins/positioner/Cargo.toml b/packages/kbot/gui/app/plugins/positioner/Cargo.toml new file mode 100644 index 00000000..cb4724f2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "tauri-plugin-positioner" +version = "2.3.0" +description = "Position your windows at well-known locations." +authors = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-positioner" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.platforms.support] +windows = { level = "full", notes = "" } +linux = { level = "full", notes = "" } +macos = { level = "full", notes = "" } +android = { level = "none", notes = "" } +ios = { level = "none", notes = "" } + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } +serde_repr = "0.1" + +[features] +tray-icon = ["tauri/tray-icon"] diff --git a/packages/kbot/gui/app/plugins/positioner/LICENSE.spdx b/packages/kbot/gui/app/plugins/positioner/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/positioner/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/positioner/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/positioner/LICENSE_MIT b/packages/kbot/gui/app/plugins/positioner/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/positioner/README.md b/packages/kbot/gui/app/plugins/positioner/README.md new file mode 100644 index 00000000..8495b616 --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/README.md @@ -0,0 +1,148 @@ +![plugin-positioner](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/positioner/banner.png) + +Position your windows at well-known locations. + +This plugin is a port of [electron-positioner](https://github.com/jenslind/electron-positioner) for Tauri. + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | x | +| iOS | x | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-positioner = "2.0.0" +# alternatively with Git: +tauri-plugin-positioner = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +```sh +pnpm add @tauri-apps/plugin-positioner +# or +npm add @tauri-apps/plugin-positioner +# or +yarn add @tauri-apps/plugin-positioner +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +use tauri::tray::TrayIconBuilder; + +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_positioner::init()) + // This is required to get tray-relative positions to work + .setup(|app| { + // note that this will create a new TrayIcon + TrayIconBuilder::new() + .on_tray_icon_event(|app, event| { + tauri_plugin_positioner::on_tray_event(app.app_handle(), &event); + }) + .build(app)?; + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Alternatively, you may handle the tray events through JavaScript. Register the plugin as previously noted. + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_positioner::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +And in JavaScript, the `action` passed to the TrayIcon should include the handler. + +```javascript +import { + moveWindow, + Position, + handleIconState, +} from "@tauri-apps/plugin-positioner"; + +const action = async (event: TrayIconEvent) => { + // add the handle in the action to update the state + await handleIconState(event); + + if (event.type === "Click") { + // note this option requires enabling the `tray-icon` + // feature in the Cargo.toml + await moveWindow(Position.TrayLeft); + } +}; + +const tray = await TrayIcon.new({ id: "main", action }); +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { moveWindow, Position } from '@tauri-apps/plugin-positioner' + +moveWindow(Position.TopRight) +``` + +If you only intend on moving the window from rust code, you can import the Window trait extension instead of registering the plugin: + +```rust +use tauri_plugin_positioner::{WindowExt, Position}; + +let mut win = app.get_window("main").unwrap(); +let _ = win.move_window(Position::TopRight); +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2021 - Jonas Kruckenberg. 2021 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/positioner/SECURITY.md b/packages/kbot/gui/app/plugins/positioner/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/positioner/api-iife.js b/packages/kbot/gui/app/plugins/positioner/api-iife.js new file mode 100644 index 00000000..dda21778 --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_POSITIONER__=function(t){"use strict";async function o(t,o={},e){return window.__TAURI_INTERNALS__.invoke(t,o,e)}var e;return"function"==typeof SuppressedError&&SuppressedError,t.Position=void 0,(e=t.Position||(t.Position={}))[e.TopLeft=0]="TopLeft",e[e.TopRight=1]="TopRight",e[e.BottomLeft=2]="BottomLeft",e[e.BottomRight=3]="BottomRight",e[e.TopCenter=4]="TopCenter",e[e.BottomCenter=5]="BottomCenter",e[e.LeftCenter=6]="LeftCenter",e[e.RightCenter=7]="RightCenter",e[e.Center=8]="Center",e[e.TrayLeft=9]="TrayLeft",e[e.TrayBottomLeft=10]="TrayBottomLeft",e[e.TrayRight=11]="TrayRight",e[e.TrayBottomRight=12]="TrayBottomRight",e[e.TrayCenter=13]="TrayCenter",e[e.TrayBottomCenter=14]="TrayBottomCenter",t.handleIconState=async function(t){await o("plugin:positioner|set_tray_icon_state",{position:t.rect.position,size:t.rect.size})},t.moveWindow=async function(t){await o("plugin:positioner|move_window",{position:t})},t.moveWindowConstrained=async function(t){await o("plugin:positioner|move_window_constrained",{position:t})},t}({});Object.defineProperty(window.__TAURI__,"positioner",{value:__TAURI_PLUGIN_POSITIONER__})} diff --git a/packages/kbot/gui/app/plugins/positioner/banner.png b/packages/kbot/gui/app/plugins/positioner/banner.png new file mode 100644 index 00000000..1461737e Binary files /dev/null and b/packages/kbot/gui/app/plugins/positioner/banner.png differ diff --git a/packages/kbot/gui/app/plugins/positioner/build.rs b/packages/kbot/gui/app/plugins/positioner/build.rs new file mode 100644 index 00000000..830c61fb --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/build.rs @@ -0,0 +1,15 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +const COMMANDS: &[&str] = &[ + "move_window", + "move_window_constrained", + "set_tray_icon_state", +]; + +fn main() { + tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .build(); +} diff --git a/packages/kbot/gui/app/plugins/positioner/guest-js/index.ts b/packages/kbot/gui/app/plugins/positioner/guest-js/index.ts new file mode 100644 index 00000000..74d0295e --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/guest-js/index.ts @@ -0,0 +1,60 @@ +// Copyright 2021 Jonas Kruckenberg +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { invoke } from '@tauri-apps/api/core' +import type { TrayIconEvent } from '@tauri-apps/api/tray' + +/** + * Well known window positions. + */ +export enum Position { + TopLeft = 0, + TopRight, + BottomLeft, + BottomRight, + TopCenter, + BottomCenter, + LeftCenter, + RightCenter, + Center, + TrayLeft, + TrayBottomLeft, + TrayRight, + TrayBottomRight, + TrayCenter, + TrayBottomCenter +} + +/** + * Moves the `Window` to the given {@link Position} using `WindowExt.move_window()` + * All positions are relative to the **current** screen. + * + * @param to The {@link Position} to move to. + */ +export async function moveWindow(to: Position): Promise { + await invoke('plugin:positioner|move_window', { + position: to + }) +} + +/** + * Moves the `Window` to the given {@link Position} using `WindowExt.move_window_constrained()` + * + * This move operation constrains the window to the screen dimensions in case of + * tray-icon positions. + * @param to The (tray) {@link Position} to move to. + */ +export async function moveWindowConstrained(to: Position): Promise { + await invoke('plugin:positioner|move_window_constrained', { + position: to + }) +} + +export async function handleIconState(event: TrayIconEvent): Promise { + await invoke('plugin:positioner|set_tray_icon_state', { + position: event.rect.position, + size: event.rect.size + }) +} diff --git a/packages/kbot/gui/app/plugins/positioner/package.json b/packages/kbot/gui/app/plugins/positioner/package.json new file mode 100644 index 00000000..dd75462a --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/package.json @@ -0,0 +1,30 @@ +{ + "name": "@tauri-apps/plugin-positioner", + "version": "2.3.0", + "description": "Position your windows at well-known locations.", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/positioner/permissions/autogenerated/commands/move_window.toml b/packages/kbot/gui/app/plugins/positioner/permissions/autogenerated/commands/move_window.toml new file mode 100644 index 00000000..f78f9c78 --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/permissions/autogenerated/commands/move_window.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-move-window" +description = "Enables the move_window command without any pre-configured scope." +commands.allow = ["move_window"] + +[[permission]] +identifier = "deny-move-window" +description = "Denies the move_window command without any pre-configured scope." +commands.deny = ["move_window"] diff --git a/packages/kbot/gui/app/plugins/positioner/permissions/autogenerated/commands/move_window_constrained.toml b/packages/kbot/gui/app/plugins/positioner/permissions/autogenerated/commands/move_window_constrained.toml new file mode 100644 index 00000000..80343990 --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/permissions/autogenerated/commands/move_window_constrained.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-move-window-constrained" +description = "Enables the move_window_constrained command without any pre-configured scope." +commands.allow = ["move_window_constrained"] + +[[permission]] +identifier = "deny-move-window-constrained" +description = "Denies the move_window_constrained command without any pre-configured scope." +commands.deny = ["move_window_constrained"] diff --git a/packages/kbot/gui/app/plugins/positioner/permissions/autogenerated/commands/set_tray_icon_state.toml b/packages/kbot/gui/app/plugins/positioner/permissions/autogenerated/commands/set_tray_icon_state.toml new file mode 100644 index 00000000..6b85f635 --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/permissions/autogenerated/commands/set_tray_icon_state.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-set-tray-icon-state" +description = "Enables the set_tray_icon_state command without any pre-configured scope." +commands.allow = ["set_tray_icon_state"] + +[[permission]] +identifier = "deny-set-tray-icon-state" +description = "Denies the set_tray_icon_state command without any pre-configured scope." +commands.deny = ["set_tray_icon_state"] diff --git a/packages/kbot/gui/app/plugins/positioner/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/positioner/permissions/autogenerated/reference.md new file mode 100644 index 00000000..f6e09133 --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/permissions/autogenerated/reference.md @@ -0,0 +1,97 @@ +## Default Permission + +Allows the moveWindow and handleIconState APIs + +#### This default permission set includes the following: + +- `allow-move-window` +- `allow-move-window-constrained` +- `allow-set-tray-icon-state` + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`positioner:allow-move-window` + + + +Enables the move_window command without any pre-configured scope. + +
+ +`positioner:deny-move-window` + + + +Denies the move_window command without any pre-configured scope. + +
+ +`positioner:allow-move-window-constrained` + + + +Enables the move_window_constrained command without any pre-configured scope. + +
+ +`positioner:deny-move-window-constrained` + + + +Denies the move_window_constrained command without any pre-configured scope. + +
+ +`positioner:allow-set-tray-icon-state` + + + +Enables the set_tray_icon_state command without any pre-configured scope. + +
+ +`positioner:deny-set-tray-icon-state` + + + +Denies the set_tray_icon_state command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/positioner/permissions/default.toml b/packages/kbot/gui/app/plugins/positioner/permissions/default.toml new file mode 100644 index 00000000..7fc3c3d9 --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/permissions/default.toml @@ -0,0 +1,8 @@ +"$schema" = "schemas/schema.json" +[default] +description = "Allows the moveWindow and handleIconState APIs" +permissions = [ + "allow-move-window", + "allow-move-window-constrained", + "allow-set-tray-icon-state", +] diff --git a/packages/kbot/gui/app/plugins/positioner/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/positioner/permissions/schemas/schema.json new file mode 100644 index 00000000..b0fc760a --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/permissions/schemas/schema.json @@ -0,0 +1,342 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the move_window command without any pre-configured scope.", + "type": "string", + "const": "allow-move-window", + "markdownDescription": "Enables the move_window command without any pre-configured scope." + }, + { + "description": "Denies the move_window command without any pre-configured scope.", + "type": "string", + "const": "deny-move-window", + "markdownDescription": "Denies the move_window command without any pre-configured scope." + }, + { + "description": "Enables the move_window_constrained command without any pre-configured scope.", + "type": "string", + "const": "allow-move-window-constrained", + "markdownDescription": "Enables the move_window_constrained command without any pre-configured scope." + }, + { + "description": "Denies the move_window_constrained command without any pre-configured scope.", + "type": "string", + "const": "deny-move-window-constrained", + "markdownDescription": "Denies the move_window_constrained command without any pre-configured scope." + }, + { + "description": "Enables the set_tray_icon_state command without any pre-configured scope.", + "type": "string", + "const": "allow-set-tray-icon-state", + "markdownDescription": "Enables the set_tray_icon_state command without any pre-configured scope." + }, + { + "description": "Denies the set_tray_icon_state command without any pre-configured scope.", + "type": "string", + "const": "deny-set-tray-icon-state", + "markdownDescription": "Denies the set_tray_icon_state command without any pre-configured scope." + }, + { + "description": "Allows the moveWindow and handleIconState APIs\n#### This default permission set includes:\n\n- `allow-move-window`\n- `allow-move-window-constrained`\n- `allow-set-tray-icon-state`", + "type": "string", + "const": "default", + "markdownDescription": "Allows the moveWindow and handleIconState APIs\n#### This default permission set includes:\n\n- `allow-move-window`\n- `allow-move-window-constrained`\n- `allow-set-tray-icon-state`" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/positioner/rollup.config.js b/packages/kbot/gui/app/plugins/positioner/rollup.config.js new file mode 100644 index 00000000..1f349ec8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/rollup.config.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' + +export default createConfig() diff --git a/packages/kbot/gui/app/plugins/positioner/src/ext.rs b/packages/kbot/gui/app/plugins/positioner/src/ext.rs new file mode 100644 index 00000000..b3d405ea --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/src/ext.rs @@ -0,0 +1,307 @@ +// Copyright 2021 Jonas Kruckenberg +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#[cfg(feature = "tray-icon")] +use crate::Tray; +use serde_repr::Deserialize_repr; +#[cfg(feature = "tray-icon")] +use tauri::Manager; +#[cfg(feature = "tray-icon")] +use tauri::Monitor; +use tauri::{PhysicalPosition, PhysicalSize, Result, Runtime, WebviewWindow, Window}; + +/// Well known window positions. +#[derive(Debug, Deserialize_repr)] +#[repr(u16)] +pub enum Position { + TopLeft = 0, + TopRight, + BottomLeft, + BottomRight, + TopCenter, + BottomCenter, + LeftCenter, + RightCenter, + Center, + #[cfg(feature = "tray-icon")] + TrayLeft, + #[cfg(feature = "tray-icon")] + TrayBottomLeft, + #[cfg(feature = "tray-icon")] + TrayRight, + #[cfg(feature = "tray-icon")] + TrayBottomRight, + #[cfg(feature = "tray-icon")] + TrayCenter, + #[cfg(feature = "tray-icon")] + TrayBottomCenter, +} + +/// A [`Window`] extension that provides extra methods related to positioning. +pub trait WindowExt { + /// Moves the [`Window`] to the given [`Position`] + /// + /// All (non-tray) positions are relative to the **current** screen. + fn move_window(&self, position: Position) -> Result<()>; + #[cfg(feature = "tray-icon")] + /// Moves the [`Window`] to the given [`Position`] while constraining Tray Positions to the dimensions of the screen. + /// + /// All non-tray positions will not be constrained by this method. + /// + /// This method allows you to position your Tray Windows without having them + /// cut off on the screen borders. + fn move_window_constrained(&self, position: Position) -> Result<()>; +} + +impl WindowExt for WebviewWindow { + fn move_window(&self, pos: Position) -> Result<()> { + self.as_ref().window().move_window(pos) + } + + #[cfg(feature = "tray-icon")] + fn move_window_constrained(&self, position: Position) -> Result<()> { + self.as_ref().window().move_window_constrained(position) + } +} + +impl WindowExt for Window { + #[cfg(feature = "tray-icon")] + fn move_window_constrained(&self, position: Position) -> Result<()> { + // Diverge to basic move_window, if the position is not a tray position + if !matches!( + position, + Position::TrayLeft + | Position::TrayBottomLeft + | Position::TrayRight + | Position::TrayBottomRight + | Position::TrayCenter + | Position::TrayBottomCenter + ) { + return self.move_window(position); + } + + let window_position = calculate_position(self, position)?; + let monitor = get_monitor_for_tray_icon(self)?; + if let Some(monitor) = monitor { + let monitor_size = monitor.size(); + let monitor_position = monitor.position(); + let window_size = self.outer_size()?; + + let right_border_monitor = monitor_position.x as f64 + monitor_size.width as f64; + let left_border_monitor = monitor_position.x as f64; + let right_border_window = window_position.x as f64 + window_size.width as f64; + let left_border_window = window_position.x as f64; + + let constrained_x = if left_border_window < left_border_monitor { + left_border_monitor + } else if right_border_window > right_border_monitor { + right_border_monitor - window_size.width as f64 + } else { + window_position.x as f64 + }; + + let bottom_border_monitor = monitor_position.y as f64 + monitor_size.height as f64; + let top_border_monitor = monitor_position.y as f64; + let bottom_border_window = window_position.y as f64 + window_size.height as f64; + let top_border_window = window_position.y as f64; + + let constrained_y = if top_border_window < top_border_monitor { + top_border_monitor + } else if bottom_border_window > bottom_border_monitor { + bottom_border_monitor - window_size.height as f64 + } else { + window_position.y as f64 + }; + + self.set_position(PhysicalPosition::new(constrained_x, constrained_y))?; + } else { + // Fallback on non constrained positioning + self.set_position(window_position)?; + } + + Ok(()) + } + + fn move_window(&self, pos: Position) -> Result<()> { + let position = calculate_position(self, pos)?; + self.set_position(position) + } +} + +#[cfg(feature = "tray-icon")] +/// Retrieve the monitor, where the tray icon is located on. +fn get_monitor_for_tray_icon(window: &Window) -> Result> { + let tray_position = window + .state::() + .0 + .lock() + .unwrap() + .map(|(pos, _)| pos) + .unwrap_or_default(); + + window.monitor_from_point(tray_position.x, tray_position.y) +} + +/// Calculate the top-left position of the window based on the given +/// [`Position`]. +fn calculate_position( + window: &Window, + pos: Position, +) -> Result> { + use Position::*; + + let screen = window.current_monitor()?.unwrap(); + // Only use the screen_position for the Tray independent positioning, + // because a tray event may not be called on the currently active monitor. + let screen_position = screen.position(); + let screen_size = PhysicalSize:: { + width: screen.size().width as i32, + height: screen.size().height as i32, + }; + let window_size = PhysicalSize:: { + width: window.outer_size()?.width as i32, + height: window.outer_size()?.height as i32, + }; + #[cfg(feature = "tray-icon")] + let (tray_position, tray_size) = window + .state::() + .0 + .lock() + .unwrap() + .map(|(pos, size)| { + ( + Some((pos.x as i32, pos.y as i32)), + Some((size.width as i32, size.height as i32)), + ) + }) + .unwrap_or_default(); + + let physical_pos = match pos { + TopLeft => *screen_position, + TopRight => PhysicalPosition { + x: screen_position.x + (screen_size.width - window_size.width), + y: screen_position.y, + }, + BottomLeft => PhysicalPosition { + x: screen_position.x, + y: screen_size.height - (window_size.height - screen_position.y), + }, + BottomRight => PhysicalPosition { + x: screen_position.x + (screen_size.width - window_size.width), + y: screen_size.height - (window_size.height - screen_position.y), + }, + TopCenter => PhysicalPosition { + x: screen_position.x + ((screen_size.width / 2) - (window_size.width / 2)), + y: screen_position.y, + }, + BottomCenter => PhysicalPosition { + x: screen_position.x + ((screen_size.width / 2) - (window_size.width / 2)), + y: screen_size.height - (window_size.height - screen_position.y), + }, + LeftCenter => PhysicalPosition { + x: screen_position.x, + y: screen_position.y + (screen_size.height / 2) - (window_size.height / 2), + }, + RightCenter => PhysicalPosition { + x: screen_position.x + (screen_size.width - window_size.width), + y: screen_position.y + (screen_size.height / 2) - (window_size.height / 2), + }, + Center => PhysicalPosition { + x: screen_position.x + ((screen_size.width / 2) - (window_size.width / 2)), + y: screen_position.y + (screen_size.height / 2) - (window_size.height / 2), + }, + #[cfg(feature = "tray-icon")] + TrayLeft => { + if let (Some((tray_x, tray_y)), Some((_, _tray_height))) = (tray_position, tray_size) { + let y = tray_y - window_size.height; + // Choose y value based on the target OS + #[cfg(target_os = "windows")] + let y = if y < 0 { tray_y + _tray_height } else { y }; + + #[cfg(target_os = "macos")] + let y = if y < 0 { tray_y } else { y }; + + PhysicalPosition { x: tray_x, y } + } else { + panic!("Tray position not set"); + } + } + #[cfg(feature = "tray-icon")] + TrayBottomLeft => { + if let Some((tray_x, tray_y)) = tray_position { + PhysicalPosition { + x: tray_x, + y: tray_y, + } + } else { + panic!("Tray position not set"); + } + } + #[cfg(feature = "tray-icon")] + TrayRight => { + if let (Some((tray_x, tray_y)), Some((tray_width, _tray_height))) = + (tray_position, tray_size) + { + let y = tray_y - window_size.height; + // Choose y value based on the target OS + #[cfg(target_os = "windows")] + let y = if y < 0 { tray_y + _tray_height } else { y }; + + #[cfg(target_os = "macos")] + let y = if y < 0 { tray_y } else { y }; + + PhysicalPosition { + x: tray_x + tray_width, + y, + } + } else { + panic!("Tray position not set"); + } + } + #[cfg(feature = "tray-icon")] + TrayBottomRight => { + if let (Some((tray_x, tray_y)), Some((tray_width, _))) = (tray_position, tray_size) { + PhysicalPosition { + x: tray_x + tray_width, + y: tray_y, + } + } else { + panic!("Tray position not set"); + } + } + #[cfg(feature = "tray-icon")] + TrayCenter => { + if let (Some((tray_x, tray_y)), Some((tray_width, _tray_height))) = + (tray_position, tray_size) + { + let x = tray_x + tray_width / 2 - window_size.width / 2; + let y = tray_y - window_size.height; + // Choose y value based on the target OS + #[cfg(target_os = "windows")] + let y = if y < 0 { tray_y + _tray_height } else { y }; + + #[cfg(target_os = "macos")] + let y = if y < 0 { tray_y } else { y }; + + PhysicalPosition { x, y } + } else { + panic!("Tray position not set"); + } + } + #[cfg(feature = "tray-icon")] + TrayBottomCenter => { + if let (Some((tray_x, tray_y)), Some((tray_width, _))) = (tray_position, tray_size) { + PhysicalPosition { + x: tray_x + (tray_width / 2) - (window_size.width / 2), + y: tray_y, + } + } else { + panic!("Tray position not set"); + } + } + }; + + Ok(physical_pos) +} diff --git a/packages/kbot/gui/app/plugins/positioner/src/lib.rs b/packages/kbot/gui/app/plugins/positioner/src/lib.rs new file mode 100644 index 00000000..59d0c3c1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/src/lib.rs @@ -0,0 +1,104 @@ +// Copyright 2021 Jonas Kruckenberg +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! A plugin for Tauri that helps position your windows at well-known locations. +//! +//! # Cargo features +//! +//! - **tray-icon**: Enables tray-icon-relative positions. +//! +//! Note: This requires attaching the Tauri plugin, *even* when using the trait extension only. + +#![doc( + html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", + html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" +)] +#![cfg(not(any(target_os = "android", target_os = "ios")))] + +mod ext; + +pub use ext::*; +use tauri::{ + plugin::{self, TauriPlugin}, + Result, Runtime, +}; + +#[cfg(feature = "tray-icon")] +use tauri::{tray::TrayIconEvent, AppHandle, Manager, PhysicalPosition, PhysicalSize}; + +#[cfg(feature = "tray-icon")] +struct Tray(std::sync::Mutex, PhysicalSize)>>); + +#[cfg(feature = "tray-icon")] +pub fn on_tray_event(app: &AppHandle, event: &TrayIconEvent) { + let (position, size) = { + match event { + TrayIconEvent::Click { rect, .. } + | TrayIconEvent::Enter { rect, .. } + | TrayIconEvent::Leave { rect, .. } + | TrayIconEvent::Move { rect, .. } => { + // tray-icon emits PhysicalSize so the scale factor should not matter. + let size = rect.size.to_physical(1.0); + let position = rect.position.to_physical(1.0); + (position, size) + } + + _ => return, + } + }; + + app.state::() + .0 + .lock() + .unwrap() + .replace((position, size)); +} + +#[tauri::command] +async fn move_window(window: tauri::Window, position: Position) -> Result<()> { + window.move_window(position) +} + +#[cfg(feature = "tray-icon")] +#[tauri::command] +async fn move_window_constrained( + window: tauri::Window, + position: Position, +) -> Result<()> { + window.move_window_constrained(position) +} + +#[cfg(feature = "tray-icon")] +#[tauri::command] +fn set_tray_icon_state( + app: AppHandle, + position: PhysicalPosition, + size: PhysicalSize, +) { + app.state::() + .0 + .lock() + .unwrap() + .replace((position, size)); +} + +/// The Tauri plugin that exposes [`WindowExt::move_window`] to the webview. +pub fn init() -> TauriPlugin { + let plugin = plugin::Builder::new("positioner").invoke_handler(tauri::generate_handler![ + move_window, + #[cfg(feature = "tray-icon")] + move_window_constrained, + #[cfg(feature = "tray-icon")] + set_tray_icon_state + ]); + + #[cfg(feature = "tray-icon")] + let plugin = plugin.setup(|app_handle, _api| { + app_handle.manage(Tray(std::sync::Mutex::new(None))); + Ok(()) + }); + + plugin.build() +} diff --git a/packages/kbot/gui/app/plugins/positioner/tsconfig.json b/packages/kbot/gui/app/plugins/positioner/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/positioner/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/process/CHANGELOG.md b/packages/kbot/gui/app/plugins/process/CHANGELOG.md new file mode 100644 index 00000000..5952c92a --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/CHANGELOG.md @@ -0,0 +1,102 @@ +# Changelog + +## \[2.3.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.2.2] + +- [`f634e524`](https://github.com/tauri-apps/plugins-workspace/commit/f634e5248ebe428f8305a59f74c13fc15147fb8e) This is an "empty" release to update the plugins' source files on crates.io and docs.rs. This should fix docs.rs build failures for projects using tauri plugins as dependencies. + +## \[2.2.1] + +- [`d2aef2fd`](https://github.com/tauri-apps/plugins-workspace/commit/d2aef2fddbdfad6526935c55ac10a94171a4f5f5) ([#2581](https://github.com/tauri-apps/plugins-workspace/pull/2581)) Migrate restart to use tauri's new `AppHandle::request_restart` method + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +## \[2.0.0-beta.8] + +- [`99d6ac0f`](https://github.com/tauri-apps/plugins-workspace/commit/99d6ac0f9506a6a4a1aa59c728157190a7441af6) ([#1606](https://github.com/tauri-apps/plugins-workspace/pull/1606) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The JS packages now specify the *minimum* `@tauri-apps/api` version instead of a single exact version. +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.7] + +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.6] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.5] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.4] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.3] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.3] + +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. + +## \[2.0.0-beta.2] + +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. + +## \[2.0.0-alpha.5] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.13. + +## \[2.0.0-alpha.4] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.12. + +## \[2.0.0-alpha.3] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to @tauri-apps/api v2.0.0-alpha.11. + +## \[2.0.0-alpha.2] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update to @tauri-apps/api v2.0.0-alpha.9. + +## \[2.0.0-alpha.2] + +- [`4e2cef9`](https://github.com/tauri-apps/plugins-workspace/commit/4e2cef9b702bbbb9cf4ee17de50791cb21f1b2a4)([#593](https://github.com/tauri-apps/plugins-workspace/pull/593)) Update to alpha.12. + +## \[2.0.0-alpha.1] + +- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11. + +## \[2.0.0-alpha.0] + +- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! diff --git a/packages/kbot/gui/app/plugins/process/Cargo.toml b/packages/kbot/gui/app/plugins/process/Cargo.toml new file mode 100644 index 00000000..663efaf5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "tauri-plugin-process" +version = "2.3.0" +description = "Access the current process of your Tauri application." +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-process" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.platforms.support] +windows = { level = "full", notes = "" } +linux = { level = "full", notes = "" } +macos = { level = "full", notes = "" } +android = { level = "none", notes = "" } +ios = { level = "none", notes = "" } + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } + +[dependencies] +tauri = { workspace = true } diff --git a/packages/kbot/gui/app/plugins/process/LICENSE.spdx b/packages/kbot/gui/app/plugins/process/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/process/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/process/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/process/LICENSE_MIT b/packages/kbot/gui/app/plugins/process/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/process/README.md b/packages/kbot/gui/app/plugins/process/README.md new file mode 100644 index 00000000..13a1c4e9 --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/README.md @@ -0,0 +1,93 @@ +![plugin-process](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/process/banner.png) + +This plugin provides APIs to access the current process. To spawn child processes, see the [`shell`](https://github.com/tauri-apps/tauri-plugin-shell) plugin. + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | x | +| iOS | x | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-process = "2.0.0" +# alternatively with Git: +tauri-plugin-process = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +```sh +pnpm add @tauri-apps/plugin-process +# or +npm add @tauri-apps/plugin-process +# or +yarn add @tauri-apps/plugin-process +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_process::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { exit, relaunch } from '@tauri-apps/plugin-process' +// exit the app with the given status code +await exit(0) +// restart the app +await relaunch() +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/process/SECURITY.md b/packages/kbot/gui/app/plugins/process/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/process/api-iife.js b/packages/kbot/gui/app/plugins/process/api-iife.js new file mode 100644 index 00000000..b214396e --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_PROCESS__=function(_){"use strict";async function n(_,n={},e){return window.__TAURI_INTERNALS__.invoke(_,n,e)}return"function"==typeof SuppressedError&&SuppressedError,_.exit=async function(_=0){await n("plugin:process|exit",{code:_})},_.relaunch=async function(){await n("plugin:process|restart")},_}({});Object.defineProperty(window.__TAURI__,"process",{value:__TAURI_PLUGIN_PROCESS__})} diff --git a/packages/kbot/gui/app/plugins/process/banner.png b/packages/kbot/gui/app/plugins/process/banner.png new file mode 100644 index 00000000..7634d6e4 Binary files /dev/null and b/packages/kbot/gui/app/plugins/process/banner.png differ diff --git a/packages/kbot/gui/app/plugins/process/build.rs b/packages/kbot/gui/app/plugins/process/build.rs new file mode 100644 index 00000000..e412b34e --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/build.rs @@ -0,0 +1,11 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +const COMMANDS: &[&str] = &["exit", "restart"]; + +fn main() { + tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .build(); +} diff --git a/packages/kbot/gui/app/plugins/process/guest-js/index.ts b/packages/kbot/gui/app/plugins/process/guest-js/index.ts new file mode 100644 index 00000000..da15831a --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/guest-js/index.ts @@ -0,0 +1,45 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/** + * Perform operations on the current process. + * @module + */ + +import { invoke } from '@tauri-apps/api/core' + +/** + * Exits immediately with the given `exitCode`. + * @example + * ```typescript + * import { exit } from '@tauri-apps/plugin-process'; + * await exit(1); + * ``` + * + * @param code The exit code to use. + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.0.0 + */ +async function exit(code = 0): Promise { + await invoke('plugin:process|exit', { code }) +} + +/** + * Exits the current instance of the app then relaunches it. + * @example + * ```typescript + * import { relaunch } from '@tauri-apps/plugin-process'; + * await relaunch(); + * ``` + * + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.0.0 + */ +async function relaunch(): Promise { + await invoke('plugin:process|restart') +} + +export { exit, relaunch } diff --git a/packages/kbot/gui/app/plugins/process/package.json b/packages/kbot/gui/app/plugins/process/package.json new file mode 100644 index 00000000..ba40619e --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/package.json @@ -0,0 +1,29 @@ +{ + "name": "@tauri-apps/plugin-process", + "version": "2.3.0", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/process/permissions/autogenerated/commands/exit.toml b/packages/kbot/gui/app/plugins/process/permissions/autogenerated/commands/exit.toml new file mode 100644 index 00000000..8abaf296 --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/permissions/autogenerated/commands/exit.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-exit" +description = "Enables the exit command without any pre-configured scope." +commands.allow = ["exit"] + +[[permission]] +identifier = "deny-exit" +description = "Denies the exit command without any pre-configured scope." +commands.deny = ["exit"] diff --git a/packages/kbot/gui/app/plugins/process/permissions/autogenerated/commands/restart.toml b/packages/kbot/gui/app/plugins/process/permissions/autogenerated/commands/restart.toml new file mode 100644 index 00000000..63b228c8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/permissions/autogenerated/commands/restart.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-restart" +description = "Enables the restart command without any pre-configured scope." +commands.allow = ["restart"] + +[[permission]] +identifier = "deny-restart" +description = "Denies the restart command without any pre-configured scope." +commands.deny = ["restart"] diff --git a/packages/kbot/gui/app/plugins/process/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/process/permissions/autogenerated/reference.md new file mode 100644 index 00000000..45d74b77 --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/permissions/autogenerated/reference.md @@ -0,0 +1,76 @@ +## Default Permission + +This permission set configures which +process features are by default exposed. + +#### Granted Permissions + +This enables to quit via `allow-exit` and restart via `allow-restart` +the application. + +#### This default permission set includes the following: + +- `allow-exit` +- `allow-restart` + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`process:allow-exit` + + + +Enables the exit command without any pre-configured scope. + +
+ +`process:deny-exit` + + + +Denies the exit command without any pre-configured scope. + +
+ +`process:allow-restart` + + + +Enables the restart command without any pre-configured scope. + +
+ +`process:deny-restart` + + + +Denies the restart command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/process/permissions/default.toml b/packages/kbot/gui/app/plugins/process/permissions/default.toml new file mode 100644 index 00000000..69a9b00d --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/permissions/default.toml @@ -0,0 +1,14 @@ +"$schema" = "schemas/schema.json" + +[default] +description = """ +This permission set configures which +process features are by default exposed. + +#### Granted Permissions + +This enables to quit via `allow-exit` and restart via `allow-restart` +the application. +""" + +permissions = ["allow-exit", "allow-restart"] diff --git a/packages/kbot/gui/app/plugins/process/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/process/permissions/schemas/schema.json new file mode 100644 index 00000000..9d68fc63 --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/permissions/schemas/schema.json @@ -0,0 +1,330 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the exit command without any pre-configured scope.", + "type": "string", + "const": "allow-exit", + "markdownDescription": "Enables the exit command without any pre-configured scope." + }, + { + "description": "Denies the exit command without any pre-configured scope.", + "type": "string", + "const": "deny-exit", + "markdownDescription": "Denies the exit command without any pre-configured scope." + }, + { + "description": "Enables the restart command without any pre-configured scope.", + "type": "string", + "const": "allow-restart", + "markdownDescription": "Enables the restart command without any pre-configured scope." + }, + { + "description": "Denies the restart command without any pre-configured scope.", + "type": "string", + "const": "deny-restart", + "markdownDescription": "Denies the restart command without any pre-configured scope." + }, + { + "description": "This permission set configures which\nprocess features are by default exposed.\n\n#### Granted Permissions\n\nThis enables to quit via `allow-exit` and restart via `allow-restart`\nthe application.\n\n#### This default permission set includes:\n\n- `allow-exit`\n- `allow-restart`", + "type": "string", + "const": "default", + "markdownDescription": "This permission set configures which\nprocess features are by default exposed.\n\n#### Granted Permissions\n\nThis enables to quit via `allow-exit` and restart via `allow-restart`\nthe application.\n\n#### This default permission set includes:\n\n- `allow-exit`\n- `allow-restart`" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/process/rollup.config.js b/packages/kbot/gui/app/plugins/process/rollup.config.js new file mode 100644 index 00000000..1f349ec8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/rollup.config.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' + +export default createConfig() diff --git a/packages/kbot/gui/app/plugins/process/src/commands.rs b/packages/kbot/gui/app/plugins/process/src/commands.rs new file mode 100644 index 00000000..3777294a --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/src/commands.rs @@ -0,0 +1,15 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tauri::{AppHandle, Runtime}; + +#[tauri::command] +pub fn exit(app: AppHandle, code: i32) { + app.exit(code) +} + +#[tauri::command] +pub fn restart(app: AppHandle) { + app.request_restart() +} diff --git a/packages/kbot/gui/app/plugins/process/src/lib.rs b/packages/kbot/gui/app/plugins/process/src/lib.rs new file mode 100644 index 00000000..b83d8964 --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/src/lib.rs @@ -0,0 +1,23 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! This plugin provides APIs to access the current process. To spawn child processes, see the [`shell`](https://github.com/tauri-apps/tauri-plugin-shell) plugin. + +#![doc( + html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", + html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" +)] + +use tauri::{ + plugin::{Builder, TauriPlugin}, + Runtime, +}; + +mod commands; + +pub fn init() -> TauriPlugin { + Builder::new("process") + .invoke_handler(tauri::generate_handler![commands::exit, commands::restart]) + .build() +} diff --git a/packages/kbot/gui/app/plugins/process/tsconfig.json b/packages/kbot/gui/app/plugins/process/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/process/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/shell/CHANGELOG.md b/packages/kbot/gui/app/plugins/shell/CHANGELOG.md new file mode 100644 index 00000000..5bbd3d8e --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/CHANGELOG.md @@ -0,0 +1,137 @@ +# Changelog + +## \[2.3.1] + +- [`d865ed47`](https://github.com/tauri-apps/plugins-workspace/commit/d865ed47685c3923e894f7d10ee4c037507037e6) ([#2950](https://github.com/tauri-apps/plugins-workspace/pull/2950) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Fix sidecar with dots in the filename not working on Windows. + +## \[2.3.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.2.2] + +- [`f634e524`](https://github.com/tauri-apps/plugins-workspace/commit/f634e5248ebe428f8305a59f74c13fc15147fb8e) This is an "empty" release to update the plugins' source files on crates.io and docs.rs. This should fix docs.rs build failures for projects using tauri plugins as dependencies. + +## \[2.2.1] + +### bug + +- [`9cf0390a`](https://github.com/tauri-apps/plugins-workspace/commit/9cf0390a52497e273db1a1b613a0e26827aa327c) Apply the default open validation regex `^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+` when the open configuration is not set, preventing unchecked input from being used in this scenario (previously the plugin would skip validation when it should disable all calls). This keeps backwards compatibility while still fixing this vulnerability. + The scope is no longer validated for Rust calls via `ShellExt::shell()` so if you need to block JavaScript from calling the API you can simply set `tauri.conf.json > plugins > shell > open` to `false`. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.1] + +- [`51ddf6a7`](https://github.com/tauri-apps/plugins-workspace/commit/51ddf6a71544acfb261ffc9393dab1342da0a219) ([#1881](https://github.com/tauri-apps/plugins-workspace/pull/1881) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) On Windows, Fix `open` JS API hanging and freezing the app. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.4] + +- [`44273b98`](https://github.com/tauri-apps/plugins-workspace/commit/44273b988957a254eff715d6be7547d2ace882e1) ([#1839](https://github.com/tauri-apps/plugins-workspace/pull/1839) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Fix the plugin schema requiring to set `sidecar` property when it is in fact optional. + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.2] + +- [`b9147758`](https://github.com/tauri-apps/plugins-workspace/commit/b914775898c2bee7ceb20bd17ee595005cd17a64) ([#1679](https://github.com/tauri-apps/plugins-workspace/pull/1679) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Explicitly set a minimum macOS version for the Swift package. + +## \[2.0.0-rc.1] + +### changes + +- [`6b079cfd`](https://github.com/tauri-apps/plugins-workspace/commit/6b079cfdd107c94abc2c7300f6af00bac3ff4040) ([#1649](https://github.com/tauri-apps/plugins-workspace/pull/1649) by [@ahqsoftwares](https://github.com/tauri-apps/plugins-workspace/../../ahqsoftwares)) Remove targetSdk from build.kts files as it is deprecated and will be removed from DSL v9.0 + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. +- [`34df132f`](https://github.com/tauri-apps/plugins-workspace/commit/34df132fb14212ba7330adc9ccd64267751950c8) ([#1603](https://github.com/tauri-apps/plugins-workspace/pull/1603)) Change the `open` scope validator regex to match on the entire string. +- [`34df132f`](https://github.com/tauri-apps/plugins-workspace/commit/34df132fb14212ba7330adc9ccd64267751950c8) ([#1603](https://github.com/tauri-apps/plugins-workspace/pull/1603)) Change the `execute` scope argument validator regex to match on the entire string by default. + If this behavior is not desired check the `raw` boolean configuration option that is available along the `validator` string. + +## \[2.0.0-beta.9] + +- [`99d6ac0f`](https://github.com/tauri-apps/plugins-workspace/commit/99d6ac0f9506a6a4a1aa59c728157190a7441af6) ([#1606](https://github.com/tauri-apps/plugins-workspace/pull/1606) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The JS packages now specify the *minimum* `@tauri-apps/api` version instead of a single exact version. +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.8] + +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.7] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.6] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.5] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.4] + +- [`eb1679b9`](https://github.com/tauri-apps/plugins-workspace/commit/eb1679b99780e5d2b867f5649a1ccc2f3f70ab56)([#1299](https://github.com/tauri-apps/plugins-workspace/pull/1299)) Fix `Command.execute` API including extra new lines. +- [`eb1679b9`](https://github.com/tauri-apps/plugins-workspace/commit/eb1679b99780e5d2b867f5649a1ccc2f3f70ab56)([#1299](https://github.com/tauri-apps/plugins-workspace/pull/1299)) Improve the speed of the JS `Command.execute` API + +## \[2.0.0-beta.3] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.3] + +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. +- [`040004a`](https://github.com/tauri-apps/plugins-workspace/commit/040004a6b9fbb89161d1b5764d79428dfe693776)([#1069](https://github.com/tauri-apps/plugins-workspace/pull/1069)) Change shell's schema property name `command` to `cmd`. + +## \[2.0.0-beta.2] + +- [`9586eab`](https://github.com/tauri-apps/plugins-workspace/commit/9586eabd5a96673e4d976757777f470ae358d68a)([#1021](https://github.com/tauri-apps/plugins-workspace/pull/1021)) On Windows, fix `open` can't open file if the file is being used by a program. +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. + +## \[2.0.0-alpha.5] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.13. + +## \[2.0.0-alpha.4] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.12. + +## \[2.0.0-alpha.3] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to @tauri-apps/api v2.0.0-alpha.11. + +## \[2.0.0-alpha.2] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update to @tauri-apps/api v2.0.0-alpha.9. + +## \[2.0.0-alpha.2] + +- [`4e2cef9`](https://github.com/tauri-apps/plugins-workspace/commit/4e2cef9b702bbbb9cf4ee17de50791cb21f1b2a4)([#593](https://github.com/tauri-apps/plugins-workspace/pull/593)) Update to alpha.12. + +## \[2.0.0-alpha.1] + +- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11. + +## \[2.0.0-alpha.0] + +- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! diff --git a/packages/kbot/gui/app/plugins/shell/Cargo.toml b/packages/kbot/gui/app/plugins/shell/Cargo.toml new file mode 100644 index 00000000..fee2ce2c --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "tauri-plugin-shell" +version = "2.3.1" +description = "Access the system shell. Allows you to spawn child processes and manage files and URLs using their default application." +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-shell" + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.platforms.support] +windows = { level = "full", notes = "" } +linux = { level = "full", notes = "" } +macos = { level = "full", notes = "" } +android = { level = "partial", notes = "Only allows to open URLs via `open`" } +ios = { level = "partial", notes = "Only allows to open URLs via `open`" } + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } +schemars = { workspace = true } +serde = { workspace = true } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +tokio = { version = "1", features = ["time"] } +log = { workspace = true } +thiserror = { workspace = true } +shared_child = "1" +regex = "1" +open = { version = "5", features = ["shellexecute-on-windows"] } +encoding_rs = "0.8" +os_pipe = "1" + +[target.'cfg(target_os = "ios")'.dependencies] +tauri = { workspace = true, features = ["wry"] } diff --git a/packages/kbot/gui/app/plugins/shell/LICENSE.spdx b/packages/kbot/gui/app/plugins/shell/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/shell/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/shell/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/shell/LICENSE_MIT b/packages/kbot/gui/app/plugins/shell/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/shell/README.md b/packages/kbot/gui/app/plugins/shell/README.md new file mode 100644 index 00000000..132d1ccb --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/README.md @@ -0,0 +1,90 @@ +![plugin-shell](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/shell/banner.png) + +Access the system shell. Allows you to spawn child processes and manage files and URLs using their default application. + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | ✓ | +| iOS | ✓ | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-shell = "2.0.0" +# alternatively with Git: +tauri-plugin-shell = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +```sh +pnpm add @tauri-apps/plugin-shell +# or +npm add @tauri-apps/plugin-shell +# or +yarn add @tauri-apps/plugin-shell +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_shell::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { Command } from '@tauri-apps/plugin-shell' +Command.create('git', ['commit', '-m', 'the commit message']) +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/shell/SECURITY.md b/packages/kbot/gui/app/plugins/shell/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/shell/android/.gitignore b/packages/kbot/gui/app/plugins/shell/android/.gitignore new file mode 100644 index 00000000..c0f21ec2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/android/.gitignore @@ -0,0 +1,2 @@ +/build +/.tauri diff --git a/packages/kbot/gui/app/plugins/shell/android/build.gradle.kts b/packages/kbot/gui/app/plugins/shell/android/build.gradle.kts new file mode 100644 index 00000000..88082c65 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/android/build.gradle.kts @@ -0,0 +1,39 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "app.tauri.shell" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation("androidx.core:core-ktx:1.9.0") + implementation("com.fasterxml.jackson.core:jackson-databind:2.15.3") + implementation(project(":tauri-android")) +} diff --git a/packages/kbot/gui/app/plugins/shell/android/proguard-rules.pro b/packages/kbot/gui/app/plugins/shell/android/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/shell/android/settings.gradle b/packages/kbot/gui/app/plugins/shell/android/settings.gradle new file mode 100644 index 00000000..14a752e4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/android/settings.gradle @@ -0,0 +1,2 @@ +include ':tauri-android' +project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/packages/kbot/gui/app/plugins/shell/android/src/main/AndroidManifest.xml b/packages/kbot/gui/app/plugins/shell/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..9a40236b --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/packages/kbot/gui/app/plugins/shell/android/src/main/java/ShellPlugin.kt b/packages/kbot/gui/app/plugins/shell/android/src/main/java/ShellPlugin.kt new file mode 100644 index 00000000..2268bc26 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/android/src/main/java/ShellPlugin.kt @@ -0,0 +1,30 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.shell + +import android.app.Activity +import android.content.Intent +import android.net.Uri +import app.tauri.annotation.Command +import app.tauri.annotation.TauriPlugin +import app.tauri.plugin.Invoke +import app.tauri.plugin.Plugin +import java.io.File + +@TauriPlugin +class ShellPlugin(private val activity: Activity) : Plugin(activity) { + @Command + fun open(invoke: Invoke) { + try { + val url = invoke.parseArgs(String::class.java) + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + activity.applicationContext?.startActivity(intent) + invoke.resolve() + } catch (ex: Exception) { + invoke.reject(ex.message) + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/shell/api-iife.js b/packages/kbot/gui/app/plugins/shell/api-iife.js new file mode 100644 index 00000000..98a2ec02 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_SHELL__=function(e){"use strict";function t(e,t,s,i){if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===s?i:"a"===s?i.call(e):i?i.value:t.get(e)}function s(e,t,s,i,n){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,s),s}var i,n,r,o;"function"==typeof SuppressedError&&SuppressedError;const a="__TAURI_TO_IPC_KEY__";class h{constructor(e){i.set(this,void 0),n.set(this,0),r.set(this,[]),o.set(this,void 0),s(this,i,e||(()=>{})),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((e=>{const a=e.index;if("end"in e)return void(a==t(this,n,"f")?this.cleanupCallback():s(this,o,a));const h=e.message;if(a==t(this,n,"f")){for(t(this,i,"f").call(this,h),s(this,n,t(this,n,"f")+1);t(this,n,"f")in t(this,r,"f");){const e=t(this,r,"f")[t(this,n,"f")];t(this,i,"f").call(this,e),delete t(this,r,"f")[t(this,n,"f")],s(this,n,t(this,n,"f")+1)}t(this,n,"f")===t(this,o,"f")&&this.cleanupCallback()}else t(this,r,"f")[a]=h}))}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(e){s(this,i,e)}get onmessage(){return t(this,i,"f")}[(i=new WeakMap,n=new WeakMap,r=new WeakMap,o=new WeakMap,a)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[a]()}}async function c(e,t={},s){return window.__TAURI_INTERNALS__.invoke(e,t,s)}class l{constructor(){this.eventListeners=Object.create(null)}addListener(e,t){return this.on(e,t)}removeListener(e,t){return this.off(e,t)}on(e,t){return e in this.eventListeners?this.eventListeners[e].push(t):this.eventListeners[e]=[t],this}once(e,t){const s=i=>{this.removeListener(e,s),t(i)};return this.addListener(e,s)}off(e,t){return e in this.eventListeners&&(this.eventListeners[e]=this.eventListeners[e].filter((e=>e!==t))),this}removeAllListeners(e){return e?delete this.eventListeners[e]:this.eventListeners=Object.create(null),this}emit(e,t){if(e in this.eventListeners){const s=this.eventListeners[e];for(const e of s)e(t);return!0}return!1}listenerCount(e){return e in this.eventListeners?this.eventListeners[e].length:0}prependListener(e,t){return e in this.eventListeners?this.eventListeners[e].unshift(t):this.eventListeners[e]=[t],this}prependOnceListener(e,t){const s=i=>{this.removeListener(e,s),t(i)};return this.prependListener(e,s)}}class u{constructor(e){this.pid=e}async write(e){await c("plugin:shell|stdin_write",{pid:this.pid,buffer:e})}async kill(){await c("plugin:shell|kill",{cmd:"killChild",pid:this.pid})}}class p extends l{constructor(e,t=[],s){super(),this.stdout=new l,this.stderr=new l,this.program=e,this.args="string"==typeof t?[t]:t,this.options=s??{}}static create(e,t=[],s){return new p(e,t,s)}static sidecar(e,t=[],s){const i=new p(e,t,s);return i.options.sidecar=!0,i}async spawn(){const e=this.program,t=this.args,s=this.options;"object"==typeof t&&Object.freeze(t);const i=new h;return i.onmessage=e=>{switch(e.event){case"Error":this.emit("error",e.payload);break;case"Terminated":this.emit("close",e.payload);break;case"Stdout":this.stdout.emit("data",e.payload);break;case"Stderr":this.stderr.emit("data",e.payload)}},await c("plugin:shell|spawn",{program:e,args:t,options:s,onEvent:i}).then((e=>new u(e)))}async execute(){const e=this.program,t=this.args,s=this.options;return"object"==typeof t&&Object.freeze(t),await c("plugin:shell|execute",{program:e,args:t,options:s})}}return e.Child=u,e.Command=p,e.EventEmitter=l,e.open=async function(e,t){await c("plugin:shell|open",{path:e,with:t})},e}({});Object.defineProperty(window.__TAURI__,"shell",{value:__TAURI_PLUGIN_SHELL__})} diff --git a/packages/kbot/gui/app/plugins/shell/banner.png b/packages/kbot/gui/app/plugins/shell/banner.png new file mode 100644 index 00000000..cbf74818 Binary files /dev/null and b/packages/kbot/gui/app/plugins/shell/banner.png differ diff --git a/packages/kbot/gui/app/plugins/shell/build.rs b/packages/kbot/gui/app/plugins/shell/build.rs new file mode 100644 index 00000000..4e19ccd8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/build.rs @@ -0,0 +1,189 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::PathBuf; + +use schemars::JsonSchema; + +#[path = "src/scope_entry.rs"] +mod scope_entry; + +/// A command argument allowed to be executed by the webview API. +#[derive(Debug, PartialEq, Eq, Clone, Hash, schemars::JsonSchema)] +#[serde(untagged, deny_unknown_fields)] +#[non_exhaustive] +pub enum ShellScopeEntryAllowedArg { + /// A non-configurable argument that is passed to the command in the order it was specified. + Fixed(String), + + /// A variable that is set while calling the command from the webview API. + /// + Var { + /// [regex] validator to require passed values to conform to an expected input. + /// + /// This will require the argument value passed to this variable to match the `validator` regex + /// before it will be executed. + /// + /// The regex string is by default surrounded by `^...$` to match the full string. + /// For example the `https?://\w+` regex would be registered as `^https?://\w+$`. + /// + /// [regex]: + validator: String, + + /// Marks the validator as a raw regex, meaning the plugin should not make any modification at runtime. + /// + /// This means the regex will not match on the entire string by default, which might + /// be exploited if your regex allow unexpected input to be considered valid. + /// When using this option, make sure your regex is correct. + #[serde(default)] + raw: bool, + }, +} + +/// A set of command arguments allowed to be executed by the webview API. +/// +/// A value of `true` will allow any arguments to be passed to the command. `false` will disable all +/// arguments. A list of [`ShellScopeEntryAllowedArg`] will set those arguments as the only valid arguments to +/// be passed to the attached command configuration. +#[derive(Debug, PartialEq, Eq, Clone, Hash, JsonSchema)] +#[serde(untagged, deny_unknown_fields)] +#[non_exhaustive] +pub enum ShellScopeEntryAllowedArgs { + /// Use a simple boolean to allow all or disable all arguments to this command configuration. + Flag(bool), + + /// A specific set of [`ShellScopeEntryAllowedArg`] that are valid to call for the command configuration. + List(Vec), +} + +impl Default for ShellScopeEntryAllowedArgs { + fn default() -> Self { + Self::Flag(false) + } +} + +/// Shell scope entry. +#[derive(JsonSchema)] +#[serde(untagged, deny_unknown_fields)] +#[allow(unused)] +pub(crate) enum ShellScopeEntry { + Command { + /// The name for this allowed shell command configuration. + /// + /// This name will be used inside of the webview API to call this command along with + /// any specified arguments. + name: String, + /// The command name. + /// It can start with a variable that resolves to a system base directory. + /// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, + /// `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, + /// `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, + /// `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`. + // use default just so the schema doesn't flag it as required + #[serde(rename = "cmd")] + command: PathBuf, + /// The allowed arguments for the command execution. + #[serde(default)] + args: ShellScopeEntryAllowedArgs, + }, + Sidecar { + /// The name for this allowed shell command configuration. + /// + /// This name will be used inside of the webview API to call this command along with + /// any specified arguments. + name: String, + /// The allowed arguments for the command execution. + #[serde(default)] + args: ShellScopeEntryAllowedArgs, + /// If this command is a sidecar command. + sidecar: bool, + }, +} + +// Ensure `ShellScopeEntry` and `scope_entry::EntryRaw` +// and `ShellScopeEntryAllowedArg` and `ShellAllowedArg` +// and `ShellScopeEntryAllowedArgs` and `ShellAllowedArgs` +// are kept in sync +#[allow(clippy::unnecessary_operation)] +fn _f() { + match (ShellScopeEntry::Sidecar { + name: String::new(), + args: ShellScopeEntryAllowedArgs::Flag(false), + sidecar: true, + }) { + ShellScopeEntry::Command { + name, + command, + args, + } => scope_entry::EntryRaw { + name, + command: Some(command), + args: match args { + ShellScopeEntryAllowedArgs::Flag(flag) => scope_entry::ShellAllowedArgs::Flag(flag), + ShellScopeEntryAllowedArgs::List(vec) => scope_entry::ShellAllowedArgs::List( + vec.into_iter() + .map(|s| match s { + ShellScopeEntryAllowedArg::Fixed(fixed) => { + scope_entry::ShellAllowedArg::Fixed(fixed) + } + ShellScopeEntryAllowedArg::Var { validator, raw } => { + scope_entry::ShellAllowedArg::Var { validator, raw } + } + }) + .collect(), + ), + }, + sidecar: false, + }, + ShellScopeEntry::Sidecar { + name, + args, + sidecar, + } => scope_entry::EntryRaw { + name, + command: None, + args: match args { + ShellScopeEntryAllowedArgs::Flag(flag) => scope_entry::ShellAllowedArgs::Flag(flag), + ShellScopeEntryAllowedArgs::List(vec) => scope_entry::ShellAllowedArgs::List( + vec.into_iter() + .map(|s| match s { + ShellScopeEntryAllowedArg::Fixed(fixed) => { + scope_entry::ShellAllowedArg::Fixed(fixed) + } + ShellScopeEntryAllowedArg::Var { validator, raw } => { + scope_entry::ShellAllowedArg::Var { validator, raw } + } + }) + .collect(), + ), + }, + sidecar, + }, + }; +} + +const COMMANDS: &[&str] = &["execute", "spawn", "stdin_write", "kill", "open"]; + +fn main() { + tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .global_scope_schema(schemars::schema_for!(ShellScopeEntry)) + .android_path("android") + .ios_path("ios") + .build(); + + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); + let mobile = target_os == "ios" || target_os == "android"; + alias("desktop", !mobile); + alias("mobile", mobile); +} + +// creates a cfg alias if `has_feature` is true. +// `alias` must be a snake case string. +fn alias(alias: &str, has_feature: bool) { + println!("cargo:rustc-check-cfg=cfg({alias})"); + if has_feature { + println!("cargo:rustc-cfg={alias}"); + } +} diff --git a/packages/kbot/gui/app/plugins/shell/guest-js/index.ts b/packages/kbot/gui/app/plugins/shell/guest-js/index.ts new file mode 100644 index 00000000..081d54c1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/guest-js/index.ts @@ -0,0 +1,616 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/** + * Access the system shell. + * Allows you to spawn child processes and manage files and URLs using their default application. + * + * ## Security + * + * This API has a scope configuration that forces you to restrict the programs and arguments that can be used. + * + * ### Restricting access to the {@link open | `open`} API + * + * On the configuration object, `open: true` means that the {@link open} API can be used with any URL, + * as the argument is validated with the `^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+` regex. + * You can change that regex by changing the boolean value to a string, e.g. `open: ^https://github.com/`. + * + * ### Restricting access to the {@link Command | `Command`} APIs + * + * The plugin permissions object has a `scope` field that defines an array of CLIs that can be used. + * Each CLI is a configuration object `{ name: string, cmd: string, sidecar?: bool, args?: boolean | Arg[] }`. + * + * - `name`: the unique identifier of the command, passed to the {@link Command.create | Command.create function}. + * If it's a sidecar, this must be the value defined on `tauri.conf.json > bundle > externalBin`. + * - `cmd`: the program that is executed on this configuration. If it's a sidecar, this value is ignored. + * - `sidecar`: whether the object configures a sidecar or a system program. + * - `args`: the arguments that can be passed to the program. By default no arguments are allowed. + * - `true` means that any argument list is allowed. + * - `false` means that no arguments are allowed. + * - otherwise an array can be configured. Each item is either a string representing the fixed argument value + * or a `{ validator: string }` that defines a regex validating the argument value. + * + * #### Example scope configuration + * + * CLI: `git commit -m "the commit message"` + * + * Capability: + * ```json + * { + * "permissions": [ + * { + * "identifier": "shell:allow-execute", + * "allow": [ + * { + * "name": "run-git-commit", + * "cmd": "git", + * "args": ["commit", "-m", { "validator": "\\S+" }] + * } + * ] + * } + * ] + * } + * ``` + * Usage: + * ```typescript + * import { Command } from '@tauri-apps/plugin-shell' + * Command.create('run-git-commit', ['commit', '-m', 'the commit message']) + * ``` + * + * Trying to execute any API with a program not configured on the scope results in a promise rejection due to denied access. + * + * @module + */ + +import { invoke, Channel } from '@tauri-apps/api/core' + +/** + * @since 2.0.0 + */ +interface SpawnOptions { + /** Current working directory. */ + cwd?: string + /** Environment variables. set to `null` to clear the process env. */ + env?: Record + /** + * Character encoding for stdout/stderr + * + * @since 2.0.0 + * */ + encoding?: string +} + +/** @ignore */ +interface InternalSpawnOptions extends SpawnOptions { + sidecar?: boolean +} + +/** + * @since 2.0.0 + */ +interface ChildProcess { + /** Exit code of the process. `null` if the process was terminated by a signal on Unix. */ + code: number | null + /** If the process was terminated by a signal, represents that signal. */ + signal: number | null + /** The data that the process wrote to `stdout`. */ + stdout: O + /** The data that the process wrote to `stderr`. */ + stderr: O +} + +/** + * @since 2.0.0 + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +class EventEmitter> { + /** @ignore */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any + private eventListeners: Record void>> = + Object.create(null) + + /** + * Alias for `emitter.on(eventName, listener)`. + * + * @since 2.0.0 + */ + addListener( + eventName: N, + listener: (arg: E[typeof eventName]) => void + ): this { + return this.on(eventName, listener) + } + + /** + * Alias for `emitter.off(eventName, listener)`. + * + * @since 2.0.0 + */ + removeListener( + eventName: N, + listener: (arg: E[typeof eventName]) => void + ): this { + return this.off(eventName, listener) + } + + /** + * Adds the `listener` function to the end of the listeners array for the + * event named `eventName`. No checks are made to see if the `listener` has + * already been added. Multiple calls passing the same combination of `eventName`and `listener` will result in the `listener` being added, and called, multiple + * times. + * + * Returns a reference to the `EventEmitter`, so that calls can be chained. + * + * @since 2.0.0 + */ + on( + eventName: N, + listener: (arg: E[typeof eventName]) => void + ): this { + if (eventName in this.eventListeners) { + // eslint-disable-next-line security/detect-object-injection + this.eventListeners[eventName].push(listener) + } else { + // eslint-disable-next-line security/detect-object-injection + this.eventListeners[eventName] = [listener] + } + return this + } + + /** + * Adds a **one-time**`listener` function for the event named `eventName`. The + * next time `eventName` is triggered, this listener is removed and then invoked. + * + * Returns a reference to the `EventEmitter`, so that calls can be chained. + * + * @since 2.0.0 + */ + once( + eventName: N, + listener: (arg: E[typeof eventName]) => void + ): this { + const wrapper = (arg: E[typeof eventName]): void => { + this.removeListener(eventName, wrapper) + listener(arg) + } + return this.addListener(eventName, wrapper) + } + + /** + * Removes the all specified listener from the listener array for the event eventName + * Returns a reference to the `EventEmitter`, so that calls can be chained. + * + * @since 2.0.0 + */ + off( + eventName: N, + listener: (arg: E[typeof eventName]) => void + ): this { + if (eventName in this.eventListeners) { + // eslint-disable-next-line security/detect-object-injection + this.eventListeners[eventName] = this.eventListeners[eventName].filter( + (l) => l !== listener + ) + } + return this + } + + /** + * Removes all listeners, or those of the specified eventName. + * + * Returns a reference to the `EventEmitter`, so that calls can be chained. + * + * @since 2.0.0 + */ + removeAllListeners(event?: N): this { + if (event) { + // eslint-disable-next-line security/detect-object-injection + delete this.eventListeners[event] + } else { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + this.eventListeners = Object.create(null) + } + return this + } + + /** + * @ignore + * Synchronously calls each of the listeners registered for the event named`eventName`, in the order they were registered, passing the supplied arguments + * to each. + * + * @returns `true` if the event had listeners, `false` otherwise. + * + * @since 2.0.0 + */ + emit(eventName: N, arg: E[typeof eventName]): boolean { + if (eventName in this.eventListeners) { + // eslint-disable-next-line security/detect-object-injection + const listeners = this.eventListeners[eventName] + for (const listener of listeners) listener(arg) + return true + } + return false + } + + /** + * Returns the number of listeners listening to the event named `eventName`. + * + * @since 2.0.0 + */ + listenerCount(eventName: N): number { + if (eventName in this.eventListeners) + // eslint-disable-next-line security/detect-object-injection + return this.eventListeners[eventName].length + return 0 + } + + /** + * Adds the `listener` function to the _beginning_ of the listeners array for the + * event named `eventName`. No checks are made to see if the `listener` has + * already been added. Multiple calls passing the same combination of `eventName`and `listener` will result in the `listener` being added, and called, multiple + * times. + * + * Returns a reference to the `EventEmitter`, so that calls can be chained. + * + * @since 2.0.0 + */ + prependListener( + eventName: N, + listener: (arg: E[typeof eventName]) => void + ): this { + if (eventName in this.eventListeners) { + // eslint-disable-next-line security/detect-object-injection + this.eventListeners[eventName].unshift(listener) + } else { + // eslint-disable-next-line security/detect-object-injection + this.eventListeners[eventName] = [listener] + } + return this + } + + /** + * Adds a **one-time**`listener` function for the event named `eventName` to the_beginning_ of the listeners array. The next time `eventName` is triggered, this + * listener is removed, and then invoked. + * + * Returns a reference to the `EventEmitter`, so that calls can be chained. + * + * @since 2.0.0 + */ + prependOnceListener( + eventName: N, + listener: (arg: E[typeof eventName]) => void + ): this { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const wrapper = (arg: any): void => { + this.removeListener(eventName, wrapper) + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + listener(arg) + } + return this.prependListener(eventName, wrapper) + } +} + +/** + * @since 2.0.0 + */ +class Child { + /** The child process `pid`. */ + pid: number + + constructor(pid: number) { + this.pid = pid + } + + /** + * Writes `data` to the `stdin`. + * + * @param data The message to write, either a string or a byte array. + * @example + * ```typescript + * import { Command } from '@tauri-apps/plugin-shell'; + * const command = Command.create('node'); + * const child = await command.spawn(); + * await child.write('message'); + * await child.write([0, 1, 2, 3, 4, 5]); + * ``` + * + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.0.0 + */ + async write(data: IOPayload | number[]): Promise { + await invoke('plugin:shell|stdin_write', { + pid: this.pid, + buffer: data + }) + } + + /** + * Kills the child process. + * + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.0.0 + */ + async kill(): Promise { + await invoke('plugin:shell|kill', { + cmd: 'killChild', + pid: this.pid + }) + } +} + +interface CommandEvents { + close: TerminatedPayload + error: string +} + +interface OutputEvents { + data: O +} + +/** + * The entry point for spawning child processes. + * It emits the `close` and `error` events. + * @example + * ```typescript + * import { Command } from '@tauri-apps/plugin-shell'; + * const command = Command.create('node'); + * command.on('close', data => { + * console.log(`command finished with code ${data.code} and signal ${data.signal}`) + * }); + * command.on('error', error => console.error(`command error: "${error}"`)); + * command.stdout.on('data', line => console.log(`command stdout: "${line}"`)); + * command.stderr.on('data', line => console.log(`command stderr: "${line}"`)); + * + * const child = await command.spawn(); + * console.log('pid:', child.pid); + * ``` + * + * @since 2.0.0 + * + */ +class Command extends EventEmitter { + /** @ignore Program to execute. */ + private readonly program: string + /** @ignore Program arguments */ + private readonly args: string[] + /** @ignore Spawn options. */ + private readonly options: InternalSpawnOptions + /** Event emitter for the `stdout`. Emits the `data` event. */ + readonly stdout = new EventEmitter>() + /** Event emitter for the `stderr`. Emits the `data` event. */ + readonly stderr = new EventEmitter>() + + /** + * @ignore + * Creates a new `Command` instance. + * + * @param program The program name to execute. + * It must be configured in your project's capabilities. + * @param args Program arguments. + * @param options Spawn options. + */ + private constructor( + program: string, + args: string | string[] = [], + options?: SpawnOptions + ) { + super() + this.program = program + this.args = typeof args === 'string' ? [args] : args + this.options = options ?? {} + } + + static create(program: string, args?: string | string[]): Command + static create( + program: string, + args?: string | string[], + options?: SpawnOptions & { encoding: 'raw' } + ): Command + static create( + program: string, + args?: string | string[], + options?: SpawnOptions + ): Command + + /** + * Creates a command to execute the given program. + * @example + * ```typescript + * import { Command } from '@tauri-apps/plugin-shell'; + * const command = Command.create('my-app', ['run', 'tauri']); + * const output = await command.execute(); + * ``` + * + * @param program The program to execute. + * It must be configured in your project's capabilities. + */ + static create( + program: string, + args: string | string[] = [], + options?: SpawnOptions + ): Command { + return new Command(program, args, options) + } + + static sidecar(program: string, args?: string | string[]): Command + static sidecar( + program: string, + args?: string | string[], + options?: SpawnOptions & { encoding: 'raw' } + ): Command + static sidecar( + program: string, + args?: string | string[], + options?: SpawnOptions + ): Command + + /** + * Creates a command to execute the given sidecar program. + * @example + * ```typescript + * import { Command } from '@tauri-apps/plugin-shell'; + * const command = Command.sidecar('my-sidecar'); + * const output = await command.execute(); + * ``` + * + * @param program The program to execute. + * It must be configured in your project's capabilities. + */ + static sidecar( + program: string, + args: string | string[] = [], + options?: SpawnOptions + ): Command { + const instance = new Command(program, args, options) + instance.options.sidecar = true + return instance + } + + /** + * Executes the command as a child process, returning a handle to it. + * + * @returns A promise resolving to the child process handle. + * + * @since 2.0.0 + */ + async spawn(): Promise { + const program = this.program + const args = this.args + const options = this.options + + if (typeof args === 'object') { + Object.freeze(args) + } + + const onEvent = new Channel>() + onEvent.onmessage = (event) => { + switch (event.event) { + case 'Error': + this.emit('error', event.payload) + break + case 'Terminated': + this.emit('close', event.payload) + break + case 'Stdout': + this.stdout.emit('data', event.payload) + break + case 'Stderr': + this.stderr.emit('data', event.payload) + break + } + } + + return await invoke('plugin:shell|spawn', { + program, + args, + options, + onEvent + }).then((pid) => new Child(pid)) + } + + /** + * Executes the command as a child process, waiting for it to finish and collecting all of its output. + * @example + * ```typescript + * import { Command } from '@tauri-apps/plugin-shell'; + * const output = await Command.create('echo', 'message').execute(); + * assert(output.code === 0); + * assert(output.signal === null); + * assert(output.stdout === 'message'); + * assert(output.stderr === ''); + * ``` + * + * @returns A promise resolving to the child process output. + * + * @since 2.0.0 + */ + async execute(): Promise> { + const program = this.program + const args = this.args + const options = this.options + + if (typeof args === 'object') { + Object.freeze(args) + } + + return await invoke>('plugin:shell|execute', { + program, + args, + options + }) + } +} + +/** + * Describes the event message received from the command. + */ +interface Event { + event: T + payload: V +} + +/** + * Payload for the `Terminated` command event. + */ +interface TerminatedPayload { + /** Exit code of the process. `null` if the process was terminated by a signal on Unix. */ + code: number | null + /** If the process was terminated by a signal, represents that signal. */ + signal: number | null +} + +/** Event payload type */ +type IOPayload = string | Uint8Array + +/** Events emitted by the child process. */ +type CommandEvent = + | Event<'Stdout', O> + | Event<'Stderr', O> + | Event<'Terminated', TerminatedPayload> + | Event<'Error', string> + +/** + * Opens a path or URL with the system's default app, + * or the one specified with `openWith`. + * + * The `openWith` value must be one of `firefox`, `google chrome`, `chromium` `safari`, + * `open`, `start`, `xdg-open`, `gio`, `gnome-open`, `kde-open` or `wslview`. + * + * @example + * ```typescript + * import { open } from '@tauri-apps/plugin-shell'; + * // opens the given URL on the default browser: + * await open('https://github.com/tauri-apps/tauri'); + * // opens the given URL using `firefox`: + * await open('https://github.com/tauri-apps/tauri', 'firefox'); + * // opens a file using the default program: + * await open('/path/to/file'); + * ``` + * + * @param path The path or URL to open. + * This value is matched against the string regex defined on `tauri.conf.json > plugins > shell > open`, + * which defaults to `^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+`. + * @param openWith The app to open the file or URL with. + * Defaults to the system default application for the specified path type. + * + * @since 2.0.0 + */ +async function open(path: string, openWith?: string): Promise { + await invoke('plugin:shell|open', { + path, + with: openWith + }) +} + +export { Command, Child, EventEmitter, open } +export type { + IOPayload, + CommandEvents, + TerminatedPayload, + OutputEvents, + ChildProcess, + SpawnOptions +} diff --git a/packages/kbot/gui/app/plugins/shell/guest-js/init.ts b/packages/kbot/gui/app/plugins/shell/guest-js/init.ts new file mode 100644 index 00000000..b7e68a15 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/guest-js/init.ts @@ -0,0 +1,40 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { invoke } from '@tauri-apps/api/core' + +// open
links with the API +function openLinks(): void { + document.querySelector('body')?.addEventListener('click', function (e) { + let target: HTMLElement | null = e.target as HTMLElement + while (target) { + if (target.matches('a')) { + const t = target as HTMLAnchorElement + if ( + t.href !== '' + && ['http://', 'https://', 'mailto:', 'tel:'].some((v) => + t.href.startsWith(v) + ) + && t.target === '_blank' + ) { + void invoke('plugin:shell|open', { + path: t.href + }) + e.preventDefault() + } + break + } + target = target.parentElement + } + }) +} + +if ( + document.readyState === 'complete' + || document.readyState === 'interactive' +) { + openLinks() +} else { + window.addEventListener('DOMContentLoaded', openLinks, true) +} diff --git a/packages/kbot/gui/app/plugins/shell/ios/Package.resolved b/packages/kbot/gui/app/plugins/shell/ios/Package.resolved new file mode 100644 index 00000000..5f998e0e --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/ios/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "SwiftRs", + "repositoryURL": "https://github.com/Brendonovich/swift-rs", + "state": { + "branch": null, + "revision": "b5ed223fcdab165bc21219c1925dc1e77e2bef5e", + "version": "1.0.6" + } + } + ] + }, + "version": 1 +} diff --git a/packages/kbot/gui/app/plugins/shell/ios/Package.swift b/packages/kbot/gui/app/plugins/shell/ios/Package.swift new file mode 100644 index 00000000..c7b2a7aa --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/ios/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version:5.3 +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import PackageDescription + +let package = Package( + name: "tauri-plugin-shell", + platforms: [ + .macOS(.v10_13), + .iOS(.v13), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "tauri-plugin-shell", + type: .static, + targets: ["tauri-plugin-shell"]) + ], + dependencies: [ + .package(name: "Tauri", path: "../.tauri/tauri-api") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "tauri-plugin-shell", + dependencies: [ + .byName(name: "Tauri") + ], + path: "Sources") + ] +) diff --git a/packages/kbot/gui/app/plugins/shell/ios/Sources/ShellPlugin.swift b/packages/kbot/gui/app/plugins/shell/ios/Sources/ShellPlugin.swift new file mode 100644 index 00000000..0fcb7dac --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/ios/Sources/ShellPlugin.swift @@ -0,0 +1,34 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import Foundation + +import SwiftRs +import Tauri +import UIKit +import WebKit + +class ShellPlugin: Plugin { + + @objc public func open(_ invoke: Invoke) throws { + do { + let urlString = try invoke.parseArgs(String.self) + if let url = URL(string: urlString) { + if #available(iOS 10, *) { + UIApplication.shared.open(url, options: [:]) + } else { + UIApplication.shared.openURL(url) + } + } + invoke.resolve() + } catch { + invoke.reject(error.localizedDescription) + } + } +} + +@_cdecl("init_plugin_shell") +func initPlugin() -> Plugin { + return ShellPlugin() +} diff --git a/packages/kbot/gui/app/plugins/shell/package.json b/packages/kbot/gui/app/plugins/shell/package.json new file mode 100644 index 00000000..839c86b6 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/package.json @@ -0,0 +1,29 @@ +{ + "name": "@tauri-apps/plugin-shell", + "version": "2.3.1", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/shell/permissions/autogenerated/commands/execute.toml b/packages/kbot/gui/app/plugins/shell/permissions/autogenerated/commands/execute.toml new file mode 100644 index 00000000..d98be899 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/permissions/autogenerated/commands/execute.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-execute" +description = "Enables the execute command without any pre-configured scope." +commands.allow = ["execute"] + +[[permission]] +identifier = "deny-execute" +description = "Denies the execute command without any pre-configured scope." +commands.deny = ["execute"] diff --git a/packages/kbot/gui/app/plugins/shell/permissions/autogenerated/commands/kill.toml b/packages/kbot/gui/app/plugins/shell/permissions/autogenerated/commands/kill.toml new file mode 100644 index 00000000..11d2f7f0 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/permissions/autogenerated/commands/kill.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-kill" +description = "Enables the kill command without any pre-configured scope." +commands.allow = ["kill"] + +[[permission]] +identifier = "deny-kill" +description = "Denies the kill command without any pre-configured scope." +commands.deny = ["kill"] diff --git a/packages/kbot/gui/app/plugins/shell/permissions/autogenerated/commands/open.toml b/packages/kbot/gui/app/plugins/shell/permissions/autogenerated/commands/open.toml new file mode 100644 index 00000000..4ea6dff1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/permissions/autogenerated/commands/open.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-open" +description = "Enables the open command without any pre-configured scope." +commands.allow = ["open"] + +[[permission]] +identifier = "deny-open" +description = "Denies the open command without any pre-configured scope." +commands.deny = ["open"] diff --git a/packages/kbot/gui/app/plugins/shell/permissions/autogenerated/commands/spawn.toml b/packages/kbot/gui/app/plugins/shell/permissions/autogenerated/commands/spawn.toml new file mode 100644 index 00000000..a3802d2a --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/permissions/autogenerated/commands/spawn.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-spawn" +description = "Enables the spawn command without any pre-configured scope." +commands.allow = ["spawn"] + +[[permission]] +identifier = "deny-spawn" +description = "Denies the spawn command without any pre-configured scope." +commands.deny = ["spawn"] diff --git a/packages/kbot/gui/app/plugins/shell/permissions/autogenerated/commands/stdin_write.toml b/packages/kbot/gui/app/plugins/shell/permissions/autogenerated/commands/stdin_write.toml new file mode 100644 index 00000000..46ca5a73 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/permissions/autogenerated/commands/stdin_write.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-stdin-write" +description = "Enables the stdin_write command without any pre-configured scope." +commands.allow = ["stdin_write"] + +[[permission]] +identifier = "deny-stdin-write" +description = "Denies the stdin_write command without any pre-configured scope." +commands.deny = ["stdin_write"] diff --git a/packages/kbot/gui/app/plugins/shell/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/shell/permissions/autogenerated/reference.md new file mode 100644 index 00000000..87086a14 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/permissions/autogenerated/reference.md @@ -0,0 +1,154 @@ +## Default Permission + +This permission set configures which +shell functionality is exposed by default. + +#### Granted Permissions + +It allows to use the `open` functionality with a reasonable +scope pre-configured. It will allow opening `http(s)://`, +`tel:` and `mailto:` links. + +#### This default permission set includes the following: + +- `allow-open` + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`shell:allow-execute` + + + +Enables the execute command without any pre-configured scope. + +
+ +`shell:deny-execute` + + + +Denies the execute command without any pre-configured scope. + +
+ +`shell:allow-kill` + + + +Enables the kill command without any pre-configured scope. + +
+ +`shell:deny-kill` + + + +Denies the kill command without any pre-configured scope. + +
+ +`shell:allow-open` + + + +Enables the open command without any pre-configured scope. + +
+ +`shell:deny-open` + + + +Denies the open command without any pre-configured scope. + +
+ +`shell:allow-spawn` + + + +Enables the spawn command without any pre-configured scope. + +
+ +`shell:deny-spawn` + + + +Denies the spawn command without any pre-configured scope. + +
+ +`shell:allow-stdin-write` + + + +Enables the stdin_write command without any pre-configured scope. + +
+ +`shell:deny-stdin-write` + + + +Denies the stdin_write command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/shell/permissions/default.toml b/packages/kbot/gui/app/plugins/shell/permissions/default.toml new file mode 100644 index 00000000..dba2ea20 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/permissions/default.toml @@ -0,0 +1,15 @@ +"$schema" = "schemas/schema.json" + +[default] +description = """ +This permission set configures which +shell functionality is exposed by default. + +#### Granted Permissions + +It allows to use the `open` functionality with a reasonable +scope pre-configured. It will allow opening `http(s)://`, +`tel:` and `mailto:` links. +""" + +permissions = ["allow-open"] diff --git a/packages/kbot/gui/app/plugins/shell/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/shell/permissions/schemas/schema.json new file mode 100644 index 00000000..9a198981 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/permissions/schemas/schema.json @@ -0,0 +1,366 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the execute command without any pre-configured scope.", + "type": "string", + "const": "allow-execute", + "markdownDescription": "Enables the execute command without any pre-configured scope." + }, + { + "description": "Denies the execute command without any pre-configured scope.", + "type": "string", + "const": "deny-execute", + "markdownDescription": "Denies the execute command without any pre-configured scope." + }, + { + "description": "Enables the kill command without any pre-configured scope.", + "type": "string", + "const": "allow-kill", + "markdownDescription": "Enables the kill command without any pre-configured scope." + }, + { + "description": "Denies the kill command without any pre-configured scope.", + "type": "string", + "const": "deny-kill", + "markdownDescription": "Denies the kill command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Enables the spawn command without any pre-configured scope.", + "type": "string", + "const": "allow-spawn", + "markdownDescription": "Enables the spawn command without any pre-configured scope." + }, + { + "description": "Denies the spawn command without any pre-configured scope.", + "type": "string", + "const": "deny-spawn", + "markdownDescription": "Denies the spawn command without any pre-configured scope." + }, + { + "description": "Enables the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "allow-stdin-write", + "markdownDescription": "Enables the stdin_write command without any pre-configured scope." + }, + { + "description": "Denies the stdin_write command without any pre-configured scope.", + "type": "string", + "const": "deny-stdin-write", + "markdownDescription": "Denies the stdin_write command without any pre-configured scope." + }, + { + "description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`", + "type": "string", + "const": "default", + "markdownDescription": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/shell/rollup.config.js b/packages/kbot/gui/app/plugins/shell/rollup.config.js new file mode 100644 index 00000000..a7dbd4f6 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/rollup.config.js @@ -0,0 +1,22 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' +import { nodeResolve } from '@rollup/plugin-node-resolve' +import typescript from '@rollup/plugin-typescript' +import terser from '@rollup/plugin-terser' + +export default createConfig({ + additionalConfigs: { + input: 'guest-js/init.ts', + output: { + file: 'src/init-iife.js', + format: 'iife' + }, + plugins: [typescript(), terser(), nodeResolve()], + onwarn: (warning) => { + throw Object.assign(new Error(), warning) + } + } +}) diff --git a/packages/kbot/gui/app/plugins/shell/src/commands.rs b/packages/kbot/gui/app/plugins/shell/src/commands.rs new file mode 100644 index 00000000..0facce71 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/src/commands.rs @@ -0,0 +1,320 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{collections::HashMap, future::Future, path::PathBuf, pin::Pin, string::FromUtf8Error}; + +use encoding_rs::Encoding; +use serde::{Deserialize, Serialize}; +use tauri::{ + ipc::{Channel, CommandScope, GlobalScope}, + Manager, Runtime, State, Window, +}; + +#[allow(deprecated)] +use crate::open::Program; +use crate::{ + process::{CommandEvent, TerminatedPayload}, + scope::ExecuteArgs, + Shell, +}; + +type ChildId = u32; + +#[derive(Debug, Clone, Serialize)] +#[serde(tag = "event", content = "payload")] +#[non_exhaustive] +pub enum JSCommandEvent { + /// Stderr bytes until a newline (\n) or carriage return (\r) is found. + Stderr(Buffer), + /// Stdout bytes until a newline (\n) or carriage return (\r) is found. + Stdout(Buffer), + /// An error happened waiting for the command to finish or converting the stdout/stderr bytes to an UTF-8 string. + Error(String), + /// Command process terminated. + Terminated(TerminatedPayload), +} + +fn get_event_buffer(line: Vec, encoding: EncodingWrapper) -> Result { + match encoding { + EncodingWrapper::Text(character_encoding) => match character_encoding { + Some(encoding) => Ok(Buffer::Text( + encoding.decode_with_bom_removal(&line).0.into(), + )), + None => String::from_utf8(line).map(Buffer::Text), + }, + EncodingWrapper::Raw => Ok(Buffer::Raw(line)), + } +} + +impl JSCommandEvent { + pub fn new(event: CommandEvent, encoding: EncodingWrapper) -> Self { + match event { + CommandEvent::Terminated(payload) => JSCommandEvent::Terminated(payload), + CommandEvent::Error(error) => JSCommandEvent::Error(error), + CommandEvent::Stderr(line) => get_event_buffer(line, encoding) + .map(JSCommandEvent::Stderr) + .unwrap_or_else(|e| JSCommandEvent::Error(e.to_string())), + CommandEvent::Stdout(line) => get_event_buffer(line, encoding) + .map(JSCommandEvent::Stdout) + .unwrap_or_else(|e| JSCommandEvent::Error(e.to_string())), + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(untagged)] +#[allow(missing_docs)] +pub enum Buffer { + Text(String), + Raw(Vec), +} + +#[derive(Debug, Copy, Clone)] +pub enum EncodingWrapper { + Raw, + Text(Option<&'static Encoding>), +} + +#[derive(Debug, Clone, Default, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandOptions { + #[serde(default)] + sidecar: bool, + cwd: Option, + // by default we don't add any env variables to the spawned process + // but the env is an `Option` so when it's `None` we clear the env. + #[serde(default = "default_env")] + env: Option>, + // Character encoding for stdout/stderr + encoding: Option, +} + +#[allow(clippy::unnecessary_wraps)] +fn default_env() -> Option> { + Some(HashMap::default()) +} + +#[inline(always)] +fn prepare_cmd( + window: Window, + program: String, + args: ExecuteArgs, + options: CommandOptions, + command_scope: CommandScope, + global_scope: GlobalScope, +) -> crate::Result<(crate::process::Command, EncodingWrapper)> { + let scope = crate::scope::ShellScope { + scopes: command_scope + .allows() + .iter() + .chain(global_scope.allows()) + .collect(), + }; + + let mut command = if options.sidecar { + let program = PathBuf::from(program); + let program_as_string = program.display().to_string(); + let has_extension = program.extension().is_some_and(|ext| ext == "exe"); + let program_no_ext_as_string = if has_extension { + program.with_extension("").display().to_string() + } else { + program_as_string.clone() + }; + let configured_sidecar = window + .config() + .bundle + .external_bin + .as_ref() + .and_then(|bins| { + bins.iter() + .find(|b| b == &&program_as_string || b == &&program_no_ext_as_string) + }) + .cloned(); + if let Some(sidecar) = configured_sidecar { + scope.prepare_sidecar(&program.to_string_lossy(), &sidecar, args)? + } else { + return Err(crate::Error::SidecarNotAllowed(program)); + } + } else { + match scope.prepare(&program, args) { + Ok(cmd) => cmd, + Err(e) => { + #[cfg(debug_assertions)] + eprintln!("{e}"); + return Err(crate::Error::ProgramNotAllowed(PathBuf::from(program))); + } + } + }; + if let Some(cwd) = options.cwd { + command = command.current_dir(cwd); + } + if let Some(env) = options.env { + command = command.envs(env); + } else { + command = command.env_clear(); + } + + let encoding = match options.encoding { + Option::None => EncodingWrapper::Text(None), + Some(encoding) => match encoding.as_str() { + "raw" => { + command = command.set_raw_out(true); + EncodingWrapper::Raw + } + _ => { + if let Some(text_encoding) = Encoding::for_label(encoding.as_bytes()) { + EncodingWrapper::Text(Some(text_encoding)) + } else { + return Err(crate::Error::UnknownEncoding(encoding)); + } + } + }, + }; + + Ok((command, encoding)) +} + +#[derive(Serialize)] +#[serde(untagged)] +enum Output { + String(String), + Raw(Vec), +} + +#[derive(Serialize)] +pub struct ChildProcessReturn { + code: Option, + signal: Option, + stdout: Output, + stderr: Output, +} + +#[allow(clippy::too_many_arguments)] +#[tauri::command] +pub async fn execute( + window: Window, + program: String, + args: ExecuteArgs, + options: CommandOptions, + command_scope: CommandScope, + global_scope: GlobalScope, +) -> crate::Result { + let (command, encoding) = + prepare_cmd(window, program, args, options, command_scope, global_scope)?; + + let mut command: std::process::Command = command.into(); + let output = command.output()?; + + let (stdout, stderr) = match encoding { + EncodingWrapper::Text(Some(encoding)) => ( + Output::String(encoding.decode_with_bom_removal(&output.stdout).0.into()), + Output::String(encoding.decode_with_bom_removal(&output.stderr).0.into()), + ), + EncodingWrapper::Text(None) => ( + Output::String(String::from_utf8(output.stdout)?), + Output::String(String::from_utf8(output.stderr)?), + ), + EncodingWrapper::Raw => (Output::Raw(output.stdout), Output::Raw(output.stderr)), + }; + + #[cfg(unix)] + use std::os::unix::process::ExitStatusExt; + + Ok(ChildProcessReturn { + code: output.status.code(), + #[cfg(windows)] + signal: None, + #[cfg(unix)] + signal: output.status.signal(), + stdout, + stderr, + }) +} + +#[allow(clippy::too_many_arguments)] +#[tauri::command] +pub fn spawn( + window: Window, + shell: State<'_, Shell>, + program: String, + args: ExecuteArgs, + on_event: Channel, + options: CommandOptions, + command_scope: CommandScope, + global_scope: GlobalScope, +) -> crate::Result { + let (command, encoding) = + prepare_cmd(window, program, args, options, command_scope, global_scope)?; + + let (mut rx, child) = command.spawn()?; + + let pid = child.pid(); + shell.children.lock().unwrap().insert(pid, child); + let children = shell.children.clone(); + + tauri::async_runtime::spawn(async move { + while let Some(event) = rx.recv().await { + if matches!(event, crate::process::CommandEvent::Terminated(_)) { + children.lock().unwrap().remove(&pid); + }; + let js_event = JSCommandEvent::new(event, encoding); + + if on_event.send(js_event.clone()).is_err() { + fn send<'a>( + on_event: &'a Channel, + js_event: &'a JSCommandEvent, + ) -> Pin + Send + 'a>> { + Box::pin(async move { + tokio::time::sleep(std::time::Duration::from_millis(15)).await; + if on_event.send(js_event.clone()).is_err() { + send(on_event, js_event).await; + } + }) + } + send(&on_event, &js_event).await; + } + } + }); + + Ok(pid) +} + +#[tauri::command] +pub fn stdin_write( + _window: Window, + shell: State<'_, Shell>, + pid: ChildId, + buffer: Buffer, +) -> crate::Result<()> { + if let Some(child) = shell.children.lock().unwrap().get_mut(&pid) { + match buffer { + Buffer::Text(t) => child.write(t.as_bytes())?, + Buffer::Raw(r) => child.write(&r)?, + } + } + Ok(()) +} + +#[tauri::command] +pub fn kill( + _window: Window, + shell: State<'_, Shell>, + pid: ChildId, +) -> crate::Result<()> { + if let Some(child) = shell.children.lock().unwrap().remove(&pid) { + child.kill()?; + } + Ok(()) +} + +#[allow(deprecated)] +#[tauri::command] +pub async fn open( + _window: Window, + shell: State<'_, Shell>, + path: String, + with: Option, +) -> crate::Result<()> { + crate::open::open(Some(&shell.open_scope), path, with) +} diff --git a/packages/kbot/gui/app/plugins/shell/src/config.rs b/packages/kbot/gui/app/plugins/shell/src/config.rs new file mode 100644 index 00000000..69a92ee1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/src/config.rs @@ -0,0 +1,43 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::Deserialize; + +/// Configuration for the shell plugin. +#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct Config { + /// Open URL with the user's default application. + #[serde(default)] + pub open: ShellAllowlistOpen, +} + +/// Defines the `shell > open` api scope. +#[derive(Debug, PartialEq, Eq, Clone, Deserialize)] +#[serde(untagged, deny_unknown_fields)] +#[non_exhaustive] +pub enum ShellAllowlistOpen { + /// Shell open API allowlist is not defined by the user. + /// In this case we add the default validation regex (same as [`Self::Flag(true)`]). + Unset, + /// If the shell open API should be enabled. + /// + /// If enabled, the default validation regex (`^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+`) is used. + Flag(bool), + + /// Enable the shell open API, with a custom regex that the opened path must match against. + /// + /// The regex string is automatically surrounded by `^...$` to match the full string. + /// For example the `https?://\w+` regex would be registered as `^https?://\w+$`. + /// + /// If using a custom regex to support a non-http(s) schema, care should be used to prevent values + /// that allow flag-like strings to pass validation. e.g. `--enable-debugging`, `-i`, `/R`. + Validate(String), +} + +impl Default for ShellAllowlistOpen { + fn default() -> Self { + Self::Unset + } +} diff --git a/packages/kbot/gui/app/plugins/shell/src/error.rs b/packages/kbot/gui/app/plugins/shell/src/error.rs new file mode 100644 index 00000000..652421b8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/src/error.rs @@ -0,0 +1,45 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::path::PathBuf; + +use serde::{Serialize, Serializer}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error("current executable path has no parent")] + CurrentExeHasNoParent, + #[error("unknown program {0}")] + UnknownProgramName(String), + #[error(transparent)] + Scope(#[from] crate::scope::Error), + /// Sidecar not allowed by the configuration. + #[error("sidecar not configured under `tauri.conf.json > bundle > externalBin`: {0}")] + SidecarNotAllowed(PathBuf), + /// Program not allowed by the scope. + #[error("program not allowed on the configured shell scope: {0}")] + ProgramNotAllowed(PathBuf), + #[error("unknown encoding {0}")] + UnknownEncoding(String), + /// JSON error. + #[error(transparent)] + Json(#[from] serde_json::Error), + /// Utf8 error. + #[error(transparent)] + Utf8(#[from] std::string::FromUtf8Error), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/packages/kbot/gui/app/plugins/shell/src/init-iife.js b/packages/kbot/gui/app/plugins/shell/src/init-iife.js new file mode 100644 index 00000000..3c91c81c --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/src/init-iife.js @@ -0,0 +1 @@ +!function(){"use strict";async function e(e,t={},n){return window.__TAURI_INTERNALS__.invoke(e,t,n)}function t(){document.querySelector("body")?.addEventListener("click",(function(t){let n=t.target;for(;n;){if(n.matches("a")){const r=n;""!==r.href&&["http://","https://","mailto:","tel:"].some((e=>r.href.startsWith(e)))&&"_blank"===r.target&&(e("plugin:shell|open",{path:r.href}),t.preventDefault());break}n=n.parentElement}}))}"function"==typeof SuppressedError&&SuppressedError,"complete"===document.readyState||"interactive"===document.readyState?t():window.addEventListener("DOMContentLoaded",t,!0)}(); diff --git a/packages/kbot/gui/app/plugins/shell/src/lib.rs b/packages/kbot/gui/app/plugins/shell/src/lib.rs new file mode 100644 index 00000000..c9503731 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/src/lib.rs @@ -0,0 +1,165 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Access the system shell. Allows you to spawn child processes and manage files and URLs using their default application. + +#![doc( + html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", + html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" +)] + +use std::{ + collections::HashMap, + ffi::OsStr, + path::Path, + sync::{Arc, Mutex}, +}; + +use process::{Command, CommandChild}; +use regex::Regex; +use tauri::{ + plugin::{Builder, TauriPlugin}, + AppHandle, Manager, RunEvent, Runtime, +}; + +mod commands; +mod config; +mod error; +#[deprecated(since = "2.1.0", note = "Use tauri-plugin-opener instead.")] +#[allow(deprecated)] +pub mod open; +pub mod process; +mod scope; +mod scope_entry; + +pub use error::Error; +type Result = std::result::Result; + +#[cfg(mobile)] +use tauri::plugin::PluginHandle; +#[cfg(target_os = "android")] +const PLUGIN_IDENTIFIER: &str = "app.tauri.shell"; +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_shell); + +type ChildStore = Arc>>; + +pub struct Shell { + #[allow(dead_code)] + app: AppHandle, + #[cfg(mobile)] + mobile_plugin_handle: PluginHandle, + open_scope: scope::OpenScope, + children: ChildStore, +} + +impl Shell { + /// Creates a new Command for launching the given program. + pub fn command(&self, program: impl AsRef) -> Command { + Command::new(program) + } + + /// Creates a new Command for launching the given sidecar program. + /// + /// A sidecar program is a embedded external binary in order to make your application work + /// or to prevent users having to install additional dependencies (e.g. Node.js, Python, etc). + pub fn sidecar(&self, program: impl AsRef) -> Result { + Command::new_sidecar(program) + } + + /// Open a (url) path with a default or specific browser opening program. + /// + /// See [`crate::open::open`] for how it handles security-related measures. + #[cfg(desktop)] + #[deprecated(since = "2.1.0", note = "Use tauri-plugin-opener instead.")] + #[allow(deprecated)] + pub fn open(&self, path: impl Into, with: Option) -> Result<()> { + open::open(None, path.into(), with) + } + + /// Open a (url) path with a default or specific browser opening program. + /// + /// See [`crate::open::open`] for how it handles security-related measures. + #[cfg(mobile)] + #[deprecated(since = "2.1.0", note = "Use tauri-plugin-opener instead.")] + pub fn open(&self, path: impl Into, _with: Option) -> Result<()> { + self.mobile_plugin_handle + .run_mobile_plugin("open", path.into()) + .map_err(Into::into) + } +} + +pub trait ShellExt { + fn shell(&self) -> &Shell; +} + +impl> ShellExt for T { + fn shell(&self) -> &Shell { + self.state::>().inner() + } +} + +pub fn init() -> TauriPlugin> { + Builder::>::new("shell") + .js_init_script(include_str!("init-iife.js").to_string()) + .invoke_handler(tauri::generate_handler![ + commands::execute, + commands::spawn, + commands::stdin_write, + commands::kill, + commands::open + ]) + .setup(|app, api| { + let default_config = config::Config::default(); + let config = api.config().as_ref().unwrap_or(&default_config); + + #[cfg(target_os = "android")] + let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "ShellPlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_shell)?; + + app.manage(Shell { + app: app.clone(), + children: Default::default(), + open_scope: open_scope(&config.open), + + #[cfg(mobile)] + mobile_plugin_handle: handle, + }); + Ok(()) + }) + .on_event(|app, event| { + if let RunEvent::Exit = event { + let shell = app.state::>(); + let children = { + let mut lock = shell.children.lock().unwrap(); + std::mem::take(&mut *lock) + }; + for child in children.into_values() { + let _ = child.kill(); + } + } + }) + .build() +} + +fn open_scope(open: &config::ShellAllowlistOpen) -> scope::OpenScope { + let shell_scope_open = match open { + config::ShellAllowlistOpen::Flag(false) => None, + // we want to add a basic regex validation even if the config is not set + config::ShellAllowlistOpen::Unset | config::ShellAllowlistOpen::Flag(true) => { + Some(Regex::new(r"^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+").unwrap()) + } + config::ShellAllowlistOpen::Validate(validator) => { + let regex = format!("^{validator}$"); + let validator = + Regex::new(®ex).unwrap_or_else(|e| panic!("invalid regex {regex}: {e}")); + Some(validator) + } + }; + + scope::OpenScope { + open: shell_scope_open, + } +} diff --git a/packages/kbot/gui/app/plugins/shell/src/open.rs b/packages/kbot/gui/app/plugins/shell/src/open.rs new file mode 100644 index 00000000..6958a832 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/src/open.rs @@ -0,0 +1,138 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Types and functions related to shell. + +use serde::{Deserialize, Deserializer}; + +use crate::scope::OpenScope; +use std::str::FromStr; + +/// Program to use on the [`open()`] call. +#[deprecated(since = "2.1.0", note = "Use tauri-plugin-opener instead.")] +pub enum Program { + /// Use the `open` program. + Open, + /// Use the `start` program. + Start, + /// Use the `xdg-open` program. + XdgOpen, + /// Use the `gio` program. + Gio, + /// Use the `gnome-open` program. + GnomeOpen, + /// Use the `kde-open` program. + KdeOpen, + /// Use the `wslview` program. + WslView, + /// Use the `Firefox` program. + Firefox, + /// Use the `Google Chrome` program. + Chrome, + /// Use the `Chromium` program. + Chromium, + /// Use the `Safari` program. + Safari, +} + +impl FromStr for Program { + type Err = super::Error; + + fn from_str(s: &str) -> Result { + let p = match s.to_lowercase().as_str() { + "open" => Self::Open, + "start" => Self::Start, + "xdg-open" => Self::XdgOpen, + "gio" => Self::Gio, + "gnome-open" => Self::GnomeOpen, + "kde-open" => Self::KdeOpen, + "wslview" => Self::WslView, + "firefox" => Self::Firefox, + "chrome" | "google chrome" => Self::Chrome, + "chromium" => Self::Chromium, + "safari" => Self::Safari, + _ => return Err(crate::Error::UnknownProgramName(s.to_string())), + }; + Ok(p) + } +} + +impl<'de> Deserialize<'de> for Program { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Program::from_str(&s).map_err(|e| serde::de::Error::custom(e.to_string())) + } +} + +impl Program { + pub(crate) fn name(self) -> &'static str { + match self { + Self::Open => "open", + Self::Start => "start", + Self::XdgOpen => "xdg-open", + Self::Gio => "gio", + Self::GnomeOpen => "gnome-open", + Self::KdeOpen => "kde-open", + Self::WslView => "wslview", + + #[cfg(target_os = "macos")] + Self::Firefox => "Firefox", + #[cfg(not(target_os = "macos"))] + Self::Firefox => "firefox", + + #[cfg(target_os = "macos")] + Self::Chrome => "Google Chrome", + #[cfg(not(target_os = "macos"))] + Self::Chrome => "google-chrome", + + #[cfg(target_os = "macos")] + Self::Chromium => "Chromium", + #[cfg(not(target_os = "macos"))] + Self::Chromium => "chromium", + + #[cfg(target_os = "macos")] + Self::Safari => "Safari", + #[cfg(not(target_os = "macos"))] + Self::Safari => "safari", + } + } +} + +/// Opens path or URL with the program specified in `with`, or system default if `None`. +/// +/// The path will be matched against the shell open validation regex, defaulting to `^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+`. +/// A custom validation regex may be supplied in the config in `plugins > shell > scope > open`. +/// +/// # Examples +/// +/// ```rust,no_run +/// use tauri_plugin_shell::ShellExt; +/// tauri::Builder::default() +/// .setup(|app| { +/// // open the given URL on the system default browser +/// app.shell().open("https://github.com/tauri-apps/tauri", None)?; +/// Ok(()) +/// }); +/// ``` +#[deprecated(since = "2.1.0", note = "Use tauri-plugin-opener instead.")] +pub fn open>( + scope: Option<&OpenScope>, + path: P, + with: Option, +) -> crate::Result<()> { + // validate scope if we have any (JS calls) + if let Some(scope) = scope { + scope.open(path.as_ref(), with).map_err(Into::into) + } else { + // when running directly from Rust code we don't need to validate the path + match with.map(Program::name) { + Some(program) => ::open::with_detached(path.as_ref(), program), + None => ::open::that_detached(path.as_ref()), + } + .map_err(Into::into) + } +} diff --git a/packages/kbot/gui/app/plugins/shell/src/process/mod.rs b/packages/kbot/gui/app/plugins/shell/src/process/mod.rs new file mode 100644 index 00000000..1384bbb8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/src/process/mod.rs @@ -0,0 +1,616 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{ + ffi::OsStr, + io::{BufRead, BufReader, Write}, + path::{Path, PathBuf}, + process::{Command as StdCommand, Stdio}, + sync::{Arc, RwLock}, + thread::spawn, +}; + +#[cfg(unix)] +use std::os::unix::process::ExitStatusExt; +#[cfg(windows)] +use std::os::windows::process::CommandExt; + +#[cfg(windows)] +const CREATE_NO_WINDOW: u32 = 0x0800_0000; +const NEWLINE_BYTE: u8 = b'\n'; + +use tauri::async_runtime::{block_on as block_on_task, channel, Receiver, Sender}; + +pub use encoding_rs::Encoding; +use os_pipe::{pipe, PipeReader, PipeWriter}; +use serde::Serialize; +use shared_child::SharedChild; +use tauri::utils::platform; + +/// Payload for the [`CommandEvent::Terminated`] command event. +#[derive(Debug, Clone, Serialize)] +pub struct TerminatedPayload { + /// Exit code of the process. + pub code: Option, + /// If the process was terminated by a signal, represents that signal. + pub signal: Option, +} + +/// A event sent to the command callback. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum CommandEvent { + /// If configured for raw output, all bytes written to stderr. + /// Otherwise, bytes until a newline (\n) or carriage return (\r) is found. + Stderr(Vec), + /// If configured for raw output, all bytes written to stdout. + /// Otherwise, bytes until a newline (\n) or carriage return (\r) is found. + Stdout(Vec), + /// An error happened waiting for the command to finish or converting the stdout/stderr bytes to a UTF-8 string. + Error(String), + /// Command process terminated. + Terminated(TerminatedPayload), +} + +/// The type to spawn commands. +#[derive(Debug)] +pub struct Command { + cmd: StdCommand, + raw_out: bool, +} + +/// Spawned child process. +#[derive(Debug)] +pub struct CommandChild { + inner: Arc, + stdin_writer: PipeWriter, +} + +impl CommandChild { + /// Writes to process stdin. + pub fn write(&mut self, buf: &[u8]) -> crate::Result<()> { + self.stdin_writer.write_all(buf)?; + Ok(()) + } + + /// Sends a kill signal to the child. + pub fn kill(self) -> crate::Result<()> { + self.inner.kill()?; + Ok(()) + } + + /// Returns the process pid. + pub fn pid(&self) -> u32 { + self.inner.id() + } +} + +/// Describes the result of a process after it has terminated. +#[derive(Debug)] +pub struct ExitStatus { + code: Option, +} + +impl ExitStatus { + /// Returns the exit code of the process, if any. + pub fn code(&self) -> Option { + self.code + } + + /// Returns true if exit status is zero. Signal termination is not considered a success, and success is defined as a zero exit status. + pub fn success(&self) -> bool { + self.code == Some(0) + } +} + +/// The output of a finished process. +#[derive(Debug)] +pub struct Output { + /// The status (exit code) of the process. + pub status: ExitStatus, + /// The data that the process wrote to stdout. + pub stdout: Vec, + /// The data that the process wrote to stderr. + pub stderr: Vec, +} + +fn relative_command_path(command: &Path) -> crate::Result { + match platform::current_exe()?.parent() { + #[cfg(windows)] + Some(exe_dir) => { + let mut command_path = exe_dir.join(command); + let already_exe = command_path.extension().is_some_and(|ext| ext == "exe"); + if !already_exe { + // do not use with_extension to retain dots in the command filename + command_path.as_mut_os_string().push(".exe"); + } + Ok(command_path) + } + #[cfg(not(windows))] + Some(exe_dir) => { + let mut command_path = exe_dir.join(command); + if command_path.extension().is_some_and(|ext| ext == "exe") { + command_path.set_extension(""); + } + Ok(command_path) + } + None => Err(crate::Error::CurrentExeHasNoParent), + } +} + +impl From for StdCommand { + fn from(cmd: Command) -> StdCommand { + cmd.cmd + } +} + +impl Command { + pub(crate) fn new>(program: S) -> Self { + log::debug!( + "Creating sidecar {}", + program.as_ref().to_str().unwrap_or("") + ); + let mut command = StdCommand::new(program); + + command.stdout(Stdio::piped()); + command.stdin(Stdio::piped()); + command.stderr(Stdio::piped()); + #[cfg(windows)] + command.creation_flags(CREATE_NO_WINDOW); + + Self { + cmd: command, + raw_out: false, + } + } + + pub(crate) fn new_sidecar>(program: S) -> crate::Result { + Ok(Self::new(relative_command_path(program.as_ref())?)) + } + + /// Appends an argument to the command. + #[must_use] + pub fn arg>(mut self, arg: S) -> Self { + self.cmd.arg(arg); + self + } + + /// Appends arguments to the command. + #[must_use] + pub fn args(mut self, args: I) -> Self + where + I: IntoIterator, + S: AsRef, + { + self.cmd.args(args); + self + } + + /// Clears the entire environment map for the child process. + #[must_use] + pub fn env_clear(mut self) -> Self { + self.cmd.env_clear(); + self + } + + /// Inserts or updates an explicit environment variable mapping. + #[must_use] + pub fn env(mut self, key: K, value: V) -> Self + where + K: AsRef, + V: AsRef, + { + self.cmd.env(key, value); + self + } + + /// Adds or updates multiple environment variable mappings. + #[must_use] + pub fn envs(mut self, envs: I) -> Self + where + I: IntoIterator, + K: AsRef, + V: AsRef, + { + self.cmd.envs(envs); + self + } + + /// Sets the working directory for the child process. + #[must_use] + pub fn current_dir>(mut self, current_dir: P) -> Self { + self.cmd.current_dir(current_dir); + self + } + + /// Configures the reader to output bytes from the child process exactly as received + pub fn set_raw_out(mut self, raw_out: bool) -> Self { + self.raw_out = raw_out; + self + } + + /// Spawns the command. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri_plugin_shell::{process::CommandEvent, ShellExt}; + /// tauri::Builder::default() + /// .setup(|app| { + /// let handle = app.handle().clone(); + /// tauri::async_runtime::spawn(async move { + /// let (mut rx, mut child) = handle.shell().command("cargo") + /// .args(["tauri", "dev"]) + /// .spawn() + /// .expect("Failed to spawn cargo"); + /// + /// let mut i = 0; + /// while let Some(event) = rx.recv().await { + /// if let CommandEvent::Stdout(line) = event { + /// println!("got: {}", String::from_utf8(line).unwrap()); + /// i += 1; + /// if i == 4 { + /// child.write("message from Rust\n".as_bytes()).unwrap(); + /// i = 0; + /// } + /// } + /// } + /// }); + /// Ok(()) + /// }); + /// ``` + pub fn spawn(self) -> crate::Result<(Receiver, CommandChild)> { + let raw = self.raw_out; + let mut command: StdCommand = self.into(); + let (stdout_reader, stdout_writer) = pipe()?; + let (stderr_reader, stderr_writer) = pipe()?; + let (stdin_reader, stdin_writer) = pipe()?; + command.stdout(stdout_writer); + command.stderr(stderr_writer); + command.stdin(stdin_reader); + + let shared_child = SharedChild::spawn(&mut command)?; + let child = Arc::new(shared_child); + let child_ = child.clone(); + let guard = Arc::new(RwLock::new(())); + + let (tx, rx) = channel(1); + + spawn_pipe_reader( + tx.clone(), + guard.clone(), + stdout_reader, + CommandEvent::Stdout, + raw, + ); + spawn_pipe_reader( + tx.clone(), + guard.clone(), + stderr_reader, + CommandEvent::Stderr, + raw, + ); + + spawn(move || { + let _ = match child_.wait() { + Ok(status) => { + let _l = guard.write().unwrap(); + block_on_task(async move { + tx.send(CommandEvent::Terminated(TerminatedPayload { + code: status.code(), + #[cfg(windows)] + signal: None, + #[cfg(unix)] + signal: status.signal(), + })) + .await + }) + } + Err(e) => { + let _l = guard.write().unwrap(); + block_on_task(async move { tx.send(CommandEvent::Error(e.to_string())).await }) + } + }; + }); + + Ok(( + rx, + CommandChild { + inner: child, + stdin_writer, + }, + )) + } + + /// Executes a command as a child process, waiting for it to finish and collecting its exit status. + /// Stdin, stdout and stderr are ignored. + /// + /// # Examples + /// ```rust,no_run + /// use tauri_plugin_shell::ShellExt; + /// tauri::Builder::default() + /// .setup(|app| { + /// let status = tauri::async_runtime::block_on(async move { app.shell().command("which").args(["ls"]).status().await.unwrap() }); + /// println!("`which` finished with status: {:?}", status.code()); + /// Ok(()) + /// }); + /// ``` + pub async fn status(self) -> crate::Result { + let (mut rx, _child) = self.spawn()?; + let mut code = None; + #[allow(clippy::collapsible_match)] + while let Some(event) = rx.recv().await { + if let CommandEvent::Terminated(payload) = event { + code = payload.code; + } + } + Ok(ExitStatus { code }) + } + + /// Executes the command as a child process, waiting for it to finish and collecting all of its output. + /// Stdin is ignored. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri_plugin_shell::ShellExt; + /// tauri::Builder::default() + /// .setup(|app| { + /// let output = tauri::async_runtime::block_on(async move { app.shell().command("echo").args(["TAURI"]).output().await.unwrap() }); + /// assert!(output.status.success()); + /// assert_eq!(String::from_utf8(output.stdout).unwrap(), "TAURI"); + /// Ok(()) + /// }); + /// ``` + pub async fn output(self) -> crate::Result { + let (mut rx, _child) = self.spawn()?; + + let mut code = None; + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + + while let Some(event) = rx.recv().await { + match event { + CommandEvent::Terminated(payload) => { + code = payload.code; + } + CommandEvent::Stdout(line) => { + stdout.extend(line); + stdout.push(NEWLINE_BYTE); + } + CommandEvent::Stderr(line) => { + stderr.extend(line); + stderr.push(NEWLINE_BYTE); + } + CommandEvent::Error(_) => {} + } + } + Ok(Output { + status: ExitStatus { code }, + stdout, + stderr, + }) + } +} + +fn read_raw_bytes) -> CommandEvent + Send + Copy + 'static>( + mut reader: BufReader, + tx: Sender, + wrapper: F, +) { + loop { + let result = reader.fill_buf(); + match result { + Ok(buf) => { + let length = buf.len(); + if length == 0 { + break; + } + let tx_ = tx.clone(); + let _ = block_on_task(async move { tx_.send(wrapper(buf.to_vec())).await }); + reader.consume(length); + } + Err(e) => { + let tx_ = tx.clone(); + let _ = block_on_task( + async move { tx_.send(CommandEvent::Error(e.to_string())).await }, + ); + } + } + } +} + +fn read_line) -> CommandEvent + Send + Copy + 'static>( + mut reader: BufReader, + tx: Sender, + wrapper: F, +) { + loop { + let mut buf = Vec::new(); + match tauri::utils::io::read_line(&mut reader, &mut buf) { + Ok(n) => { + if n == 0 { + break; + } + let tx_ = tx.clone(); + let _ = block_on_task(async move { tx_.send(wrapper(buf)).await }); + } + Err(e) => { + let tx_ = tx.clone(); + let _ = block_on_task( + async move { tx_.send(CommandEvent::Error(e.to_string())).await }, + ); + break; + } + } + } +} + +fn spawn_pipe_reader) -> CommandEvent + Send + Copy + 'static>( + tx: Sender, + guard: Arc>, + pipe_reader: PipeReader, + wrapper: F, + raw_out: bool, +) { + spawn(move || { + let _lock = guard.read().unwrap(); + let reader = BufReader::new(pipe_reader); + + if raw_out { + read_raw_bytes(reader, tx, wrapper); + } else { + read_line(reader, tx, wrapper); + } + }); +} + +// tests for the commands functions. +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn relative_command_path_resolves() { + let cwd_parent = platform::current_exe() + .unwrap() + .parent() + .unwrap() + .to_owned(); + assert_eq!( + relative_command_path(Path::new("Tauri.Example")).unwrap(), + cwd_parent.join(if cfg!(windows) { + "Tauri.Example.exe" + } else { + "Tauri.Example" + }) + ); + assert_eq!( + relative_command_path(Path::new("Tauri.Example.exe")).unwrap(), + cwd_parent.join(if cfg!(windows) { + "Tauri.Example.exe" + } else { + "Tauri.Example" + }) + ); + } + + #[cfg(not(windows))] + #[test] + fn test_cmd_spawn_output() { + let cmd = Command::new("cat").args(["test/test.txt"]); + let (mut rx, _) = cmd.spawn().unwrap(); + + tauri::async_runtime::block_on(async move { + while let Some(event) = rx.recv().await { + match event { + CommandEvent::Terminated(payload) => { + assert_eq!(payload.code, Some(0)); + } + CommandEvent::Stdout(line) => { + assert_eq!(String::from_utf8(line).unwrap(), "This is a test doc!"); + } + _ => {} + } + } + }); + } + + #[cfg(not(windows))] + #[test] + fn test_cmd_spawn_raw_output() { + let cmd = Command::new("cat").args(["test/test.txt"]); + let (mut rx, _) = cmd.spawn().unwrap(); + + tauri::async_runtime::block_on(async move { + while let Some(event) = rx.recv().await { + match event { + CommandEvent::Terminated(payload) => { + assert_eq!(payload.code, Some(0)); + } + CommandEvent::Stdout(line) => { + assert_eq!(String::from_utf8(line).unwrap(), "This is a test doc!"); + } + _ => {} + } + } + }); + } + + #[cfg(not(windows))] + #[test] + // test the failure case + fn test_cmd_spawn_fail() { + let cmd = Command::new("cat").args(["test/"]); + let (mut rx, _) = cmd.spawn().unwrap(); + + tauri::async_runtime::block_on(async move { + while let Some(event) = rx.recv().await { + match event { + CommandEvent::Terminated(payload) => { + assert_eq!(payload.code, Some(1)); + } + CommandEvent::Stderr(line) => { + assert_eq!( + String::from_utf8(line).unwrap(), + "cat: test/: Is a directory\n" + ); + } + _ => {} + } + } + }); + } + + #[cfg(not(windows))] + #[test] + // test the failure case (raw encoding) + fn test_cmd_spawn_raw_fail() { + let cmd = Command::new("cat").args(["test/"]); + let (mut rx, _) = cmd.spawn().unwrap(); + + tauri::async_runtime::block_on(async move { + while let Some(event) = rx.recv().await { + match event { + CommandEvent::Terminated(payload) => { + assert_eq!(payload.code, Some(1)); + } + CommandEvent::Stderr(line) => { + assert_eq!( + String::from_utf8(line).unwrap(), + "cat: test/: Is a directory\n" + ); + } + _ => {} + } + } + }); + } + + #[cfg(not(windows))] + #[test] + fn test_cmd_output_output() { + let cmd = Command::new("cat").args(["test/test.txt"]); + let output = tauri::async_runtime::block_on(cmd.output()).unwrap(); + + assert_eq!(String::from_utf8(output.stderr).unwrap(), ""); + assert_eq!( + String::from_utf8(output.stdout).unwrap(), + "This is a test doc!\n" + ); + } + + #[cfg(not(windows))] + #[test] + fn test_cmd_output_output_fail() { + let cmd = Command::new("cat").args(["test/"]); + let output = tauri::async_runtime::block_on(cmd.output()).unwrap(); + + assert_eq!(String::from_utf8(output.stdout).unwrap(), ""); + assert_eq!( + String::from_utf8(output.stderr).unwrap(), + "cat: test/: Is a directory\n\n" + ); + } +} diff --git a/packages/kbot/gui/app/plugins/shell/src/scope.rs b/packages/kbot/gui/app/plugins/shell/src/scope.rs new file mode 100644 index 00000000..35fdeaff --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/src/scope.rs @@ -0,0 +1,322 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::sync::Arc; + +#[allow(deprecated)] +use crate::open::Program; +use crate::process::Command; + +use regex::Regex; +use tauri::ipc::ScopeObject; +use tauri::Manager; + +/// Allowed representation of `Execute` command arguments. +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(untagged, deny_unknown_fields)] +#[non_exhaustive] +pub enum ExecuteArgs { + /// No arguments + None, + + /// A single string argument + Single(String), + + /// Multiple string arguments + List(Vec), +} + +impl ExecuteArgs { + /// Whether the argument list is empty or not. + pub fn is_empty(&self) -> bool { + match self { + Self::None => true, + Self::Single(s) if s.is_empty() => true, + Self::List(l) => l.is_empty(), + _ => false, + } + } +} + +impl From<()> for ExecuteArgs { + fn from(_: ()) -> Self { + Self::None + } +} + +impl From for ExecuteArgs { + fn from(string: String) -> Self { + Self::Single(string) + } +} + +impl From> for ExecuteArgs { + fn from(vec: Vec) -> Self { + Self::List(vec) + } +} + +/// A configured scoped shell command. +#[derive(Debug, Clone)] +pub struct ScopeAllowedCommand { + /// Name of the command (key). + pub name: String, + + /// The shell command to be called. + pub command: std::path::PathBuf, + + /// The arguments the command is allowed to be called with. + pub args: Option>, + + /// If this command is a sidecar command. + pub sidecar: bool, +} + +impl ScopeObject for ScopeAllowedCommand { + type Error = crate::Error; + fn deserialize( + app: &tauri::AppHandle, + raw: tauri::utils::acl::Value, + ) -> Result { + let scope = serde_json::from_value::(raw.into())?; + + let args = match scope.args.clone() { + crate::scope_entry::ShellAllowedArgs::Flag(true) => None, + crate::scope_entry::ShellAllowedArgs::Flag(false) => Some(Vec::new()), + crate::scope_entry::ShellAllowedArgs::List(list) => { + let list = list.into_iter().map(|arg| match arg { + crate::scope_entry::ShellAllowedArg::Fixed(fixed) => { + crate::scope::ScopeAllowedArg::Fixed(fixed) + } + crate::scope_entry::ShellAllowedArg::Var { validator, raw } => { + let regex = if raw { + validator + } else { + format!("^{validator}$") + }; + let validator = Regex::new(®ex) + .unwrap_or_else(|e| panic!("invalid regex {regex}: {e}")); + crate::scope::ScopeAllowedArg::Var { validator } + } + }); + Some(list.collect()) + } + }; + + let command = if let Ok(path) = app.path().parse(&scope.command) { + path + } else { + scope.command.clone() + }; + + Ok(Self { + name: scope.name, + command, + args, + sidecar: scope.sidecar, + }) + } +} + +/// A configured argument to a scoped shell command. +#[derive(Debug, Clone)] +pub enum ScopeAllowedArg { + /// A non-configurable argument. + Fixed(String), + + /// An argument with a value to be evaluated at runtime, must pass a regex validation. + Var { + /// The validation that the variable value must pass in order to be called. + validator: Regex, + }, +} + +impl ScopeAllowedArg { + /// If the argument is fixed. + pub fn is_fixed(&self) -> bool { + matches!(self, Self::Fixed(_)) + } +} + +/// Scope for the open command +pub struct OpenScope { + /// The validation regex that `shell > open` paths must match against. + /// When set to `None`, no values are accepted. + pub open: Option, +} + +/// Scope for shell process spawning. +#[derive(Clone)] +pub struct ShellScope<'a> { + /// All allowed commands, using their unique command name as the keys. + pub scopes: Vec<&'a Arc>, +} + +/// All errors that can happen while validating a scoped command. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// At least one argument did not pass input validation. + #[error("The scoped command was called with the improper sidecar flag set")] + BadSidecarFlag, + + /// The sidecar program validated but failed to find the sidecar path. + #[error( + "The scoped sidecar command was validated, but failed to create the path to the command: {0}" + )] + Sidecar(String), + + /// The named command was not found in the scoped config. + #[error("Scoped command {0} not found")] + NotFound(String), + + /// A command variable has no value set in the arguments. + #[error( + "Scoped command argument at position {0} must match regex validation {1} but it was not found" + )] + MissingVar(usize, String), + + /// At least one argument did not pass input validation. + #[error("Scoped command argument at position {index} was found, but failed regex validation {validation}")] + Validation { + /// Index of the variable. + index: usize, + + /// Regex that the variable value failed to match. + validation: String, + }, + + /// The format of the passed input does not match the expected shape. + /// + /// This can happen from passing a string or array of strings to a command that is expecting + /// named variables, and vice-versa. + #[error("Scoped command {0} received arguments in an unexpected format")] + InvalidInput(String), + + /// A generic IO error that occurs while executing specified shell commands. + #[error("Scoped shell IO error: {0}")] + Io(#[from] std::io::Error), +} + +impl OpenScope { + /// Open a path in the default (or specified) browser. + /// + /// The path is validated against the `plugins > shell > open` validation regex, which + /// defaults to `^((mailto:\w+)|(tel:\w+)|(https?://\w+)).+`. + #[allow(deprecated)] + pub fn open(&self, path: &str, with: Option) -> Result<(), Error> { + // ensure we pass validation if the configuration has one + if let Some(regex) = &self.open { + if !regex.is_match(path) { + return Err(Error::Validation { + index: 0, + validation: regex.as_str().into(), + }); + } + } else { + log::warn!("open() command called but the plugin configuration denies calls from JavaScript; set `tauri.conf.json > plugins > shell > open` to true or a validation regex string"); + return Err(Error::Validation { + index: 0, + validation: "tauri^".to_string(), // purposefully impossible regex + }); + } + + // The prevention of argument escaping is handled by the usage of std::process::Command::arg by + // the `open` dependency. This behavior should be re-confirmed during upgrades of `open`. + match with.map(Program::name) { + Some(program) => ::open::with_detached(path, program), + None => ::open::that_detached(path), + } + .map_err(Into::into) + } +} + +impl ShellScope<'_> { + /// Validates argument inputs and creates a Tauri sidecar [`Command`]. + pub fn prepare_sidecar( + &self, + command_name: &str, + command_script: &str, + args: ExecuteArgs, + ) -> Result { + self._prepare(command_name, args, Some(command_script)) + } + + /// Validates argument inputs and creates a Tauri [`Command`]. + pub fn prepare(&self, command_name: &str, args: ExecuteArgs) -> Result { + self._prepare(command_name, args, None) + } + + /// Validates argument inputs and creates a Tauri [`Command`]. + pub fn _prepare( + &self, + command_name: &str, + args: ExecuteArgs, + sidecar: Option<&str>, + ) -> Result { + let command = match self.scopes.iter().find(|s| s.name == command_name) { + Some(command) => command, + None => return Err(Error::NotFound(command_name.into())), + }; + + if command.sidecar != sidecar.is_some() { + return Err(Error::BadSidecarFlag); + } + + let args = match (&command.args, args) { + (None, ExecuteArgs::None) => Ok(vec![]), + (None, ExecuteArgs::List(list)) => Ok(list), + (None, ExecuteArgs::Single(string)) => Ok(vec![string]), + (Some(list), ExecuteArgs::List(args)) => list + .iter() + .enumerate() + .map(|(i, arg)| match arg { + ScopeAllowedArg::Fixed(fixed) => Ok(fixed.to_string()), + ScopeAllowedArg::Var { validator } => { + let value = args + .get(i) + .ok_or_else(|| Error::MissingVar(i, validator.to_string()))? + .to_string(); + if validator.is_match(&value) { + Ok(value) + } else { + Err(Error::Validation { + index: i, + validation: validator.to_string(), + }) + } + } + }) + .collect(), + (Some(list), arg) if arg.is_empty() && list.iter().all(ScopeAllowedArg::is_fixed) => { + list.iter() + .map(|arg| match arg { + ScopeAllowedArg::Fixed(fixed) => Ok(fixed.to_string()), + _ => unreachable!(), + }) + .collect() + } + (Some(list), _) if list.is_empty() => Err(Error::InvalidInput(command_name.into())), + (Some(_), _) => Err(Error::InvalidInput(command_name.into())), + }?; + + let command_s = sidecar + .map(|s| { + std::path::PathBuf::from(s) + .components() + .next_back() + .unwrap() + .as_os_str() + .to_string_lossy() + .into_owned() + }) + .unwrap_or_else(|| command.command.to_string_lossy().into_owned()); + let command = if command.sidecar { + Command::new_sidecar(command_s).map_err(|e| Error::Sidecar(e.to_string()))? + } else { + Command::new(command_s) + }; + + Ok(command.args(args)) + } +} diff --git a/packages/kbot/gui/app/plugins/shell/src/scope_entry.rs b/packages/kbot/gui/app/plugins/shell/src/scope_entry.rs new file mode 100644 index 00000000..8a70fff7 --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/src/scope_entry.rs @@ -0,0 +1,77 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{de::Error as DeError, Deserialize, Deserializer}; + +use std::path::PathBuf; + +/// A command allowed to be executed by the webview API. +#[allow(dead_code)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct Entry { + pub(crate) name: String, + pub(crate) command: PathBuf, + pub(crate) args: ShellAllowedArgs, + pub(crate) sidecar: bool, +} + +#[allow(dead_code)] +#[derive(Deserialize)] +pub(crate) struct EntryRaw { + pub(crate) name: String, + #[serde(rename = "cmd")] + pub(crate) command: Option, + #[serde(default)] + pub(crate) args: ShellAllowedArgs, + #[serde(default)] + pub(crate) sidecar: bool, +} + +impl<'de> Deserialize<'de> for Entry { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let config = EntryRaw::deserialize(deserializer)?; + + if !config.sidecar && config.command.is_none() { + return Err(DeError::custom( + "The shell scope `command` value is required.", + )); + } + + Ok(Entry { + name: config.name, + command: config.command.unwrap_or_default(), + args: config.args, + sidecar: config.sidecar, + }) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize)] +#[serde(untagged, deny_unknown_fields)] +#[non_exhaustive] +pub enum ShellAllowedArgs { + Flag(bool), + List(Vec), +} + +impl Default for ShellAllowedArgs { + fn default() -> Self { + Self::Flag(false) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Hash, Deserialize)] +#[serde(untagged, deny_unknown_fields)] +#[non_exhaustive] +pub enum ShellAllowedArg { + Fixed(String), + Var { + validator: String, + #[serde(default)] + raw: bool, + }, +} diff --git a/packages/kbot/gui/app/plugins/shell/test/test.txt b/packages/kbot/gui/app/plugins/shell/test/test.txt new file mode 100644 index 00000000..ec77307a --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/test/test.txt @@ -0,0 +1 @@ +This is a test doc! \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/shell/tsconfig.json b/packages/kbot/gui/app/plugins/shell/tsconfig.json new file mode 100644 index 00000000..5098169a --- /dev/null +++ b/packages/kbot/gui/app/plugins/shell/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/packages/kbot/gui/app/plugins/single-instance/CHANGELOG.md b/packages/kbot/gui/app/plugins/single-instance/CHANGELOG.md new file mode 100644 index 00000000..e0c65a47 --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/CHANGELOG.md @@ -0,0 +1,206 @@ +# Changelog + +## \[2.3.4] + +### Dependencies + +- Upgraded to `deep-link@2.4.3` + +## \[2.3.3] + +### Dependencies + +- Upgraded to `deep-link@2.4.2` + +## \[2.3.2] + +### Dependencies + +- Upgraded to `deep-link@2.4.1` + +## \[2.3.1] + +- [`6f345870`](https://github.com/tauri-apps/plugins-workspace/commit/6f345870df4e7b187deb869df03b79858e03b4fe) ([#2860](https://github.com/tauri-apps/plugins-workspace/pull/2860) by [@MorpheusXAUT](https://github.com/tauri-apps/plugins-workspace/../../MorpheusXAUT)) Fix D-Bus name replacement logic on Linux to prevent multiple instances from acquiring the same well-known name.\ + This patch updates the `zbus` dependency to the latest compatible version (`^5.9`) and explicitly sets `RequestNameFlags` to ensure a second instance fails to acquire the D-Bus name when another one is already running. + +## \[2.3.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +### Dependencies + +- Upgraded to `deep-link@2.4.0` + +## \[2.2.4] + +- [`dc84f8d8`](https://github.com/tauri-apps/plugins-workspace/commit/dc84f8d8bbaa70de3bb3185fbacb472993b996ef) ([#2609](https://github.com/tauri-apps/plugins-workspace/pull/2609) by [@Simon-Laux](https://github.com/tauri-apps/plugins-workspace/../../Simon-Laux)) fix `cwd` in single instance on macOS, which was the cwd of the first instance, instead of the second (like it is on windows and linux) + +### Dependencies + +- Upgraded to `deep-link@2.3.0` + +## \[2.2.3] + +### Dependencies + +- Upgraded to `deep-link@2.2.1` + +## \[2.2.2] + +- [`1ab5f157`](https://github.com/tauri-apps/plugins-workspace/commit/1ab5f1576333174095bc7dad4bef7a8576bb29ab) ([#2452](https://github.com/tauri-apps/plugins-workspace/pull/2452) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed `null pointer dereference` panic on rust nightly on Windows. + +## \[2.2.1] + +- [`da5c59e2`](https://github.com/tauri-apps/plugins-workspace/commit/da5c59e2fe879d177e3cfd52fcacce85440423cb) ([#2271](https://github.com/tauri-apps/plugins-workspace/pull/2271) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated `zbus` dependency to version 5. No API changes. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +### Dependencies + +- Upgraded to `deep-link@2.1.0` + +## \[2.0.2] + +### Dependencies + +- Upgraded to `deep-link@2.0.2` + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +### Dependencies + +- Upgraded to `deep-link@2.0.1` + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +### Dependencies + +- Upgraded to `deep-link@2.0.0` + +## \[2.0.0-rc.5] + +### Dependencies + +- Upgraded to `deep-link@2.0.0-rc.7` + +## \[2.0.0-rc.4] + +### Dependencies + +- Upgraded to `deep-link@2.0.0-rc.6` + +## \[2.0.0-rc.3] + +- [`b2269333`](https://github.com/tauri-apps/plugins-workspace/commit/b2269333e39afe32629a11763a8e25d0b12b132b) ([#1766](https://github.com/tauri-apps/plugins-workspace/pull/1766) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Put deep link integration behined a feature + +### Dependencies + +- Upgraded to `deep-link@2.0.0-rc.5` + +## \[2.0.0-rc.2] + +- [`64a6240f`](https://github.com/tauri-apps/plugins-workspace/commit/64a6240f79fcd52267c8d721b727ae695055d7ff) ([#1759](https://github.com/tauri-apps/plugins-workspace/pull/1759) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Integrate with the deep link plugin out of the box. + +### Dependencies + +- Upgraded to `deep-link@2.0.0-rc.4` + +## \[2.0.0-rc.1] + +- [`3c52f30e`](https://github.com/tauri-apps/plugins-workspace/commit/3c52f30ea4ec29c51f7021aa7871614d72e43258) ([#1665](https://github.com/tauri-apps/plugins-workspace/pull/1665) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Updated `windows-sys` crate to `0.59` +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +## \[2.0.0-beta.12] + +- [`e847cedc`](https://github.com/tauri-apps/plugins-workspace/commit/e847cedc1f46f3e7a2ad81ea579b620bc5b992d7) ([#1402](https://github.com/tauri-apps/plugins-workspace/pull/1402) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Use no default features on tauri for all plugins so that consumers can use `default-features = false` on tauri, note that this will still enable wry feature on iOS +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.11] + +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.10] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.9] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.8] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.7] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.6] + +- [`ed46dca`](https://github.com/tauri-apps/plugins-workspace/commit/ed46dca74ff3947dbbcb26a7b571c129bf925698) Added the `semver` feature flag to make the single instance mechanism only trigger for semver compatible versions. + +## \[2.0.0-beta.5] + +- [`dabac0e`](https://github.com/tauri-apps/plugins-workspace/commit/dabac0eedfd6e6d192c6c5a214e708b3c0223f6f)([#1035](https://github.com/tauri-apps/plugins-workspace/pull/1035)) Added implementation for MacOS. + +## \[2.0.0-beta.4] + +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. + +## \[2.0.0-beta.3] + +- [`2397ec5`](https://github.com/tauri-apps/plugins-workspace/commit/2397ec5937e594397e533925ccd257cae30b4cd1)([#1019](https://github.com/tauri-apps/plugins-workspace/pull/1019)) Fix doesn't shutdown immediately. +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.2] + +- [`6d1e621`](https://github.com/tauri-apps/plugins-workspace/commit/6d1e6218b5877ef91f589f790f7251acda9c9605)([#981](https://github.com/tauri-apps/plugins-workspace/pull/981)) Fix `zbus::blocking::connection::Builder` import. + +## \[2.0.0-beta.1] + +- [`14f381a`](https://github.com/tauri-apps/plugins-workspace/commit/14f381acf8fe690acecc676922c6f05939b95734) Update MSRV to 1.75. +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Add permissions. + +## \[2.0.0-alpha.6] + +- [`2cf8faa`](https://github.com/tauri-apps/plugins-workspace/commit/2cf8faa3e149af55eb86e5aba8ebfc54210ca703)([#839](https://github.com/tauri-apps/plugins-workspace/pull/839)) Update to tauri@alpha.20. + +## \[2.0.0-alpha.5] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to tauri@alpha.18. + +## \[2.0.0-alpha.4] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to tauri@alpha.17. + +## \[2.0.0-alpha.3] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update MSRV to 1.75. + +## \[2.0.0-alpha.2] + +- [`4e2cef9`](https://github.com/tauri-apps/plugins-workspace/commit/4e2cef9b702bbbb9cf4ee17de50791cb21f1b2a4)([#593](https://github.com/tauri-apps/plugins-workspace/pull/593)) Update to alpha.12. + +## \[2.0.0-alpha.1] + +- [`d5a7c77`](https://github.com/tauri-apps/plugins-workspace/commit/d5a7c77a8d0e7912a6b07b22ed329004edd6e80b)([#545](https://github.com/tauri-apps/plugins-workspace/pull/545)) Fixes docs.rs build by enabling the `tauri/dox` feature flag. +- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11. + +## \[2.0.0-alpha.0] + +- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! diff --git a/packages/kbot/gui/app/plugins/single-instance/Cargo.toml b/packages/kbot/gui/app/plugins/single-instance/Cargo.toml new file mode 100644 index 00000000..5486fd64 --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "tauri-plugin-single-instance" +version = "2.3.4" +description = "Ensure a single instance of your tauri app is running." +authors = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } +exclude = ["/examples"] + +[package.metadata.docs.rs] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.platforms.support] +windows = { level = "full", notes = "" } +linux = { level = "full", notes = "" } +macos = { level = "full", notes = "" } +android = { level = "none", notes = "" } +ios = { level = "none", notes = "" } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +tracing = { workspace = true } +thiserror = { workspace = true } +tauri-plugin-deep-link = { path = "../deep-link", version = "2.4.3", optional = true } +semver = { version = "1", optional = true } + +[target."cfg(target_os = \"windows\")".dependencies.windows-sys] +version = "0.60" +features = [ + "Win32_System_Threading", + "Win32_System_DataExchange", + "Win32_Foundation", + "Win32_UI_WindowsAndMessaging", + "Win32_Security", + "Win32_System_LibraryLoader", + "Win32_Graphics_Gdi", +] + +[target."cfg(target_os = \"linux\")".dependencies] +zbus = { workspace = true } + +[features] +semver = ["dep:semver"] +deep-link = ["dep:tauri-plugin-deep-link"] diff --git a/packages/kbot/gui/app/plugins/single-instance/LICENSE.spdx b/packages/kbot/gui/app/plugins/single-instance/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/single-instance/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/single-instance/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/single-instance/LICENSE_MIT b/packages/kbot/gui/app/plugins/single-instance/LICENSE_MIT new file mode 100644 index 00000000..0840164e --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present The Tauri Programme in the Commons Conservancy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/single-instance/README.md b/packages/kbot/gui/app/plugins/single-instance/README.md new file mode 100644 index 00000000..711be6ae --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/README.md @@ -0,0 +1,86 @@ +![tauri-plugin-single-instance](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/single-instance/banner.png) + +Ensure a single instance of your tauri app is running. + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | x | +| iOS | x | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-single-instance = "2.0.0" +# alternatively with Git: +tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +use tauri::{Manager}; + +#[derive(Clone, serde::Serialize)] +struct Payload { + args: Vec, + cwd: String, +} + +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_single_instance::init(|app, argv, cwd| { + println!("{}, {argv:?}, {cwd}", app.package_info().name); + app.emit("single-instance", Payload { args: argv, cwd }).unwrap(); + })) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Note that currently, plugins run in the order they were added in to the builder, so make sure that this plugin is registered first. + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/single-instance/SECURITY.md b/packages/kbot/gui/app/plugins/single-instance/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/single-instance/banner.png b/packages/kbot/gui/app/plugins/single-instance/banner.png new file mode 100644 index 00000000..8e3cc5a8 Binary files /dev/null and b/packages/kbot/gui/app/plugins/single-instance/banner.png differ diff --git a/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/.gitignore b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/.gitignore new file mode 100644 index 00000000..b3f41c3f --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/** +!dist/.gitkeep \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/package.json b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/package.json new file mode 100644 index 00000000..fe9120dd --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/package.json @@ -0,0 +1,14 @@ +{ + "name": "app", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "tauri": "tauri" + }, + "author": "", + "license": "MIT", + "devDependencies": { + "@tauri-apps/cli": "2.8.4" + } +} diff --git a/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/public/index.html b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/public/index.html new file mode 100644 index 00000000..9c95f40e --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/public/index.html @@ -0,0 +1,5 @@ + + +
Plugin example
+ + diff --git a/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/.gitignore b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/.gitignore new file mode 100644 index 00000000..949785a1 --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/.gitignore @@ -0,0 +1,6 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ +WixTools + +/gen/schemas diff --git a/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/Cargo.toml b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/Cargo.toml new file mode 100644 index 00000000..dd9c8d8a --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "single-instance-example" +version = "0.1.0" +description = "A Tauri App" +authors = ["You"] +repository = "" +edition = "2021" +rust-version = "1.77.2" + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true, features = ["wry", "common-controls-v6", "x11"] } +tauri-plugin-single-instance = { path = "../../../" } +tauri-plugin-cli = { path = "../../../../cli" } + +[build-dependencies] +tauri-build = { workspace = true } + +[features] +prod = ["tauri/custom-protocol"] diff --git a/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/build.rs b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/build.rs new file mode 100644 index 00000000..5ebf8d2f --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/build.rs @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +fn main() { + tauri_build::build() +} diff --git a/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/icons/128x128.png b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/icons/128x128.png new file mode 100644 index 00000000..6be5e50e Binary files /dev/null and b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/icons/128x128.png differ diff --git a/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/icons/128x128@2x.png b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/icons/128x128@2x.png new file mode 100644 index 00000000..e81becee Binary files /dev/null and b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/icons/128x128@2x.png differ diff --git a/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/icons/32x32.png b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/icons/32x32.png new file mode 100644 index 00000000..a437dd51 Binary files /dev/null and b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/icons/32x32.png differ diff --git a/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/icons/icon.icns b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/icons/icon.icns new file mode 100644 index 00000000..8254645a Binary files /dev/null and b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/icons/icon.icns differ diff --git a/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/icons/icon.ico b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/icons/icon.ico new file mode 100644 index 00000000..b3636e4b Binary files /dev/null and b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/icons/icon.ico differ diff --git a/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/icons/icon.png b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/icons/icon.png new file mode 100644 index 00000000..e1cd2619 Binary files /dev/null and b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/icons/icon.png differ diff --git a/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/src/main.rs b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/src/main.rs new file mode 100644 index 00000000..e736c4f3 --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/src/main.rs @@ -0,0 +1,17 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_single_instance::init(|app, argv, cwd| { + println!("{}, {argv:?}, {cwd}", app.package_info().name); + })) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/tauri.conf.json b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/tauri.conf.json new file mode 100644 index 00000000..41623cc5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/examples/vanilla/src-tauri/tauri.conf.json @@ -0,0 +1,45 @@ +{ + "productName": "app", + "version": "0.1.0", + "identifier": "com.tauri.single-instance", + "build": { + "frontendDist": "../public" + }, + "app": { + "windows": [ + { + "title": "app", + "width": 800, + "height": 600, + "resizable": true, + "fullscreen": false + } + ], + "security": { + "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: http://tauri.localhost 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'" + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + }, + "plugins": { + "cli": { + "description": "Testing single-instance on MacOS", + "args": [ + { + "name": "somearg", + "index": 1, + "takesValue": true + } + ] + } + } +} diff --git a/packages/kbot/gui/app/plugins/single-instance/src/lib.rs b/packages/kbot/gui/app/plugins/single-instance/src/lib.rs new file mode 100644 index 00000000..03fd1ed7 --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/src/lib.rs @@ -0,0 +1,45 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +//! Ensure a single instance of your tauri app is running. + +#![doc( + html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", + html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" +)] +#![cfg(not(any(target_os = "android", target_os = "ios")))] + +use tauri::{plugin::TauriPlugin, AppHandle, Manager, Runtime}; + +#[cfg(target_os = "windows")] +#[path = "platform_impl/windows.rs"] +mod platform_impl; +#[cfg(target_os = "linux")] +#[path = "platform_impl/linux.rs"] +mod platform_impl; +#[cfg(target_os = "macos")] +#[path = "platform_impl/macos.rs"] +mod platform_impl; + +#[cfg(feature = "semver")] +mod semver_compat; + +pub(crate) type SingleInstanceCallback = + dyn FnMut(&AppHandle, Vec, String) + Send + Sync + 'static; + +pub fn init, Vec, String) + Send + Sync + 'static>( + mut f: F, +) -> TauriPlugin { + platform_impl::init(Box::new(move |app, args, cwd| { + #[cfg(feature = "deep-link")] + if let Some(deep_link) = app.try_state::>() { + deep_link.handle_cli_arguments(args.iter()); + } + f(app, args, cwd) + })) +} + +pub fn destroy>(manager: &M) { + platform_impl::destroy(manager) +} diff --git a/packages/kbot/gui/app/plugins/single-instance/src/platform_impl/linux.rs b/packages/kbot/gui/app/plugins/single-instance/src/platform_impl/linux.rs new file mode 100644 index 00000000..577965c5 --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/src/platform_impl/linux.rs @@ -0,0 +1,118 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#[cfg(feature = "semver")] +use crate::semver_compat::semver_compat_string; + +use crate::SingleInstanceCallback; +use tauri::{ + plugin::{self, TauriPlugin}, + AppHandle, Config, Manager, RunEvent, Runtime, +}; +use zbus::{ + blocking::{connection::Builder, Connection}, + interface, +}; + +struct ConnectionHandle(Connection); + +struct SingleInstanceDBus { + callback: Box>, + app_handle: AppHandle, +} + +#[interface(name = "org.SingleInstance.DBus")] +impl SingleInstanceDBus { + fn execute_callback(&mut self, argv: Vec, cwd: String) { + (self.callback)(&self.app_handle, argv, cwd); + } +} + +#[cfg(feature = "semver")] +fn dbus_id(config: &Config, version: semver::Version) -> String { + let mut id = config.identifier.replace(['.', '-'], "_"); + id.push('_'); + id.push_str(semver_compat_string(version).as_str()); + id +} + +#[cfg(not(feature = "semver"))] +fn dbus_id(config: &Config) -> String { + config.identifier.replace(['.', '-'], "_") +} + +pub fn init(f: Box>) -> TauriPlugin { + plugin::Builder::new("single-instance") + .setup(|app, _api| { + #[cfg(feature = "semver")] + let id = dbus_id(app.config(), app.package_info().version.clone()); + #[cfg(not(feature = "semver"))] + let id = dbus_id(app.config()); + + let single_instance_dbus = SingleInstanceDBus { + callback: f, + app_handle: app.clone(), + }; + let dbus_name = format!("org.{id}.SingleInstance"); + let dbus_path = format!("/org/{id}/SingleInstance"); + + match Builder::session() + .unwrap() + .name(dbus_name.as_str()) + .unwrap() + .replace_existing_names(false) + .allow_name_replacements(false) + .serve_at(dbus_path.as_str(), single_instance_dbus) + .unwrap() + .build() + { + Ok(connection) => { + app.manage(ConnectionHandle(connection)); + } + Err(zbus::Error::NameTaken) => { + if let Ok(connection) = Connection::session() { + let _ = connection.call_method( + Some(dbus_name.as_str()), + dbus_path.as_str(), + Some("org.SingleInstance.DBus"), + "ExecuteCallback", + &( + std::env::args().collect::>(), + std::env::current_dir() + .unwrap_or_default() + .to_str() + .unwrap_or_default(), + ), + ); + } + app.cleanup_before_exit(); + std::process::exit(0); + } + _ => {} + } + + Ok(()) + }) + .on_event(|app, event| { + if let RunEvent::Exit = event { + destroy(app); + } + }) + .build() +} + +pub fn destroy>(manager: &M) { + if let Some(connection) = manager.try_state::() { + #[cfg(feature = "semver")] + let id = dbus_id( + manager.config(), + manager.app_handle().package_info().version.clone(), + ); + #[cfg(not(feature = "semver"))] + let id = dbus_id(manager.config()); + + let dbus_name = format!("org.{id}.SingleInstance",); + let _ = connection.0.release_name(dbus_name); + } +} diff --git a/packages/kbot/gui/app/plugins/single-instance/src/platform_impl/macos.rs b/packages/kbot/gui/app/plugins/single-instance/src/platform_impl/macos.rs new file mode 100644 index 00000000..63991513 --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/src/platform_impl/macos.rs @@ -0,0 +1,133 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{ + io::{BufWriter, Error, ErrorKind, Read, Write}, + os::unix::net::{UnixListener, UnixStream}, + path::PathBuf, +}; + +#[cfg(feature = "semver")] +use crate::semver_compat::semver_compat_string; +use crate::SingleInstanceCallback; +use tauri::{ + plugin::{self, TauriPlugin}, + AppHandle, Config, Manager, RunEvent, Runtime, +}; + +pub fn init(cb: Box>) -> TauriPlugin { + plugin::Builder::new("single-instance") + .setup(|app, _api| { + let socket = socket_path(app.config(), app.package_info()); + + // Notify the singleton which may or may not exist. + match notify_singleton(&socket) { + Ok(_) => { + std::process::exit(0); + } + Err(e) => { + match e.kind() { + ErrorKind::NotFound | ErrorKind::ConnectionRefused => { + // This process claims itself as singleton as likely none exists + socket_cleanup(&socket); + listen_for_other_instances(&socket, app.clone(), cb); + } + _ => { + tracing::debug!( + "single_instance failed to notify - launching normally: {}", + e + ); + } + } + } + } + Ok(()) + }) + .on_event(|app, event| { + if let RunEvent::Exit = event { + destroy(app); + } + }) + .build() +} + +pub fn destroy>(manager: &M) { + let socket = socket_path(manager.config(), manager.package_info()); + socket_cleanup(&socket); +} + +fn socket_path(config: &Config, _package_info: &tauri::PackageInfo) -> PathBuf { + let identifier = config.identifier.replace(['.', '-'].as_ref(), "_"); + + #[cfg(feature = "semver")] + let identifier = format!( + "{identifier}_{}", + semver_compat_string(_package_info.version.clone()), + ); + + // Use /tmp as socket path must be shorter than 100 chars. + PathBuf::from(format!("/tmp/{}_si.sock", identifier)) +} + +fn socket_cleanup(socket: &PathBuf) { + let _ = std::fs::remove_file(socket); +} + +fn notify_singleton(socket: &PathBuf) -> Result<(), Error> { + let stream = UnixStream::connect(socket)?; + let mut bf = BufWriter::new(&stream); + let cwd = std::env::current_dir() + .unwrap_or_default() + .to_str() + .unwrap_or_default() + .to_string(); + bf.write_all(cwd.as_bytes())?; + bf.write_all(b"\0\0")?; + let args_joined = std::env::args().collect::>().join("\0"); + bf.write_all(args_joined.as_bytes())?; + bf.flush()?; + drop(bf); + Ok(()) +} + +fn listen_for_other_instances( + socket: &PathBuf, + app: AppHandle
, + mut cb: Box>, +) { + match UnixListener::bind(socket) { + Ok(listener) => { + tauri::async_runtime::spawn(async move { + for stream in listener.incoming() { + match stream { + Ok(mut stream) => { + let mut s = String::new(); + match stream.read_to_string(&mut s) { + Ok(_) => { + let (cwd, args) = s.split_once("\0\0").unwrap_or_default(); + let args: Vec = + args.split('\0').map(String::from).collect(); + cb(app.app_handle(), args, cwd.to_string()); + } + Err(e) => { + tracing::debug!("single_instance failed to be notified: {e}") + } + } + } + Err(err) => { + tracing::debug!("single_instance failed to be notified: {}", err); + continue; + } + } + } + }); + } + Err(err) => { + tracing::error!( + "single_instance failed to listen to other processes - launching normally: {}", + err + ); + } + } +} diff --git a/packages/kbot/gui/app/plugins/single-instance/src/platform_impl/windows.rs b/packages/kbot/gui/app/plugins/single-instance/src/platform_impl/windows.rs new file mode 100644 index 00000000..832d0803 --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/src/platform_impl/windows.rs @@ -0,0 +1,256 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#[cfg(feature = "semver")] +use crate::semver_compat::semver_compat_string; + +use crate::SingleInstanceCallback; +use std::ffi::CStr; +use tauri::{ + plugin::{self, TauriPlugin}, + AppHandle, Manager, RunEvent, Runtime, +}; +use windows_sys::Win32::{ + Foundation::{CloseHandle, GetLastError, ERROR_ALREADY_EXISTS, HWND, LPARAM, LRESULT, WPARAM}, + System::{ + DataExchange::COPYDATASTRUCT, + LibraryLoader::GetModuleHandleW, + Threading::{CreateMutexW, ReleaseMutex}, + }, + UI::WindowsAndMessaging::{ + self as w32wm, CreateWindowExW, DefWindowProcW, DestroyWindow, FindWindowW, + RegisterClassExW, SendMessageW, CREATESTRUCTW, GWLP_USERDATA, GWL_STYLE, + WINDOW_LONG_PTR_INDEX, WM_COPYDATA, WM_CREATE, WM_DESTROY, WNDCLASSEXW, WS_EX_LAYERED, + WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, WS_VISIBLE, + }, +}; + +const WMCOPYDATA_SINGLE_INSTANCE_DATA: usize = 1542; + +struct MutexHandle(isize); + +struct TargetWindowHandle(isize); + +struct UserData { + app: AppHandle, + callback: Box>, +} + +impl UserData { + unsafe fn from_hwnd_raw(hwnd: HWND) -> *mut Self { + GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut Self + } + + unsafe fn from_hwnd<'a>(hwnd: HWND) -> &'a mut Self { + &mut *Self::from_hwnd_raw(hwnd) + } + + fn run_callback(&mut self, args: Vec, cwd: String) { + (self.callback)(&self.app, args, cwd) + } +} + +pub fn init(callback: Box>) -> TauriPlugin { + plugin::Builder::new("single-instance") + .setup(|app, _api| { + #[allow(unused_mut)] + let mut id = app.config().identifier.clone(); + #[cfg(feature = "semver")] + { + id.push('_'); + id.push_str(semver_compat_string(app.package_info().version.clone()).as_str()); + } + + let class_name = encode_wide(format!("{id}-sic")); + let window_name = encode_wide(format!("{id}-siw")); + let mutex_name = encode_wide(format!("{id}-sim")); + + let hmutex = + unsafe { CreateMutexW(std::ptr::null(), true.into(), mutex_name.as_ptr()) }; + + if unsafe { GetLastError() } == ERROR_ALREADY_EXISTS { + unsafe { + let hwnd = FindWindowW(class_name.as_ptr(), window_name.as_ptr()); + + if !hwnd.is_null() { + let cwd = std::env::current_dir().unwrap_or_default(); + let cwd = cwd.to_str().unwrap_or_default(); + + let args = std::env::args().collect::>().join("|"); + + let data = format!("{cwd}|{args}\0",); + + let bytes = data.as_bytes(); + let cds = COPYDATASTRUCT { + dwData: WMCOPYDATA_SINGLE_INSTANCE_DATA, + cbData: bytes.len() as _, + lpData: bytes.as_ptr() as _, + }; + + SendMessageW(hwnd, WM_COPYDATA, 0, &cds as *const _ as _); + + app.cleanup_before_exit(); + std::process::exit(0); + } + } + } else { + app.manage(MutexHandle(hmutex as _)); + + let userdata = UserData { + app: app.clone(), + callback, + }; + let userdata = Box::into_raw(Box::new(userdata)); + let hwnd = create_event_target_window::(&class_name, &window_name, userdata); + app.manage(TargetWindowHandle(hwnd as _)); + } + + Ok(()) + }) + .on_event(|app, event| { + if let RunEvent::Exit = event { + destroy(app); + } + }) + .build() +} + +pub fn destroy>(manager: &M) { + if let Some(hmutex) = manager.try_state::() { + unsafe { + ReleaseMutex(hmutex.0 as _); + CloseHandle(hmutex.0 as _); + } + } + if let Some(hwnd) = manager.try_state::() { + unsafe { DestroyWindow(hwnd.0 as _) }; + } +} + +unsafe extern "system" fn single_instance_window_proc( + hwnd: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + match msg { + WM_CREATE => { + let create_struct = &*(lparam as *const CREATESTRUCTW); + let userdata = create_struct.lpCreateParams as *const UserData; + SetWindowLongPtrW(hwnd, GWLP_USERDATA, userdata as _); + 0 + } + + WM_COPYDATA => { + let cds_ptr = lparam as *const COPYDATASTRUCT; + if (*cds_ptr).dwData == WMCOPYDATA_SINGLE_INSTANCE_DATA { + let userdata = UserData::::from_hwnd(hwnd); + + let data = CStr::from_ptr((*cds_ptr).lpData as _).to_string_lossy(); + let mut s = data.split('|'); + let cwd = s.next().unwrap(); + let args = s.map(|s| s.to_string()).collect(); + + userdata.run_callback(args, cwd.to_string()); + } + 1 + } + + WM_DESTROY => { + let userdata = UserData::::from_hwnd_raw(hwnd); + drop(Box::from_raw(userdata)); + 0 + } + _ => DefWindowProcW(hwnd, msg, wparam, lparam), + } +} + +fn create_event_target_window( + class_name: &[u16], + window_name: &[u16], + userdata: *const UserData, +) -> HWND { + unsafe { + let class = WNDCLASSEXW { + cbSize: std::mem::size_of::() as u32, + style: 0, + lpfnWndProc: Some(single_instance_window_proc::), + cbClsExtra: 0, + cbWndExtra: 0, + hInstance: GetModuleHandleW(std::ptr::null()), + hIcon: std::ptr::null_mut(), + hCursor: std::ptr::null_mut(), + hbrBackground: std::ptr::null_mut(), + lpszMenuName: std::ptr::null(), + lpszClassName: class_name.as_ptr(), + hIconSm: std::ptr::null_mut(), + }; + + RegisterClassExW(&class); + + let hwnd = CreateWindowExW( + WS_EX_NOACTIVATE + | WS_EX_TRANSPARENT + | WS_EX_LAYERED + // WS_EX_TOOLWINDOW prevents this window from ever showing up in the taskbar, which + // we want to avoid. If you remove this style, this window won't show up in the + // taskbar *initially*, but it can show up at some later point. This can sometimes + // happen on its own after several hours have passed, although this has proven + // difficult to reproduce. Alternatively, it can be manually triggered by killing + // `explorer.exe` and then starting the process back up. + // It is unclear why the bug is triggered by waiting for several hours. + | WS_EX_TOOLWINDOW, + class_name.as_ptr(), + window_name.as_ptr(), + WS_OVERLAPPED, + 0, + 0, + 0, + 0, + std::ptr::null_mut(), + std::ptr::null_mut(), + GetModuleHandleW(std::ptr::null()), + userdata as _, + ); + SetWindowLongPtrW( + hwnd, + GWL_STYLE, + // The window technically has to be visible to receive WM_PAINT messages (which are used + // for delivering events during resizes), but it isn't displayed to the user because of + // the LAYERED style. + (WS_VISIBLE | WS_POPUP) as isize, + ); + hwnd + } +} + +pub fn encode_wide(string: impl AsRef) -> Vec { + std::os::windows::prelude::OsStrExt::encode_wide(string.as_ref()) + .chain(std::iter::once(0)) + .collect() +} + +#[cfg(target_pointer_width = "32")] +#[allow(non_snake_case)] +unsafe fn SetWindowLongPtrW(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX, value: isize) -> isize { + w32wm::SetWindowLongW(hwnd, index, value as _) as _ +} + +#[cfg(target_pointer_width = "64")] +#[allow(non_snake_case)] +unsafe fn SetWindowLongPtrW(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX, value: isize) -> isize { + w32wm::SetWindowLongPtrW(hwnd, index, value) +} + +#[cfg(target_pointer_width = "32")] +#[allow(non_snake_case)] +unsafe fn GetWindowLongPtrW(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX) -> isize { + w32wm::GetWindowLongW(hwnd, index) as _ +} + +#[cfg(target_pointer_width = "64")] +#[allow(non_snake_case)] +unsafe fn GetWindowLongPtrW(hwnd: HWND, index: WINDOW_LONG_PTR_INDEX) -> isize { + w32wm::GetWindowLongPtrW(hwnd, index) +} diff --git a/packages/kbot/gui/app/plugins/single-instance/src/semver_compat.rs b/packages/kbot/gui/app/plugins/single-instance/src/semver_compat.rs new file mode 100644 index 00000000..80487cae --- /dev/null +++ b/packages/kbot/gui/app/plugins/single-instance/src/semver_compat.rs @@ -0,0 +1,19 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/// Takes a version and spits out a String with trailing _x, thus only considering the digits +/// relevant regarding semver compatibility +pub fn semver_compat_string(version: semver::Version) -> String { + // for pre-release always treat each version separately + if !version.pre.is_empty() { + return version.to_string().replace(['.', '-'], "_"); + } + match version.major { + 0 => match version.minor { + 0 => format!("0_0_{}", version.patch), + _ => format!("0_{}_x", version.minor), + }, + _ => format!("{}_x_x", version.major), + } +} diff --git a/packages/kbot/gui/app/plugins/sql/CHANGELOG.md b/packages/kbot/gui/app/plugins/sql/CHANGELOG.md new file mode 100644 index 00000000..31886312 --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/CHANGELOG.md @@ -0,0 +1,126 @@ +# Changelog + +## \[2.3.0] + +- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 + +## \[2.2.1] + +- [`f634e524`](https://github.com/tauri-apps/plugins-workspace/commit/f634e5248ebe428f8305a59f74c13fc15147fb8e) This is an "empty" release to update the plugins' source files on crates.io and docs.rs. This should fix docs.rs build failures for projects using tauri plugins as dependencies. + +## \[2.2.0] + +- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. + +## \[2.0.2] + +- [`31956469`](https://github.com/tauri-apps/plugins-workspace/commit/319564699638c080b73d506bcaad186ecc4a8236) ([#1928](https://github.com/tauri-apps/plugins-workspace/pull/1928) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed the QueryResult typing by marking `lastInsertId` as optional to reflect postgres-only changes made in the 2.0.0 release. + +## \[2.0.3] + +- [`90ef77c8`](https://github.com/tauri-apps/plugins-workspace/commit/90ef77c8723ac9d0ba7bd3b52a80a2b14843ff99) ([#2038](https://github.com/tauri-apps/plugins-workspace/pull/2038) by [@johncarmack1984](https://github.com/tauri-apps/plugins-workspace/../../johncarmack1984)) Allow blocking on async code without creating a nested runtime. + +## \[2.0.1] + +- [`0ca4cc91`](https://github.com/tauri-apps/plugins-workspace/commit/0ca4cc914c5ea995c98f9e60a2ab49827c219350) ([#1963](https://github.com/tauri-apps/plugins-workspace/pull/1963) by [@yoggys](https://github.com/tauri-apps/plugins-workspace/../../yoggys)) Fixed incorrect documentation of the select method in the Database class. + +## \[2.0.1] + +- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7. + +## \[2.0.0] + +- [`e2c4dfb6`](https://github.com/tauri-apps/plugins-workspace/commit/e2c4dfb6af43e5dd8d9ceba232c315f5febd55c1) Update to tauri v2 stable release. + +## \[2.0.0-rc.3] + +- [`30bcf5dc`](https://github.com/tauri-apps/plugins-workspace/commit/30bcf5dcc22e1bb1fb983a8d2887edc39404e6df) ([#1838](https://github.com/tauri-apps/plugins-workspace/pull/1838) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) It is now possible to enable multiple SQL backends at the same time. There will be no compile error anymore if no backends are enabled! + +## \[2.0.0-rc.2] + +- [`0dd97d91`](https://github.com/tauri-apps/plugins-workspace/commit/0dd97d911569cdedab07f504b708036d62ff83c1) ([#1375](https://github.com/tauri-apps/plugins-workspace/pull/1375) by [@KauanCurbani](https://github.com/tauri-apps/plugins-workspace/../../KauanCurbani)) Added support for `UUID` columns to the postgres implementation. + +## \[2.0.0-rc.1] + +- [`e2e97db5`](https://github.com/tauri-apps/plugins-workspace/commit/e2e97db51983267f5be84d4f6f0278d58834d1f5) ([#1701](https://github.com/tauri-apps/plugins-workspace/pull/1701) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri 2.0.0-rc.8 + +## \[2.0.0-rc.0] + +- [`9887d1`](https://github.com/tauri-apps/plugins-workspace/commit/9887d14bd0e971c4c0f5c1188fc4005d3fc2e29e) Update to tauri RC. + +## \[2.0.0-beta.8] + +- [`99d6ac0f`](https://github.com/tauri-apps/plugins-workspace/commit/99d6ac0f9506a6a4a1aa59c728157190a7441af6) ([#1606](https://github.com/tauri-apps/plugins-workspace/pull/1606) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) The JS packages now specify the *minimum* `@tauri-apps/api` version instead of a single exact version. +- [`6de87966`](https://github.com/tauri-apps/plugins-workspace/commit/6de87966ecc00ad9d91c25be452f1f46bd2b7e1f) ([#1597](https://github.com/tauri-apps/plugins-workspace/pull/1597) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Update to tauri beta.25. + +## \[2.0.0-beta.10] + +- [`37cb9a66`](https://github.com/tauri-apps/plugins-workspace/commit/37cb9a6681b948908cd9443340f6b23401607df7) ([#1575](https://github.com/tauri-apps/plugins-workspace/pull/1575) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update sqlx to 0.8 - Check out their changelog for behavior changes: https://github.com/launchbadge/sqlx/blob/main/CHANGELOG.md#080---2024-07-22 + +## \[2.0.0-beta.7] + +- [`22a17980`](https://github.com/tauri-apps/plugins-workspace/commit/22a17980ff4f6f8c40adb1b8f4ffc6dae2fe7e30) ([#1537](https://github.com/tauri-apps/plugins-workspace/pull/1537) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Update to tauri beta.24. + +## \[2.0.0-beta.6] + +- [`76daee7a`](https://github.com/tauri-apps/plugins-workspace/commit/76daee7aafece34de3092c86e531cf9eb1138989) ([#1512](https://github.com/tauri-apps/plugins-workspace/pull/1512) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Update to tauri beta.23. + +## \[2.0.0-beta.7] + +- [`4216c051`](https://github.com/tauri-apps/plugins-workspace/commit/4216c0517fd1dcb29d0162dc2fc15291472a2b00) ([#1381](https://github.com/tauri-apps/plugins-workspace/pull/1381) by [@thewh1teagle](https://github.com/tauri-apps/plugins-workspace/../../thewh1teagle)) Made `DbInstances` public for managing database instances directly from `Rust`. + +## \[2.0.0-beta.5] + +- [`9013854f`](https://github.com/tauri-apps/plugins-workspace/commit/9013854f42a49a230b9dbb9d02774765528a923f)([#1382](https://github.com/tauri-apps/plugins-workspace/pull/1382)) Update to tauri beta.22. + +## \[2.0.0-beta.4] + +- [`430bd6f4`](https://github.com/tauri-apps/plugins-workspace/commit/430bd6f4f379bee5d232ae6b098ae131db7f178a)([#1363](https://github.com/tauri-apps/plugins-workspace/pull/1363)) Update to tauri beta.20. + +## \[2.0.0-beta.3] + +- [`bd1ed590`](https://github.com/tauri-apps/plugins-workspace/commit/bd1ed5903ffcce5500310dac1e59e8c67674ef1e)([#1237](https://github.com/tauri-apps/plugins-workspace/pull/1237)) Update to tauri beta.17. + +## \[2.0.0-beta.3] + +- [`a04ea2f`](https://github.com/tauri-apps/plugins-workspace/commit/a04ea2f38294d5a3987578283badc8eec87a7752)([#1071](https://github.com/tauri-apps/plugins-workspace/pull/1071)) The global API script is now only added to the binary when the `withGlobalTauri` config is true. + +## \[2.0.0-beta.2] + +- [`99bea25`](https://github.com/tauri-apps/plugins-workspace/commit/99bea2559c2c0648c2519c50a18cd124dacef57b)([#1005](https://github.com/tauri-apps/plugins-workspace/pull/1005)) Update to tauri beta.8. + +## \[2.0.0-beta.1] + +- [`569defb`](https://github.com/tauri-apps/plugins-workspace/commit/569defbe9492e38938554bb7bdc1be9151456d21) Update to tauri beta.4. + +## \[2.0.0-beta.0] + +- [`d198c01`](https://github.com/tauri-apps/plugins-workspace/commit/d198c014863ee260cb0de88a14b7fc4356ef7474)([#862](https://github.com/tauri-apps/plugins-workspace/pull/862)) Update to tauri beta. + +## \[2.0.0-alpha.5] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.13. + +## \[2.0.0-alpha.4] + +- [`387c2f9`](https://github.com/tauri-apps/plugins-workspace/commit/387c2f9e0ce4c75c07ffa3fd76391a25b58f5daf)([#802](https://github.com/tauri-apps/plugins-workspace/pull/802)) Update to @tauri-apps/api v2.0.0-alpha.12. + +## \[2.0.0-alpha.3] + +- [`e438e0a`](https://github.com/tauri-apps/plugins-workspace/commit/e438e0a62d4b430a5159f05f13ecd397dd891a0d)([#676](https://github.com/tauri-apps/plugins-workspace/pull/676)) Update to @tauri-apps/api v2.0.0-alpha.11. + +## \[2.0.0-alpha.2] + +- [`5c13736`](https://github.com/tauri-apps/plugins-workspace/commit/5c137365c60790e8d4037d449e8237aa3fffdab0)([#673](https://github.com/tauri-apps/plugins-workspace/pull/673)) Update to @tauri-apps/api v2.0.0-alpha.9. + +## \[2.0.0-alpha.2] + +- [`4e2cef9`](https://github.com/tauri-apps/plugins-workspace/commit/4e2cef9b702bbbb9cf4ee17de50791cb21f1b2a4)([#593](https://github.com/tauri-apps/plugins-workspace/pull/593)) Update to alpha.12. + +## \[2.0.0-alpha.1] + +- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11. + +## \[2.0.0-alpha.0] + +- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! diff --git a/packages/kbot/gui/app/plugins/sql/Cargo.toml b/packages/kbot/gui/app/plugins/sql/Cargo.toml new file mode 100644 index 00000000..e0d55983 --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "tauri-plugin-sql" +version = "2.3.0" +description = "Interface with SQL databases." +authors = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +rust-version = { workspace = true } +repository = { workspace = true } +links = "tauri-plugin-sql" + +[package.metadata.docs.rs] +features = ["sqlite"] +rustc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.platforms.support] +windows = { level = "full", notes = "" } +linux = { level = "full", notes = "" } +macos = { level = "full", notes = "" } +android = { level = "full", notes = "" } +ios = { level = "full", notes = "" } + +[build-dependencies] +tauri-plugin = { workspace = true, features = ["build"] } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } +futures-core = "0.3" +sqlx = { version = "0.8", features = ["json", "time"] } +time = "0.3" +tokio = { version = "1", features = ["sync"] } +indexmap = { version = "2", features = ["serde"] } + +[features] +sqlite = ["sqlx/sqlite", "sqlx/runtime-tokio"] +mysql = ["sqlx/mysql", "sqlx/runtime-tokio-rustls"] +postgres = ["sqlx/postgres", "sqlx/runtime-tokio-rustls"] +# TODO: bundled-cipher etc diff --git a/packages/kbot/gui/app/plugins/sql/LICENSE.spdx b/packages/kbot/gui/app/plugins/sql/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/sql/LICENSE_APACHE-2.0 b/packages/kbot/gui/app/plugins/sql/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/sql/LICENSE_MIT b/packages/kbot/gui/app/plugins/sql/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/sql/README.md b/packages/kbot/gui/app/plugins/sql/README.md new file mode 100644 index 00000000..2e1e4ca4 --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/README.md @@ -0,0 +1,211 @@ +![plugin-sql](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/sql/banner.png) + +Interface with SQL databases through [sqlx](https://github.com/launchbadge/sqlx). It supports the `sqlite`, `mysql` and `postgres` drivers, enabled by a Cargo feature. + +| Platform | Supported | +| -------- | --------- | +| Linux | ✓ | +| Windows | ✓ | +| macOS | ✓ | +| Android | ✓ | +| iOS | x | + +## Install + +_This plugin requires a Rust version of at least **1.77.2**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies.tauri-plugin-sql] +features = ["sqlite"] # or "postgres", or "mysql" +version = "2.0.0" +# alternatively with Git +git = "https://github.com/tauri-apps/plugins-workspace" +branch = "v2" +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +```sh +pnpm add @tauri-apps/plugin-sql +# or +npm add @tauri-apps/plugin-sql +# or +yarn add @tauri-apps/plugin-sql +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/lib.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_sql::Builder::default().build()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import Database from '@tauri-apps/plugin-sql' + +// sqlite. The path is relative to `tauri::api::path::BaseDirectory::AppConfig`. +const db = await Database.load('sqlite:test.db') +// mysql +const db = await Database.load('mysql://user:pass@host/database') +// postgres +const db = await Database.load('postgres://postgres:password@localhost/test') + +await db.execute('INSERT INTO ...') +``` + +## Syntax + +We use sqlx as our underlying library, adopting their query syntax: + +- sqlite and postgres use the "$#" syntax when substituting query data +- mysql uses "?" when substituting query data + +```javascript +// INSERT and UPDATE examples for sqlite and postgres +const result = await db.execute( + 'INSERT into todos (id, title, status) VALUES ($1, $2, $3)', + [todos.id, todos.title, todos.status] +) + +const result = await db.execute( + 'UPDATE todos SET title = $1, status = $2 WHERE id = $3', + [todos.title, todos.status, todos.id] +) + +// INSERT and UPDATE examples for mysql +const result = await db.execute( + 'INSERT into todos (id, title, status) VALUES (?, ?, ?)', + [todos.id, todos.title, todos.status] +) + +const result = await db.execute( + 'UPDATE todos SET title = ?, status = ? WHERE id = ?', + [todos.title, todos.status, todos.id] +) +``` + +## Migrations + +This plugin supports database migrations, allowing you to manage database schema evolution over time. + +### Defining Migrations + +Migrations are defined in Rust using the `Migration` struct. Each migration should include a unique version number, a description, the SQL to be executed, and the type of migration (Up or Down). + +Example of a migration: + +```rust +use tauri_plugin_sql::{Migration, MigrationKind}; + +let migration = Migration { + version: 1, + description: "create_initial_tables", + sql: "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);", + kind: MigrationKind::Up, +}; +``` + +### Adding Migrations to the Plugin Builder + +Migrations are registered with the `Builder` struct provided by the plugin. Use the `add_migrations` method to add your migrations to the plugin for a specific database connection. + +Example of adding migrations: + +```rust +use tauri_plugin_sql::{Builder, Migration, MigrationKind}; + +fn main() { + let migrations = vec![ + // Define your migrations here + Migration { + version: 1, + description: "create_initial_tables", + sql: "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);", + kind: MigrationKind::Up, + } + ]; + + tauri::Builder::default() + .plugin( + tauri_plugin_sql::Builder::default() + .add_migrations("sqlite:mydatabase.db", migrations) + .build(), + ) + ... +} +``` + +### Applying Migrations + +To apply the migrations when the plugin is initialized, add the connection string to the `tauri.conf.json` file: + +```json +{ + "plugins": { + "sql": { + "preload": ["sqlite:mydatabase.db"] + } + } +} +``` + +Alternatively, the client side `load()` also runs the migrations for a given connection string: + +```ts +import Database from '@tauri-apps/plugin-sql' +const db = await Database.load('sqlite:mydatabase.db') +``` + +Ensure that the migrations are defined in the correct order and are safe to run multiple times. + +### Migration Management + +- **Version Control**: Each migration must have a unique version number. This is crucial for ensuring the migrations are applied in the correct order. +- **Idempotency**: Write migrations in a way that they can be safely re-run without causing errors or unintended consequences. +- **Testing**: Thoroughly test migrations to ensure they work as expected and do not compromise the integrity of your database. + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Partners + + + + + + + +
+ + CrabNebula + +
+ +For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri). + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/packages/kbot/gui/app/plugins/sql/SECURITY.md b/packages/kbot/gui/app/plugins/sql/SECURITY.md new file mode 100644 index 00000000..4f09bbac --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +**Do not report security vulnerabilities through public GitHub issues.** + +**Please use the [Private Vulnerability Disclosure](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability) feature of GitHub.** + +Include as much of the following information: + +- Type of issue (e.g. improper input parsing, privilege escalation, etc.) +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- The distribution affected or used to help us with reproduction of the issue +- Step-by-step instructions to reproduce the issue +- Ideally a reproduction repository +- Impact of the issue, including how an attacker might exploit the issue + +We prefer to receive reports in English. + +## Contact + +Please disclose a vulnerability or security relevant issue here: [https://github.com/tauri-apps/plugins-workspace/security/advisories/new](https://github.com/tauri-apps/plugins-workspace/security/advisories/new). + +Alternatively, you can also contact us by email via [security@tauri.app](mailto:security@tauri.app). diff --git a/packages/kbot/gui/app/plugins/sql/api-iife.js b/packages/kbot/gui/app/plugins/sql/api-iife.js new file mode 100644 index 00000000..a30f68d9 --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_PLUGIN_SQL__=function(){"use strict";async function e(e,t={},s){return window.__TAURI_INTERNALS__.invoke(e,t,s)}"function"==typeof SuppressedError&&SuppressedError;class t{constructor(e){this.path=e}static async load(s){const n=await e("plugin:sql|load",{db:s});return new t(n)}static get(e){return new t(e)}async execute(t,s){const[n,r]=await e("plugin:sql|execute",{db:this.path,query:t,values:s??[]});return{lastInsertId:r,rowsAffected:n}}async select(t,s){return await e("plugin:sql|select",{db:this.path,query:t,values:s??[]})}async close(t){return await e("plugin:sql|close",{db:t})}}return t}();Object.defineProperty(window.__TAURI__,"sql",{value:__TAURI_PLUGIN_SQL__})} diff --git a/packages/kbot/gui/app/plugins/sql/banner.png b/packages/kbot/gui/app/plugins/sql/banner.png new file mode 100644 index 00000000..5e53a31b Binary files /dev/null and b/packages/kbot/gui/app/plugins/sql/banner.png differ diff --git a/packages/kbot/gui/app/plugins/sql/build.rs b/packages/kbot/gui/app/plugins/sql/build.rs new file mode 100644 index 00000000..fbbca422 --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/build.rs @@ -0,0 +1,11 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +const COMMANDS: &[&str] = &["load", "execute", "select", "close"]; + +fn main() { + tauri_plugin::Builder::new(COMMANDS) + .global_api_script_path("./api-iife.js") + .build(); +} diff --git a/packages/kbot/gui/app/plugins/sql/guest-js/index.ts b/packages/kbot/gui/app/plugins/sql/guest-js/index.ts new file mode 100644 index 00000000..11d39e70 --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/guest-js/index.ts @@ -0,0 +1,168 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { invoke } from '@tauri-apps/api/core' + +export interface QueryResult { + /** The number of rows affected by the query. */ + rowsAffected: number + /** + * The last inserted `id`. + * + * This value is not set for Postgres databases. If the + * last inserted id is required on Postgres, the `select` function + * must be used, with a `RETURNING` clause + * (`INSERT INTO todos (title) VALUES ($1) RETURNING id`). + */ + lastInsertId?: number +} + +/** + * **Database** + * + * The `Database` class serves as the primary interface for + * communicating with the rust side of the sql plugin. + */ +export default class Database { + path: string + constructor(path: string) { + this.path = path + } + + /** + * **load** + * + * A static initializer which connects to the underlying database and + * returns a `Database` instance once a connection to the database is established. + * + * # Sqlite + * + * The path is relative to `tauri::path::BaseDirectory::App` and must start with `sqlite:`. + * + * @example + * ```ts + * const db = await Database.load("sqlite:test.db"); + * ``` + */ + static async load(path: string): Promise { + const _path = await invoke('plugin:sql|load', { + db: path + }) + + return new Database(_path) + } + + /** + * **get** + * + * A static initializer which synchronously returns an instance of + * the Database class while deferring the actual database connection + * until the first invocation or selection on the database. + * + * # Sqlite + * + * The path is relative to `tauri::path::BaseDirectory::App` and must start with `sqlite:`. + * + * @example + * ```ts + * const db = Database.get("sqlite:test.db"); + * ``` + */ + static get(path: string): Database { + return new Database(path) + } + + /** + * **execute** + * + * Passes a SQL expression to the database for execution. + * + * @example + * ```ts + * // for sqlite & postgres + * // INSERT example + * const result = await db.execute( + * "INSERT into todos (id, title, status) VALUES ($1, $2, $3)", + * [ todos.id, todos.title, todos.status ] + * ); + * // UPDATE example + * const result = await db.execute( + * "UPDATE todos SET title = $1, completed = $2 WHERE id = $3", + * [ todos.title, todos.status, todos.id ] + * ); + * + * // for mysql + * // INSERT example + * const result = await db.execute( + * "INSERT into todos (id, title, status) VALUES (?, ?, ?)", + * [ todos.id, todos.title, todos.status ] + * ); + * // UPDATE example + * const result = await db.execute( + * "UPDATE todos SET title = ?, completed = ? WHERE id = ?", + * [ todos.title, todos.status, todos.id ] + * ); + * ``` + */ + async execute(query: string, bindValues?: unknown[]): Promise { + const [rowsAffected, lastInsertId] = await invoke<[number, number]>( + 'plugin:sql|execute', + { + db: this.path, + query, + values: bindValues ?? [] + } + ) + return { + lastInsertId, + rowsAffected + } + } + + /** + * **select** + * + * Passes in a SELECT query to the database for execution. + * + * @example + * ```ts + * // for sqlite & postgres + * const result = await db.select( + * "SELECT * from todos WHERE id = $1", [ id ] + * ); + * + * // for mysql + * const result = await db.select( + * "SELECT * from todos WHERE id = ?", [ id ] + * ); + * ``` + */ + async select(query: string, bindValues?: unknown[]): Promise { + const result = await invoke('plugin:sql|select', { + db: this.path, + query, + values: bindValues ?? [] + }) + + return result + } + + /** + * **close** + * + * Closes the database connection pool. + * + * @example + * ```ts + * const success = await db.close() + * ``` + * @param db - Optionally state the name of a database if you are managing more than one. Otherwise, all database pools will be in scope. + */ + async close(db?: string): Promise { + const success = await invoke('plugin:sql|close', { + db + }) + return success + } +} diff --git a/packages/kbot/gui/app/plugins/sql/package.json b/packages/kbot/gui/app/plugins/sql/package.json new file mode 100644 index 00000000..afbb6bd9 --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/package.json @@ -0,0 +1,30 @@ +{ + "name": "@tauri-apps/plugin-sql", + "version": "2.3.0", + "description": "Interface with SQL databases", + "license": "MIT OR Apache-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "repository": "https://github.com/tauri-apps/plugins-workspace", + "type": "module", + "types": "./dist-js/index.d.ts", + "main": "./dist-js/index.cjs", + "module": "./dist-js/index.js", + "exports": { + "types": "./dist-js/index.d.ts", + "import": "./dist-js/index.js", + "require": "./dist-js/index.cjs" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "README.md", + "LICENSE" + ], + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } +} diff --git a/packages/kbot/gui/app/plugins/sql/permissions/autogenerated/commands/close.toml b/packages/kbot/gui/app/plugins/sql/permissions/autogenerated/commands/close.toml new file mode 100644 index 00000000..fad12d15 --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/permissions/autogenerated/commands/close.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-close" +description = "Enables the close command without any pre-configured scope." +commands.allow = ["close"] + +[[permission]] +identifier = "deny-close" +description = "Denies the close command without any pre-configured scope." +commands.deny = ["close"] diff --git a/packages/kbot/gui/app/plugins/sql/permissions/autogenerated/commands/execute.toml b/packages/kbot/gui/app/plugins/sql/permissions/autogenerated/commands/execute.toml new file mode 100644 index 00000000..d98be899 --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/permissions/autogenerated/commands/execute.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-execute" +description = "Enables the execute command without any pre-configured scope." +commands.allow = ["execute"] + +[[permission]] +identifier = "deny-execute" +description = "Denies the execute command without any pre-configured scope." +commands.deny = ["execute"] diff --git a/packages/kbot/gui/app/plugins/sql/permissions/autogenerated/commands/load.toml b/packages/kbot/gui/app/plugins/sql/permissions/autogenerated/commands/load.toml new file mode 100644 index 00000000..f6e47ad8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/permissions/autogenerated/commands/load.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-load" +description = "Enables the load command without any pre-configured scope." +commands.allow = ["load"] + +[[permission]] +identifier = "deny-load" +description = "Denies the load command without any pre-configured scope." +commands.deny = ["load"] diff --git a/packages/kbot/gui/app/plugins/sql/permissions/autogenerated/commands/select.toml b/packages/kbot/gui/app/plugins/sql/permissions/autogenerated/commands/select.toml new file mode 100644 index 00000000..5a13a022 --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/permissions/autogenerated/commands/select.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-select" +description = "Enables the select command without any pre-configured scope." +commands.allow = ["select"] + +[[permission]] +identifier = "deny-select" +description = "Denies the select command without any pre-configured scope." +commands.deny = ["select"] diff --git a/packages/kbot/gui/app/plugins/sql/permissions/autogenerated/reference.md b/packages/kbot/gui/app/plugins/sql/permissions/autogenerated/reference.md new file mode 100644 index 00000000..c829b103 --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/permissions/autogenerated/reference.md @@ -0,0 +1,131 @@ +## Default Permission + +### Default Permissions + +This permission set configures what kind of +database operations are available from the sql plugin. + +### Granted Permissions + +All reading related operations are enabled. +Also allows to load or close a connection. + +#### This default permission set includes the following: + +- `allow-close` +- `allow-load` +- `allow-select` + +## Permission Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IdentifierDescription
+ +`sql:allow-close` + + + +Enables the close command without any pre-configured scope. + +
+ +`sql:deny-close` + + + +Denies the close command without any pre-configured scope. + +
+ +`sql:allow-execute` + + + +Enables the execute command without any pre-configured scope. + +
+ +`sql:deny-execute` + + + +Denies the execute command without any pre-configured scope. + +
+ +`sql:allow-load` + + + +Enables the load command without any pre-configured scope. + +
+ +`sql:deny-load` + + + +Denies the load command without any pre-configured scope. + +
+ +`sql:allow-select` + + + +Enables the select command without any pre-configured scope. + +
+ +`sql:deny-select` + + + +Denies the select command without any pre-configured scope. + +
diff --git a/packages/kbot/gui/app/plugins/sql/permissions/default.toml b/packages/kbot/gui/app/plugins/sql/permissions/default.toml new file mode 100644 index 00000000..eb5fa555 --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/permissions/default.toml @@ -0,0 +1,16 @@ +"$schema" = "schemas/schema.json" + +[default] +description = """ +### Default Permissions + +This permission set configures what kind of +database operations are available from the sql plugin. + +### Granted Permissions + +All reading related operations are enabled. +Also allows to load or close a connection. + +""" +permissions = ["allow-close", "allow-load", "allow-select"] diff --git a/packages/kbot/gui/app/plugins/sql/permissions/schemas/schema.json b/packages/kbot/gui/app/plugins/sql/permissions/schemas/schema.json new file mode 100644 index 00000000..488a953c --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/permissions/schemas/schema.json @@ -0,0 +1,354 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": [ + "description", + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "version": { + "description": "The version of the permission.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": [ + "string", + "null" + ] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Enables the execute command without any pre-configured scope.", + "type": "string", + "const": "allow-execute", + "markdownDescription": "Enables the execute command without any pre-configured scope." + }, + { + "description": "Denies the execute command without any pre-configured scope.", + "type": "string", + "const": "deny-execute", + "markdownDescription": "Denies the execute command without any pre-configured scope." + }, + { + "description": "Enables the load command without any pre-configured scope.", + "type": "string", + "const": "allow-load", + "markdownDescription": "Enables the load command without any pre-configured scope." + }, + { + "description": "Denies the load command without any pre-configured scope.", + "type": "string", + "const": "deny-load", + "markdownDescription": "Denies the load command without any pre-configured scope." + }, + { + "description": "Enables the select command without any pre-configured scope.", + "type": "string", + "const": "allow-select", + "markdownDescription": "Enables the select command without any pre-configured scope." + }, + { + "description": "Denies the select command without any pre-configured scope.", + "type": "string", + "const": "deny-select", + "markdownDescription": "Denies the select command without any pre-configured scope." + }, + { + "description": "### Default Permissions\n\nThis permission set configures what kind of\ndatabase operations are available from the sql plugin.\n\n### Granted Permissions\n\nAll reading related operations are enabled.\nAlso allows to load or close a connection.\n\n\n#### This default permission set includes:\n\n- `allow-close`\n- `allow-load`\n- `allow-select`", + "type": "string", + "const": "default", + "markdownDescription": "### Default Permissions\n\nThis permission set configures what kind of\ndatabase operations are available from the sql plugin.\n\n### Granted Permissions\n\nAll reading related operations are enabled.\nAlso allows to load or close a connection.\n\n\n#### This default permission set includes:\n\n- `allow-close`\n- `allow-load`\n- `allow-select`" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/kbot/gui/app/plugins/sql/rollup.config.js b/packages/kbot/gui/app/plugins/sql/rollup.config.js new file mode 100644 index 00000000..1f349ec8 --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/rollup.config.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { createConfig } from '../../shared/rollup.config.js' + +export default createConfig() diff --git a/packages/kbot/gui/app/plugins/sql/src/commands.rs b/packages/kbot/gui/app/plugins/sql/src/commands.rs new file mode 100644 index 00000000..760d00b2 --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/src/commands.rs @@ -0,0 +1,80 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use indexmap::IndexMap; +use serde_json::Value as JsonValue; +use sqlx::migrate::Migrator; +use tauri::{command, AppHandle, Runtime, State}; + +use crate::{DbInstances, DbPool, Error, LastInsertId, Migrations}; + +#[command] +pub(crate) async fn load( + app: AppHandle, + db_instances: State<'_, DbInstances>, + migrations: State<'_, Migrations>, + db: String, +) -> Result { + let pool = DbPool::connect(&db, &app).await?; + + if let Some(migrations) = migrations.0.lock().await.remove(&db) { + let migrator = Migrator::new(migrations).await?; + pool.migrate(&migrator).await?; + } + + db_instances.0.write().await.insert(db.clone(), pool); + + Ok(db) +} + +/// Allows the database connection(s) to be closed; if no database +/// name is passed in then _all_ database connection pools will be +/// shut down. +#[command] +pub(crate) async fn close( + db_instances: State<'_, DbInstances>, + db: Option, +) -> Result { + let instances = db_instances.0.read().await; + + let pools = if let Some(db) = db { + vec![db] + } else { + instances.keys().cloned().collect() + }; + + for pool in pools { + let db = instances.get(&pool).ok_or(Error::DatabaseNotLoaded(pool))?; + db.close().await; + } + + Ok(true) +} + +/// Execute a command against the database +#[command] +pub(crate) async fn execute( + db_instances: State<'_, DbInstances>, + db: String, + query: String, + values: Vec, +) -> Result<(u64, LastInsertId), crate::Error> { + let instances = db_instances.0.read().await; + + let db = instances.get(&db).ok_or(Error::DatabaseNotLoaded(db))?; + db.execute(query, values).await +} + +#[command] +pub(crate) async fn select( + db_instances: State<'_, DbInstances>, + db: String, + query: String, + values: Vec, +) -> Result>, crate::Error> { + let instances = db_instances.0.read().await; + + let db = instances.get(&db).ok_or(Error::DatabaseNotLoaded(db))?; + db.select(query, values).await +} diff --git a/packages/kbot/gui/app/plugins/sql/src/decode/mod.rs b/packages/kbot/gui/app/plugins/sql/src/decode/mod.rs new file mode 100644 index 00000000..0a2d2cdd --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/src/decode/mod.rs @@ -0,0 +1,10 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#[cfg(feature = "mysql")] +pub(crate) mod mysql; +#[cfg(feature = "postgres")] +pub(crate) mod postgres; +#[cfg(feature = "sqlite")] +pub(crate) mod sqlite; diff --git a/packages/kbot/gui/app/plugins/sql/src/decode/mysql.rs b/packages/kbot/gui/app/plugins/sql/src/decode/mysql.rs new file mode 100644 index 00000000..53fd20c7 --- /dev/null +++ b/packages/kbot/gui/app/plugins/sql/src/decode/mysql.rs @@ -0,0 +1,101 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde_json::Value as JsonValue; +use sqlx::{mysql::MySqlValueRef, TypeInfo, Value, ValueRef}; +use time::{Date, OffsetDateTime, PrimitiveDateTime, Time}; + +use crate::Error; + +pub(crate) fn to_json(v: MySqlValueRef) -> Result { + if v.is_null() { + return Ok(JsonValue::Null); + } + + let res = match v.type_info().name() { + "CHAR" | "VARCHAR" | "TINYTEXT" | "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "ENUM" => { + if let Ok(v) = ValueRef::to_owned(&v).try_decode() { + JsonValue::String(v) + } else { + JsonValue::Null + } + } + "FLOAT" => { + if let Ok(v) = ValueRef::to_owned(&v).try_decode::() { + JsonValue::from(v) + } else { + JsonValue::Null + } + } + "DOUBLE" => { + if let Ok(v) = ValueRef::to_owned(&v).try_decode::() { + JsonValue::from(v) + } else { + JsonValue::Null + } + } + "TINYINT" | "SMALLINT" | "INT" | "MEDIUMINT" | "BIGINT" => { + if let Ok(v) = ValueRef::to_owned(&v).try_decode::() { + JsonValue::Number(v.into()) + } else { + JsonValue::Null + } + } + "TINYINT UNSIGNED" | "SMALLINT UNSIGNED" | "INT UNSIGNED" | "MEDIUMINT UNSIGNED" + | "BIGINT UNSIGNED" | "YEAR" => { + if let Ok(v) = ValueRef::to_owned(&v).try_decode::() { + JsonValue::Number(v.into()) + } else { + JsonValue::Null + } + } + "BOOLEAN" => { + if let Ok(v) = ValueRef::to_owned(&v).try_decode() { + JsonValue::Bool(v) + } else { + JsonValue::Null + } + } + "DATE" => { + if let Ok(v) = ValueRef::to_owned(&v).try_decode::() { + JsonValue::String(v.to_string()) + } else { + JsonValue::Null + } + } + "TIME" => { + if let Ok(v) = ValueRef::to_owned(&v).try_decode::
+

Welcome to Tauri!

+ +
+ +

Click on the Tauri logo to learn more about the framework

+ + + +

+