├── .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 | [](https://marketplace.visualstudio.com/items?itemName=mkloubert.vs-rest-api)
4 | [](https://marketplace.visualstudio.com/items?itemName=mkloubert.vs-rest-api)
5 | [](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 | [](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 | 
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 | 
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 |
--------------------------------------------------------------------------------