diff --git a/__mocks__/setup.js b/__mocks__/setup.js index cf0ec6da..4e8b03d4 100644 --- a/__mocks__/setup.js +++ b/__mocks__/setup.js @@ -28,6 +28,12 @@ class Worker { const noop = () => {}; +const dummyMetadata = [{ + name: 'ValidApplication', + mimes: ['valid/bazz'], + files: [] +}]; + const fetchMocks = { text: { @@ -46,11 +52,8 @@ const fetchMocks = { '/apps/Jest/test': true, - '/metadata.json': [{ - name: 'ValidApplication', - mimes: ['valid/bazz'], - files: [] - }], + '/metadata.json': dummyMetadata, + '/api/packages/metadata': dummyMetadata, '/settings': ({method}) => String(method).toLowerCase() === 'post' ? true diff --git a/src/config.js b/src/config.js index 10b92f09..560c72a7 100644 --- a/src/config.js +++ b/src/config.js @@ -70,7 +70,10 @@ export const defaultConfiguration = { }, packages: { - manifest: '/metadata.json', + manifest: '/api/packages/metadata', + vfsPaths: [ + 'home:/.packages' + ], metadata: [] }, diff --git a/src/packages.js b/src/packages.js index 3302ee83..d090252e 100644 --- a/src/packages.js +++ b/src/packages.js @@ -55,6 +55,21 @@ import logger from './logger'; * @typedef PackageMetadata */ +/** + * @property {string} root Installation root (except on system) + * @property {boolean} [system] Install on system + * @property {object} [headers] Forward HTTP headers + * @typedef PackageInstallationOption + */ + +/** + * @return {PackageInstallationOption} + */ +const createPackageInstallationOptions = options => Object.assign({}, { + root: 'home:/.packages', + system: false +}, options); + /** * Package Manager * @@ -134,8 +149,12 @@ export default class Packages { const manifest = this.core.config('packages.manifest'); + const query = this.core.config('packages.vfsPaths', []) + .map(str => `root[]=${encodeURIComponent(str)}`) + .join('&'); + return manifest - ? this.core.request(manifest, {}, 'json') + ? this.core.request(`${manifest}?${query}`, {}, 'json') .then(metadata => this.addPackages(metadata)) .then(() => true) .catch(error => logger.error(error)) @@ -333,6 +352,60 @@ export default class Packages { [...meta, ...configured].forEach(({name, args}) => this.launch(name, args || {})); } + /** + * Uninstalls a package + * @param {string} name Package name + * @param {PackageInstallationOption} [options] + */ + uninstall(name, options = {}) { + return this._manageApiRequest('uninstall', options, {name}); + } + + /** + * Installs a package + * @param {string} url URL to package + * @param {PackageInstallationOption} [options] + */ + install(url, options = {}) { + return this._manageApiRequest('install', options, {url}); + } + + /** + * Creates a new API request for package management + * @param {string} endpoint + * @param {object} body + * @param {object} append + * @return {object} JSON + */ + _manageApiRequest(endpoint, options, append) { + return this._apiRequest(endpoint, Object.assign({ + options: createPackageInstallationOptions(options) + }, append)) + .then((body) => { + if (body.reload) { + this.init(); + } + }); + } + + /** + * Creates a new API request + * @param {string} endpoint + * @param {object} body + * @return {object} JSON + */ + _apiRequest(endpoint, body) { + return this.core + .request(`/api/packages/${endpoint}`, { + method: 'post', + headers: { + 'content-type': 'application/json' + }, + body: JSON.stringify(body) + }) + .then(response => response.json()); + } + /** * Registers a package * @@ -369,6 +442,7 @@ export default class Packages { if (list instanceof Array) { const append = list .map(iter => Object.assign({ + _vfs: null, type: 'application', files: [] }, iter)); diff --git a/src/utils/url.js b/src/utils/url.js index 68c73aa5..6990245e 100644 --- a/src/utils/url.js +++ b/src/utils/url.js @@ -52,7 +52,11 @@ export const urlResolver = configuration => { metadata.type === 'icons' ? 'icons' : 'apps' ); - url = `${type}/${metadata.name}${path}`; + if (metadata._vfs) { + url = `vfs/readfile?path=${encodeURIComponent(`${metadata._vfs}/${metadata.name}${path}`)}`; + } else { + url = `${type}/${metadata.name}${path}`; + } } return prefix