├── .gitignore ├── icon.png ├── demos ├── demo1.gif ├── demo2.gif └── screenshot1.png ├── copyright.txt ├── .vscodeignore ├── tsconfig.json ├── .vscode ├── settings.json ├── launch.json └── tasks.json ├── test ├── extension.test.ts └── index.ts ├── LICENSE ├── src ├── lang │ ├── de-de.ts │ ├── en-gb.ts │ ├── en-us.ts │ ├── en.ts │ └── de.ts ├── api │ ├── test.ts │ ├── languages.ts │ ├── html.ts │ ├── extensions.ts │ ├── appglobals.ts │ ├── globals.ts │ ├── appstate.ts │ ├── state.ts │ ├── popups.ts │ ├── commands.ts │ ├── files.ts │ ├── deploy.ts │ ├── outputs.ts │ ├── editors.ts │ ├── editor.ts │ ├── whiteboard.ts │ └── cron.ts ├── html │ └── modules │ │ └── html.ts ├── workspace.ts ├── content.ts ├── extension.ts ├── i18.ts ├── hooks.ts ├── host │ └── helpers.ts └── controller.ts ├── CHANGELOG.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | /pushall.sh 4 | /typedoc.cmd 5 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkloubert/vs-rest-api/HEAD/icon.png -------------------------------------------------------------------------------- /demos/demo1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkloubert/vs-rest-api/HEAD/demos/demo1.gif -------------------------------------------------------------------------------- /demos/demo2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkloubert/vs-rest-api/HEAD/demos/demo2.gif -------------------------------------------------------------------------------- /copyright.txt: -------------------------------------------------------------------------------- 1 | Icons: 2 | - /icon.png (Vecteezy; https://www.vecteezy.com/free-vector/icons) 3 | -------------------------------------------------------------------------------- /demos/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkloubert/vs-rest-api/HEAD/demos/screenshot1.png -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | test/** 5 | src/** 6 | **/*.map 7 | .gitignore 8 | tsconfig.json 9 | demos/** 10 | pushall.sh 11 | typedoc.cmd 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "." 11 | }, 12 | "exclude": [ 13 | "node_modules", 14 | ".vscode-test" 15 | ] 16 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version 10 | } -------------------------------------------------------------------------------- /test/extension.test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Note: This example test is leveraging the Mocha test framework. 3 | // Please refer to their documentation on https://mochajs.org/ for help. 4 | // 5 | 6 | // The module 'assert' provides assertion methods from node 7 | import * as assert from 'assert'; 8 | 9 | // You can import and use all API from the 'vscode' module 10 | // as well as import your extension to test it 11 | import * as vscode from 'vscode'; 12 | import * as myExtension from '../src/extension'; 13 | 14 | // Defines a Mocha test suite to group tests of similar kind together 15 | suite("Extension Tests", () => { 16 | 17 | // Defines a Mocha unit test 18 | test("Something 1", () => { 19 | assert.equal(-1, [1, 2, 3].indexOf(5)); 20 | assert.equal(-1, [1, 2, 3].indexOf(0)); 21 | }); 22 | }); -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING 3 | // 4 | // This file is providing the test runner to use when running extension tests. 5 | // By default the test runner in use is Mocha based. 6 | // 7 | // You can provide your own test runner if you want to override it by exporting 8 | // a function run(testRoot: string, clb: (error:Error) => void) that the extension 9 | // host can call to run the tests. The test runner is expected to use console.log 10 | // to report the results back to the caller. When the tests are finished, return 11 | // a possible error to the callback or null if none. 12 | 13 | var testRunner = require('vscode/lib/testrunner'); 14 | 15 | // You can directly control Mocha options by uncommenting the following lines 16 | // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info 17 | testRunner.configure({ 18 | ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) 19 | useColors: true // colored output from test results 20 | }); 21 | 22 | module.exports = testRunner; -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], 11 | "stopOnEntry": false, 12 | "sourceMaps": true, 13 | "outFiles": [ "${workspaceRoot}/out/src/**/*.js" ], 14 | "preLaunchTask": "npm" 15 | }, 16 | { 17 | "name": "Launch Tests", 18 | "type": "extensionHost", 19 | "request": "launch", 20 | "runtimeExecutable": "${execPath}", 21 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], 22 | "stopOnEntry": false, 23 | "sourceMaps": true, 24 | "outFiles": [ "${workspaceRoot}/out/test/**/*.js" ], 25 | "preLaunchTask": "npm" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | // A task runner that calls a custom npm script that compiles the extension. 10 | { 11 | "version": "0.1.0", 12 | 13 | // we want to run npm 14 | "command": "npm", 15 | 16 | // the command is a shell script 17 | "isShellCommand": true, 18 | 19 | // show the output window only if unrecognized errors occur. 20 | "showOutput": "silent", 21 | 22 | // we run the custom script "compile" as defined in package.json 23 | "args": ["run", "compile", "--loglevel", "silent"], 24 | 25 | // The tsc compiler is started in watching mode 26 | "isWatching": true, 27 | 28 | // use the standard tsc in watch mode problem matcher to find compile problems in the output. 29 | "problemMatcher": "$tsc-watch" 30 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Marcel Kloubert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/lang/de-de.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as lang_de from './de'; 27 | 28 | 29 | // alias for 'de' 30 | export const TRANSLATION = lang_de.TRANSLATION; 31 | -------------------------------------------------------------------------------- /src/lang/en-gb.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as lang_en from './en'; 27 | 28 | 29 | // alias for 'en' 30 | export const TRANSLATION = lang_en.TRANSLATION; 31 | -------------------------------------------------------------------------------- /src/lang/en-us.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as lang_en from './en'; 27 | 28 | 29 | // alias for 'en' 30 | export const TRANSLATION = lang_en.TRANSLATION; 31 | -------------------------------------------------------------------------------- /src/api/test.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as rapi_contracts from '../contracts'; 27 | import * as rapi_helpers from '../helpers'; 28 | 29 | 30 | // TESTCODE 31 | export function GET(args: rapi_contracts.ApiMethodArguments): PromiseLike { 32 | return new Promise((resolve, reject) => { 33 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 34 | 35 | let a = true; 36 | if (a) { 37 | args.sendNotFound(); 38 | completed(); 39 | return; 40 | } 41 | 42 | try { 43 | args.executeBuildIn('workspace').then((r) => { 44 | completed(null, r); 45 | }, (err) => { 46 | completed(err); 47 | }); 48 | } 49 | catch (e) { 50 | completed(e); 51 | } 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /src/api/languages.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as rapi_contracts from '../contracts'; 27 | import * as rapi_helpers from '../helpers'; 28 | import * as vscode from 'vscode'; 29 | 30 | 31 | // [GET] /languages 32 | export function GET(args: rapi_contracts.ApiMethodArguments): PromiseLike { 33 | return new Promise((resolve, reject) => { 34 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 35 | 36 | vscode.languages.getLanguages().then((languages) => { 37 | languages = languages || []; 38 | 39 | args.response.data = languages.map(x => rapi_helpers.toStringSafe(x)) 40 | .filter(x => !rapi_helpers.isEmptyString(x)); 41 | 42 | args.response.data.sort((x, y) => { 43 | return rapi_helpers.compareValues(rapi_helpers.normalizeString(x), 44 | rapi_helpers.normalizeString(y)); 45 | }); 46 | 47 | completed(); 48 | }, (err) => { 49 | completed(err); 50 | }); 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /src/lang/en.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import { Translation } from '../i18'; 27 | 28 | 29 | // english 30 | // 31 | // Translated by: Marcel Joachim Kloubert (https://github.com/mkloubert) 32 | export const TRANSLATION: Translation = { 33 | browser: { 34 | openFailed: "Could not OPEN url {0:trim,surround}: {1:trim}", 35 | }, 36 | errors: { 37 | withCategory: '[FEHLER] {0:trim}: {1}', 38 | }, 39 | host: { 40 | notStarted: "Server has NOT been started!", 41 | started: "Host now runs on port {0:trim}", 42 | startFailed: "The host could not be STARTED: {0:trim}", 43 | stopFailed: "The host could not be STOPPED: {0:trim}", 44 | stopped: "Host has been STOPPED.", 45 | }, 46 | isNo: { 47 | dir: "{0:trim,surround} is no directory!", 48 | file: "{0:trim,surround} is no file!", 49 | }, 50 | popups: { 51 | newVersion: { 52 | message: "You are running new version of 'vs-rest-api' ({0:trim})!", 53 | showChangeLog: 'Show changelog...', 54 | }, 55 | }, 56 | whiteboard: { 57 | initFailed: "Could not initialize whiteboard: {0}", 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /src/lang/de.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import { Translation } from '../i18'; 27 | 28 | // deutsch (german) 29 | // 30 | // Translated by: Marcel Joachim Kloubert (https://github.com/mkloubert) 31 | export const TRANSLATION: Translation = { 32 | browser: { 33 | openFailed: "Konnte Adresse {0:trim,surround} nicht ÖFFNEN: {1:trim}", 34 | }, 35 | errors: { 36 | withCategory: '[ERROR] {0:trim}: {1}', 37 | }, 38 | host: { 39 | notStarted: "Der Dienst wurde NICHT gestartet!", 40 | started: "Der Dienst läuft nun auf Port {0:trim}", 41 | startFailed: "Der Dienst konnte nicht GESTARTET werden: {0:trim}", 42 | stopFailed: "Der Dienst konnte nicht ANGEHALTEN werden: {0:trim}", 43 | stopped: "Der Dienst wurde ANGEHALTEN.", 44 | }, 45 | isNo: { 46 | dir: "{0:trim,surround} ist kein Verzeichnis!", 47 | file: "{0:trim,surround} ist keine Datei!", 48 | }, 49 | popups: { 50 | newVersion: { 51 | message: "Sie nutzen die neue Version {0:trim} von 'vs-rest-api'!", 52 | showChangeLog: 'Änderungsprotokoll anzeigen (englisch)...', 53 | }, 54 | }, 55 | whiteboard: { 56 | initFailed: "Whiteboard konnte nicht initialisiert werden: {0}", 57 | }, 58 | }; 59 | -------------------------------------------------------------------------------- /src/html/modules/html.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as rapi_content from '../../content'; 27 | import * as rapi_contracts from '../../contracts'; 28 | import * as rapi_helpers from '../../helpers'; 29 | 30 | 31 | export function execute(args: rapi_content.HtmlViewerExecutorArguments): string { 32 | let htmlDocs: rapi_contracts.Document[] = args.workspaceState[rapi_contracts.VAR_HTML_DOCS]; 33 | 34 | let doc: rapi_contracts.Document; 35 | 36 | let params = rapi_helpers.uriParamsToObject(args.uri); 37 | 38 | let idValue = decodeURIComponent(rapi_helpers.getUrlParam(params, 'id')); 39 | if (!rapi_helpers.isEmptyString(idValue)) { 40 | let id = idValue.trim(); 41 | 42 | // search for document 43 | for (let i = 0; i < htmlDocs.length; i++) { 44 | let d = htmlDocs[i]; 45 | 46 | if (rapi_helpers.toStringSafe(d.id).trim() == id) { 47 | doc = d; 48 | break; 49 | } 50 | } 51 | } 52 | 53 | let html = ''; 54 | 55 | if (doc) { 56 | if (doc.body) { 57 | let enc = rapi_helpers.normalizeString(doc.encoding); 58 | if (!enc) { 59 | enc = 'utf8'; 60 | } 61 | 62 | html = doc.body.toString(enc); 63 | } 64 | } 65 | 66 | return html; 67 | } 68 | -------------------------------------------------------------------------------- /src/workspace.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as Path from 'path'; 27 | import * as vscode from 'vscode'; 28 | 29 | 30 | let currentFolder: vscode.WorkspaceFolder | false = false; 31 | 32 | /** 33 | * Returns the root path of the selected workspace folder. 34 | * 35 | * @return {string} The root path. 36 | */ 37 | export function getRootPath() { 38 | let folder: vscode.WorkspaceFolder; 39 | 40 | if (false === currentFolder) { 41 | if (vscode.workspace.workspaceFolders) { 42 | if (vscode.workspace.workspaceFolders.length > 0) { 43 | folder = vscode.workspace.workspaceFolders[0]; 44 | } 45 | } 46 | } 47 | else { 48 | folder = currentFolder; 49 | } 50 | 51 | let workspace_root: string; 52 | 53 | if (folder) { 54 | workspace_root = vscode.workspace.getWorkspaceFolder(folder.uri) 55 | .uri 56 | .fsPath; 57 | } 58 | else { 59 | try { 60 | workspace_root = vscode.workspace.rootPath; 61 | } 62 | catch (e) { 63 | //TODO: log 64 | 65 | workspace_root = undefined; 66 | } 67 | } 68 | 69 | if ('undefined' !== typeof workspace_root) { 70 | return Path.resolve(workspace_root); 71 | } 72 | } 73 | 74 | /** 75 | * Resets the selected workspace folder. 76 | */ 77 | export function resetSelectedWorkspaceFolder() { 78 | currentFolder = false; 79 | } 80 | -------------------------------------------------------------------------------- /src/api/html.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as rapi_contracts from '../contracts'; 27 | import * as rapi_helpers from '../helpers'; 28 | import * as rapi_host_users from '../host/users'; 29 | import * as vscode from 'vscode'; 30 | 31 | /** 32 | * Object that contains the data for a new HTML document tab. 33 | */ 34 | export interface NewHtmlDocument { 35 | /** 36 | * The content. 37 | */ 38 | content?: string; 39 | /** 40 | * The title of the new tab. 41 | */ 42 | title?: string; 43 | } 44 | 45 | 46 | // [POST] /html 47 | export function POST(args: rapi_contracts.ApiMethodArguments): PromiseLike { 48 | let canOpen = args.request.user.can('open'); 49 | 50 | return new Promise((resolve, reject) => { 51 | let htmlDocs: rapi_contracts.Document[] = args.workspaceState[rapi_contracts.VAR_HTML_DOCS]; 52 | 53 | let newHtmlDoc: rapi_contracts.Document; 54 | let completed = (err?: any) => { 55 | if (err) { 56 | rapi_helpers.removeDocuments(newHtmlDoc, htmlDocs); 57 | 58 | reject(err); 59 | } 60 | else { 61 | resolve(); 62 | } 63 | }; 64 | 65 | if (!canOpen) { 66 | args.sendForbidden(); 67 | completed(); 68 | 69 | return; 70 | } 71 | 72 | let enc = 'utf8'; 73 | 74 | args.getJSON(enc).then((obj) => { 75 | try { 76 | newHtmlDoc = { 77 | body: undefined, 78 | encoding: enc, 79 | id: ++args.workspaceState[rapi_contracts.VAR_NEXT_HTML_DOC_ID], 80 | mime: 'text/html', 81 | }; 82 | 83 | if (obj) { 84 | if ('object' !== typeof obj) { 85 | // string 86 | newHtmlDoc.body = new Buffer(rapi_helpers.toStringSafe(obj), enc); 87 | } 88 | else { 89 | // NewHtmlDocument 90 | 91 | if (obj.content) { 92 | newHtmlDoc.body = new Buffer(rapi_helpers.toStringSafe(obj.content), enc); 93 | } 94 | 95 | if (obj.title) { 96 | newHtmlDoc.title = rapi_helpers.toStringSafe(obj.title); 97 | } 98 | } 99 | } 100 | 101 | htmlDocs.push(newHtmlDoc); 102 | 103 | vscode.commands.executeCommand('extension.restApi.openHtmlDoc', newHtmlDoc).then(() => { 104 | completed(); 105 | }, (err) => { 106 | completed(err); 107 | }); 108 | } 109 | catch (e) { 110 | completed(e); 111 | } 112 | }, (err) => { 113 | completed(err); 114 | }); 115 | }); 116 | } 117 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log (vs-rest-api) 2 | 3 | ## 3.0.1 (March 13th, 2018; npm updates) 4 | 5 | * updated the following [npm](https://www.npmjs.com/) modules: 6 | * [mime](https://www.npmjs.com/package/mime) `1.6.0` 7 | * [moment](https://www.npmjs.com/package/moment) `2.21.0` 8 | * extension requires at least [Visual Studio Code 1.20](https://code.visualstudio.com/updates/v1_20) now 9 | 10 | ## 2.0.0 (October 14th, 2017; multi root support) 11 | 12 | * started to refactor to new, upcoming [Multi Root Workspace API](https://github.com/Microsoft/vscode/wiki/Extension-Authoring:-Adopting-Multi-Root-Workspace-APIs) 13 | 14 | ## 1.18.0 (February 20th, 2017; deploy files from API scripts) 15 | 16 | * added [deploy()](https://mkloubert.github.io/vs-rest-api/interfaces/_contracts_.apimethodarguments.html#deploy) method to [ApiMethodArguments](https://mkloubert.github.io/vs-rest-api/interfaces/_contracts_.apimethodarguments.html) which make use of `extension.deploy.filesTo` command, provided by [vs-deploy](https://github.com/mkloubert/vs-deploy) extension 17 | 18 | ## 1.17.0 (February 20th, 2017; custom endpoints only) 19 | 20 | * added `customOnly` properties for [global](https://github.com/mkloubert/vs-rest-api/wiki#settings-) and [guest/users](https://github.com/mkloubert/vs-rest-api/wiki#users-and-guests-) settings 21 | 22 | ## 1.16.0 (February 19th, 2017; whiteboards) 23 | 24 | * added feature for handling a virtual [whitebard](https://github.com/mkloubert/vs-rest-api/wiki/whiteboard) 25 | 26 | ## 1.15.0 (February 17th, 2017; cron jobs) 27 | 28 | * added endpoint to handle [cron jobs](https://github.com/mkloubert/vs-rest-api/wiki#apicron-) 29 | 30 | ## 1.14.1 (February 16th, 2017; bugfixes) 31 | 32 | * fixed search for API methods in [modules](https://mkloubert.github.io/vs-rest-api/interfaces/_contracts_.apimodule.html) as described in the [wiki](https://github.com/mkloubert/vs-rest-api/wiki#custom-endpoints-) 33 | 34 | ## 1.14.0 (February 14th, 2017; deploy files) 35 | 36 | * can receive a list of available [deploy targets](https://github.com/mkloubert/vs-rest-api/wiki/buildin_endpoints_get_deploy) now 37 | 38 | ## 1.13.0 (February 14th, 2017; deploy files) 39 | 40 | * added endpoint to [deploy files](https://github.com/mkloubert/vs-rest-api/wiki#apideploy-) 41 | 42 | ## 1.11.0 (February 13th, 2017; machine specific TCP ports) 43 | 44 | * can define more than one TCP port in the [settings](https://github.com/mkloubert/vs-rest-api/wiki#settings-) now 45 | 46 | ## 1.10.0 (February 12th, 2017; ApiMethodArguments interface) 47 | 48 | * added `endpoint`, `parameters` and `url` properties to [ApiMethodArguments](https://mkloubert.github.io/vs-rest-api/interfaces/_contracts_.apimethodarguments.html) interface 49 | 50 | ## 1.9.0 (February 12th, 2017; ApiMethodArguments interface) 51 | 52 | * added `getString()` method to [ApiMethodArguments](https://mkloubert.github.io/vs-rest-api/interfaces/_contracts_.apimethodarguments.html) interface 53 | 54 | ## 1.8.0 (February 12th, 2017; open HTML documents in tabs from scripts) 55 | 56 | * added `openHtml()` method to [ScriptArguments](https://mkloubert.github.io/vs-rest-api/interfaces/_contracts_.scriptarguments.html) interface 57 | 58 | ## 1.7.0 (February 12th, 2017; user specific endpoints) 59 | 60 | * can define [whitelists for users and guests](https://github.com/mkloubert/vs-rest-api/wiki#user--guest-endpoints-) now, that define the endpoints which are available for the underlying account(s) 61 | 62 | ## 1.6.0 (February 12th, 2017; hooks) 63 | 64 | * added support for [hooks](https://github.com/mkloubert/vs-rest-api/wiki/settings_hooks) 65 | 66 | ## 1.4.0 (February 11th, 2017; HTML documents) 67 | 68 | * can open custom [HTML documents](https://github.com/mkloubert/vs-rest-api/wiki/buildin_endpoints_post_html) now 69 | 70 | ## 1.3.0 (February 11th, 2017; cleanups and improvements) 71 | 72 | * added `executeBuildIn()` method to [ApiMethodArguments](https://mkloubert.github.io/vs-rest-api/interfaces/_contracts_.apimethodarguments.html) 73 | * code cleanups and improvements 74 | * bugfixes 75 | 76 | ## 1.2.0 (February 11th, 2017; translation) 77 | 78 | * bugfixes 79 | * continued translation 80 | * improved logging 81 | 82 | ## 1.1.0 (February 11th, 2017; popups) 83 | 84 | * can display [popups](https://github.com/mkloubert/vs-rest-api/wiki/buildin_endpoints_post_popups) now 85 | 86 | ## 1.0.0 (February 11th, 2017; initial release) 87 | 88 | * read the [wiki](https://github.com/mkloubert/vs-rest-api/wiki) or the [README](https://github.com/mkloubert/vs-rest-api/blob/master/README.md) to learn more 89 | -------------------------------------------------------------------------------- /src/api/extensions.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | 25 | import * as rapi_contracts from '../contracts'; 26 | import * as rapi_helpers from '../helpers'; 27 | import * as rapi_host_users from '../host/users'; 28 | import * as vscode from 'vscode'; 29 | 30 | 31 | function extensionToObject(extension: vscode.Extension): Object { 32 | let obj: Object; 33 | 34 | if (extension) { 35 | obj = { 36 | id: rapi_helpers.toStringSafe(extension.id), 37 | isActive: rapi_helpers.toBooleanSafe(extension.isActive), 38 | localPath: rapi_helpers.toStringSafe(extension.extensionPath), 39 | }; 40 | 41 | obj['path'] = '/api/extensions/' + encodeURIComponent(obj['id']); 42 | } 43 | 44 | return obj; 45 | } 46 | 47 | // [GET] /extensions 48 | export function GET(args: rapi_contracts.ApiMethodArguments): PromiseLike { 49 | return new Promise((resolve, reject) => { 50 | let completed = (err?: any, extensions?: Object[]) => { 51 | if (err) { 52 | reject(err); 53 | } 54 | else { 55 | args.response.data = extensions; 56 | 57 | resolve(); 58 | } 59 | }; 60 | 61 | try { 62 | let extensions = vscode.extensions.all.filter(x => x) 63 | .map(x => extensionToObject(x)); 64 | 65 | extensions.sort((x, y) => { 66 | return rapi_helpers.compareValues(rapi_helpers.normalizeString(x['id']), 67 | rapi_helpers.normalizeString(y['id'])); 68 | }); 69 | 70 | completed(null, 71 | extensions); 72 | } 73 | catch (e) { 74 | completed(e); 75 | } 76 | }); 77 | }; 78 | 79 | // [POST] /extensions/{id} 80 | export function POST(args: rapi_contracts.ApiMethodArguments): PromiseLike { 81 | let canActivate = args.request.user.can('activate'); 82 | 83 | return new Promise((resolve, reject) => { 84 | let completed = (err?: any) => { 85 | if (err) { 86 | reject(err); 87 | } 88 | else { 89 | resolve(); 90 | } 91 | }; 92 | 93 | if (!canActivate) { 94 | args.sendForbidden(); 95 | completed(); 96 | 97 | return; 98 | } 99 | 100 | try { 101 | let parts = args.path.split('/'); 102 | 103 | let id: string; 104 | if (parts.length > 1) { 105 | id = rapi_helpers.normalizeString(parts[1]); 106 | } 107 | 108 | let extensions = vscode.extensions.all.filter(x => x); 109 | 110 | let result = []; 111 | 112 | let nextExtension: () => void; 113 | nextExtension = () => { 114 | if (extensions.length < 1) { 115 | if (result.length < 1) { 116 | args.sendNotFound(); 117 | } 118 | else { 119 | args.response.data = result; 120 | } 121 | 122 | completed(); 123 | return; 124 | } 125 | 126 | let ext = extensions.shift(); 127 | 128 | if (rapi_helpers.normalizeString(ext.id) == id) { 129 | ext.activate().then(() => { 130 | result.push(extensionToObject(ext)); 131 | 132 | nextExtension(); 133 | }, (err) => { 134 | completed(err); 135 | }); 136 | } 137 | else { 138 | nextExtension(); 139 | } 140 | }; 141 | 142 | nextExtension(); 143 | } 144 | catch (e) { 145 | completed(e); 146 | } 147 | }); 148 | }; 149 | -------------------------------------------------------------------------------- /src/api/appglobals.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as rapi_contracts from '../contracts'; 27 | import * as rapi_helpers from '../helpers'; 28 | import * as rapi_host_users from '../host/users'; 29 | 30 | 31 | // [DELETE] /appglobals/{name} 32 | export function DELETE(args: rapi_contracts.ApiMethodArguments): PromiseLike { 33 | let canDelete = args.request.user.can('delete'); 34 | 35 | return new Promise((resolve, reject) => { 36 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 37 | 38 | if (!canDelete) { 39 | args.sendForbidden(); 40 | completed(); 41 | return; 42 | } 43 | 44 | try { 45 | let name = getVarName(args); 46 | 47 | let item = getRepoItem(args); 48 | 49 | let exists = (item.item).hasOwnProperty(name); 50 | 51 | let oldValue = item.item[name]; 52 | delete item.item[name]; 53 | 54 | args.extension.globalState.update(rapi_contracts.VAR_STATE, item.repository); 55 | 56 | args.response.data = {}; 57 | if (exists) { 58 | args.response.data['old'] = oldValue; 59 | } 60 | 61 | completed(); 62 | } 63 | catch (e) { 64 | completed(e); 65 | } 66 | }); 67 | } 68 | 69 | // [GET] /appglobals 70 | export function GET(args: rapi_contracts.ApiMethodArguments): PromiseLike { 71 | return new Promise((resolve, reject) => { 72 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 73 | 74 | try { 75 | let item = getRepoItem(args); 76 | 77 | args.response.data = item.item; 78 | 79 | completed(); 80 | } 81 | catch (e) { 82 | completed(e); 83 | } 84 | }); 85 | } 86 | 87 | function getRepoItem(args: rapi_contracts.ApiMethodArguments): rapi_contracts.StateRepositoryWithItem { 88 | let item: rapi_contracts.StateRepositoryWithItem = { 89 | item: undefined, 90 | repository: rapi_helpers.getStateRepository(args.extension.globalState, rapi_contracts.VAR_STATE), 91 | }; 92 | 93 | item.item = item.repository.globals; 94 | 95 | return item; 96 | } 97 | 98 | function getVarName(args: rapi_contracts.ApiMethodArguments): string { 99 | let name: string; 100 | 101 | let parts = args.path.split('/'); 102 | if (parts.length > 1) { 103 | name = parts[1]; 104 | } 105 | 106 | return rapi_helpers.normalizeString(name); 107 | } 108 | 109 | // [PUT] /appglobals/{name} 110 | export function PUT(args: rapi_contracts.ApiMethodArguments): PromiseLike { 111 | let canWrite = args.request.user.can('write'); 112 | 113 | return new Promise((resolve, reject) => { 114 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 115 | 116 | if (!canWrite) { 117 | args.sendForbidden(); 118 | completed(); 119 | return; 120 | } 121 | 122 | try { 123 | let name = getVarName(args); 124 | 125 | let item = getRepoItem(args); 126 | 127 | args.getJSON().then((newValue) => { 128 | try { 129 | let isNew = !(item.item).hasOwnProperty(name); 130 | 131 | let oldValue = item.item[name]; 132 | item.item[name] = newValue; 133 | 134 | args.extension.globalState.update(rapi_contracts.VAR_STATE, item.repository); 135 | 136 | args.response.data = { 137 | isNew: isNew, 138 | new: newValue, 139 | old: oldValue, 140 | }; 141 | 142 | completed(); 143 | } 144 | catch (e) { 145 | completed(e); 146 | } 147 | }, (err) => { 148 | completed(err); 149 | }); 150 | } 151 | catch (e) { 152 | completed(e); 153 | } 154 | }); 155 | } 156 | -------------------------------------------------------------------------------- /src/api/globals.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as rapi_contracts from '../contracts'; 27 | import * as rapi_helpers from '../helpers'; 28 | import * as rapi_host_users from '../host/users'; 29 | 30 | 31 | // [DELETE] /globals/{name} 32 | export function DELETE(args: rapi_contracts.ApiMethodArguments): PromiseLike { 33 | let canDelete = args.request.user.can('delete'); 34 | 35 | return new Promise((resolve, reject) => { 36 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 37 | 38 | if (!canDelete) { 39 | args.sendForbidden(); 40 | completed(); 41 | return; 42 | } 43 | 44 | try { 45 | let name = getVarName(args); 46 | 47 | let item = getRepoItem(args); 48 | 49 | let exists = (item.item).hasOwnProperty(name); 50 | 51 | let oldValue = item.item[name]; 52 | delete item.item[name]; 53 | 54 | args.extension.workspaceState.update(rapi_contracts.VAR_STATE, item.repository); 55 | 56 | args.response.data = {}; 57 | if (exists) { 58 | args.response.data['old'] = oldValue; 59 | } 60 | 61 | completed(); 62 | } 63 | catch (e) { 64 | completed(e); 65 | } 66 | }); 67 | } 68 | 69 | // [GET] /globals 70 | export function GET(args: rapi_contracts.ApiMethodArguments): PromiseLike { 71 | return new Promise((resolve, reject) => { 72 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 73 | 74 | try { 75 | let item = getRepoItem(args); 76 | 77 | args.response.data = item.item; 78 | 79 | completed(); 80 | } 81 | catch (e) { 82 | completed(e); 83 | } 84 | }); 85 | } 86 | 87 | function getRepoItem(args: rapi_contracts.ApiMethodArguments): rapi_contracts.StateRepositoryWithItem { 88 | let item: rapi_contracts.StateRepositoryWithItem = { 89 | item: undefined, 90 | repository: rapi_helpers.getStateRepository(args.extension.workspaceState, rapi_contracts.VAR_STATE), 91 | }; 92 | 93 | item.item = item.repository.globals; 94 | 95 | return item; 96 | } 97 | 98 | function getVarName(args: rapi_contracts.ApiMethodArguments): string { 99 | let name: string; 100 | 101 | let parts = args.path.split('/'); 102 | if (parts.length > 1) { 103 | name = parts[1]; 104 | } 105 | 106 | return rapi_helpers.normalizeString(name); 107 | } 108 | 109 | // [PUT] /globals/{name} 110 | export function PUT(args: rapi_contracts.ApiMethodArguments): PromiseLike { 111 | let canWrite = args.request.user.can('write'); 112 | 113 | return new Promise((resolve, reject) => { 114 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 115 | 116 | if (!canWrite) { 117 | args.sendForbidden(); 118 | completed(); 119 | return; 120 | } 121 | 122 | try { 123 | let name = getVarName(args); 124 | 125 | let item = getRepoItem(args); 126 | 127 | args.getJSON().then((newValue) => { 128 | try { 129 | let isNew = !(item.item).hasOwnProperty(name); 130 | 131 | let oldValue = item.item[name]; 132 | item.item[name] = newValue; 133 | 134 | args.extension.workspaceState.update(rapi_contracts.VAR_STATE, item.repository); 135 | 136 | args.response.data = { 137 | isNew: isNew, 138 | new: newValue, 139 | old: oldValue, 140 | }; 141 | 142 | completed(); 143 | } 144 | catch (e) { 145 | completed(e); 146 | } 147 | }, (err) => { 148 | completed(err); 149 | }); 150 | } 151 | catch (e) { 152 | completed(e); 153 | } 154 | }); 155 | } 156 | -------------------------------------------------------------------------------- /src/api/appstate.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as rapi_contracts from '../contracts'; 27 | import * as rapi_helpers from '../helpers'; 28 | import * as rapi_host_users from '../host/users'; 29 | 30 | 31 | // [DELETE] /appstate/{name} 32 | export function DELETE(args: rapi_contracts.ApiMethodArguments): PromiseLike { 33 | let canDelete = args.request.user.can('delete'); 34 | 35 | return new Promise((resolve, reject) => { 36 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 37 | 38 | if (!canDelete) { 39 | args.sendForbidden(); 40 | completed(); 41 | return; 42 | } 43 | 44 | try { 45 | let name = getVarName(args); 46 | 47 | let item = getRepoItem(args); 48 | 49 | let exists = (item.item).hasOwnProperty(name); 50 | 51 | let oldValue = item.item[name]; 52 | delete item.item[name]; 53 | 54 | args.extension.globalState.update(rapi_contracts.VAR_STATE, item.repository); 55 | 56 | args.response.data = {}; 57 | if (exists) { 58 | args.response.data['old'] = oldValue; 59 | } 60 | 61 | completed(); 62 | } 63 | catch (e) { 64 | completed(e); 65 | } 66 | }); 67 | } 68 | 69 | // [GET] /appstate 70 | export function GET(args: rapi_contracts.ApiMethodArguments): PromiseLike { 71 | return new Promise((resolve, reject) => { 72 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 73 | 74 | try { 75 | let item = getRepoItem(args); 76 | 77 | args.response.data = item.item; 78 | 79 | completed(); 80 | } 81 | catch (e) { 82 | completed(e); 83 | } 84 | }); 85 | } 86 | 87 | function getRepoItem(args: rapi_contracts.ApiMethodArguments): rapi_contracts.StateRepositoryWithItem { 88 | let item: rapi_contracts.StateRepositoryWithItem = { 89 | item: undefined, 90 | repository: rapi_helpers.getStateRepository(args.extension.globalState, rapi_contracts.VAR_STATE), 91 | }; 92 | 93 | if (args.request.user.isGuest) { 94 | item.item = item.repository.guest; 95 | } 96 | else { 97 | let user: rapi_contracts.UserAccount = args.request.user; 98 | let username = rapi_helpers.normalizeString(user.name); 99 | 100 | if (!item.repository.users[username]) { 101 | item.repository.users[username] = {}; 102 | } 103 | 104 | item.item = item.repository.users[username]; 105 | } 106 | 107 | return item; 108 | } 109 | 110 | function getVarName(args: rapi_contracts.ApiMethodArguments): string { 111 | let name: string; 112 | 113 | let parts = args.path.split('/'); 114 | if (parts.length > 1) { 115 | name = parts[1]; 116 | } 117 | 118 | return rapi_helpers.normalizeString(name); 119 | } 120 | 121 | // [PUT] /appstate/{name} 122 | export function PUT(args: rapi_contracts.ApiMethodArguments): PromiseLike { 123 | let canWrite = args.request.user.can('write'); 124 | 125 | return new Promise((resolve, reject) => { 126 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 127 | 128 | if (!canWrite) { 129 | args.sendForbidden(); 130 | completed(); 131 | return; 132 | } 133 | 134 | try { 135 | let name = getVarName(args); 136 | 137 | let item = getRepoItem(args); 138 | 139 | args.getJSON().then((newValue) => { 140 | try { 141 | let isNew = !(item.item).hasOwnProperty(name); 142 | 143 | let oldValue = item.item[name]; 144 | item.item[name] = newValue; 145 | 146 | args.extension.globalState.update(rapi_contracts.VAR_STATE, item.repository); 147 | 148 | args.response.data = { 149 | isNew: isNew, 150 | new: newValue, 151 | old: oldValue, 152 | }; 153 | 154 | completed(); 155 | } 156 | catch (e) { 157 | completed(e); 158 | } 159 | }, (err) => { 160 | completed(err); 161 | }); 162 | } 163 | catch (e) { 164 | completed(e); 165 | } 166 | }); 167 | } 168 | -------------------------------------------------------------------------------- /src/api/state.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as rapi_contracts from '../contracts'; 27 | import * as rapi_helpers from '../helpers'; 28 | import * as rapi_host_users from '../host/users'; 29 | 30 | 31 | // [DELETE] /state/{name} 32 | export function DELETE(args: rapi_contracts.ApiMethodArguments): PromiseLike { 33 | let canDelete = args.request.user.can('delete'); 34 | 35 | return new Promise((resolve, reject) => { 36 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 37 | 38 | if (!canDelete) { 39 | args.sendForbidden(); 40 | completed(); 41 | return; 42 | } 43 | 44 | try { 45 | let name = getVarName(args); 46 | 47 | let item = getRepoItem(args); 48 | 49 | let exists = (item.item).hasOwnProperty(name); 50 | 51 | let oldValue = item.item[name]; 52 | delete item.item[name]; 53 | 54 | args.extension.workspaceState.update(rapi_contracts.VAR_STATE, item.repository); 55 | 56 | args.response.data = {}; 57 | if (exists) { 58 | args.response.data['old'] = oldValue; 59 | } 60 | 61 | completed(); 62 | } 63 | catch (e) { 64 | completed(e); 65 | } 66 | }); 67 | } 68 | 69 | // [GET] /state 70 | export function GET(args: rapi_contracts.ApiMethodArguments): PromiseLike { 71 | return new Promise((resolve, reject) => { 72 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 73 | 74 | try { 75 | let item = getRepoItem(args); 76 | 77 | args.response.data = item.item; 78 | 79 | completed(); 80 | } 81 | catch (e) { 82 | completed(e); 83 | } 84 | }); 85 | } 86 | 87 | function getRepoItem(args: rapi_contracts.ApiMethodArguments): rapi_contracts.StateRepositoryWithItem { 88 | let item: rapi_contracts.StateRepositoryWithItem = { 89 | item: undefined, 90 | repository: rapi_helpers.getStateRepository(args.extension.workspaceState, rapi_contracts.VAR_STATE), 91 | }; 92 | 93 | if (args.request.user.isGuest) { 94 | item.item = item.repository.guest; 95 | } 96 | else { 97 | let user: rapi_contracts.UserAccount = args.request.user; 98 | let username = rapi_helpers.normalizeString(user.name); 99 | 100 | if (!item.repository.users[username]) { 101 | item.repository.users[username] = {}; 102 | } 103 | 104 | item.item = item.repository.users[username]; 105 | } 106 | 107 | return item; 108 | } 109 | 110 | function getVarName(args: rapi_contracts.ApiMethodArguments): string { 111 | let name: string; 112 | 113 | let parts = args.path.split('/'); 114 | if (parts.length > 1) { 115 | name = parts[1]; 116 | } 117 | 118 | return rapi_helpers.normalizeString(name); 119 | } 120 | 121 | // [PUT] /state/{name} 122 | export function PUT(args: rapi_contracts.ApiMethodArguments): PromiseLike { 123 | let canWrite = args.request.user.can('write'); 124 | 125 | return new Promise((resolve, reject) => { 126 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 127 | 128 | if (!canWrite) { 129 | args.sendForbidden(); 130 | completed(); 131 | return; 132 | } 133 | 134 | try { 135 | let name = getVarName(args); 136 | 137 | let item = getRepoItem(args); 138 | 139 | args.getJSON().then((newValue) => { 140 | try { 141 | let isNew = !(item.item).hasOwnProperty(name); 142 | 143 | let oldValue = item.item[name]; 144 | item.item[name] = newValue; 145 | 146 | args.extension.workspaceState.update(rapi_contracts.VAR_STATE, item.repository); 147 | 148 | args.response.data = { 149 | isNew: isNew, 150 | new: newValue, 151 | old: oldValue, 152 | }; 153 | 154 | completed(); 155 | } 156 | catch (e) { 157 | completed(e); 158 | } 159 | }, (err) => { 160 | completed(err); 161 | }); 162 | } 163 | catch (e) { 164 | completed(e); 165 | } 166 | }); 167 | } 168 | -------------------------------------------------------------------------------- /src/content.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as rapi_contracts from './contracts'; 27 | import * as rapi_controller from './controller'; 28 | import * as rapi_helpers from './helpers'; 29 | import * as vscode from 'vscode'; 30 | 31 | 32 | /** 33 | * HTML executor. 34 | * 35 | * @param {HtmlViewerExecutorArguments} args The arguments. 36 | * 37 | * @return {HtmlViewerExecutorResult} The result. 38 | */ 39 | export type HtmlViewerExecutor = (args: HtmlViewerExecutorArguments) => HtmlViewerExecutorResult; 40 | 41 | /** 42 | * Arguments for a HTML executor. 43 | */ 44 | export interface HtmlViewerExecutorArguments { 45 | /** 46 | * The cancellation token. 47 | */ 48 | readonly cancelToken: vscode.CancellationToken; 49 | /** 50 | * The URI. 51 | */ 52 | readonly uri: vscode.Uri; 53 | /** 54 | * 55 | */ 56 | readonly workspaceState: Object; 57 | } 58 | 59 | /** 60 | * The result of a HTML executor. 61 | */ 62 | export type HtmlViewerExecutorResult = string | Thenable; 63 | 64 | /** 65 | * A module that executes logic for a HTML content provider. 66 | */ 67 | export interface HtmlViewerModule { 68 | /** 69 | * The HTML executor. 70 | */ 71 | readonly execute: HtmlViewerExecutor; 72 | } 73 | 74 | 75 | /** 76 | * HTML content provider. 77 | */ 78 | export class HtmlTextDocumentContentProvider implements vscode.TextDocumentContentProvider { 79 | /** 80 | * Stores the underlying controller. 81 | */ 82 | protected readonly _CONTROLLER: rapi_controller.Controller; 83 | 84 | /** 85 | * Initializes a new instance of that class. 86 | * 87 | * @param {rapi_controller.Controller} controller The underlying controller instance. 88 | */ 89 | constructor(controller: rapi_controller.Controller) { 90 | this._CONTROLLER = controller; 91 | } 92 | 93 | /** 94 | * Gets the underlying controller. 95 | */ 96 | public get controller(): rapi_controller.Controller { 97 | return this._CONTROLLER; 98 | } 99 | 100 | /** @inheritdoc */ 101 | public provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): Thenable { 102 | let me = this; 103 | 104 | return new Promise((resolve, reject) => { 105 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 106 | 107 | try { 108 | let executor: HtmlViewerExecutor; 109 | 110 | const REGEX_MODULE = new RegExp(/^(\s)*(\/)?([^\/]+)/, 'i'); 111 | 112 | let match = REGEX_MODULE.exec(uri.path); 113 | if (match) { 114 | let moduleName = rapi_helpers.normalizeString(match[3]); 115 | if (moduleName) { 116 | let htmlModule: HtmlViewerModule = require('./html/modules/' + moduleName); 117 | if (htmlModule) { 118 | executor = htmlModule.execute; 119 | } 120 | } 121 | } 122 | 123 | let executed = (err?: any, result?: any) => { 124 | if (err) { 125 | completed(err); 126 | } 127 | else { 128 | completed(null, result ? rapi_helpers.toStringSafe(result) 129 | : result); 130 | } 131 | }; 132 | 133 | if (executor) { 134 | let executorArgs: HtmlViewerExecutorArguments = { 135 | cancelToken: token, 136 | uri: uri, 137 | workspaceState: undefined, 138 | }; 139 | 140 | // executorArgs.workspaceState 141 | Object.defineProperty(executorArgs, 'workspaceState', { 142 | enumerable: true, 143 | get: () => { 144 | return me.controller.workspaceState; 145 | } 146 | }); 147 | 148 | let executorResult = executor(executorArgs); 149 | if ('object' === typeof executorResult) { 150 | executorResult.then((result) => { 151 | executed(null, result); 152 | }, (err) => { 153 | executed(err); 154 | }); 155 | } 156 | else { 157 | executed(null, executorResult); 158 | } 159 | } 160 | else { 161 | executed(new Error('No executor found!')); 162 | } 163 | } 164 | catch (e) { 165 | completed(e); 166 | } 167 | }); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /// 4 | 5 | // The MIT License (MIT) 6 | // 7 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 8 | // Copyright (c) Marcel Joachim Kloubert 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to 12 | // deal in the Software without restriction, including without limitation the 13 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 14 | // sell copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in 18 | // all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | // DEALINGS IN THE SOFTWARE. 27 | 28 | import * as FS from 'fs'; 29 | import * as i18 from './i18'; 30 | import * as Moment from 'moment'; 31 | import * as Path from 'path'; 32 | import * as rapi_content from './content'; 33 | import * as rapi_contracts from './contracts'; 34 | import * as rapi_controller from './controller'; 35 | import * as rapi_helpers from './helpers'; 36 | import * as rapi_workspace from './workspace'; 37 | import * as vscode from 'vscode'; 38 | 39 | 40 | let controller: rapi_controller.Controller; 41 | 42 | export function activate(context: vscode.ExtensionContext) { 43 | let now = Moment(); 44 | 45 | // package file 46 | let pkgFile: rapi_contracts.PackageFile; 47 | try { 48 | pkgFile = JSON.parse(FS.readFileSync(Path.join(__dirname, '../../package.json'), 'utf8')); 49 | } 50 | catch (e) { 51 | rapi_helpers.log(`[ERROR] extension.activate(): ${rapi_helpers.toStringSafe(e)}`); 52 | } 53 | 54 | let outputChannel = vscode.window.createOutputChannel("REST API"); 55 | 56 | // show infos about the app 57 | { 58 | if (pkgFile) { 59 | outputChannel.appendLine(`${pkgFile.displayName} (${pkgFile.name}) - v${pkgFile.version}`); 60 | } 61 | 62 | outputChannel.appendLine(`Copyright (c) ${now.format('YYYY')} Marcel Joachim Kloubert `); 63 | outputChannel.appendLine(''); 64 | outputChannel.appendLine(`GitHub : https://github.com/mkloubert/vs-rest-api`); 65 | outputChannel.appendLine(`Twitter: https://twitter.com/mjkloubert`); 66 | outputChannel.appendLine(`Donate : [PayPal] https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9J3ZR95RJE2BA`); 67 | outputChannel.appendLine(` [Flattr] https://flattr.com/submit/auto?fid=o62pkd&url=https%3A%2F%2Fgithub.com%2Fmkloubert%2Fvs-rest-api`); 68 | 69 | outputChannel.appendLine(''); 70 | } 71 | 72 | controller = new rapi_controller.Controller(context, outputChannel, pkgFile); 73 | rapi_workspace.resetSelectedWorkspaceFolder(); 74 | 75 | // (re)start host 76 | let startHost = vscode.commands.registerCommand('extension.restApi.startHost', () => { 77 | controller.start().then(() => { 78 | //TODO 79 | }, (err) => { 80 | rapi_helpers.log(`[ERROR] extension.restApi.startHost: ${err}`); 81 | }); 82 | }); 83 | 84 | // stop host 85 | let stopHost = vscode.commands.registerCommand('extension.restApi.stopHost', () => { 86 | controller.stop().then(() => { 87 | //TODO 88 | }, (err) => { 89 | rapi_helpers.log(`[ERROR] extension.restApi.stopHost: ${err}`); 90 | }); 91 | }); 92 | 93 | // toggle host state 94 | let toggleServerState = vscode.commands.registerCommand('extension.restApi.toggleHostState', () => { 95 | controller.toggleHostState().then(() => { 96 | //TODO 97 | }, (err) => { 98 | rapi_helpers.log(`[ERROR] extension.restApi.toggleHostState: ${err}`); 99 | }); 100 | }); 101 | 102 | // open HTML document 103 | let openHtmlDoc = vscode.commands.registerCommand('extension.restApi.openHtmlDoc', (doc: rapi_contracts.Document) => { 104 | try { 105 | let htmlDocs = controller.workspaceState[rapi_contracts.VAR_HTML_DOCS]; 106 | 107 | let url = vscode.Uri.parse(`vs-rest-api-html://authority/html?id=${encodeURIComponent(rapi_helpers.toStringSafe(doc.id))}` + 108 | `&x=${encodeURIComponent(rapi_helpers.toStringSafe(new Date().getTime()))}`); 109 | 110 | let title = rapi_helpers.toStringSafe(doc.title).trim(); 111 | if (!title) { 112 | title = `[vs-rest-api] HTML document #${rapi_helpers.toStringSafe(doc.id)}`; 113 | } 114 | 115 | vscode.commands.executeCommand('vscode.previewHtml', url, vscode.ViewColumn.One, title).then((success) => { 116 | rapi_helpers.removeDocuments(doc, htmlDocs); 117 | }, (err) => { 118 | rapi_helpers.removeDocuments(doc, htmlDocs); 119 | 120 | rapi_helpers.log(`[ERROR] extension.restApi.openHtmlDoc(2): ${err}`); 121 | }); 122 | } 123 | catch (e) { 124 | rapi_helpers.log(`[ERROR] extension.restApi.openHtmlDoc(1): ${e}`); 125 | } 126 | }); 127 | 128 | let htmlViewer = vscode.workspace.registerTextDocumentContentProvider('vs-rest-api-html', 129 | new rapi_content.HtmlTextDocumentContentProvider(controller)); 130 | 131 | // viewers 132 | context.subscriptions 133 | .push(htmlViewer); 134 | 135 | // notfiy setting changes 136 | context.subscriptions 137 | .push(vscode.workspace.onDidChangeConfiguration(controller.onDidChangeConfiguration, controller)); 138 | 139 | // commands 140 | context.subscriptions 141 | .push(startHost, stopHost, toggleServerState, 142 | openHtmlDoc); 143 | 144 | controller.onActivated(); 145 | } 146 | 147 | export function deactivate() { 148 | if (controller) { 149 | controller.onDeactivate(); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/api/popups.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as rapi_contracts from '../contracts'; 27 | import * as rapi_helpers from '../helpers'; 28 | import * as rapi_host_users from '../host/users'; 29 | import * as vscode from 'vscode'; 30 | 31 | 32 | interface ActionMessageItem extends vscode.MessageItem { 33 | action: () => void; 34 | } 35 | 36 | /** 37 | * A message item. 38 | */ 39 | export interface ShowMessageItem { 40 | /** 41 | * The arguments for the command. 42 | */ 43 | args?: any[]; 44 | /** 45 | * The command to execute. 46 | */ 47 | command?: string; 48 | /** 49 | * The caption / title for the item. 50 | */ 51 | title: string; 52 | } 53 | 54 | /** 55 | * Options for showing a message. 56 | */ 57 | export interface ShowMessageOptions { 58 | /** 59 | * The message. 60 | */ 61 | message: string; 62 | /** 63 | * The items / buttons. 64 | */ 65 | items?: ShowMessageItem | ShowMessageItem[]; 66 | /** 67 | * The type. 68 | */ 69 | type?: "e" | "err" | "error" | 70 | "i" | "info" | "information" | 71 | "w" | "warn" | "warning"; 72 | } 73 | 74 | // [POST] /popups 75 | export function POST(args: rapi_contracts.ApiMethodArguments): PromiseLike { 76 | let canExecute = args.request.user.can('execute'); 77 | 78 | return new Promise((resolve, reject) => { 79 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 80 | 81 | if (!canExecute) { 82 | args.sendForbidden(); 83 | completed(); 84 | return; 85 | } 86 | 87 | args.getJSON().then((obj) => { 88 | try { 89 | let opts: ShowMessageOptions = obj; 90 | if (opts) { 91 | if ("object" !== typeof obj) { 92 | opts = { 93 | message: rapi_helpers.toStringSafe(opts), 94 | type: "i", 95 | }; 96 | } 97 | } 98 | 99 | opts = opts || { 100 | message: undefined, 101 | type: "i", 102 | }; 103 | 104 | let items: ActionMessageItem[] = rapi_helpers.asArray(opts.items).filter(x => x).map(x => { 105 | let msgItem: ActionMessageItem = { 106 | action: undefined, 107 | title: rapi_helpers.toStringSafe(x.title), 108 | }; 109 | 110 | let cmd = rapi_helpers.toStringSafe(x.command).trim(); 111 | if (cmd) { 112 | let cmdArgs = [ cmd ].concat(x.args || []); 113 | 114 | msgItem.action = () => { 115 | vscode.commands.executeCommand.apply(null, cmdArgs).then(() => { 116 | //TODO 117 | }, (err) => { 118 | rapi_helpers.log(`[ERROR] api.popups.POST.${cmd}: ${rapi_helpers.toStringSafe(err)}`); 119 | }); 120 | }; 121 | } 122 | else { 123 | msgItem.action = () => { }; 124 | } 125 | 126 | return msgItem; 127 | }).filter(x => x); 128 | 129 | let func: Function; 130 | switch (rapi_helpers.normalizeString(opts.type)) { 131 | case 'e': 132 | case 'error': 133 | case 'err': 134 | func = vscode.window.showErrorMessage; 135 | break; 136 | 137 | case 'w': 138 | case 'warning': 139 | case 'warn': 140 | func = vscode.window.showWarningMessage; 141 | break; 142 | 143 | default: 144 | func = vscode.window.showInformationMessage; 145 | break; 146 | } 147 | 148 | let funcArgs: any[] = [ rapi_helpers.toStringSafe(opts.message) ]; 149 | funcArgs = funcArgs.concat(items); 150 | 151 | func.apply(null, funcArgs).then((i: ActionMessageItem) => { 152 | if (i) { 153 | try { 154 | i.action(); 155 | } 156 | catch (e) { 157 | rapi_helpers.log(`[ERROR] api.popups.POST(1): ${rapi_helpers.toStringSafe(e)}`); 158 | } 159 | } 160 | }, (err) => { 161 | rapi_helpers.log(`[ERROR] api.popups.POST(2): ${rapi_helpers.toStringSafe(err)}`); 162 | }); 163 | 164 | completed(); 165 | } 166 | catch (e) { 167 | completed(e); 168 | } 169 | }, (err) => { 170 | completed(err); 171 | }); 172 | }); 173 | } 174 | -------------------------------------------------------------------------------- /src/api/commands.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as rapi_contracts from '../contracts'; 27 | import * as rapi_helpers from '../helpers'; 28 | import * as rapi_host_users from '../host/users'; 29 | import * as vscode from 'vscode'; 30 | 31 | 32 | // [GET] /commands 33 | export function GET(args: rapi_contracts.ApiMethodArguments): PromiseLike { 34 | let canExecute = args.request.user.can('execute'); 35 | 36 | return new Promise((resolve, reject) => { 37 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 38 | 39 | if (!canExecute) { 40 | args.sendNotFound(); 41 | 42 | completed(); 43 | return; 44 | } 45 | 46 | vscode.commands.getCommands(false).then((commands) => { 47 | args.response.data = commands.map(x => { 48 | let cmdItem = { 49 | name: x, 50 | path: '/api/commands/' + encodeURIComponent(x), 51 | }; 52 | 53 | return cmdItem; 54 | }); 55 | 56 | args.response.data.sort((x, y) => { 57 | return rapi_helpers.compareValues(rapi_helpers.normalizeString(x.name), 58 | rapi_helpers.normalizeString(y.name)); 59 | }); 60 | 61 | completed(); 62 | }, (err) => { 63 | completed(err); 64 | }); 65 | }); 66 | } 67 | 68 | // [POST] /commands/{commandId} 69 | export function POST(args: rapi_contracts.ApiMethodArguments): PromiseLike { 70 | let canExecute = args.request.user.can('execute'); 71 | 72 | return new Promise((resolve, reject) => { 73 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 74 | 75 | if (!canExecute) { 76 | args.sendForbidden(); 77 | completed(); 78 | 79 | return; 80 | } 81 | 82 | vscode.commands.getCommands(false).then((commands) => { 83 | let path = args.path; 84 | let firstSep = path.indexOf('/'); 85 | 86 | let commandToExecute: string; 87 | if (firstSep > -1) { 88 | commandToExecute = rapi_helpers.normalizeString(path.substring(firstSep + 1)); 89 | } 90 | 91 | if (commandToExecute) { 92 | // find machting commands 93 | let knownCommands: string[] = []; 94 | for (let i = 0; i < commands.length; i++) { 95 | let kc = commands[i]; 96 | 97 | if (rapi_helpers.normalizeString(kc) == commandToExecute) { 98 | knownCommands.push(kc); 99 | break; 100 | } 101 | } 102 | 103 | if (knownCommands.length) { 104 | // try read arguments from body 105 | args.getJSON().then((body) => { 106 | let cmdArgs: any[]; 107 | if (body) { 108 | cmdArgs = rapi_helpers.asArray(body); 109 | } 110 | cmdArgs = cmdArgs || []; 111 | 112 | try { 113 | let nextCommand: () => void; 114 | nextCommand = () => { 115 | if (knownCommands.length < 1) { 116 | completed(); 117 | return; 118 | } 119 | 120 | try { 121 | vscode.commands 122 | .executeCommand 123 | .apply(null, [ knownCommands.shift() ].concat(cmdArgs)) 124 | .then(() => { 125 | nextCommand(); 126 | }, (err) => { 127 | completed(err); 128 | }); 129 | } 130 | catch (e) { 131 | completed(e); 132 | } 133 | }; 134 | 135 | nextCommand(); 136 | } 137 | catch (e) { 138 | completed(e); 139 | } 140 | }, (err) => { 141 | completed(err); 142 | }); 143 | } 144 | else { 145 | // no matching command(s) found 146 | 147 | args.sendNotFound(); 148 | completed(); 149 | } 150 | } 151 | else { 152 | // no command defined 153 | 154 | args.statusCode = 400; 155 | completed(); 156 | } 157 | }, (err) => { 158 | completed(err); 159 | }); 160 | }); 161 | } 162 | -------------------------------------------------------------------------------- /src/i18.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as FS from 'fs'; 27 | const i18next = require('i18next'); 28 | import * as Path from 'path'; 29 | import * as rapi_helpers from './helpers'; 30 | import * as vscode from 'vscode'; 31 | 32 | 33 | /** 34 | * Stores the strings of a translation. 35 | */ 36 | export interface Translation { 37 | browser: { 38 | openFailed: string; 39 | }, 40 | errors: { 41 | withCategory: string; 42 | }, 43 | host: { 44 | notStarted: string; 45 | started: string; 46 | startFailed: string; 47 | stopFailed: string; 48 | stopped: string; 49 | }, 50 | isNo: { 51 | dir: string; 52 | file: string; 53 | }, 54 | popups: { 55 | newVersion: { 56 | message: string; 57 | showChangeLog: string; 58 | }, 59 | }, 60 | whiteboard: { 61 | initFailed: string; 62 | }, 63 | } 64 | 65 | 66 | /** 67 | * Returns a translated string by key. 68 | * 69 | * @param {string} key The key. 70 | * @param {any} [args] The optional arguments. 71 | * 72 | * @return {string} The "translated" string. 73 | */ 74 | export function t(key: string, ...args: any[]): string { 75 | let formatStr = i18next.t(rapi_helpers.toStringSafe(key).trim()); 76 | formatStr = rapi_helpers.toStringSafe(formatStr); 77 | 78 | return rapi_helpers.formatArray(formatStr, args); 79 | } 80 | 81 | /** 82 | * Initializes the language repository. 83 | * 84 | * @param {string} [lang] The custom language to use. 85 | * 86 | * @returns {PromiseLike} The promise. 87 | */ 88 | export function init(lang?: string): PromiseLike { 89 | if (rapi_helpers.isEmptyString(lang)) { 90 | lang = vscode.env.language; 91 | } 92 | lang = rapi_helpers.toStringSafe(lang).toLowerCase().trim(); 93 | if (!lang) { 94 | lang = 'en'; 95 | } 96 | 97 | return new Promise((resolve, reject) => { 98 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 99 | 100 | try { 101 | let langDir = Path.join(__dirname, 'lang'); 102 | 103 | let resources: any = {}; 104 | 105 | // initialize 'i18next' 106 | // with collected data 107 | let initLang = () => { 108 | i18next.init({ 109 | lng: lang, 110 | resources: resources, 111 | fallbackLng: 'en', 112 | }, (err, tr) => { 113 | completed(err, tr); 114 | }); 115 | }; 116 | 117 | // load language files 118 | let loadFiles = () => { 119 | FS.readdir(langDir, (err, files) => { 120 | if (err) { 121 | completed(err); 122 | return; 123 | } 124 | 125 | // load files 126 | for (let i = 0; i < files.length; i++) { 127 | try { 128 | let fileName = files[i]; 129 | if (fileName.length < 3) { 130 | continue; 131 | } 132 | 133 | if ('.js' != fileName.substr(fileName.length - 3)) { 134 | continue; // no JavaScript file 135 | } 136 | 137 | let langName = fileName.substr(0, fileName.length - 3).toLowerCase().trim(); 138 | if (!langName) { 139 | continue; // no language name available 140 | } 141 | 142 | let fullPath = Path.join(langDir, fileName); 143 | fullPath = Path.resolve(fullPath); 144 | 145 | let stats = FS.lstatSync(fullPath); 146 | if (!stats.isFile()) { 147 | continue; // no file 148 | } 149 | 150 | // deleted cached data 151 | // and load current translation 152 | // from file 153 | delete require.cache[fullPath]; 154 | resources[langName] = { 155 | translation: require(fullPath).TRANSLATION, 156 | }; 157 | } 158 | catch (e) { 159 | rapi_helpers.log(`[vs-deploy :: ERROR] i18.init(): ${rapi_helpers.toStringSafe(e)}`); 160 | } 161 | } 162 | 163 | initLang(); 164 | }) 165 | }; 166 | 167 | // check if directory 168 | let checkIfDirectory = () => { 169 | FS.lstat(langDir, (err, stats) => { 170 | if (stats.isDirectory()) { 171 | loadFiles(); 172 | } 173 | else { 174 | completed(new Error(`'${langDir}' is no directory!`)); 175 | } 176 | }); 177 | }; 178 | 179 | FS.exists(langDir, (exists) => { 180 | if (exists) { 181 | checkIfDirectory(); 182 | } 183 | else { 184 | initLang(); 185 | } 186 | }); 187 | } 188 | catch (e) { 189 | completed(e); 190 | } 191 | }); 192 | } 193 | -------------------------------------------------------------------------------- /src/api/files.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as FS from 'fs'; 27 | import * as Moment from 'moment'; 28 | import * as Path from 'path'; 29 | import * as rapi_contracts from '../contracts'; 30 | import * as rapi_helpers from '../helpers'; 31 | import * as rapi_host_users from '../host/users'; 32 | import * as rapi_workspace from '../workspace'; 33 | import * as vscode from 'vscode'; 34 | 35 | 36 | /** 37 | * Options for a file search. 38 | */ 39 | export interface FindFilesOptions { 40 | /** 41 | * The glob pattern of files to exclude. 42 | */ 43 | exclude?: string; 44 | /** 45 | * The glob pattern of files to include. 46 | */ 47 | include?: string; 48 | /** 49 | * Maximum number of items to return. 50 | */ 51 | maxResults?: number; 52 | } 53 | 54 | 55 | function normalizePath(p: string) { 56 | p = rapi_helpers.toStringSafe(p); 57 | if (!p) { 58 | return p; 59 | } 60 | 61 | p = rapi_helpers.replaceAllStrings(p, "\\", '/'); 62 | p = rapi_helpers.replaceAllStrings(p, Path.sep, '/'); 63 | 64 | return p; 65 | } 66 | 67 | // [POST] /files 68 | export function POST(args: rapi_contracts.ApiMethodArguments): PromiseLike { 69 | let canOpen = args.request.user.can('open'); 70 | 71 | return new Promise((resolve, reject) => { 72 | let files: rapi_contracts.File[] = []; 73 | let completed = (err?: any) => { 74 | if (err) { 75 | reject(err); 76 | } 77 | else { 78 | files.sort((x, y) => { 79 | return rapi_helpers.compareValues(rapi_helpers.normalizeString(x.path), 80 | rapi_helpers.normalizeString(y.path)); 81 | }); 82 | 83 | args.response.data = files; 84 | 85 | resolve(); 86 | } 87 | }; 88 | 89 | args.getJSON().then((opts) => { 90 | opts = opts || { 91 | include: undefined, 92 | }; 93 | 94 | let include = rapi_helpers.toStringSafe(opts.include); 95 | if (rapi_helpers.isEmptyString(include)) { 96 | include = '**'; 97 | } 98 | 99 | let exclude = rapi_helpers.toStringSafe(opts.exclude); 100 | if (rapi_helpers.isEmptyString(exclude)) { 101 | exclude = undefined; 102 | } 103 | 104 | let maxResult = parseInt(rapi_helpers.toStringSafe(opts.maxResults).trim()); 105 | if (isNaN(maxResult)) { 106 | maxResult = undefined; 107 | } 108 | 109 | vscode.workspace.findFiles(include, exclude, maxResult).then((uris) => { 110 | let nextFile: () => void; 111 | nextFile = () => { 112 | if (uris.length < 1) { 113 | completed(); 114 | return; 115 | } 116 | 117 | let u = uris.shift(); 118 | 119 | let fullPath = u.fsPath; 120 | if (!Path.isAbsolute(fullPath)) { 121 | fullPath = Path.join(rapi_workspace.getRootPath(), fullPath); 122 | } 123 | fullPath = Path.resolve(fullPath); 124 | 125 | args.request.user.isFileVisible(u.fsPath, args.request.user.account.withDot).then((isVisible) => { 126 | if (isVisible) { 127 | FS.stat(fullPath, (err, stats) => { 128 | if (err) { 129 | completed(err); 130 | } 131 | 132 | let relativePath = rapi_helpers.toRelativePath(fullPath); 133 | if (false !== relativePath) { 134 | if (stats.isFile()) { 135 | let filePath = normalizePath(relativePath).split('/') 136 | .map(x => encodeURIComponent(x)) 137 | .join('/'); 138 | 139 | let newFileItem: rapi_contracts.File = { 140 | creationTime: toISODateString(stats.birthtime), 141 | lastChangeTime: toISODateString(stats.ctime), 142 | lastModifiedTime: toISODateString(stats.mtime), 143 | mime: rapi_helpers.detectMimeByFilename(fullPath), 144 | name: Path.basename(filePath), 145 | path: '/api/workspace' + filePath, 146 | size: stats.size, 147 | type: 'file', 148 | }; 149 | 150 | if (canOpen) { 151 | newFileItem.openPath = '/api/editor' + filePath; 152 | } 153 | 154 | files.push(newFileItem); 155 | } 156 | } 157 | 158 | nextFile(); 159 | }); 160 | } 161 | else { 162 | nextFile(); 163 | } 164 | }); 165 | }; 166 | 167 | nextFile(); 168 | }, (err) => { 169 | completed(err); 170 | }); 171 | }, (err) => { 172 | completed(err); 173 | }); 174 | }); 175 | } 176 | 177 | function toISODateString(dt: Date): string { 178 | if (!dt) { 179 | return; 180 | } 181 | 182 | return Moment(dt).utc().toISOString(); 183 | } 184 | -------------------------------------------------------------------------------- /src/api/deploy.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as Path from 'path'; 27 | import * as rapi_contracts from '../contracts'; 28 | import * as rapi_helpers from '../helpers'; 29 | import * as rapi_workspace from '../workspace'; 30 | import * as vscode from 'vscode'; 31 | 32 | /** 33 | * A deploy target entry. 34 | */ 35 | export interface DeployTargetEntry { 36 | /** 37 | * The description. 38 | */ 39 | description?: string; 40 | /** 41 | * The name. 42 | */ 43 | name?: string; 44 | /** 45 | * The type. 46 | */ 47 | type?: string; 48 | } 49 | 50 | /** 51 | * Possible types for deploy targets. 52 | */ 53 | export type DeployTargets = string | string[]; 54 | 55 | 56 | // [GET] /deploy 57 | export function GET(args: rapi_contracts.ApiMethodArguments): PromiseLike { 58 | let canDeploy = args.request.user.can('deploy'); 59 | 60 | return new Promise((resolve, reject) => { 61 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 62 | 63 | if (!canDeploy) { 64 | args.sendForbidden(); 65 | completed(); 66 | 67 | return; 68 | } 69 | 70 | vscode.commands.getCommands(true).then((commands) => { 71 | let deployCmd = commands.filter(x => 'extension.deploy.getTargets' == x); 72 | if (deployCmd.length < 1) { // 'vs-deploy' is NOT installed 73 | args.sendResponse(410); 74 | completed(); 75 | 76 | return; 77 | } 78 | 79 | try { 80 | let callback = (err, targets: DeployTargetEntry[]) => { 81 | try { 82 | if (!err) { 83 | if (targets) { 84 | args.response.data = targets.filter(x => x).map(x => { 85 | return { 86 | description: rapi_helpers.isEmptyString(x.description) ? undefined : rapi_helpers.toStringSafe(x.description).trim(), 87 | name: x.name ? rapi_helpers.toStringSafe(x.name) : x.name, 88 | type: rapi_helpers.isEmptyString(x.type) ? 'open' : rapi_helpers.normalizeString(x.type), 89 | }; 90 | }); 91 | } 92 | } 93 | 94 | completed(err); 95 | } 96 | catch (e) { 97 | completed(e); 98 | } 99 | }; 100 | 101 | vscode.commands.executeCommand(deployCmd[0], callback).then(() => { 102 | //TODO 103 | }, (err) => { 104 | completed(err); 105 | }); 106 | } 107 | catch (e) { 108 | completed(e); 109 | } 110 | }, (err) => { 111 | completed(err); 112 | }); 113 | }); 114 | } 115 | 116 | // [POST] /deploy/{file} 117 | export function POST(args: rapi_contracts.ApiMethodArguments): PromiseLike { 118 | let canDeploy = args.request.user.can('deploy'); 119 | 120 | return new Promise((resolve, reject) => { 121 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 122 | 123 | let notFound = () => { 124 | args.sendNotFound(); 125 | completed(); 126 | 127 | return; 128 | }; 129 | 130 | if (!canDeploy) { 131 | args.sendForbidden(); 132 | completed(); 133 | 134 | return; 135 | } 136 | 137 | vscode.commands.getCommands(true).then((commands) => { 138 | let deployCmd = commands.filter(x => 'extension.deploy.filesTo' == x); 139 | if (deployCmd.length < 1) { // 'vs-deploy' is NOT installed 140 | args.sendResponse(410); 141 | completed(); 142 | 143 | return; 144 | } 145 | 146 | try { 147 | let normalizedPath = rapi_helpers.toStringSafe(args.request.url.pathname); 148 | normalizedPath = rapi_helpers.replaceAllStrings(normalizedPath, "\\", '/'); 149 | normalizedPath = rapi_helpers.replaceAllStrings(normalizedPath, Path.sep, '/'); 150 | 151 | let parts = normalizedPath.split('/') 152 | .filter((x, i) => i > 2) 153 | .map(x => decodeURIComponent(x)) 154 | .filter(x => x); 155 | 156 | let fullPath = Path.join(rapi_workspace.getRootPath(), parts.join('/')); 157 | 158 | let relativePath = rapi_helpers.toRelativePath(fullPath); 159 | if (false === relativePath) { 160 | notFound(); // only inside workspace 161 | return; 162 | } 163 | 164 | args.request.user.isFileVisible(fullPath, args.request.user.account.withDot).then((isVisible) => { 165 | if (isVisible) { 166 | args.getJSON().then((submittedTargetList) => { 167 | let targets = rapi_helpers.asArray(submittedTargetList) 168 | .map(x => rapi_helpers.normalizeString(x)) 169 | .filter(x => x); 170 | targets = rapi_helpers.distinctArray(targets); 171 | 172 | vscode.commands.executeCommand(deployCmd[0], [ fullPath ], targets).then(() => { 173 | completed(); 174 | }, (err) => { 175 | completed(err); 176 | }); 177 | }); 178 | } 179 | else { 180 | notFound(); // not visible for user 181 | } 182 | }, (err) => { 183 | completed(err); 184 | }); 185 | } 186 | catch (e) { 187 | completed(e); 188 | } 189 | }, (err) => { 190 | completed(err); 191 | }); 192 | }); 193 | } 194 | -------------------------------------------------------------------------------- /src/hooks.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as HTTP from 'http'; 27 | import * as HTTPs from 'https'; 28 | import * as i18 from './i18'; 29 | import * as Moment from 'moment'; 30 | import * as Path from 'path'; 31 | import * as rapi_contracts from './contracts'; 32 | import * as rapi_helpers from './helpers'; 33 | import * as rapi_workspace from './workspace'; 34 | import * as URL from 'url'; 35 | import * as vscode from 'vscode'; 36 | 37 | 38 | export function emitHooks(apiArgs: rapi_contracts.ApiMethodArguments, 39 | hookToEmit: string, hookArgs: any[]): boolean { 40 | hookToEmit = rapi_helpers.normalizeString(hookToEmit); 41 | hookArgs = hookArgs || []; 42 | 43 | let emitted = false; 44 | 45 | if (hookToEmit) { 46 | try { 47 | let listOfHooks = apiArgs.request.config.hooks; 48 | if (listOfHooks) { 49 | for (let hp in listOfHooks) { 50 | try { 51 | let hookPattern = new RegExp(rapi_helpers.toStringSafe(hp), 'i'); 52 | 53 | if (!hookPattern.test(hookToEmit)) { 54 | continue; 55 | } 56 | 57 | emitted = true; 58 | 59 | let allHooks = rapi_helpers.asArray(listOfHooks[hp]) 60 | .filter(x => x) 61 | .map(x => { 62 | let hookObj: rapi_contracts.ApiHook = x; 63 | if ('object' !== typeof x) { 64 | hookObj = { 65 | script: rapi_helpers.toStringSafe(x), 66 | }; 67 | } 68 | 69 | return hookObj; 70 | }) 71 | .filter(x => !rapi_helpers.isEmptyString(x.script)); 72 | 73 | allHooks.forEach((h) => { 74 | try { 75 | let hookScript = h.script; 76 | if (!Path.isAbsolute(hookScript)) { 77 | hookScript = Path.join(rapi_workspace.getRootPath(), hookScript); 78 | } 79 | hookScript = Path.resolve(hookScript); 80 | 81 | let hookModule: rapi_contracts.ApiHookModule = require(hookScript); 82 | if (hookModule) { 83 | let executor = hookModule.onHook; 84 | if (executor) { 85 | let executorArgs: rapi_contracts.ApiHookExecutorArguments = { 86 | api: apiArgs, 87 | globals: apiArgs.globals, 88 | globalState: undefined, 89 | hook: hookToEmit, 90 | log: function(msg) { 91 | apiArgs.log(msg); 92 | return this; 93 | }, 94 | openHtml: (html, title, docId) => { 95 | return rapi_helpers.openHtmlDocument(apiArgs.workspaceState[rapi_contracts.VAR_HTML_DOCS], 96 | html, title, docId); 97 | }, 98 | options: h.options, 99 | require: (id) => { 100 | return rapi_helpers.requireModule(id); 101 | }, 102 | state: undefined, 103 | whiteboard: undefined, 104 | workspaceState: undefined, 105 | }; 106 | 107 | // executorArgs.globalState 108 | Object.defineProperty(executorArgs, 'globalState', { 109 | enumerable: true, 110 | get: function() { 111 | return this.workspaceState['globalHookStates']; 112 | } 113 | }); 114 | 115 | // executorArgs.state 116 | Object.defineProperty(executorArgs, 'state', { 117 | enumerable: true, 118 | get: function() { 119 | return this.workspaceState['globalHookScriptStates'][hookScript]; 120 | }, 121 | set: function(newValue) { 122 | this.workspaceState['globalHookScriptStates'][hookScript] = newValue; 123 | } 124 | }); 125 | 126 | // executorArgs.whiteboard 127 | Object.defineProperty(executorArgs, 'whiteboard', { 128 | enumerable: true, 129 | get: () => { 130 | return apiArgs.whiteboard; 131 | } 132 | }); 133 | 134 | // executorArgs.workspaceState 135 | Object.defineProperty(executorArgs, 'workspaceState', { 136 | enumerable: true, 137 | get: function() { 138 | return apiArgs.workspaceState; 139 | } 140 | }); 141 | 142 | let executorResult = executor(executorArgs); 143 | if (executorResult) { 144 | executorResult.then(() => { 145 | //TODO 146 | }, (err) => { 147 | rapi_helpers.log(i18.t('errors.withCategory', 'hooks.emitHooks(4)', err)); 148 | }); 149 | } 150 | } 151 | } 152 | } 153 | catch (e) { 154 | rapi_helpers.log(i18.t('errors.withCategory', 'hooks.emitHooks(3)', e)); 155 | } 156 | }); 157 | } 158 | catch (e) { 159 | rapi_helpers.log(i18.t('errors.withCategory', 'hooks.emitHooks(2)', e)); 160 | } 161 | } 162 | } 163 | } 164 | catch (e) { 165 | emitted = null; 166 | 167 | rapi_helpers.log(i18.t('errors.withCategory', 'hooks.emitHooks(1)', e)); 168 | } 169 | } 170 | 171 | return emitted; 172 | } 173 | -------------------------------------------------------------------------------- /src/api/outputs.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as rapi_contracts from '../contracts'; 27 | import * as rapi_helpers from '../helpers'; 28 | import * as rapi_host_users from '../host/users'; 29 | import * as vscode from 'vscode'; 30 | 31 | 32 | interface ChannelWithId { 33 | channel: vscode.OutputChannel; 34 | id?: number; 35 | } 36 | 37 | 38 | // [DELETE] /outputs/{id} 39 | export function DELETE(args: rapi_contracts.ApiMethodArguments): PromiseLike { 40 | let canDelete = args.request.user.can('delete'); 41 | 42 | return new Promise((resolve, reject) => { 43 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 44 | 45 | if (!canDelete) { 46 | args.sendForbidden(); 47 | completed(); 48 | 49 | return; 50 | } 51 | 52 | try { 53 | let channel = getChannelById(args); 54 | if (channel) { 55 | let outputChannels: vscode.OutputChannel[] = args.workspaceState['outputChannels']; 56 | if (outputChannels) { 57 | outputChannels.splice(channel.id, 1); 58 | } 59 | 60 | channel.channel.dispose(); 61 | } 62 | else { 63 | args.sendNotFound(); 64 | } 65 | 66 | completed(); 67 | } 68 | catch (e) { 69 | completed(e); 70 | } 71 | }); 72 | } 73 | 74 | // [GET] /outputs 75 | export function GET(args: rapi_contracts.ApiMethodArguments): PromiseLike { 76 | return new Promise((resolve, reject) => { 77 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 78 | 79 | try { 80 | let channels: Object[] = [ 81 | outputChannelToObject(args.outputChannel), 82 | ]; 83 | 84 | let outputChannels: vscode.OutputChannel[] = args.workspaceState['outputChannels']; 85 | if (outputChannels) { 86 | outputChannels.filter(x => x).forEach((x, i) => { 87 | channels.push(outputChannelToObject(x, i)); 88 | }); 89 | } 90 | 91 | args.response.data = channels; 92 | 93 | completed(); 94 | } 95 | catch (e) { 96 | completed(e); 97 | } 98 | }); 99 | } 100 | 101 | function getChannelById(args: rapi_contracts.ApiMethodArguments): ChannelWithId { 102 | let channel: ChannelWithId; 103 | 104 | let parts = args.path.split('/'); 105 | if (parts.length > 1) { 106 | let id = parts[1].trim(); 107 | if (rapi_helpers.isEmptyString(id)) { 108 | channel = { 109 | channel: args.outputChannel, 110 | }; 111 | } 112 | else { 113 | let idValue = parseInt(id); 114 | if (!isNaN(idValue)) { 115 | let outputChannels: vscode.OutputChannel[] = args.workspaceState['outputChannels']; 116 | if (!outputChannels) { 117 | outputChannels = []; 118 | } 119 | outputChannels = outputChannels.filter(x => x); 120 | 121 | if (idValue >= 0 && idValue < outputChannels.length) { 122 | channel = { 123 | channel: outputChannels[idValue], 124 | id: idValue, 125 | }; 126 | } 127 | } 128 | } 129 | } 130 | 131 | return channel; 132 | } 133 | 134 | function outputChannelToObject(channel: vscode.OutputChannel, id?: number): Object { 135 | if (!channel) { 136 | return; 137 | } 138 | 139 | let obj: Object = { 140 | name: rapi_helpers.toStringSafe(channel.name), 141 | }; 142 | 143 | if (!rapi_helpers.isEmptyString(id)) { 144 | obj['id'] = id; 145 | obj['path'] = '/api/outputs/' + id; 146 | } 147 | 148 | return obj; 149 | } 150 | 151 | // [PATCH] /outputs/{id} 152 | export function PATCH(args: rapi_contracts.ApiMethodArguments): PromiseLike { 153 | let canWrite = args.request.user.can('write'); 154 | 155 | return new Promise((resolve, reject) => { 156 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 157 | 158 | if (!canWrite) { 159 | args.sendForbidden(); 160 | completed(); 161 | 162 | return; 163 | } 164 | 165 | try { 166 | let channel = getChannelById(args); 167 | if (channel) { 168 | rapi_helpers.readHttpBody(args.request.request).then((body) => { 169 | try { 170 | channel.channel.clear(); 171 | 172 | let str = body.toString('utf8'); 173 | if (str) { 174 | channel.channel.append(str); 175 | } 176 | 177 | args.response.data = outputChannelToObject(channel.channel, channel.id); 178 | completed(); 179 | } 180 | catch (e) { 181 | completed(e); 182 | } 183 | }, (err) => { 184 | completed(err); 185 | }); 186 | } 187 | else { 188 | args.sendNotFound(); 189 | 190 | completed(); 191 | } 192 | } 193 | catch (e) { 194 | completed(e); 195 | } 196 | }); 197 | } 198 | 199 | // [POST] /outputs/{name} 200 | export function POST(args: rapi_contracts.ApiMethodArguments): PromiseLike { 201 | let canCreate = args.request.user.can('create'); 202 | 203 | return new Promise((resolve, reject) => { 204 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 205 | 206 | if (!canCreate) { 207 | args.sendForbidden(); 208 | completed(); 209 | 210 | return; 211 | } 212 | 213 | try { 214 | let channelName: string; 215 | { 216 | let urlPath = rapi_helpers.toStringSafe(args.request.url.pathname).trim(); 217 | 218 | let parts = urlPath.split('/'); 219 | if (parts.length > 3) { 220 | channelName = decodeURIComponent(parts[3]); 221 | } 222 | } 223 | channelName = rapi_helpers.toStringSafe(channelName).trim(); 224 | 225 | let newChannel = vscode.window.createOutputChannel(channelName); 226 | 227 | let outputChannels: vscode.OutputChannel[] = args.workspaceState['outputChannels']; 228 | if (!outputChannels) { 229 | args.workspaceState['outputChannels'] = []; 230 | } 231 | 232 | args.workspaceState['outputChannels'].push(newChannel); 233 | args.response.data = outputChannelToObject(newChannel, 234 | args.workspaceState['outputChannels'].length - 1); 235 | 236 | try { 237 | newChannel.show(); 238 | } 239 | catch (e) { 240 | args.response.code = 1; // create, but not shown 241 | } 242 | 243 | completed(); 244 | } 245 | catch (e) { 246 | completed(e); 247 | } 248 | }); 249 | } 250 | 251 | 252 | // [PUT] /outputs/{id} 253 | export function PUT(args: rapi_contracts.ApiMethodArguments): PromiseLike { 254 | let canWrite = args.request.user.can('write'); 255 | 256 | return new Promise((resolve, reject) => { 257 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 258 | 259 | if (!canWrite) { 260 | args.sendForbidden(); 261 | completed(); 262 | 263 | return; 264 | } 265 | 266 | try { 267 | let channel = getChannelById(args); 268 | if (channel) { 269 | rapi_helpers.readHttpBody(args.request.request).then((body) => { 270 | try { 271 | let str = body.toString('utf8'); 272 | if (str) { 273 | channel.channel.append(str); 274 | } 275 | 276 | args.response.data = outputChannelToObject(channel.channel, channel.id); 277 | completed(); 278 | } 279 | catch (e) { 280 | completed(e); 281 | } 282 | }, (err) => { 283 | completed(err); 284 | }); 285 | } 286 | else { 287 | args.sendNotFound(); 288 | 289 | completed(); 290 | } 291 | } 292 | catch (e) { 293 | completed(e); 294 | } 295 | }); 296 | } 297 | -------------------------------------------------------------------------------- /src/host/helpers.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as rapi_contracts from '../contracts'; 27 | import * as rapi_helpers from '../helpers'; 28 | import * as URL from 'url'; 29 | import * as ZLib from 'zlib'; 30 | 31 | 32 | /** 33 | * Describes a callback for the 'loadCSSAndJavascript()' function. 34 | * 35 | * @param {string} css The loaded CSS data (if found). 36 | * @param {string} js The loaded JavaScript data (if found). 37 | */ 38 | export type LoadCSSAndJavascriptCallback = (css: string, js: string) => void; 39 | 40 | /** 41 | * The result of 'compressForResponse()' function. 42 | */ 43 | export interface CompressForResponseResult { 44 | /** 45 | * The compressed data. 46 | */ 47 | compressed?: Buffer; 48 | /** 49 | * The suggested content encoding to use for the response. 50 | */ 51 | contentEncoding?: string; 52 | /** 53 | * The suggested data to send. 54 | */ 55 | dataToSend: Buffer; 56 | /** 57 | * The occurred error. 58 | */ 59 | error?: any; 60 | /** 61 | * The uncompressed data as buffer. 62 | */ 63 | uncompressed: Buffer; 64 | } 65 | 66 | 67 | /** 68 | * Tries to compress data for a reponse. 69 | * 70 | * @param {any} data The data to compress. 71 | * @param {rapi_contracts.RequestContext} ctx The underlying request context. 72 | * @param {string} encoding The custom text encoding to use, if 'data' is no buffer. 73 | * 74 | * @return {PromiseLike} The result. 75 | */ 76 | export function compressForResponse(data: any, 77 | ctx: rapi_contracts.RequestContext, 78 | encoding?: string): PromiseLike { 79 | encoding = rapi_helpers.normalizeString(encoding); 80 | if (!encoding) { 81 | encoding = 'utf8'; 82 | } 83 | 84 | return new Promise((resolve, reject) => { 85 | try { 86 | let uncompressed = rapi_helpers.asBuffer(data, encoding); 87 | if (!uncompressed) { 88 | uncompressed = Buffer.alloc(0); 89 | } 90 | 91 | let compressed: Buffer; 92 | let contentEncoding: string; 93 | let dataToSend = uncompressed; 94 | let completed = (err?: any) => { 95 | resolve({ 96 | compressed: compressed, 97 | contentEncoding: contentEncoding, 98 | dataToSend: dataToSend, 99 | error: err, 100 | uncompressed: uncompressed, 101 | }); 102 | }; 103 | 104 | let acceptEncodings = rapi_helpers.toStringSafe( 105 | rapi_helpers.getHeaderValue(ctx.request.headers, 'Accept-Encoding')) 106 | .toLowerCase() 107 | .split(',') 108 | .map(x => rapi_helpers.toStringSafe(x).toLowerCase().trim()) 109 | .filter(x => x); 110 | 111 | if (acceptEncodings.indexOf('gzip') > -1) { 112 | // gzip 113 | 114 | ZLib.gzip(uncompressed, (err, compressedData) => { 115 | if (!err) { 116 | if (compressedData.length < uncompressed.length) { 117 | contentEncoding = 'gzip'; 118 | 119 | compressed = compressedData; 120 | dataToSend = compressed; 121 | } 122 | } 123 | 124 | completed(err); 125 | }); 126 | } 127 | else if (acceptEncodings.indexOf('deflate') > -1) { 128 | // deflate 129 | 130 | ZLib.deflate(uncompressed, (err, compressedData) => { 131 | if (!err) { 132 | if (compressedData.length < uncompressed.length) { 133 | contentEncoding = 'deflate'; 134 | 135 | compressed = compressedData; 136 | dataToSend = compressed; 137 | } 138 | } 139 | 140 | completed(err); 141 | }); 142 | } 143 | else { 144 | // no encoding 145 | 146 | completed(); 147 | } 148 | } 149 | catch (e) { 150 | reject(e); 151 | } 152 | }); 153 | } 154 | 155 | /** 156 | * Sends an error response. 157 | * 158 | * @param {any} err The error to send. 159 | * @param {rapi_contracts.RequestContext} ctx The request context. 160 | * @param {number} code The custom status code to send. 161 | */ 162 | export function sendError(err: any, ctx: rapi_contracts.RequestContext, code = 500) { 163 | try { 164 | ctx.response.statusCode = code; 165 | ctx.response.statusMessage = rapi_helpers.toStringSafe(err); 166 | 167 | ctx.response.end(); 168 | } 169 | catch (e) { 170 | this.controller.log(`[ERROR] host.helpers.sendError(): ${rapi_helpers.toStringSafe(e)}`); 171 | } 172 | } 173 | 174 | /** 175 | * Sends a "forbidden" response. 176 | * 177 | * @param {rapi_contracts.RequestContext} ctx The request context. 178 | * @param {number} code The custom status code to send. 179 | */ 180 | export function sendForbidden(ctx: rapi_contracts.RequestContext, code = 403) { 181 | try { 182 | ctx.response.statusCode = code; 183 | 184 | ctx.response.end(); 185 | } 186 | catch (e) { 187 | this.controller.log(`[ERROR] host.helpers.sendForbidden(): ${rapi_helpers.toStringSafe(e)}`); 188 | } 189 | } 190 | 191 | /** 192 | * Sends a "method not allowed" response. 193 | * 194 | * @param {rapi_contracts.RequestContext} ctx The request context. 195 | * @param {number} code The custom status code to send. 196 | */ 197 | export function sendMethodNotAllowed(ctx: rapi_contracts.RequestContext, code = 405) { 198 | try { 199 | ctx.response.statusCode = code; 200 | 201 | ctx.response.end(); 202 | } 203 | catch (e) { 204 | this.controller.log(`[ERROR] host.helpers.sendMethodNotAllowed(): ${rapi_helpers.toStringSafe(e)}`); 205 | } 206 | } 207 | 208 | /** 209 | * Sends a "not found" response. 210 | * 211 | * @param {rapi_contracts.RequestContext} ctx The request context. 212 | * @param {number} code The custom status code to send. 213 | */ 214 | export function sendNotFound(ctx: rapi_contracts.RequestContext, code = 404) { 215 | try { 216 | ctx.response.statusCode = code; 217 | 218 | ctx.response.end(); 219 | } 220 | catch (e) { 221 | this.controller.log(`[ERROR] host.helpers.sendNotFound(): ${rapi_helpers.toStringSafe(e)}`); 222 | } 223 | } 224 | 225 | /** 226 | * Sends a "not implemented" response. 227 | * 228 | * @param {rapi_contracts.RequestContext} ctx The request context. 229 | * @param {number} code The custom status code to send. 230 | */ 231 | export function sendNotImplemented(ctx: rapi_contracts.RequestContext, code = 501) { 232 | try { 233 | ctx.response.statusCode = code; 234 | 235 | ctx.response.end(); 236 | } 237 | catch (e) { 238 | this.controller.log(`[ERROR] host.helpers.sendNotImplemented(): ${rapi_helpers.toStringSafe(e)}`); 239 | } 240 | } 241 | 242 | /** 243 | * Sends an "unauthorized" response. 244 | * 245 | * @param {rapi_contracts.RequestContext} ctx The request context. 246 | * @param {number} code The custom status code to send. 247 | */ 248 | export function sendUnauthorized(ctx: rapi_contracts.RequestContext, code = 401) { 249 | try { 250 | let realm = rapi_helpers.toStringSafe(ctx.config.realm); 251 | if (rapi_helpers.isEmptyString(realm)) { 252 | realm = 'REST API for Visual Studio Code (vs-rest-api)'; 253 | } 254 | 255 | let headers: any = { 256 | 'WWW-Authenticate': `Basic realm="${realm}"`, 257 | }; 258 | 259 | ctx.response.writeHead(code, headers); 260 | 261 | ctx.response.end(); 262 | } 263 | catch (e) { 264 | this.controller.log(`[ERROR] host.helpers.sendUnauthorized(): ${rapi_helpers.toStringSafe(e)}`); 265 | } 266 | } 267 | 268 | /** 269 | * Extracts the query parameters of an URL to an object. 270 | * 271 | * @param {URL.Url} url The URL. 272 | * 273 | * @return {Object} The parameters of the URL as object. 274 | */ 275 | export function urlParamsToObject(url: URL.Url): Object { 276 | if (!url) { 277 | return url; 278 | } 279 | 280 | let params: any; 281 | if (!rapi_helpers.isEmptyString(url.query)) { 282 | // s. https://css-tricks.com/snippets/jquery/get-query-params-object/ 283 | params = url.query.replace(/(^\?)/,'') 284 | .split("&") 285 | .map(function(n) { return n = n.split("="), this[rapi_helpers.normalizeString(n[0])] = 286 | rapi_helpers.toStringSafe(decodeURIComponent(n[1])), this} 287 | .bind({}))[0]; 288 | } 289 | 290 | if (!params) { 291 | params = {}; 292 | } 293 | 294 | return params; 295 | } 296 | -------------------------------------------------------------------------------- /src/api/editors.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | 25 | import * as rapi_contracts from '../contracts'; 26 | import * as rapi_helpers from '../helpers'; 27 | import * as rapi_host_users from '../host/users'; 28 | import * as vscode from 'vscode'; 29 | 30 | 31 | interface EditorWithId { 32 | editor: vscode.TextEditor; 33 | id?: number; 34 | } 35 | 36 | // [DELETE] /editors(/{id}) 37 | export function DELETE(args: rapi_contracts.ApiMethodArguments): PromiseLike { 38 | let canClose = args.request.user.can('close'); 39 | 40 | return new Promise((resolve, reject) => { 41 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 42 | 43 | if (!canClose) { 44 | args.sendForbidden(); 45 | completed(); 46 | return; 47 | } 48 | 49 | try { 50 | let editor: any = getEditorById(args); 51 | 52 | if (editor) { 53 | // DEPRECATED 54 | editor.editor.hide(); 55 | } 56 | else { 57 | // no (matching) tab found 58 | args.sendNotFound(); 59 | } 60 | 61 | completed(); 62 | } 63 | catch (e) { 64 | completed(e); 65 | } 66 | }); 67 | } 68 | 69 | function editorToObject(editor: EditorWithId, user: rapi_contracts.User): PromiseLike { 70 | return new Promise((resolve, reject) => { 71 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 72 | 73 | try { 74 | if (!editor) { 75 | completed(); 76 | return; 77 | } 78 | 79 | rapi_helpers.textDocumentToObject(editor.editor.document, user).then((obj) => { 80 | if (obj) { 81 | delete obj['openPath']; 82 | 83 | if (!rapi_helpers.isNullOrUndefined(editor.id)) { 84 | obj['id'] = editor.id; 85 | obj['path'] = '/api/editors/' + editor.id; 86 | } 87 | } 88 | 89 | completed(null, obj); 90 | }, (err) => { 91 | completed(err); 92 | }); 93 | } 94 | catch (e) { 95 | completed(e); 96 | } 97 | }); 98 | } 99 | 100 | // [GET] /editors 101 | export function GET(args: rapi_contracts.ApiMethodArguments): PromiseLike { 102 | return new Promise((resolve, reject) => { 103 | let docs = []; 104 | let completed = (err?: any) => { 105 | if (err) { 106 | reject(err); 107 | } 108 | else { 109 | args.response.data = docs; 110 | 111 | resolve(); 112 | } 113 | }; 114 | 115 | try { 116 | let visibleEditors = vscode.window.visibleTextEditors.filter(x => x); 117 | 118 | let id = -1; 119 | let nextEditor: () => void; 120 | nextEditor = () => { 121 | if (visibleEditors.length < 1) { 122 | completed(); 123 | return; 124 | } 125 | 126 | let editor: EditorWithId = { 127 | editor: visibleEditors.shift(), 128 | id: ++id, 129 | }; 130 | 131 | editorToObject(editor, args.request.user).then((obj) => { 132 | if (obj) { 133 | obj['id'] = id; 134 | obj['path'] = '/api/editors/' + id; 135 | 136 | docs.push(obj); 137 | } 138 | 139 | nextEditor(); 140 | }, (err) => { 141 | completed(err); 142 | }); 143 | }; 144 | 145 | nextEditor(); 146 | } 147 | catch (e) { 148 | completed(e); 149 | } 150 | }); 151 | } 152 | 153 | function getEditorById(args: rapi_contracts.ApiMethodArguments): EditorWithId { 154 | let editor: EditorWithId; 155 | 156 | let parts = args.path.split('/'); 157 | if (parts.length > 1) { 158 | let id = parts[1]; 159 | if (rapi_helpers.isEmptyString(id)) { 160 | editor = { 161 | editor: vscode.window.activeTextEditor, 162 | }; 163 | } 164 | else { 165 | let idValue = parseInt(id.trim()); 166 | if (!isNaN(idValue)) { 167 | let visibleEditors = vscode.window.visibleTextEditors.filter(x => x); 168 | if (idValue >= 0 && idValue < visibleEditors.length) { 169 | editor = { 170 | editor: visibleEditors[idValue], 171 | id: idValue, 172 | }; 173 | } 174 | } 175 | } 176 | } 177 | else { 178 | editor = { 179 | editor: vscode.window.activeTextEditor, 180 | }; 181 | } 182 | 183 | return editor; 184 | } 185 | 186 | // [PATCH] /editors(/{id}) 187 | export function PATCH(args: rapi_contracts.ApiMethodArguments): PromiseLike { 188 | let canWrite = args.request.user.can('write'); 189 | 190 | return new Promise((resolve, reject) => { 191 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 192 | 193 | if (!canWrite) { 194 | args.sendForbidden(); 195 | completed(); 196 | return; 197 | } 198 | 199 | try { 200 | let editor = getEditorById(args); 201 | 202 | if (editor) { 203 | args.getBody().then((body) => { 204 | try { 205 | let str = (body || Buffer.alloc(0)).toString('utf8'); 206 | 207 | rapi_helpers.setContentOfTextEditor(editor.editor, str).then((doc) => { 208 | editorToObject(editor, args.request.user).then((obj) => { 209 | args.response.data = obj; 210 | 211 | completed(); 212 | }, (err) => { 213 | completed(err); 214 | }); 215 | }, (err) => { 216 | completed(err); 217 | }); 218 | } 219 | catch (e) { 220 | completed(e); 221 | } 222 | }, (err) => { 223 | completed(err); 224 | }); 225 | } 226 | else { 227 | args.sendNotFound(); 228 | 229 | completed(); 230 | } 231 | } 232 | catch (e) { 233 | completed(e); 234 | } 235 | }); 236 | } 237 | 238 | // [POST] /editors(/{id}) 239 | export function POST(args: rapi_contracts.ApiMethodArguments): PromiseLike { 240 | let canOpen = args.request.user.can('open'); 241 | 242 | return new Promise((resolve, reject) => { 243 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 244 | 245 | if (!canOpen) { 246 | args.sendForbidden(); 247 | completed(); 248 | return; 249 | } 250 | 251 | try { 252 | let editor = getEditorById(args); 253 | if (editor) { 254 | editor.editor.show(); 255 | 256 | editorToObject(editor, args.request.user).then((obj) => { 257 | args.response.data = obj; 258 | 259 | completed(); 260 | }, (err) => { 261 | completed(err); 262 | }); 263 | } 264 | else { 265 | args.sendNotFound(); 266 | completed(); 267 | } 268 | } 269 | catch (e) { 270 | completed(e); 271 | } 272 | }); 273 | } 274 | 275 | // [PUT] /editors(/{id}) 276 | export function PUT(args: rapi_contracts.ApiMethodArguments): PromiseLike { 277 | let canWrite = args.request.user.can('write'); 278 | 279 | return new Promise((resolve, reject) => { 280 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 281 | 282 | if (!canWrite) { 283 | args.sendForbidden(); 284 | completed(); 285 | return; 286 | } 287 | 288 | try { 289 | let editor = getEditorById(args); 290 | let doc: vscode.TextDocument; 291 | 292 | if (editor) { 293 | doc = editor.editor.document; 294 | } 295 | 296 | if (doc) { 297 | doc.save(); 298 | 299 | editorToObject(editor, args.request.user).then((obj) => { 300 | args.response.data = obj; 301 | 302 | completed(); 303 | }, (err) => { 304 | completed(err); 305 | }); 306 | } 307 | else { 308 | args.sendNotFound(); 309 | 310 | completed(); 311 | } 312 | } 313 | catch (e) { 314 | completed(e); 315 | } 316 | }); 317 | } 318 | -------------------------------------------------------------------------------- /src/api/editor.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as FS from 'fs'; 27 | import * as Path from 'path'; 28 | import * as rapi_contracts from '../contracts'; 29 | import * as rapi_helpers from '../helpers'; 30 | import * as rapi_host_users from '../host/users'; 31 | import * as rapi_workspace from '../workspace'; 32 | import * as vscode from 'vscode'; 33 | 34 | 35 | // [DELETE] /editor 36 | export function DELETE(args: rapi_contracts.ApiMethodArguments): PromiseLike { 37 | let canClose = args.request.user.can('close'); 38 | 39 | return new Promise((resolve, reject) => { 40 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 41 | 42 | if (!canClose) { 43 | args.sendForbidden(); 44 | completed(); 45 | return; 46 | } 47 | 48 | try { 49 | let editor = vscode.window.activeTextEditor; 50 | if (editor) { 51 | vscode.commands.executeCommand('workbench.action.closeActiveEditor').then(() => { 52 | completed(); 53 | }, (err) => { 54 | completed(err); 55 | }); 56 | } 57 | else { 58 | // no (matching) tab found 59 | args.sendNotFound(); 60 | 61 | completed(); 62 | } 63 | } 64 | catch (e) { 65 | completed(e); 66 | } 67 | }); 68 | } 69 | 70 | // [GET] /editor 71 | export function GET(args: rapi_contracts.ApiMethodArguments): PromiseLike { 72 | return new Promise((resolve, reject) => { 73 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 74 | 75 | try { 76 | let doc: vscode.TextDocument; 77 | 78 | let editor = vscode.window.activeTextEditor; 79 | if (editor) { 80 | doc = editor.document; 81 | } 82 | 83 | rapi_helpers.textDocumentToObject(doc, args.request.user).then((obj) => { 84 | if (obj) { 85 | args.response.data = obj; 86 | } 87 | else { 88 | args.sendNotFound(); 89 | } 90 | 91 | completed(); 92 | }, (err) => { 93 | completed(err); 94 | }); 95 | } 96 | catch (e) { 97 | completed(e); 98 | } 99 | }); 100 | } 101 | 102 | // [PATCH] /editor 103 | export function PATCH(args: rapi_contracts.ApiMethodArguments): PromiseLike { 104 | let canWrite = args.request.user.can('write'); 105 | 106 | return new Promise((resolve, reject) => { 107 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 108 | 109 | if (!canWrite) { 110 | args.sendForbidden(); 111 | completed(); 112 | return; 113 | } 114 | 115 | try { 116 | let editor = vscode.window.activeTextEditor; 117 | if (editor) { 118 | args.getBody().then((body) => { 119 | try { 120 | let str = (body || Buffer.alloc(0)).toString('utf8'); 121 | 122 | rapi_helpers.setContentOfTextEditor(editor, str).then((doc) => { 123 | rapi_helpers.textDocumentToObject(editor.document, args.request.user).then((obj) => { 124 | args.response.data = obj; 125 | 126 | completed(); 127 | }, (err) => { 128 | completed(err); 129 | }); 130 | }, (err) => { 131 | completed(err); 132 | }); 133 | } 134 | catch (e) { 135 | completed(e); 136 | } 137 | }, (err) => { 138 | completed(err); 139 | }); 140 | } 141 | else { 142 | args.sendNotFound(); 143 | 144 | completed(); 145 | } 146 | } 147 | catch (e) { 148 | completed(e); 149 | } 150 | }); 151 | } 152 | 153 | // [POST] /editor[/{file}] 154 | export function POST(args: rapi_contracts.ApiMethodArguments): PromiseLike { 155 | let canOpen = args.request.user.can('open'); 156 | 157 | return new Promise((resolve, reject) => { 158 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 159 | 160 | let notFound = () => { 161 | args.sendNotFound(); 162 | completed(); 163 | }; 164 | 165 | if (!canOpen) { 166 | args.sendForbidden(); 167 | completed(); 168 | return; 169 | } 170 | 171 | try { 172 | let path = args.path; 173 | let firstSep = path.indexOf('/'); 174 | 175 | let fileToOpen: string; 176 | if (firstSep > -1) { 177 | fileToOpen = path.substring(firstSep + 1); 178 | } 179 | 180 | if (rapi_helpers.isEmptyString(fileToOpen)) { 181 | fileToOpen = null; 182 | } 183 | 184 | let openFile = () => { 185 | vscode.workspace.openTextDocument(fileToOpen).then((doc) => { 186 | let returnDoc = () => { 187 | completed(); 188 | }; 189 | 190 | vscode.window.showTextDocument(doc).then(() => { 191 | rapi_helpers.textDocumentToObject(doc, args.request.user).then((obj) => { 192 | args.response.data = obj; 193 | 194 | returnDoc(); 195 | }, (err) => { 196 | completed(err); 197 | }); 198 | }, (err) => { 199 | // opened, but not shown 200 | 201 | args.response.code = 1; 202 | 203 | returnDoc(); 204 | }); 205 | }, (err) => { 206 | completed(err); 207 | }); 208 | }; 209 | 210 | if (fileToOpen) { 211 | let fullPath = Path.join(rapi_workspace.getRootPath(), fileToOpen); 212 | 213 | let relativePath = rapi_helpers.toRelativePath(fullPath); 214 | if (false === relativePath) { 215 | // cannot open files outside workspace 216 | notFound(); 217 | } 218 | else { 219 | FS.stat(fullPath, (err, stats) => { 220 | if (err) { 221 | completed(err); 222 | } 223 | else { 224 | if (stats.isFile()) { 225 | args.request.user.isFileVisible(fullPath, args.request.user.get(rapi_host_users.VAR_WITH_DOT)).then((isVisible) => { 226 | if (isVisible) { 227 | fileToOpen = fullPath; 228 | 229 | openFile(); 230 | } 231 | else { 232 | notFound(); // not visible 233 | } 234 | }, (err) => { 235 | completed(err); 236 | }); 237 | } 238 | else { 239 | notFound(); // we can only open files 240 | } 241 | } 242 | }); 243 | } 244 | } 245 | else { 246 | openFile(); // open untiled tab 247 | } 248 | } 249 | catch (e) { 250 | completed(e); 251 | } 252 | }); 253 | } 254 | 255 | // [PUT] /editor 256 | export function PUT(args: rapi_contracts.ApiMethodArguments): PromiseLike { 257 | let canWrite = args.request.user.can('write'); 258 | 259 | return new Promise((resolve, reject) => { 260 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 261 | 262 | if (!canWrite) { 263 | args.sendForbidden(); 264 | completed(); 265 | return; 266 | } 267 | 268 | try { 269 | let editor = vscode.window.activeTextEditor; 270 | let doc: vscode.TextDocument; 271 | 272 | if (editor) { 273 | doc = editor.document; 274 | } 275 | 276 | if (doc) { 277 | doc.save(); 278 | 279 | rapi_helpers.textDocumentToObject(doc, args.request.user).then((obj) => { 280 | args.response.data = obj; 281 | 282 | completed(); 283 | }, (err) => { 284 | completed(err); 285 | }); 286 | } 287 | else { 288 | args.sendNotFound(); 289 | 290 | completed(); 291 | } 292 | } 293 | catch (e) { 294 | completed(e); 295 | } 296 | }); 297 | } 298 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vs-rest-api 2 | 3 | [![Latest Release](https://vsmarketplacebadge.apphb.com/version-short/mkloubert.vs-rest-api.svg)](https://marketplace.visualstudio.com/items?itemName=mkloubert.vs-rest-api) 4 | [![Installs](https://vsmarketplacebadge.apphb.com/installs/mkloubert.vs-rest-api.svg)](https://marketplace.visualstudio.com/items?itemName=mkloubert.vs-rest-api) 5 | [![Rating](https://vsmarketplacebadge.apphb.com/rating-short/mkloubert.vs-rest-api.svg)](https://marketplace.visualstudio.com/items?itemName=mkloubert.vs-rest-api#review-details) 6 | 7 | A [Visual Studio Code](https://code.visualstudio.com/) (VS Code) extension that provides a REST API to control your editor. 8 | 9 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://paypal.me/MarcelKloubert) 10 | 11 | ## Table of contents 12 | 13 | 1. [Install](#install-) 14 | 2. [How to use](#how-to-use-) 15 | * [Settings](#settings-) 16 | * [Users](#users-) 17 | * [HTTPs](#https-) 18 | * [Build-in endpoints](#build-in-endpoints-) 19 | * [Custom endpoints](#custom-endpoints-) 20 | * [Commands](#commands--1) 21 | 3. [Documentation](#documentation-) 22 | 23 | ## Install [[↑](#table-of-contents)] 24 | 25 | Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter: 26 | 27 | ```bash 28 | ext install vs-rest-api 29 | ``` 30 | 31 | Or search for things like `vs-rest-api` in your editor: 32 | 33 | ![Demo Search and install extension](https://raw.githubusercontent.com/mkloubert/vs-rest-api/master/demos/screenshot1.png) 34 | 35 | ## How to use [[↑](#table-of-contents)] 36 | 37 | ### Settings [[↑](#how-to-use-)] 38 | 39 | Open (or create) your `settings.json` in your `.vscode` subfolder of your workspace. 40 | 41 | Add a `deploy` section: 42 | 43 | ```json 44 | { 45 | "rest.api": { 46 | "autoStart": true, 47 | "openInBrowser": true, 48 | "port": 1781 49 | } 50 | } 51 | ``` 52 | 53 | This example will run the host on port `1781` on startup and opens the URL `https://localhost:1781/` in your default application, like your browser. 54 | 55 | #### Users [[↑](#settings-)] 56 | 57 | By default anyone can access the API with read-only access. 58 | 59 | You can define one or more users, that can access the API via [Basic Authentification](https://en.wikipedia.org/wiki/Basic_access_authentication): 60 | 61 | ```json 62 | { 63 | "rest.api": { 64 | // ... 65 | 66 | "guest": false, 67 | "users": [ 68 | { 69 | "name": "mkloubert", 70 | "password": "P@sswort123!" 71 | }, 72 | { 73 | "name": "jlpicard", 74 | "password": "NCC-1701-D" 75 | }, 76 | { 77 | "name": "neo", 78 | "password": "Follow_the_white_rabbit" 79 | } 80 | ] 81 | } 82 | } 83 | ``` 84 | 85 | By default any user (and guest) have read-only access. 86 | 87 | #### HTTPs [[↑](#settings-)] 88 | 89 | For secure access, you can define a SSL certificate: 90 | 91 | ```json 92 | { 93 | "rest.api": { 94 | // ... 95 | 96 | "ssl": { 97 | "cert": "./api-host.crt", 98 | "key": "./api-host.key" 99 | } 100 | } 101 | } 102 | ``` 103 | 104 | ### Build-in endpoints [[↑](#how-to-use-)] 105 | 106 | Visit the [wiki](https://github.com/mkloubert/vs-rest-api/wiki#build-in-endpoints-) to get more information about build-in endpoints. 107 | 108 | | Name | Description | 109 | | ---- | --------- | 110 | | [/api/appglobals](https://github.com/mkloubert/vs-rest-api/wiki#apiappglobals-) | Accesses permanent data for all users outside the current workspace. | 111 | | [/api/appstate](https://github.com/mkloubert/vs-rest-api/wiki#apiappstate-) | Accesses permanent data for the current user / guest outside the current workspace. | 112 | | [/api/commands](https://github.com/mkloubert/vs-rest-api/wiki#apicommands-) | Accesses commands. | 113 | | [/api/cron](https://github.com/mkloubert/vs-rest-api/wiki#apicron-) | Accesses cron jobs. | 114 | | [/api/deploy](https://github.com/mkloubert/vs-rest-api/wiki#apideploy-) | Accesses features to deploy files. | 115 | | [/api/editor](https://github.com/mkloubert/vs-rest-api/wiki#apieditor-) | Accesses resources of the active editor (tab). | 116 | | [/api/editors](https://github.com/mkloubert/vs-rest-api/wiki#apieditors-) | Accesses resources of all opened editors. | 117 | | [/api/extensions](https://github.com/mkloubert/vs-rest-api/wiki#apiextensions-) | Accesses resources of all known extensions. | 118 | | [/api/files](https://github.com/mkloubert/vs-rest-api/wiki#apifiles-) | Accesses resources for handling file operations. | 119 | | [/api/globals](https://github.com/mkloubert/vs-rest-api/wiki#apiglobals-) | Accesses permanent data for all users. | 120 | | [/api/html](https://github.com/mkloubert/vs-rest-api/wiki#apihtml-) | Accesses resources for handling HTML documents. | 121 | | [/api/languages](https://github.com/mkloubert/vs-rest-api/wiki#apilanguages-) | Accesses resources of all known languages. | 122 | | [/api/outputs](https://github.com/mkloubert/vs-rest-api/wiki#apioutputs-) | Accesses resources of output channels handled by the extension. | 123 | | [/api/popups](https://github.com/mkloubert/vs-rest-api/wiki#apipopups-) | Accesses resources for handling popup messages. | 124 | | [/api/state](https://github.com/mkloubert/vs-rest-api/wiki#apistate-) | Accesses permanent data for the current user / guest. | 125 | | [/api/whiteboard](https://github.com/mkloubert/vs-rest-api/wiki#apiwhiteboard-) | Accesses resources for handling a virtual whiteboard. | 126 | | [/api/workspace](https://github.com/mkloubert/vs-rest-api/wiki#apiworkspace-) | Accesses or manipulates resources, like files or folders, inside the current workspace. | 127 | 128 | ### Custom endpoints [[↑](#how-to-use-)] 129 | 130 | Detailed information can be found at the [wiki](https://github.com/mkloubert/vs-rest-api/wiki#custom-endpoints-). Otherwise... 131 | 132 | You can define custom endpoints that are executed via script. 133 | 134 | Define one ore more [regular expressions](https://en.wikipedia.org/wiki/Regular_expression) in your [settings](#settings-) and the scripts that should be executed, if a pattern matches: 135 | 136 | ```json 137 | { 138 | "rest.api": { 139 | // ... 140 | 141 | "endpoints": { 142 | "myendpoint": { 143 | "script": "./my-endpoint.js", 144 | "options": "Hello!" 145 | } 146 | } 147 | } 148 | } 149 | ``` 150 | 151 | The `./my-endpoint.js` must contain a public function with the name of the current HTTP request method (upper case). 152 | 153 | For example if you want to make a simple `GET` request 154 | 155 | ```http 156 | GET /api/myendpoint 157 | ``` 158 | 159 | your script should look like this: 160 | 161 | ```javascript 162 | exports.GET = function(args) { 163 | // access VS Code API (s. https://code.visualstudio.com/Docs/extensionAPI/vscode-api) 164 | var vscode = require('vscode'); 165 | 166 | // access Node.js API provided by VS Code 167 | // s. (s. https://nodejs.org/api/) 168 | var fs = require('fs'); 169 | 170 | // access an own module 171 | var myModule = require('./my-module.js'); 172 | 173 | // access a module used by the extension: 174 | // s. https://mkloubert.github.io/vs-rest-api/modules/_helpers_.html 175 | var helpers = args.require('./helpers'); 176 | // s. https://mkloubert.github.io/vs-rest-api/modules/_host_helpers_.html 177 | var hostHelpers = args.require('./host/helpers'); 178 | 179 | // access a module that is part of the extentsion 180 | // s. https://github.com/mkloubert/vs-rest-api/blob/master/package.json 181 | var glob = args.require('glob'); 182 | 183 | // access the data from the settings 184 | // from the example above this is: "Hello!" 185 | var opts = args.options; 186 | 187 | // share / store data (while current session)... 188 | // ... for this script 189 | var myState = args.state; 190 | args.state = new Date(); 191 | // ... with other scripts of this type 192 | args.globalState['myEndpoint'] = new Date(); 193 | // ... with the whole workspace 194 | args.workspaceState['myEndpoint'] = new Date(); 195 | 196 | // if you want to return an AJAX response object: 197 | // s. https://mkloubert.github.io/vs-rest-api/interfaces/_contracts_.apiresponse.html 198 | { 199 | args.response.code = 666; // the response code (not the HTTP response code!) 200 | args.response.msg = 'Result of the evil!'; // a custom message for more information 201 | args.response.data = { 202 | 'mk': 23979, 203 | 'TM': '5979' 204 | }; 205 | } 206 | 207 | // if you want to return custom content 208 | // instead of the object in 'args.response' 209 | // s. https://mkloubert.github.io/vs-rest-api/interfaces/_contracts_.apimethodarguments.html#setcontent 210 | { 211 | var html = fs.readFileSync('/path/to/my/file.html'); 212 | 213 | // open HTML document in new tab (for reports e.g.) 214 | args.openHtml(html.toString('utf8'), 'My HTML document from "file.html"').then(function() { 215 | // HTML opened 216 | }, function(err) { 217 | // opening HTML document failed 218 | }); 219 | 220 | args.setContent(html, 'text/html'); 221 | } 222 | 223 | // deploys 'index.html' to 'My SFTP server' 224 | // s. https://github.com/mkloubert/vs-deploy 225 | args.deploy(['./index.html'], ['My SFTP server']).then(function() { 226 | // file deployed 227 | }, function(err) { 228 | // deployment failed 229 | }); 230 | 231 | // custom HTTP status code 232 | args.statusCode = 202; 233 | 234 | // ... 235 | } 236 | ``` 237 | 238 | The `args` parameter of the function uses the [ApiMethodArguments](https://mkloubert.github.io/vs-rest-api/interfaces/_contracts_.apimethodarguments.html) interface. 239 | 240 | You can return a [Promise](https://github.com/Microsoft/vscode-extension-vscode/blob/master/thenable.d.ts) for async executions or nothing for sync executions (as in this example). 241 | 242 | You are also able to define functions for other request methods, like `POST` or `DELETE`, which are supported by [http](https://nodejs.org/api/http.html) / [https](https://nodejs.org/api/https.html) modules of [Node.js](https://nodejs.org/api/): 243 | 244 | ```javascript 245 | // [DELETE] /api/myendpoint 246 | exports.DELETE = function(args) { 247 | return new Promise(function(resolve, reject) { 248 | // for async executions 249 | 250 | try { 251 | // ... 252 | 253 | resolve(); // MUST be called at the end 254 | // on SUCCESS 255 | } 256 | catch (e) { 257 | reject(e); // MUST be called at the end 258 | // on ERROR 259 | } 260 | }); 261 | } 262 | 263 | // [POST] /api/myendpoint 264 | exports.POST = function(args) { 265 | // no (promise) result means: sync execution 266 | } 267 | ``` 268 | 269 | HINT: Custom endpoints will always overwrite build-in ones! 270 | 271 | ### Commands [[↑](#how-to-use-)] 272 | 273 | Press `F1` to open the list of commands and select one of the following commands: 274 | 275 | ![Demo How to execute](https://raw.githubusercontent.com/mkloubert/vs-rest-api/master/demos/demo2.gif) 276 | 277 | | Name | Description | ID | 278 | | ---- | --------- | --------- | 279 | | `REST API: Starts or stops the api server` | Toggles the state of the API's HTTP server. | `extension.restApi.toggleHostState` | 280 | | `REST API: (Re)start the api server` | (Re-)Starts the API's HTTP server. | `extension.restApi.startHost` | 281 | | `REST API: Stop the api server` | Stops the API. | `extension.restApi.stopHost` | 282 | 283 | ## Documentation [[↑](#table-of-contents)] 284 | 285 | The full documentation of the extension's API can be found [here](https://mkloubert.github.io/vs-rest-api/). 286 | 287 | Detailed information on how to use the extension, can be found at the [wiki](https://github.com/mkloubert/vs-rest-api/wiki). 288 | -------------------------------------------------------------------------------- /src/api/whiteboard.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as rapi_contracts from '../contracts'; 27 | import * as rapi_helpers from '../helpers'; 28 | 29 | 30 | /** 31 | * Name of the HTTP response header for a revision number. 32 | */ 33 | export const HTTP_HEADER_REVISION = 'X-Vscode-Restapi-Revision'; 34 | /** 35 | * Name of the HTTP response header for whiteboard title. 36 | */ 37 | export const HTTP_HEADER_TITLE = 'X-Vscode-Restapi-Title'; 38 | 39 | /** 40 | * A new whiteboard (revision), 41 | */ 42 | export interface NewWhiteboardRevision { 43 | /** 44 | * The Base64 content. 45 | */ 46 | content: string; 47 | /** 48 | * The encoding. 49 | */ 50 | encoding?: string; 51 | /** 52 | * The mime type. 53 | */ 54 | mime?: string; 55 | /** 56 | * The title. 57 | */ 58 | title?: string; 59 | } 60 | 61 | // [DELETE] /api/whiteboard 62 | export function DELETE(args: rapi_contracts.ApiMethodArguments): PromiseLike { 63 | let canDelete = args.request.user.can('delete'); 64 | let whiteboard = args.whiteboard; 65 | 66 | return new Promise((resolve, reject) => { 67 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 68 | 69 | if (!whiteboard) { 70 | args.sendNotFound(); 71 | completed(); 72 | 73 | return; 74 | } 75 | 76 | if (!canDelete) { 77 | args.sendForbidden(); 78 | completed(); 79 | 80 | return; 81 | } 82 | 83 | whiteboard.setBoard(null).then(() => { 84 | completed(); 85 | }, (err) => { 86 | completed(err); 87 | }); 88 | }); 89 | } 90 | 91 | // [GET] /api/whiteboard(/{revisiion}) 92 | export function GET(args: rapi_contracts.ApiMethodArguments): PromiseLike { 93 | let whiteboard = args.whiteboard; 94 | 95 | return new Promise((resolve, reject) => { 96 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 97 | 98 | let notFound = () => { 99 | args.sendNotFound(); 100 | completed(); 101 | }; 102 | 103 | if (!whiteboard) { 104 | notFound(); 105 | return; 106 | } 107 | 108 | let nr: number; 109 | if (args.endpoint.arguments.length > 0) { 110 | nr = parseInt(args.endpoint.arguments[0].trim()); 111 | } 112 | 113 | whiteboard.get(nr).then((revision) => { 114 | try { 115 | if (revision) { 116 | let buffer: Buffer; 117 | let mime: string; 118 | let title: string; 119 | if (revision.board) { 120 | if (revision.board.body) { 121 | buffer = revision.board.body; 122 | } 123 | 124 | if (!rapi_helpers.isEmptyString(revision.board.mime)) { 125 | mime = rapi_helpers.toStringSafe(revision.board.mime); 126 | 127 | if (!rapi_helpers.isEmptyString(revision.board.encoding)) { 128 | mime += '; charset=' + rapi_helpers.normalizeString(revision.board.encoding); 129 | } 130 | } 131 | 132 | if (!rapi_helpers.isEmptyString(revision.board.title)) { 133 | title = rapi_helpers.toStringSafe(revision.board.title); 134 | } 135 | } 136 | 137 | if (!buffer) { 138 | buffer = Buffer.alloc(0); 139 | } 140 | 141 | args.headers[HTTP_HEADER_REVISION] = revision.nr; 142 | 143 | if (title) { 144 | args.headers[HTTP_HEADER_TITLE] = title; 145 | } 146 | 147 | args.setContent(buffer, mime); 148 | completed(); 149 | } 150 | else { 151 | notFound(); 152 | } 153 | } 154 | catch (e) { 155 | completed(e); 156 | } 157 | }, (err) => { 158 | completed(err); 159 | }); 160 | }); 161 | } 162 | 163 | function handleSubmittedRevision(args: rapi_contracts.ApiMethodArguments, 164 | repo: rapi_contracts.WhiteboardRepository, 165 | func: (board: rapi_contracts.Whiteboard) => PromiseLike): PromiseLike { 166 | return new Promise((resolve, reject) => { 167 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 168 | 169 | args.getJSON().then((submittedRevision) => { 170 | try { 171 | if (submittedRevision) { 172 | if ('object' !== typeof submittedRevision) { 173 | submittedRevision = { 174 | content: (new Buffer(rapi_helpers.toStringSafe(submittedRevision))).toString('base64'), 175 | encoding: 'utf-8', 176 | }; 177 | 178 | for (let h in args.request.request.headers) { 179 | if ('content-type' === rapi_helpers.normalizeString(h)) { 180 | submittedRevision.mime = rapi_helpers.normalizeString(args.request.request.headers[h]); 181 | } 182 | } 183 | 184 | if (!submittedRevision.mime) { 185 | submittedRevision.mime = 'text/plain'; 186 | } 187 | } 188 | } 189 | else { 190 | submittedRevision = { 191 | content: undefined, 192 | }; 193 | } 194 | 195 | let newBoard: rapi_contracts.Whiteboard = { 196 | body: undefined, 197 | }; 198 | 199 | // content 200 | if (!rapi_helpers.isEmptyString(submittedRevision.content)) { 201 | newBoard.body = new Buffer(submittedRevision.content, 'base64'); 202 | } 203 | 204 | // title 205 | if (!rapi_helpers.isEmptyString(submittedRevision.title)) { 206 | newBoard.title = rapi_helpers.toStringSafe(submittedRevision.title); 207 | } 208 | 209 | // mime 210 | if (!rapi_helpers.isEmptyString(submittedRevision.mime)) { 211 | newBoard.mime = rapi_helpers.normalizeString(submittedRevision.mime); 212 | } 213 | if (!newBoard.mime) { 214 | newBoard.mime = undefined; 215 | } 216 | 217 | // encoding 218 | if (!rapi_helpers.isEmptyString(submittedRevision.encoding)) { 219 | newBoard.encoding = rapi_helpers.normalizeString(submittedRevision.encoding); 220 | } 221 | if (!newBoard.encoding) { 222 | newBoard.encoding = undefined; 223 | } 224 | 225 | func.apply(repo, [ newBoard ]).then((newRevision: rapi_contracts.WhiteboardRevision) => { 226 | args.headers[HTTP_HEADER_REVISION] = newRevision.nr; 227 | 228 | if (newRevision.board) { 229 | if (!rapi_helpers.isEmptyString(newRevision.board.title)) { 230 | args.headers[HTTP_HEADER_TITLE] = rapi_helpers.toStringSafe(newRevision.board.title); 231 | } 232 | } 233 | 234 | args.response.data = revisionToObject(newRevision); 235 | 236 | completed(null, newRevision); 237 | }, (err) => { 238 | completed(err); 239 | }); 240 | } 241 | catch (e) { 242 | completed(e); 243 | } 244 | }, (err) => { 245 | completed(err); 246 | }); 247 | }); 248 | } 249 | 250 | // [POST] /api/whiteboard 251 | export function POST(args: rapi_contracts.ApiMethodArguments): PromiseLike { 252 | let canDelete = args.request.user.can('delete'); 253 | let canWrite = args.request.user.can('write'); 254 | let whiteboard = args.whiteboard; 255 | 256 | return new Promise((resolve, reject) => { 257 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 258 | 259 | if (!whiteboard) { 260 | args.sendNotFound(); 261 | completed(); 262 | 263 | return; 264 | } 265 | 266 | if (!canDelete || !canWrite) { 267 | args.sendForbidden(); 268 | completed(); 269 | 270 | return; 271 | } 272 | 273 | handleSubmittedRevision(args, whiteboard, whiteboard.setBoard).then(() => { 274 | completed(); 275 | }, (err) => { 276 | completed(err); 277 | }); 278 | }); 279 | } 280 | 281 | // [PUT] /api/whiteboard 282 | export function PUT(args: rapi_contracts.ApiMethodArguments): PromiseLike { 283 | let canWrite = args.request.user.can('write'); 284 | let whiteboard = args.whiteboard; 285 | 286 | return new Promise((resolve, reject) => { 287 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 288 | 289 | if (!whiteboard) { 290 | args.sendNotFound(); 291 | completed(); 292 | 293 | return; 294 | } 295 | 296 | if (!canWrite) { 297 | args.sendForbidden(); 298 | completed(); 299 | 300 | return; 301 | } 302 | 303 | handleSubmittedRevision(args, whiteboard, whiteboard.addRevision).then(() => { 304 | completed(); 305 | }, (err) => { 306 | completed(err); 307 | }); 308 | }); 309 | } 310 | 311 | function revisionToObject(revision: rapi_contracts.WhiteboardRevision): Object { 312 | let obj: Object; 313 | 314 | if (revision) { 315 | obj = {}; 316 | 317 | if (isNaN(revision.nr)) { 318 | obj['path'] = '/api/whiteboard'; 319 | } 320 | else { 321 | obj['path'] = '/api/whiteboard/' + revision.nr; 322 | obj['revision'] = revision.nr; 323 | } 324 | 325 | if (revision.board) { 326 | if (!rapi_helpers.isNullOrUndefined(revision.board.title)) { 327 | obj['title'] = rapi_helpers.toStringSafe(revision.board.title); 328 | } 329 | 330 | if (!rapi_helpers.isEmptyString(revision.board.mime)) { 331 | obj['mime'] = rapi_helpers.normalizeString(revision.board.mime); 332 | } 333 | 334 | if (!rapi_helpers.isEmptyString(revision.board.encoding)) { 335 | obj['encoding'] = rapi_helpers.normalizeString(revision.board.encoding); 336 | } 337 | 338 | let length: number; 339 | if (revision.board.body) { 340 | length = revision.board.body.length; 341 | } 342 | obj['length'] = length; 343 | } 344 | } 345 | 346 | return obj; 347 | } -------------------------------------------------------------------------------- /src/api/cron.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as rapi_contracts from '../contracts'; 27 | import * as rapi_helpers from '../helpers'; 28 | import * as vscode from 'vscode'; 29 | 30 | /** 31 | * Information about a job. 32 | */ 33 | export interface JobInfo { 34 | /** 35 | * The description for the job. 36 | */ 37 | description: string; 38 | /** 39 | * Detail information for the job. 40 | */ 41 | detail: string; 42 | /** 43 | * Gets if the job is currently running or not. 44 | */ 45 | isRunning: boolean; 46 | /** 47 | * Gets the timestamp of the last execution in ISO format. 48 | */ 49 | lastExecution: string; 50 | /** 51 | * Gets the name of the job. 52 | */ 53 | name: string; 54 | } 55 | 56 | 57 | // [DELETE] /api/cron(/{name}) 58 | export function DELETE(args: rapi_contracts.ApiMethodArguments): PromiseLike { 59 | let canActivate = args.request.user.can('activate'); 60 | 61 | return new Promise((resolve, reject) => { 62 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 63 | 64 | if (!canActivate) { 65 | args.sendForbidden(); 66 | completed(); 67 | 68 | return; 69 | } 70 | 71 | getJobs().then((jobs) => { 72 | if (false === jobs) { 73 | args.sendResponse(410); // 'vs-cron' is NOT installed 74 | completed(); 75 | 76 | return; 77 | } 78 | 79 | let jobName: string; 80 | if (args.endpoint.arguments.length > 0) { 81 | jobName = rapi_helpers.normalizeString(args.endpoint.arguments[0]); 82 | } 83 | 84 | let filterJobs = (j?: JobInfo[]): JobInfo[] => { 85 | return (j || jobs).filter(x => rapi_helpers.normalizeString(x.name) == jobName || 86 | !jobName); 87 | }; 88 | 89 | let machtingJobs = filterJobs(); 90 | if (!jobName || machtingJobs.length > 0) { 91 | vscode.commands.getCommands(true).then((commands) => { 92 | let stopJobsCmd = commands.filter(x => 'extension.cronJons.stopJobsByName' == x); 93 | if (stopJobsCmd.length < 1) { // 'vs-cron' is NOT installed 94 | completed(null, false); 95 | return; 96 | } 97 | 98 | vscode.commands.executeCommand(stopJobsCmd[0], machtingJobs.map(x => x.name)).then(() => { 99 | getJobs().then((upToDateJobs) => { 100 | if (false !== upToDateJobs) { 101 | upToDateJobs = upToDateJobs.filter(utdj => machtingJobs.map(mj => rapi_helpers.normalizeString(mj.name)) 102 | .indexOf(rapi_helpers.normalizeString(utdj.name)) > -1); 103 | 104 | args.response.data = filterJobs(upToDateJobs).map(x => jobInfoToObject(x)); 105 | } 106 | 107 | completed(); 108 | }, (err) => { 109 | completed(err); 110 | }); 111 | }, (err) => { 112 | completed(err); 113 | }); 114 | }); 115 | } 116 | else { 117 | // not found 118 | 119 | args.sendNotFound(); 120 | completed(); 121 | } 122 | }, (err) => { 123 | completed(err); 124 | }); 125 | }); 126 | } 127 | 128 | // [GET] /api/cron 129 | export function GET(args: rapi_contracts.ApiMethodArguments): PromiseLike { 130 | return new Promise((resolve, reject) => { 131 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 132 | 133 | getJobs().then((jobs) => { 134 | if (false === jobs) { 135 | args.sendResponse(410); // 'vs-cron' is NOT installed 136 | } 137 | else { 138 | args.response.data = jobs.map(x => jobInfoToObject(x)); 139 | } 140 | 141 | completed(); 142 | }, (err) => { 143 | completed(err); 144 | }); 145 | }); 146 | } 147 | 148 | function getJobs(): PromiseLike { 149 | return new Promise((resolve, reject) => { 150 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 151 | 152 | try { 153 | vscode.commands.getCommands(true).then((commands) => { 154 | let getJobsCmd = commands.filter(x => 'extension.cronJons.getJobs' == x); 155 | if (getJobsCmd.length < 1) { // 'vs-cron' is NOT installed 156 | completed(null, false); 157 | return; 158 | } 159 | 160 | try { 161 | let callback = (err, jobs: JobInfo[]) => { 162 | if (!err) { 163 | jobs = (jobs || []).filter(x => x); 164 | } 165 | 166 | completed(err, jobs); 167 | }; 168 | 169 | vscode.commands.executeCommand(getJobsCmd[0], callback).then(() => { 170 | //TODO 171 | }, (err) => { 172 | completed(err); 173 | }); 174 | } 175 | catch (e) { 176 | completed(e); 177 | } 178 | }, (err) => { 179 | completed(err); 180 | }); 181 | } 182 | catch (e) { 183 | completed(e); 184 | } 185 | }); 186 | } 187 | 188 | function jobInfoToObject(job: JobInfo): Object { 189 | let obj: Object; 190 | 191 | if (job) { 192 | obj = { 193 | description: rapi_helpers.isEmptyString(job.description) ? undefined : rapi_helpers.toStringSafe(job.description), 194 | detail: rapi_helpers.isEmptyString(job.detail) ? undefined : rapi_helpers.toStringSafe(job.detail), 195 | isRunning: rapi_helpers.toBooleanSafe(job.isRunning), 196 | lastExecution: rapi_helpers.isEmptyString(job.lastExecution) ? undefined : rapi_helpers.toStringSafe(job.lastExecution), 197 | name: rapi_helpers.isEmptyString(job.name) ? undefined : rapi_helpers.toStringSafe(job.name), 198 | path: '/api/cron/' + encodeURIComponent(rapi_helpers.toStringSafe(job.name)), 199 | }; 200 | } 201 | 202 | return obj; 203 | } 204 | 205 | 206 | // [POST] /api/cron(/{name}) 207 | export function POST(args: rapi_contracts.ApiMethodArguments): PromiseLike { 208 | let canActivate = args.request.user.can('activate'); 209 | 210 | return new Promise((resolve, reject) => { 211 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 212 | 213 | if (!canActivate) { 214 | args.sendForbidden(); 215 | completed(); 216 | 217 | return; 218 | } 219 | 220 | getJobs().then((jobs) => { 221 | if (false === jobs) { 222 | args.sendResponse(410); // 'vs-cron' is NOT installed 223 | completed(); 224 | 225 | return; 226 | } 227 | 228 | let jobName: string; 229 | if (args.endpoint.arguments.length > 0) { 230 | jobName = rapi_helpers.normalizeString(args.endpoint.arguments[0]); 231 | } 232 | 233 | let filterJobs = (j?: JobInfo[]): JobInfo[] => { 234 | return (j || jobs).filter(x => rapi_helpers.normalizeString(x.name) == jobName || 235 | !jobName); 236 | }; 237 | 238 | let machtingJobs = filterJobs(); 239 | if (!jobName || machtingJobs.length > 0) { 240 | if (jobName && machtingJobs.filter(x => x.isRunning).length > 0) { 241 | // at least one job is running 242 | 243 | args.sendResponse(409); 244 | completed(); 245 | 246 | return; 247 | } 248 | 249 | vscode.commands.getCommands(true).then((commands) => { 250 | let startJobsCmd = commands.filter(x => 'extension.cronJons.startJobsByName' == x); 251 | if (startJobsCmd.length < 1) { // 'vs-cron' is NOT installed 252 | completed(null, false); 253 | return; 254 | } 255 | 256 | vscode.commands.executeCommand(startJobsCmd[0], machtingJobs.map(x => x.name)).then(() => { 257 | getJobs().then((upToDateJobs) => { 258 | if (false !== upToDateJobs) { 259 | upToDateJobs = upToDateJobs.filter(utdj => machtingJobs.map(mj => rapi_helpers.normalizeString(mj.name)) 260 | .indexOf(rapi_helpers.normalizeString(utdj.name)) > -1); 261 | 262 | args.response.data = filterJobs(upToDateJobs).map(x => jobInfoToObject(x)); 263 | } 264 | 265 | completed(); 266 | }, (err) => { 267 | completed(err); 268 | }); 269 | }, (err) => { 270 | completed(err); 271 | }); 272 | }); 273 | } 274 | else { 275 | // not found 276 | 277 | args.sendNotFound(); 278 | completed(); 279 | } 280 | }, (err) => { 281 | completed(err); 282 | }); 283 | }); 284 | } 285 | 286 | // [PUT] /api/cron(/{name}) 287 | export function PUT(args: rapi_contracts.ApiMethodArguments): PromiseLike { 288 | let canActivate = args.request.user.can('activate'); 289 | 290 | return new Promise((resolve, reject) => { 291 | let completed = rapi_helpers.createSimplePromiseCompletedAction(resolve, reject); 292 | 293 | if (!canActivate) { 294 | args.sendForbidden(); 295 | completed(); 296 | 297 | return; 298 | } 299 | 300 | getJobs().then((jobs) => { 301 | if (false === jobs) { 302 | args.sendResponse(410); // 'vs-cron' is NOT installed 303 | completed(); 304 | 305 | return; 306 | } 307 | 308 | let jobName: string; 309 | if (args.endpoint.arguments.length > 0) { 310 | jobName = rapi_helpers.normalizeString(args.endpoint.arguments[0]); 311 | } 312 | 313 | let filterJobs = (j?: JobInfo[]): JobInfo[] => { 314 | return (j || jobs).filter(x => rapi_helpers.normalizeString(x.name) == jobName || 315 | !jobName); 316 | }; 317 | 318 | let machtingJobs = filterJobs(); 319 | if (!jobName || machtingJobs.length > 0) { 320 | vscode.commands.getCommands(true).then((commands) => { 321 | let restartJobsCmd = commands.filter(x => 'extension.cronJons.restartJobsByName' == x); 322 | if (restartJobsCmd.length < 1) { // 'vs-cron' is NOT installed 323 | completed(null, false); 324 | return; 325 | } 326 | 327 | vscode.commands.executeCommand(restartJobsCmd[0], machtingJobs.map(x => x.name)).then(() => { 328 | getJobs().then((upToDateJobs) => { 329 | if (false !== upToDateJobs) { 330 | upToDateJobs = upToDateJobs.filter(utdj => machtingJobs.map(mj => rapi_helpers.normalizeString(mj.name)) 331 | .indexOf(rapi_helpers.normalizeString(utdj.name)) > -1); 332 | 333 | args.response.data = filterJobs(upToDateJobs).map(x => jobInfoToObject(x)); 334 | } 335 | 336 | completed(); 337 | }, (err) => { 338 | completed(err); 339 | }); 340 | }, (err) => { 341 | completed(err); 342 | }); 343 | }); 344 | } 345 | else { 346 | // not found 347 | 348 | args.sendNotFound(); 349 | completed(); 350 | } 351 | }, (err) => { 352 | completed(err); 353 | }); 354 | }); 355 | } 356 | -------------------------------------------------------------------------------- /src/controller.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // The MIT License (MIT) 4 | // 5 | // vs-rest-api (https://github.com/mkloubert/vs-rest-api) 6 | // Copyright (c) Marcel Joachim Kloubert 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to 10 | // deal in the Software without restriction, including without limitation the 11 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | // sell copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | 26 | import * as i18 from './i18'; 27 | import * as Moment from 'moment'; 28 | import * as OS from 'os'; 29 | import * as rapi_contracts from './contracts'; 30 | import * as rapi_helpers from './helpers'; 31 | import * as rapi_host from './host'; 32 | import * as vscode from 'vscode'; 33 | import * as rapi_whiteboard from './whiteboard'; 34 | 35 | 36 | /** 37 | * The controller of that extension. 38 | */ 39 | export class Controller implements vscode.Disposable { 40 | /** 41 | * The current configuration. 42 | */ 43 | protected _config: rapi_contracts.Configuration; 44 | /** 45 | * Stores the underlying extension context. 46 | */ 47 | protected readonly _CONTEXT: vscode.ExtensionContext; 48 | /** 49 | * The current host. 50 | */ 51 | protected _host: rapi_host.ApiHost; 52 | /** 53 | * Stores the global output channel. 54 | */ 55 | protected readonly _OUTPUT_CHANNEL: vscode.OutputChannel; 56 | /** 57 | * Stores the package file of that extension. 58 | */ 59 | protected readonly _PACKAGE_FILE: rapi_contracts.PackageFile; 60 | /** 61 | * Stores the current whiteboard (repository). 62 | */ 63 | protected _whiteboard: rapi_contracts.WhiteboardRepository; 64 | /** 65 | * Stores the object that shares data workspace wide. 66 | */ 67 | protected _workspaceState: Object; 68 | 69 | /** 70 | * Initializes a new instance of that class. 71 | * 72 | * @param {vscode.ExtensionContext} context The underlying extension context. 73 | * @param {vscode.OutputChannel} outputChannel The global output channel to use. 74 | * @param {rapi_contracts.PackageFile} pkgFile The package file of that extension. 75 | */ 76 | constructor(context: vscode.ExtensionContext, 77 | outputChannel: vscode.OutputChannel, 78 | pkgFile: rapi_contracts.PackageFile) { 79 | this._CONTEXT = context; 80 | this._OUTPUT_CHANNEL = outputChannel; 81 | this._PACKAGE_FILE = pkgFile; 82 | } 83 | 84 | /** 85 | * Gets the current configuration. 86 | */ 87 | public get config(): rapi_contracts.Configuration { 88 | return this._config; 89 | } 90 | 91 | /** 92 | * Gets the extension context. 93 | */ 94 | public get context(): vscode.ExtensionContext { 95 | return this._CONTEXT; 96 | } 97 | 98 | /** 99 | * Logs a message. 100 | * 101 | * @param {any} msg The message to log. 102 | * 103 | * @chainable 104 | */ 105 | public log(msg: any): Controller { 106 | let now = Moment(); 107 | 108 | msg = rapi_helpers.toStringSafe(msg); 109 | this.outputChannel 110 | .appendLine(`[${now.format('YYYY-MM-DD HH:mm:ss')}] ${msg}`); 111 | 112 | return this; 113 | } 114 | 115 | /** @inheritdoc */ 116 | public dispose() { 117 | } 118 | 119 | /** 120 | * Returns a copy of the global data from the settings. 121 | * 122 | * @return {any} The global data from the settings. 123 | */ 124 | public getGlobals(): any { 125 | let globals = this.config.globals; 126 | if (globals) { 127 | globals = rapi_helpers.cloneObject(globals); 128 | } 129 | 130 | return globals; 131 | } 132 | 133 | /** 134 | * Get the name that represents that machine. 135 | */ 136 | public get name(): string { 137 | return rapi_helpers.normalizeString(OS.hostname()); 138 | } 139 | 140 | /** 141 | * The 'on activated' event. 142 | */ 143 | public onActivated() { 144 | this.reloadConfiguration(); 145 | } 146 | 147 | /** 148 | * The 'on deactivate' event. 149 | */ 150 | public onDeactivate() { 151 | } 152 | 153 | /** 154 | * Event after configuration changed. 155 | */ 156 | public onDidChangeConfiguration() { 157 | this.reloadConfiguration(); 158 | } 159 | 160 | /** 161 | * Gets the global output channel. 162 | */ 163 | public get outputChannel(): vscode.OutputChannel { 164 | return this._OUTPUT_CHANNEL; 165 | } 166 | 167 | /** 168 | * Gets the package file of that extension. 169 | */ 170 | public get packageFile(): rapi_contracts.PackageFile { 171 | return this._PACKAGE_FILE; 172 | } 173 | 174 | /** 175 | * Reloads configuration. 176 | */ 177 | public reloadConfiguration() { 178 | let me = this; 179 | 180 | let oldWorkspaceState = this._workspaceState; 181 | if (oldWorkspaceState) { 182 | // dispose old output channels 183 | let oldOutputChannels: vscode.OutputChannel[] = oldWorkspaceState['outputChannels']; 184 | if (oldOutputChannels) { 185 | oldOutputChannels.filter(x => x).forEach(x => { 186 | rapi_helpers.tryDispose(x); 187 | }); 188 | 189 | delete oldWorkspaceState['outputChannels']; 190 | } 191 | } 192 | 193 | let cfg = vscode.workspace.getConfiguration("rest.api"); 194 | me._workspaceState = { 195 | globalAccountPreparerStates: {}, 196 | globalAccountPreparerScriptStates: {}, 197 | globalHookStates: {}, 198 | globalHookScriptStates: {}, 199 | outputChannels: [], 200 | }; 201 | me._workspaceState[rapi_contracts.VAR_HTML_DOCS] = []; 202 | me._workspaceState[rapi_contracts.VAR_NEXT_HTML_DOC_ID] = -1; 203 | 204 | let nextSteps = (err?: any) => { 205 | if (err) { 206 | vscode.window.showErrorMessage(`Could not load language: ${rapi_helpers.toStringSafe(err)}`); 207 | return; 208 | } 209 | 210 | me._config = cfg; 211 | 212 | // whiteboard 213 | me._whiteboard = null; 214 | { 215 | let whiteboardCfg: rapi_contracts.WhiteboardConfiguration; 216 | if (!rapi_helpers.isNullOrUndefined(cfg.whiteboard)) { 217 | if ('object' === typeof cfg.whiteboard) { 218 | whiteboardCfg = cfg.whiteboard; 219 | } 220 | else { 221 | whiteboardCfg = { 222 | isActive: rapi_helpers.toBooleanSafe(cfg.whiteboard, true), 223 | }; 224 | } 225 | } 226 | else { 227 | whiteboardCfg = {}; 228 | } 229 | 230 | if (rapi_helpers.toBooleanSafe(whiteboardCfg.isActive, true)) { 231 | let newWhiteboard: rapi_contracts.WhiteboardRepository = new rapi_whiteboard.MemoryWhitespaceRepository(me, 232 | whiteboardCfg); 233 | 234 | newWhiteboard.init().then(() => { 235 | me._whiteboard = newWhiteboard; 236 | }, (err) => { 237 | vscode.window.showErrorMessage('[vs-rest-api] ' + i18.t('whiteboard.initFailed', err)); 238 | }); 239 | } 240 | } 241 | 242 | me.showNewVersionPopup(); 243 | 244 | if (rapi_helpers.toBooleanSafe(cfg.autoStart)) { 245 | this.start().then(() => { 246 | //TODO 247 | }, (e) => { 248 | me.log(`[ERROR] Controller.reloadConfiguration().autoStart(1): ${rapi_helpers.toStringSafe(e)}`); 249 | }); 250 | } 251 | else { 252 | this.stop().then(() => { 253 | //TODO 254 | }, (e) => { 255 | me.log(`[ERROR] Controller.reloadConfiguration().autoStart(2): ${rapi_helpers.toStringSafe(e)}`); 256 | }); 257 | } 258 | }; 259 | 260 | // load language 261 | try { 262 | i18.init(cfg.lang).then(() => { 263 | nextSteps(); 264 | }, (err) => { 265 | nextSteps(err); 266 | }); 267 | } 268 | catch (e) { 269 | nextSteps(e); 270 | } 271 | } 272 | 273 | /** 274 | * Shows the popup for a new version. 275 | */ 276 | protected showNewVersionPopup() { 277 | let me = this; 278 | 279 | let pkg = me.packageFile; 280 | if (!pkg) { 281 | return; 282 | } 283 | 284 | let currentVersion = pkg.version; 285 | if (!currentVersion) { 286 | return; 287 | } 288 | 289 | const KEY_LAST_KNOWN_VERSION = 'vsraLastKnownVersion'; 290 | 291 | // update last known version 292 | let updateCurrentVersion = false; 293 | try { 294 | let lastKnownVersion: any = this._CONTEXT.globalState.get(KEY_LAST_KNOWN_VERSION, false); 295 | if (lastKnownVersion != currentVersion) { 296 | if (!rapi_helpers.toBooleanSafe(this.config.disableNewVersionPopups)) { 297 | // tell the user that it runs on a new version 298 | updateCurrentVersion = true; 299 | 300 | // [BUTTON] show change log 301 | let changeLogBtn: rapi_contracts.PopupButton = { 302 | action: () => { 303 | rapi_helpers.open('https://github.com/mkloubert/vs-rest-api/blob/master/CHANGELOG.md').then(() => { 304 | }, (err) => { 305 | me.log(i18.t('errors.withCategory', 'Controller.showNewVersionPopup(4)', err)); 306 | }); 307 | }, 308 | title: i18.t('popups.newVersion.showChangeLog'), 309 | }; 310 | 311 | vscode.window 312 | .showInformationMessage(i18.t('popups.newVersion.message', currentVersion), 313 | changeLogBtn) 314 | .then((item) => { 315 | if (!item || !item.action) { 316 | return; 317 | } 318 | 319 | try { 320 | item.action(); 321 | } 322 | catch (e) { 323 | me.log(i18.t('errors.withCategory', 'Controller.showNewVersionPopup(3)', e)); 324 | } 325 | }); 326 | } 327 | } 328 | } 329 | catch (e) { 330 | me.log(i18.t('errors.withCategory', 'Controller.showNewVersionPopup(2)', e)); 331 | } 332 | 333 | if (updateCurrentVersion) { 334 | // update last known version 335 | try { 336 | this._CONTEXT.globalState.update(KEY_LAST_KNOWN_VERSION, currentVersion); 337 | } 338 | catch (e) { 339 | me.log(i18.t('errors.withCategory', 'Controller.showNewVersionPopup(1)', e)); 340 | } 341 | } 342 | } 343 | 344 | /** 345 | * Starts the host. 346 | * 347 | * @return {PromiseLike} The promise. 348 | */ 349 | public start(): PromiseLike { 350 | let me = this; 351 | 352 | let cfg = me.config; 353 | 354 | let port: number; 355 | let defaultPort = rapi_host.DEFAULT_PORT; 356 | if ('object' === typeof cfg.port) { 357 | for (let p in cfg.port) { 358 | if (rapi_helpers.normalizeString(p) == me.name) { 359 | port = parseInt(rapi_helpers.toStringSafe(cfg.port[p]).trim()); 360 | break; 361 | } 362 | 363 | if (rapi_helpers.isEmptyString(p)) { 364 | defaultPort = parseInt(rapi_helpers.toStringSafe(cfg.port[p]).trim()); 365 | } 366 | } 367 | } 368 | else { 369 | if (!rapi_helpers.isEmptyString(cfg.port)) { 370 | port = parseInt(rapi_helpers.toStringSafe(cfg.port).trim()); 371 | } 372 | } 373 | if (rapi_helpers.isNullOrUndefined(port)) { 374 | port = defaultPort; 375 | } 376 | 377 | return new Promise((resolve, reject) => { 378 | let completed = (err: any, h?: rapi_host.ApiHost) => { 379 | if (err) { 380 | vscode.window.showErrorMessage(`[vs-rest-api] ${i18.t('host.startFailed', err)}`); 381 | 382 | reject(err); 383 | } 384 | else { 385 | if (rapi_helpers.toBooleanSafe(cfg.showPopupOnSuccess, true)) { 386 | vscode.window.showInformationMessage(`[vs-rest-api] ${i18.t('host.started', port)}.`); 387 | } 388 | 389 | let protocol = 'http'; 390 | if (cfg.ssl) { 391 | protocol += 's'; 392 | } 393 | 394 | let browserUrl = `${protocol}://127.0.0.1:${port}/api/`; 395 | 396 | me.outputChannel.appendLine(`${i18.t('host.started', port)}:`); 397 | try { 398 | me.outputChannel.appendLine(`\t- ${protocol}://${rapi_helpers.normalizeString(OS.hostname())}:${port}/api/`); 399 | 400 | let networkInterfaces = OS.networkInterfaces(); 401 | let networkInterfaceNames = Object.keys(networkInterfaces); 402 | 403 | if (networkInterfaceNames.length > 0) { 404 | networkInterfaceNames.forEach((ifName) => { 405 | let ifaces = networkInterfaces[ifName].filter(x => { 406 | let addr = rapi_helpers.normalizeString(x.address); 407 | if ('IPv4' == x.family) { 408 | return !/^(127\.[\d.]+|[0:]+1|localhost)$/.test(addr); 409 | } 410 | 411 | return false; 412 | }); 413 | 414 | ifaces.forEach((x) => { 415 | me.outputChannel.appendLine(`\t- ${protocol}://${x.address}:${port}/api/`); 416 | }); 417 | }); 418 | } 419 | } 420 | catch (e) { 421 | me.log(i18.t('errors.withCategory', e)); 422 | } 423 | me.outputChannel.appendLine(''); 424 | 425 | if (rapi_helpers.toBooleanSafe(cfg.openInBrowser)) { 426 | rapi_helpers.open(browserUrl).then(() => { 427 | //TODO 428 | }, (err) => { 429 | vscode.window.showWarningMessage(`[vs-rest-api] ${i18.t('browser.openFailed', browserUrl, err)}`); 430 | }); 431 | } 432 | 433 | resolve(h); 434 | } 435 | }; 436 | 437 | let startHost = () => { 438 | me._host = null; 439 | 440 | let newHost = new rapi_host.ApiHost(me); 441 | 442 | newHost.start(port).then((started) => { 443 | if (started) { 444 | me._host = newHost; 445 | 446 | completed(null, newHost); 447 | } 448 | else { 449 | completed(new Error(`[vs-rest-api] ${i18.t('host.notStarted')}`)); 450 | } 451 | }, (err) => { 452 | completed(err); 453 | }); 454 | }; 455 | 456 | let currentHost = me._host; 457 | if (currentHost) { 458 | // restart 459 | 460 | currentHost.stop().then(() => { 461 | startHost(); 462 | }, (err) => { 463 | completed(err); 464 | }); 465 | } 466 | else { 467 | startHost(); 468 | } 469 | }); 470 | } 471 | 472 | /** 473 | * Stops the host. 474 | * 475 | * @return {PromiseLike} The promise. 476 | */ 477 | public stop(): PromiseLike { 478 | let me = this; 479 | 480 | let cfg = me.config; 481 | 482 | return new Promise((resolve, reject) => { 483 | let completed = (err: any, stopped?: boolean) => { 484 | if (err) { 485 | vscode.window.showErrorMessage(`[vs-rest-api] ${i18.t('host.stopFailed', err)}`); 486 | 487 | reject(err); 488 | } 489 | else { 490 | if (stopped) { 491 | if (rapi_helpers.toBooleanSafe(cfg.showPopupOnSuccess, true)) { 492 | vscode.window.showInformationMessage(`[vs-rest-api] ${i18.t('host.stopped')}`); 493 | } 494 | } 495 | 496 | resolve(stopped); 497 | } 498 | }; 499 | 500 | let currentHost = me._host; 501 | if (currentHost) { 502 | currentHost.stop().then((stopped) => { 503 | me._host = null; 504 | 505 | completed(null, stopped); 506 | }, (err) => { 507 | completed(err); 508 | }); 509 | } 510 | else { 511 | // nothing to stop 512 | completed(null, false); 513 | } 514 | }); 515 | } 516 | 517 | /** 518 | * Toggle the state of the current host. 519 | * 520 | * @returns {PromiseLike} The promise. 521 | */ 522 | public toggleHostState(): PromiseLike { 523 | let me = this; 524 | 525 | return new Promise((resolve, reject) => { 526 | if (me._host) { 527 | me.stop().then(() => { 528 | resolve(false); 529 | }, (err) => { 530 | reject(err); 531 | }); 532 | } 533 | else { 534 | me.start().then(() => { 535 | resolve(true); 536 | }, (err) => { 537 | reject(err); 538 | }); 539 | } 540 | }); 541 | } 542 | 543 | /** 544 | * Gets the current whiteboard (repository). 545 | */ 546 | public get whiteboard(): rapi_contracts.WhiteboardRepository { 547 | return this._whiteboard; 548 | } 549 | 550 | /** 551 | * Gets the object that shares data workspace wide. 552 | */ 553 | public get workspaceState(): Object { 554 | return this._workspaceState; 555 | } 556 | } 557 | --------------------------------------------------------------------------------