├── .npmrc ├── .gitignore ├── dist ├── docs │ ├── icon-128.png │ ├── e18bbf611f2a2e43afc071aa2f4e1512.ttf │ ├── f4769f9bdb7466be65088239c12046d1.eot │ ├── 2725306a42ebdc07761dc58d9313c3b2.woff2 │ ├── 2f3c7709effe4b17c051534d186851ae.woff │ ├── 3919beb4a174b0dd85b5394385797ab2.woff │ ├── 448c34a56d699c29117adc64c43affeb.woff2 │ ├── 46541d7e305fb9addd5587982622b6d5.woff │ ├── 47d236461410ac106632ff91703dc1e7.woff2 │ ├── 587de8ec039052f50e69c9654439b991.woff2 │ ├── 6da41a0de9bcf1627a01686cb1cd0d31.woff │ ├── bf9fec987ff2e712826d1da62c84d86c.woff │ ├── c074f8ef4aea2b67fa0ae380041dacdf.woff2 │ ├── e21bf4e6adbbcebeedb2d078d9dbeeca.woff2 │ ├── fa2772327f55d8198301fdb8bcfc8158.woff │ ├── fb176197b2a78824ad98f96d1ab63f59.woff │ ├── fd4c5ff666d375be9ef9fb958af6e602.woff2 │ ├── index.js.map │ ├── icon.svg │ ├── c7ef06ad1c258635344d27f8c68093ed.svg │ ├── index.html │ ├── logo.svg │ └── 89e40751d0e7b7f69bd0e2dec3e98f18.svg ├── api.js.map ├── api.js ├── services │ ├── base.service.js.map │ ├── base.service.js │ ├── info.service.js.map │ ├── newSyncLogs.service.js.map │ ├── info.service.js │ ├── newSyncLogs.service.js │ └── bookmarks.service.js.map ├── routers │ ├── docs.router.js.map │ ├── docs.router.js │ ├── info.router.js.map │ ├── base.router.js.map │ ├── base.router.js │ ├── info.router.js │ ├── bookmarks.router.js.map │ └── bookmarks.router.js ├── models │ ├── newSyncLogs.model.js.map │ ├── bookmarks.model.js.map │ ├── newSyncLogs.model.js │ └── bookmarks.model.js └── core │ ├── config.js.map │ ├── config.js │ ├── db.js.map │ ├── exception.js.map │ ├── db.js │ ├── exception.js │ └── server.js.map ├── src ├── docs │ ├── images │ │ ├── icon-128.png │ │ ├── icon.svg │ │ └── logo.svg │ ├── variables.scss │ ├── mixins.scss │ ├── index.ts │ ├── index.html │ └── styles.scss ├── api.ts ├── routers │ ├── docs.router.ts │ ├── info.router.ts │ ├── base.router.ts │ └── bookmarks.router.ts ├── services │ ├── base.service.ts │ ├── info.service.ts │ └── newSyncLogs.service.ts ├── models │ ├── newSyncLogs.model.ts │ └── bookmarks.model.ts └── core │ ├── config.ts │ ├── db.ts │ ├── exception.ts │ └── server.ts ├── .travis.yml ├── .vscode ├── settings.json ├── tasks.json └── launch.json ├── tslint.json ├── tsconfig.json ├── config └── settings.default.json ├── LICENSE.md ├── test ├── unit │ ├── server.test.ts │ ├── info.service.test.ts │ └── newSyncLogs.service.test.ts └── integration │ ├── docs.test.ts │ ├── info.test.ts │ └── server.test.ts ├── webpack.config.js ├── package.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | loglevel=silent -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | config/settings.json 3 | node_modules/ -------------------------------------------------------------------------------- /dist/docs/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/API/master/dist/docs/icon-128.png -------------------------------------------------------------------------------- /src/docs/images/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/API/master/src/docs/images/icon-128.png -------------------------------------------------------------------------------- /dist/docs/e18bbf611f2a2e43afc071aa2f4e1512.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/API/master/dist/docs/e18bbf611f2a2e43afc071aa2f4e1512.ttf -------------------------------------------------------------------------------- /dist/docs/f4769f9bdb7466be65088239c12046d1.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/API/master/dist/docs/f4769f9bdb7466be65088239c12046d1.eot -------------------------------------------------------------------------------- /dist/docs/2725306a42ebdc07761dc58d9313c3b2.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/API/master/dist/docs/2725306a42ebdc07761dc58d9313c3b2.woff2 -------------------------------------------------------------------------------- /dist/docs/2f3c7709effe4b17c051534d186851ae.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/API/master/dist/docs/2f3c7709effe4b17c051534d186851ae.woff -------------------------------------------------------------------------------- /dist/docs/3919beb4a174b0dd85b5394385797ab2.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/API/master/dist/docs/3919beb4a174b0dd85b5394385797ab2.woff -------------------------------------------------------------------------------- /dist/docs/448c34a56d699c29117adc64c43affeb.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/API/master/dist/docs/448c34a56d699c29117adc64c43affeb.woff2 -------------------------------------------------------------------------------- /dist/docs/46541d7e305fb9addd5587982622b6d5.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/API/master/dist/docs/46541d7e305fb9addd5587982622b6d5.woff -------------------------------------------------------------------------------- /dist/docs/47d236461410ac106632ff91703dc1e7.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/API/master/dist/docs/47d236461410ac106632ff91703dc1e7.woff2 -------------------------------------------------------------------------------- /dist/docs/587de8ec039052f50e69c9654439b991.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/API/master/dist/docs/587de8ec039052f50e69c9654439b991.woff2 -------------------------------------------------------------------------------- /dist/docs/6da41a0de9bcf1627a01686cb1cd0d31.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/API/master/dist/docs/6da41a0de9bcf1627a01686cb1cd0d31.woff -------------------------------------------------------------------------------- /dist/docs/bf9fec987ff2e712826d1da62c84d86c.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/API/master/dist/docs/bf9fec987ff2e712826d1da62c84d86c.woff -------------------------------------------------------------------------------- /dist/docs/c074f8ef4aea2b67fa0ae380041dacdf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/API/master/dist/docs/c074f8ef4aea2b67fa0ae380041dacdf.woff2 -------------------------------------------------------------------------------- /dist/docs/e21bf4e6adbbcebeedb2d078d9dbeeca.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/API/master/dist/docs/e21bf4e6adbbcebeedb2d078d9dbeeca.woff2 -------------------------------------------------------------------------------- /dist/docs/fa2772327f55d8198301fdb8bcfc8158.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/API/master/dist/docs/fa2772327f55d8198301fdb8bcfc8158.woff -------------------------------------------------------------------------------- /dist/docs/fb176197b2a78824ad98f96d1ab63f59.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/API/master/dist/docs/fb176197b2a78824ad98f96d1ab63f59.woff -------------------------------------------------------------------------------- /dist/docs/fd4c5ff666d375be9ef9fb958af6e602.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/API/master/dist/docs/fd4c5ff666d375be9ef9fb958af6e602.woff2 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | install: 5 | - npm install 6 | script: 7 | - npm run build 8 | - npm run test 9 | -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | import Server from './core/server'; 2 | 3 | // Entry point into server 4 | const server = new Server(); 5 | server.init().then(server.start); -------------------------------------------------------------------------------- /dist/api.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":";;AAAA,0CAAmC;AAEnC,0BAA0B;AAC1B,MAAM,MAAM,GAAG,IAAI,gBAAM,EAAE,CAAC;AAC5B,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC"} -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules\\typescript\\lib", 3 | "git.ignoreLimitWarning": true, 4 | "search.exclude": { 5 | "dist": true, 6 | }, 7 | "typescript.tsc.autoDetect": "off", 8 | "editor.tabSize": 2 9 | } -------------------------------------------------------------------------------- /dist/api.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const server_1 = require("./core/server"); 4 | // Entry point into server 5 | const server = new server_1.default(); 6 | server.init().then(server.start); 7 | //# sourceMappingURL=api.js.map -------------------------------------------------------------------------------- /dist/services/base.service.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"base.service.js","sourceRoot":"","sources":["../../src/services/base.service.ts"],"names":[],"mappings":";;AAIA,8CAA8C;AAC9C,6DAA6D;AAC7D;IAIE,YAAY,OAAU,EAAE,GAA2E;QACjG,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;CACF;AARD,8BAQC"} -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "ban-comma-operator": false, 4 | "max-classes-per-file": false, 5 | "no-console": false, 6 | "no-submodule-imports": false 7 | }, 8 | "extends": [ 9 | "tslint:latest", 10 | "tslint-config-prettier" 11 | ], 12 | "linterOptions": { 13 | "exclude": [ 14 | "test/*.ts" 15 | ] 16 | } 17 | } -------------------------------------------------------------------------------- /dist/routers/docs.router.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"docs.router.js","sourceRoot":"","sources":["../../src/routers/docs.router.ts"],"names":[],"mappings":";;AAAA,mCAAmC;AACnC,6BAA6B;AAE7B,wDAAgE;AAEhE,iDAAiD;AACjD,gBAAgC,SAAQ,qBAAgB;IACtD,wDAAwD;IACjD,UAAU;QACf,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IACrE,CAAC;CACF;AALD,6BAKC"} -------------------------------------------------------------------------------- /dist/services/base.service.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | // Base class for data service implementations 4 | // Implements the functionality executed when calling a route 5 | class BaseService { 6 | constructor(service, log) { 7 | this.service = service; 8 | this.log = log; 9 | } 10 | } 11 | exports.default = BaseService; 12 | //# sourceMappingURL=base.service.js.map -------------------------------------------------------------------------------- /src/routers/docs.router.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import * as path from 'path'; 3 | 4 | import BaseRouter, { IApiRouter } from '../routers/base.router'; 5 | 6 | // Implementation of routes for API documentation 7 | export default class DocsRouter extends BaseRouter implements IApiRouter { 8 | // Initialises the routes for this router implementation 9 | public initRoutes(): void { 10 | this.app.use('/', express.static(path.join(__dirname, '../docs'))); 11 | } 12 | } -------------------------------------------------------------------------------- /src/services/base.service.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | 3 | import { LogLevel } from '../core/server'; 4 | 5 | // Base class for data service implementations 6 | // Implements the functionality executed when calling a route 7 | export default class BaseService { 8 | protected log: (level: LogLevel, message: string, req?: Request, err?: Error) => void; 9 | protected service: T; 10 | 11 | constructor(service: T, log: (level: LogLevel, message: string, req?: Request, err?: Error) => void) { 12 | this.service = service; 13 | this.log = log; 14 | } 15 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "forceConsistentCasingInFileNames": true, 6 | "experimentalDecorators": true, 7 | "skipLibCheck": true, 8 | "typeRoots": [ 9 | "./node_modules/@types" 10 | ], 11 | "lib": [ 12 | "dom", 13 | "es2017", 14 | "scripthost" 15 | ], 16 | "rootDir": "src", 17 | "outDir": "dist", 18 | "sourceMap": true 19 | }, 20 | "include": [ 21 | "src/**/*.ts" 22 | ], 23 | "exclude": [ 24 | "node_modules" 25 | ] 26 | } -------------------------------------------------------------------------------- /dist/routers/docs.router.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const express = require("express"); 4 | const path = require("path"); 5 | const base_router_1 = require("../routers/base.router"); 6 | // Implementation of routes for API documentation 7 | class DocsRouter extends base_router_1.default { 8 | // Initialises the routes for this router implementation 9 | initRoutes() { 10 | this.app.use('/', express.static(path.join(__dirname, '../docs'))); 11 | } 12 | } 13 | exports.default = DocsRouter; 14 | //# sourceMappingURL=docs.router.js.map -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "build", 9 | "identifier": "build", 10 | "label": "Build API", 11 | "group": { 12 | "kind": "build", 13 | "isDefault": true 14 | }, 15 | "presentation": { 16 | "echo": false, 17 | "reveal": "always", 18 | "focus": false, 19 | "panel": "shared" 20 | }, 21 | "problemMatcher": [ 22 | "$tsc" 23 | ] 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /dist/routers/info.router.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"info.router.js","sourceRoot":"","sources":["../../src/routers/info.router.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,qDAA2C;AAG3C,2CAAyC;AACzC,wDAAgE;AAGhE,uDAAuD;AACvD,gBAAgC,SAAQ,qBAAuB;IAC7D,wDAAwD;IACjD,UAAU;QACf,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,CAAC,WAAW,CAAC,gBAAO,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,iDAAiD;IAEnC,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB;;YACnE,IAAI;gBACF,sEAAsE;gBACtE,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBACpD,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;aACvB;YACD,OAAO,GAAG,EAAE;gBACV,IAAI,CAAC,GAAG,CAAC,CAAC;aACX;QACH,CAAC;KAAA;CACF;AAVC;IADC,0BAAQ;yCAUR;AAlBH,6BAmBC"} -------------------------------------------------------------------------------- /dist/models/newSyncLogs.model.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"newSyncLogs.model.js","sourceRoot":"","sources":["../../src/models/newSyncLogs.model.ts"],"names":[],"mappings":";;AAAA,iCAAiC;AACjC,qCAAqC;AACrC,6BAA6B;AAY7B,+FAA+F;AAC/F,kBAAe,CAAC,GAAG,EAAE;IACnB,mEAAmE;IACnE,gDAAgD;IAChD,MAAM,iBAAiB,GAAG,IAAI,QAAQ,CAAC,MAAM,CAC3C;QACE,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE;QACvC,SAAS,EAAE;YACT,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE;YAC9D,IAAI,EAAE,IAAI;SACX;QACD,SAAS,EAAE,MAAM;QACjB,WAAW,EAAE;YACX,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE;YACzB,IAAI,EAAE,IAAI;SACX;KACF,EACD;QACE,UAAU,EAAE,KAAK;KAClB,CACF,CAAC;IAEF,OAAO,QAAQ,CAAC,KAAK,CAAoB,YAAY,EAAE,iBAAiB,EAAE,aAAa,CAAC,CAAC;AAC3F,CAAC,CAAC,EAAE,CAAC"} -------------------------------------------------------------------------------- /dist/routers/base.router.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"base.router.js","sourceRoot":"","sources":["../../src/routers/base.router.ts"],"names":[],"mappings":";;;;;;;;AAAA,qDAA2C;AAC3C,qCAA8C;AAE9C,iDAAyF;AAQzF,wCAAwC;AACxC,oDAAoD;AACpD;IAIE,YAAsB,GAAgB,EAAY,OAAW;QAAvC,QAAG,GAAH,GAAG,CAAa;QAAY,YAAO,GAAP,OAAO,CAAI;QAFrD,qBAAgB,GAAG,OAAO,CAAC,2BAA2B,CAAC,EAAE,CAAC;QAGhE,mBAAmB;QACnB,IAAI,CAAC,MAAM,GAAG,gBAAM,EAAE,CAAC;QACvB,IAAI,CAAC,UAAU,EAAE,CAAC;IACpB,CAAC;IAED,wDAAwD;IACjD,UAAU;QACf,MAAM,IAAI,mCAAuB,EAAE,CAAC;IACtC,CAAC;IAED,iDAAiD;IAEvC,WAAW,CAAC,IAAa,EAAE,IAAY,EAAE,eAAoB;QACrE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,gBAAgB,CAAC,eAAe,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAC3F,CAAC;IAED,oEAAoE;IAC5D,kBAAkB;QACxB,MAAM,IAAI,uCAA2B,EAAE,CAAC;IAC1C,CAAC;CACF;AARC;IADC,0BAAQ;6CAGR;AAnBH,6BAyBC"} -------------------------------------------------------------------------------- /dist/models/bookmarks.model.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"bookmarks.model.js","sourceRoot":"","sources":["../../src/models/bookmarks.model.ts"],"names":[],"mappings":";;AAAA,qCAAqC;AACrC,6BAA6B;AAgB7B,+FAA+F;AAC/F,kBAAe,CAAC,GAAG,EAAE;IACnB,OAAO,CAAC,gBAAgB,CAAC,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,KAAK,GAAQ,QAAQ,CAAC,KAAK,CAAC;IAClC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IAExB,uDAAuD;IACvD,gEAAgE;IAChE,gDAAgD;IAChD,MAAM,eAAe,GAAG,IAAI,QAAQ,CAAC,MAAM,CACzC;QACE,GAAG,EAAE;YACH,OAAO,EAAE,IAAI,CAAC,EAAE;YAChB,IAAI,EAAE,IAAI;SACX;QACD,SAAS,EAAE,MAAM;QACjB,YAAY,EAAE;YACZ,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE;YACzB,IAAI,EAAE,IAAI;SACX;QACD,WAAW,EAAE;YACX,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE;YACzB,IAAI,EAAE,IAAI;SACX;QACD,OAAO,EAAE,MAAM;KAChB,EACD;QACE,GAAG,EAAE,KAAK;QACV,EAAE,EAAE,KAAK;QACT,UAAU,EAAE,KAAK;KAClB,CACF,CAAC;IAEF,OAAO,QAAQ,CAAC,KAAK,CAAkB,UAAU,EAAE,eAAe,EAAE,WAAW,CAAC,CAAC;AACnF,CAAC,CAAC,EAAE,CAAC"} -------------------------------------------------------------------------------- /src/docs/variables.scss: -------------------------------------------------------------------------------- 1 | $font-stack-regular: 'Roboto Condensed', sans-serif; 2 | $font-stack-code: Consolas, Menlo, 'Courier New', monospace; 3 | 4 | $colour-bg1: #01AB8A; 5 | $colour-bg2: #35C6E8; 6 | $colour-text1: #EDFEFF; 7 | $colour-text2: #083039; 8 | $colour-link: #75959C; 9 | $colour-link-highlight: $colour-bg2; 10 | $colour-section1-bg: $colour-text2; 11 | $colour-section1-text: $colour-text1; 12 | $colour-section2-bg: $colour-text1; 13 | $colour-section2-text: $colour-text2; 14 | $colour-success: #30D278; 15 | $colour-warning: #BDC71B; 16 | $colour-danger: #EA3869; 17 | 18 | $font-size: 17px; 19 | 20 | $width-small: 768px; 21 | $width-medium: 992px; 22 | $width-large: 1200px; -------------------------------------------------------------------------------- /dist/core/config.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":";;AAAA,mCAAmC;AACnC,yBAAyB;AACzB,6BAA6B;AAE7B;IACS,MAAM,CAAC,GAAG,CAAC,KAAe;QAC/B,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE;YACzB,OAAO,IAAI,CAAC,MAAM,CAAC;SACpB;QAED,iCAAiC;QACjC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;QAE1D,8BAA8B;QAC9B,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,uBAAuB,CAAC,CAAC;QACxE,MAAM,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;QAEhD,sCAAsC;QACtC,MAAM,kBAAkB,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;QACpE,IAAI,YAAY,GAAG,EAAE,CAAC;QACtB,IAAI,EAAE,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE;YACrC,YAAY,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;SAC5C;QAED,kCAAkC;QAClC,MAAM,QAAQ,GAAG,KAAK,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC;QAEtD,6BAA6B;QAC7B,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;QAElD,IAAI,CAAC,MAAM,qBACN,QAAQ,IACX,OAAO,GACR,CAAC;QAEF,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;CAGF;AAnCD,yBAmCC"} -------------------------------------------------------------------------------- /config/settings.default.json: -------------------------------------------------------------------------------- 1 | { 2 | "allowedOrigins": [], 3 | "dailyNewSyncsLimit": 3, 4 | "db": { 5 | "connTimeout": 30000, 6 | "host": "127.0.0.1", 7 | "name": "xbrowsersync", 8 | "username": "", 9 | "password": "", 10 | "port": 27017 11 | }, 12 | "log": { 13 | "enabled": true, 14 | "level": "info", 15 | "path": "/var/log/xBrowserSync/api.log", 16 | "rotatedFilesToKeep": 5, 17 | "rotationPeriod": "1d" 18 | }, 19 | "maxSyncs": 5242, 20 | "maxSyncSize": 512000, 21 | "server": { 22 | "behindProxy": false, 23 | "host": "127.0.0.1", 24 | "https": { 25 | "certPath": "", 26 | "enabled": false, 27 | "keyPath": "" 28 | }, 29 | "port": 8080 30 | }, 31 | "status": { 32 | "allowNewSyncs": true, 33 | "message": "", 34 | "online": true 35 | }, 36 | "tests": { 37 | "db": "xbrowsersynctest", 38 | "port": 8081 39 | }, 40 | "throttle": { 41 | "maxRequests": 1000, 42 | "timeWindow": 300000 43 | } 44 | } -------------------------------------------------------------------------------- /dist/models/newSyncLogs.model.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const moment = require("moment"); 4 | const mongoose = require("mongoose"); 5 | const uuid = require("uuid"); 6 | // Implements a mongoose schema and model to connect data service methods to MongoDB collection 7 | exports.default = (() => { 8 | // Create new sync logs schema to store ip address and created date 9 | // No concurrent updates so disable version keys 10 | const newSyncLogsSchema = new mongoose.Schema({ 11 | _id: { type: String, default: uuid.v4 }, 12 | expiresAt: { 13 | default: () => moment().add(1, 'days').startOf('day').toDate(), 14 | type: Date 15 | }, 16 | ipAddress: String, 17 | syncCreated: { 18 | default: () => new Date(), 19 | type: Date 20 | } 21 | }, { 22 | versionKey: false 23 | }); 24 | return mongoose.model('NewSyncLog', newSyncLogsSchema, 'newsynclogs'); 25 | })(); 26 | //# sourceMappingURL=newSyncLogs.model.js.map -------------------------------------------------------------------------------- /dist/services/info.service.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"info.service.js","sourceRoot":"","sources":["../../src/services/info.service.ts"],"names":[],"mappings":";;;;;;;;;;AAEA,2CAAoC;AACpC,2CAAqD;AACrD,iDAAyC;AAWzC,6DAA6D;AAC7D,iBAAiC,SAAQ,sBAA6B;IACpE,0DAA0D;IAC7C,OAAO,CAAC,GAAY;;YAC/B,8CAA8C;YAC9C,MAAM,WAAW,GAAqB;gBACpC,WAAW,EAAE,gBAAM,CAAC,GAAG,EAAE,CAAC,WAAW;gBACrC,OAAO,EAAE,IAAI,CAAC,oBAAoB,CAAC,gBAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC;gBAC/D,MAAM,EAAE,kBAAS,CAAC,OAAO;gBACzB,OAAO,EAAE,gBAAM,CAAC,GAAG,EAAE,CAAC,OAAO;aAC9B,CAAC;YAEF,IAAI,gBAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE;gBAC9B,IAAI;oBACF,sDAAsD;oBACtD,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,CAAC;oBACnE,WAAW,CAAC,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC,kBAAS,CAAC,MAAM,CAAC,CAAC,CAAC,kBAAS,CAAC,UAAU,CAAC;iBAClF;gBACD,OAAO,GAAG,EAAE;oBACV,IAAI,CAAC,GAAG,CAAC,iBAAQ,CAAC,KAAK,EAAE,2CAA2C,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;iBACjF;aACF;YAED,OAAO,WAAW,CAAC;QACrB,CAAC;KAAA;IAED,+CAA+C;IACvC,oBAAoB,CAAC,IAAY;QACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,qDAAqD,EAAE,EAAE,CAAC,CAAC;IAC9F,CAAC;CACF;AA7BD,8BA6BC"} -------------------------------------------------------------------------------- /src/routers/info.router.ts: -------------------------------------------------------------------------------- 1 | import { autobind } from 'core-decorators'; 2 | import { NextFunction, Request, Response } from 'express'; 3 | 4 | import { ApiVerb } from '../core/server'; 5 | import BaseRouter, { IApiRouter } from '../routers/base.router'; 6 | import InfoService from '../services/info.service'; 7 | 8 | // Implementation of routes for service info operations 9 | export default class InfoRouter extends BaseRouter implements IApiRouter { 10 | // Initialises the routes for this router implementation 11 | public initRoutes(): void { 12 | this.app.use('/info', this.router); 13 | this.createRoute(ApiVerb.get, '/', { '^1.0.0': this.getInfo }); 14 | } 15 | 16 | // Gets service info such as status, version, etc 17 | @autobind 18 | private async getInfo(req: Request, res: Response, next: NextFunction): Promise { 19 | try { 20 | // Call service method to get service info and return response as json 21 | const serviceInfo = await this.service.getInfo(req); 22 | res.send(serviceInfo); 23 | } 24 | catch (err) { 25 | next(err); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 xBrowserSync 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/models/newSyncLogs.model.ts: -------------------------------------------------------------------------------- 1 | import * as moment from 'moment'; 2 | import * as mongoose from 'mongoose'; 3 | import * as uuid from 'uuid'; 4 | 5 | // Interface for new sync log model 6 | export interface INewSyncLog { 7 | expiresAt?: Date, 8 | ipAddress: string, 9 | syncCreated?: Date 10 | } 11 | 12 | export interface INewSyncLogsModel extends INewSyncLog, mongoose.Document { 13 | } 14 | 15 | // Implements a mongoose schema and model to connect data service methods to MongoDB collection 16 | export default (() => { 17 | // Create new sync logs schema to store ip address and created date 18 | // No concurrent updates so disable version keys 19 | const newSyncLogsSchema = new mongoose.Schema( 20 | { 21 | _id: { type: String, default: uuid.v4 }, 22 | expiresAt: { 23 | default: () => moment().add(1, 'days').startOf('day').toDate(), 24 | type: Date 25 | }, 26 | ipAddress: String, 27 | syncCreated: { 28 | default: () => new Date(), 29 | type: Date 30 | } 31 | }, 32 | { 33 | versionKey: false 34 | } 35 | ); 36 | 37 | return mongoose.model('NewSyncLog', newSyncLogsSchema, 'newsynclogs'); 38 | })(); -------------------------------------------------------------------------------- /src/core/config.ts: -------------------------------------------------------------------------------- 1 | import * as merge from 'deepmerge'; 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | 5 | export default class Config { 6 | public static get(force?: boolean): any { 7 | if (this.config && !force) { 8 | return this.config; 9 | } 10 | 11 | // Get full path to config folder 12 | const pathToConfig = path.join(__dirname, '../../config'); 13 | 14 | // Get default settings values 15 | const pathToSettings = path.join(pathToConfig, 'settings.default.json'); 16 | const defaultSettings = require(pathToSettings); 17 | 18 | // Get user settings values if present 19 | const pathToUserSettings = path.join(pathToConfig, 'settings.json'); 20 | let userSettings = {}; 21 | if (fs.existsSync(pathToUserSettings)) { 22 | userSettings = require(pathToUserSettings); 23 | } 24 | 25 | // Merge default and user settings 26 | const settings = merge(defaultSettings, userSettings); 27 | 28 | // Get current version number 29 | const { version } = require('../../package.json'); 30 | 31 | this.config = { 32 | ...settings, 33 | version 34 | }; 35 | 36 | return this.config; 37 | } 38 | 39 | private static config: any; 40 | } -------------------------------------------------------------------------------- /dist/models/bookmarks.model.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const mongoose = require("mongoose"); 4 | const uuid = require("uuid"); 5 | // Implements a mongoose schema and model to connect data service methods to MongoDB collection 6 | exports.default = (() => { 7 | require('mongoose-uuid2')(mongoose); 8 | const types = mongoose.Types; 9 | const UUID = types.UUID; 10 | // Create bookmarks schema to store bookmarks sync data 11 | // Store IDs as binary uuid v4 and disable default id properties 12 | // No concurrent updates so disable version keys 13 | const bookmarksSchema = new mongoose.Schema({ 14 | _id: { 15 | default: uuid.v4, 16 | type: UUID 17 | }, 18 | bookmarks: String, 19 | lastAccessed: { 20 | default: () => new Date(), 21 | type: Date 22 | }, 23 | lastUpdated: { 24 | default: () => new Date(), 25 | type: Date 26 | }, 27 | version: String 28 | }, { 29 | _id: false, 30 | id: false, 31 | versionKey: false 32 | }); 33 | return mongoose.model('Bookmark', bookmarksSchema, 'bookmarks'); 34 | })(); 35 | //# sourceMappingURL=bookmarks.model.js.map -------------------------------------------------------------------------------- /dist/core/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const merge = require("deepmerge"); 4 | const fs = require("fs"); 5 | const path = require("path"); 6 | class Config { 7 | static get(force) { 8 | if (this.config && !force) { 9 | return this.config; 10 | } 11 | // Get full path to config folder 12 | const pathToConfig = path.join(__dirname, '../../config'); 13 | // Get default settings values 14 | const pathToSettings = path.join(pathToConfig, 'settings.default.json'); 15 | const defaultSettings = require(pathToSettings); 16 | // Get user settings values if present 17 | const pathToUserSettings = path.join(pathToConfig, 'settings.json'); 18 | let userSettings = {}; 19 | if (fs.existsSync(pathToUserSettings)) { 20 | userSettings = require(pathToUserSettings); 21 | } 22 | // Merge default and user settings 23 | const settings = merge(defaultSettings, userSettings); 24 | // Get current version number 25 | const { version } = require('../../package.json'); 26 | this.config = Object.assign({}, settings, { version }); 27 | return this.config; 28 | } 29 | } 30 | exports.default = Config; 31 | //# sourceMappingURL=config.js.map -------------------------------------------------------------------------------- /src/routers/base.router.ts: -------------------------------------------------------------------------------- 1 | import { autobind } from 'core-decorators'; 2 | import { Application, Router } from 'express'; 3 | 4 | import { NotImplementedException, UnsupportedVersionException } from '../core/exception'; 5 | import { ApiVerb } from '../core/server'; 6 | 7 | // Interface for router implementations 8 | export interface IApiRouter { 9 | initRoutes(): void; 10 | } 11 | 12 | // Base class for router implementations 13 | // Implements the routes that are served by the api 14 | export default class BaseRouter implements IApiRouter { 15 | protected router: Router; 16 | private routesVersioning = require('express-routes-versioning')(); 17 | 18 | constructor(protected app: Application, protected service?: T) { 19 | // Configure routes 20 | this.router = Router(); 21 | this.initRoutes(); 22 | } 23 | 24 | // Initialises the routes for this router implementation 25 | public initRoutes(): void { 26 | throw new NotImplementedException(); 27 | } 28 | 29 | // Adds a new route to this router implementation 30 | @autobind 31 | protected createRoute(verb: ApiVerb, path: string, versionMappings: any): void { 32 | this.router[verb](path, this.routesVersioning(versionMappings, this.unsupportedVersion)); 33 | } 34 | 35 | // Throws an error for when a requested api version is not supported 36 | private unsupportedVersion(): void { 37 | throw new UnsupportedVersionException(); 38 | } 39 | } -------------------------------------------------------------------------------- /test/unit/server.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from 'chai'; 2 | import decache = require('decache'); 3 | import 'mocha'; 4 | import * as sinon from 'sinon'; 5 | 6 | import Config from '../../src/core/config'; 7 | import { ServiceNotAvailableException } from '../../src/core/exception'; 8 | import Server from '../../src/core/server'; 9 | 10 | describe('Server', () => { 11 | let sandbox: sinon.SinonSandbox; 12 | let testConfig: any; 13 | 14 | beforeEach(() => { 15 | testConfig = Config.get(true); 16 | sandbox = sinon.createSandbox(); 17 | }); 18 | 19 | afterEach(() => { 20 | sandbox.restore(); 21 | }); 22 | 23 | it('checkServiceAvailability: should not throw an error when status set as online in config settings', done => { 24 | testConfig.status.online = true; 25 | sandbox.stub(Config, 'get').returns(testConfig); 26 | 27 | try { 28 | Server.checkServiceAvailability(); 29 | } 30 | catch (err) { 31 | assert.fail(); 32 | } 33 | 34 | done(); 35 | }); 36 | 37 | it('checkServiceAvailability: should throw a ServiceNotAvailableException when status set as offline in config settings', done => { 38 | testConfig.status.online = false; 39 | sandbox.stub(Config, 'get').returns(testConfig); 40 | 41 | try { 42 | Server.checkServiceAvailability(); 43 | } 44 | catch (err) { 45 | expect(err).to.be.an.instanceOf(ServiceNotAvailableException); 46 | } 47 | 48 | done(); 49 | }); 50 | }); -------------------------------------------------------------------------------- /test/integration/docs.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, request, use } from 'chai'; 2 | import chaiHttp = require('chai-http'); 3 | import decache = require('decache'); 4 | import 'mocha'; 5 | import * as sinon from 'sinon'; 6 | 7 | import Config from '../../src/core/config'; 8 | import Server from '../../src/core/server'; 9 | 10 | before(() => { 11 | use(chaiHttp); 12 | }); 13 | 14 | describe('Docs', () => { 15 | let sandbox: sinon.SinonSandbox; 16 | let server: Server; 17 | let testConfig: any; 18 | 19 | beforeEach(async () => { 20 | testConfig = Config.get(true); 21 | testConfig.log.enabled = false; 22 | testConfig.db.name = testConfig.tests.db; 23 | testConfig.server.port = testConfig.tests.port; 24 | sandbox = sinon.createSandbox(); 25 | 26 | server = new Server(); 27 | server.logToConsoleEnabled(false); 28 | await server.init(); 29 | await server.start(); 30 | }); 31 | 32 | afterEach(async () => { 33 | await server.stop(); 34 | sandbox.restore(); 35 | }); 36 | 37 | it('GET /: Should return a 200 code', async () => { 38 | sandbox.stub(Config, 'get').returns(testConfig); 39 | 40 | await new Promise((resolve) => { 41 | request(server.Application) 42 | .get('/') 43 | .set('content-type', 'application/json') 44 | .end((err, res) => { 45 | expect(res).to.have.status(200); 46 | expect(res).to.be.html; 47 | resolve(); 48 | }); 49 | }); 50 | }); 51 | }); -------------------------------------------------------------------------------- /test/integration/info.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, request, use } from 'chai'; 2 | import chaiHttp = require('chai-http'); 3 | import decache = require('decache'); 4 | import 'mocha'; 5 | import * as sinon from 'sinon'; 6 | 7 | import Config from '../../src/core/config'; 8 | import Server from '../../src/core/server'; 9 | 10 | before(() => { 11 | use(chaiHttp); 12 | }); 13 | 14 | describe('InfoRouter', () => { 15 | let sandbox: sinon.SinonSandbox; 16 | let server: Server; 17 | let testConfig: any; 18 | 19 | beforeEach(async () => { 20 | testConfig = Config.get(true); 21 | testConfig.log.enabled = false; 22 | testConfig.db.name = testConfig.tests.db; 23 | testConfig.server.port = testConfig.tests.port; 24 | sandbox = sinon.createSandbox(); 25 | 26 | server = new Server(); 27 | server.logToConsoleEnabled(false); 28 | await server.init(); 29 | await server.start(); 30 | }); 31 | 32 | afterEach(async () => { 33 | await server.stop(); 34 | sandbox.restore(); 35 | }); 36 | 37 | it('GET info: should return api status info', async () => { 38 | sandbox.stub(Config, 'get').returns(testConfig); 39 | 40 | await new Promise((resolve) => { 41 | request(server.Application) 42 | .get('/info') 43 | .end((err, res) => { 44 | expect(res).to.have.status(200); 45 | expect(res).to.be.json; 46 | expect(res.body).to.be.an('object'); 47 | resolve(); 48 | }); 49 | }); 50 | }); 51 | }); -------------------------------------------------------------------------------- /src/models/bookmarks.model.ts: -------------------------------------------------------------------------------- 1 | import * as mongoose from 'mongoose'; 2 | import * as uuid from 'uuid'; 3 | 4 | // Interface for bookmarks model 5 | export interface IBookmarks { 6 | _id?: any, 7 | bookmarks?: string, 8 | lastAccessed?: Date, 9 | lastUpdated?: Date, 10 | version?: string 11 | } 12 | 13 | // Interface for bookmarks mongoose model 14 | export interface IBookmarksModel extends IBookmarks, mongoose.Document { 15 | _id: any 16 | } 17 | 18 | // Implements a mongoose schema and model to connect data service methods to MongoDB collection 19 | export default (() => { 20 | require('mongoose-uuid2')(mongoose); 21 | const types: any = mongoose.Types; 22 | const UUID = types.UUID; 23 | 24 | // Create bookmarks schema to store bookmarks sync data 25 | // Store IDs as binary uuid v4 and disable default id properties 26 | // No concurrent updates so disable version keys 27 | const bookmarksSchema = new mongoose.Schema( 28 | { 29 | _id: { 30 | default: uuid.v4, 31 | type: UUID 32 | }, 33 | bookmarks: String, 34 | lastAccessed: { 35 | default: () => new Date(), 36 | type: Date 37 | }, 38 | lastUpdated: { 39 | default: () => new Date(), 40 | type: Date 41 | }, 42 | version: String 43 | }, 44 | { 45 | _id: false, 46 | id: false, 47 | versionKey: false 48 | } 49 | ); 50 | 51 | return mongoose.model('Bookmark', bookmarksSchema, 'bookmarks'); 52 | })(); -------------------------------------------------------------------------------- /dist/core/db.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/core/db.ts"],"names":[],"mappings":";;;;;;;;;;AACA,mCAAmC;AACnC,qCAAqC;AAErC,qCAA8B;AAC9B,2CAAqD;AACrD,qCAAoC;AAEpC,+BAA+B;AAC/B;IAoBE,YAAoB,GAA2E;QAA3E,QAAG,GAAH,GAAG,CAAwE;IAAI,CAAC;IAnB7F,MAAM,CAAC,SAAS,CAAC,EAAE;QACxB,IAAI,MAAM,CAAC;QAEX,IAAI,CAAC,EAAE,EAAE;YACP,MAAM,IAAI,kCAAsB,EAAE,CAAC;SACpC;QAED,IAAI;YACF,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;SAC7E;QACD,OAAO,GAAG,EAAE;YACV,MAAM,IAAI,kCAAsB,EAAE,CAAC;SACpC;QAED,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE;YAC/B,MAAM,IAAI,kCAAsB,EAAE,CAAC;SACpC;IACH,CAAC;IAID,iCAAiC;IACpB,eAAe;;YAC1B,MAAM,QAAQ,CAAC,UAAU,EAAE,CAAA;QAC7B,CAAC;KAAA;IAED,4DAA4D;IAC/C,cAAc;;YACzB,qDAAqD;YACrD,MAAM,OAAO,GAA+B;gBAC1C,gBAAgB,EAAE,gBAAM,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,WAAW;gBAC7C,SAAS,EAAE,CAAC;gBACZ,IAAI,EAAE,gBAAM,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,mBAAmB;gBACjE,eAAe,EAAE,IAAI;gBACrB,IAAI,EAAE,gBAAM,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB;aACnE,CAAC;YAEF,6DAA6D;YAC7D,MAAM,WAAW,GAAG,aAAa,gBAAM,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,IAAI,IAAI,gBAAM,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,IAAI,IAAI,gBAAM,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;YACxG,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;YACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,UAAU,CAAC;YAEnC,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBACpC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;oBACtB,MAAM,CAAC,kBAAkB,EAAE,CAAC;gBAC9B,CAAC,CAAC,CAAC;gBAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAmB,EAAE,EAAE;oBACzC,IAAI,CAAC,GAAG,CAAC,iBAAQ,CAAC,KAAK,EAAE,yCAAyC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;oBAC/E,MAAM,CAAC,GAAG,CAAC,CAAC;gBACd,CAAC,CAAC,CAAC;gBAEH,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC/B,CAAC,CAAC,CAAC;QACL,CAAC;KAAA;CACF;AAxDD,qBAwDC"} -------------------------------------------------------------------------------- /src/services/info.service.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | 3 | import Config from '../core/config'; 4 | import { ApiStatus, LogLevel } from '../core/server'; 5 | import BaseService from './base.service'; 6 | import BookmarksService from './bookmarks.service'; 7 | 8 | // Interface for get info operation response object 9 | export interface IGetInfoResponse { 10 | maxSyncSize: number, 11 | message: string, 12 | status: number, 13 | version: string 14 | } 15 | 16 | // Implementation of data service for service info operations 17 | export default class InfoService extends BaseService { 18 | // Returns information describing the xBrowserSync service 19 | public async getInfo(req: Request): Promise { 20 | // Create response object from config settings 21 | const serviceInfo: IGetInfoResponse = { 22 | maxSyncSize: Config.get().maxSyncSize, 23 | message: this.stripScriptsFromHtml(Config.get().status.message), 24 | status: ApiStatus.offline, 25 | version: Config.get().version 26 | }; 27 | 28 | if (Config.get().status.online) { 29 | try { 30 | // Call service method to check if accepting new syncs 31 | const acceptingNewSyncs = await this.service.isAcceptingNewSyncs(); 32 | serviceInfo.status = acceptingNewSyncs ? ApiStatus.online : ApiStatus.noNewSyncs; 33 | } 34 | catch (err) { 35 | this.log(LogLevel.Error, 'Exception occurred in InfoService.getInfo', req, err); 36 | } 37 | } 38 | 39 | return serviceInfo; 40 | } 41 | 42 | // Removes script tags from a given HTML string 43 | private stripScriptsFromHtml(html: string): string { 44 | return !html ? '' : html.replace(/)<[^<]*)*<\/script>/gi, ''); 45 | } 46 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug API", 9 | "type": "node", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/dist/api.js", 12 | "preLaunchTask": "build", 13 | "outFiles": [ 14 | "${workspaceFolder}/dist/**/*.js" 15 | ], 16 | "sourceMaps": true, 17 | "smartStep": true 18 | }, 19 | { 20 | "name": "Debug docs", 21 | "type": "chrome", 22 | "request": "launch", 23 | "url": "http://127.0.0.1:8080", 24 | "webRoot": "${workspaceFolder}/dist/docs", 25 | "sourceMaps": true, 26 | "smartStep": true, 27 | "sourceMapPathOverrides": { 28 | "webpack:///./src/docs/*": "${workspaceFolder}/src/docs/*" 29 | }, 30 | "runtimeArgs": [ 31 | "--remote-debugging-port=9222", 32 | "-incognito" 33 | ] 34 | }, 35 | { 36 | "name": "Run unit tests", 37 | "type": "node", 38 | "request": "launch", 39 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 40 | "args": ["-r", "ts-node/register", "test/unit/*.test.ts"], 41 | "cwd": "${workspaceRoot}", 42 | "protocol": "inspector", 43 | "sourceMaps": true, 44 | "smartStep": true 45 | }, 46 | { 47 | "name": "Run integration tests", 48 | "type": "node", 49 | "request": "launch", 50 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 51 | "args": ["-r", "ts-node/register", "test/integration/*.test.ts"], 52 | "cwd": "${workspaceRoot}", 53 | "protocol": "inspector", 54 | "sourceMaps": true, 55 | "smartStep": true 56 | } 57 | ] 58 | } -------------------------------------------------------------------------------- /dist/services/newSyncLogs.service.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"newSyncLogs.service.js","sourceRoot":"","sources":["../../src/services/newSyncLogs.service.ts"],"names":[],"mappings":";;;;;;;;;;AAEA,2CAAoC;AACpC,iDAAwF;AACxF,2CAA0C;AAC1C,mEAA4E;AAC5E,iDAAyC;AAEzC,6DAA6D;AAC7D,wBAAwC,SAAQ,sBAAiB;IAC/D,8DAA8D;IACjD,SAAS,CAAC,GAAY;;YACjC,8BAA8B;YAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;YAC9C,IAAI,CAAC,QAAQ,EAAE;gBACb,MAAM,GAAG,GAAG,IAAI,yCAA6B,EAAE,CAAC;gBAChD,IAAI,CAAC,GAAG,CAAC,iBAAQ,CAAC,KAAK,EAAE,oDAAoD,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;gBACzF,MAAM,GAAG,CAAC;aACX;YAED,8BAA8B;YAC9B,MAAM,aAAa,GAAgB;gBACjC,SAAS,EAAE,QAAQ;aACpB,CAAC;YACF,MAAM,gBAAgB,GAAG,IAAI,2BAAgB,CAAC,aAAa,CAAC,CAAC;YAE7D,+BAA+B;YAC/B,IAAI;gBACF,MAAM,gBAAgB,CAAC,IAAI,EAAE,CAAC;aAC/B;YACD,OAAO,GAAG,EAAE;gBACV,IAAI,CAAC,GAAG,CAAC,iBAAQ,CAAC,KAAK,EAAE,oDAAoD,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;gBACzF,MAAM,GAAG,CAAC;aACX;YAED,OAAO,aAAa,CAAC;QACvB,CAAC;KAAA;IAED,qHAAqH;IACxG,gBAAgB,CAAC,GAAY;;YACxC,8BAA8B;YAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;YAC9C,IAAI,CAAC,QAAQ,EAAE;gBACb,MAAM,GAAG,GAAG,IAAI,yCAA6B,EAAE,CAAC;gBAChD,IAAI,CAAC,GAAG,CAAC,iBAAQ,CAAC,KAAK,EAAE,2DAA2D,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;gBAChG,MAAM,GAAG,CAAC;aACX;YAED,IAAI,eAAe,GAAG,CAAC,CAAC,CAAC;YAEzB,yFAAyF;YACzF,IAAI;gBACF,eAAe,GAAG,MAAM,2BAAgB,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;aAChF;YACD,OAAO,GAAG,EAAE;gBACV,IAAI,CAAC,GAAG,CAAC,iBAAQ,CAAC,KAAK,EAAE,2DAA2D,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;gBAChG,MAAM,GAAG,CAAC;aACX;YAED,oCAAoC;YACpC,IAAI,eAAe,GAAG,CAAC,EAAE;gBACvB,MAAM,GAAG,GAAG,IAAI,gCAAoB,CAAC,kDAAkD,CAAC,CAAC;gBACzF,IAAI,CAAC,GAAG,CAAC,iBAAQ,CAAC,KAAK,EAAE,2DAA2D,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;gBAChG,MAAM,GAAG,CAAC;aACX;YAED,8CAA8C;YAC9C,OAAO,eAAe,IAAI,gBAAM,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC;QAC5D,CAAC;KAAA;IAED,wDAAwD;IAChD,kBAAkB,CAAC,GAAY;QACrC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE;YACnB,OAAO;SACR;QAED,OAAO,GAAG,CAAC,EAAE,CAAC;IAChB,CAAC;CACF;AArED,qCAqEC"} -------------------------------------------------------------------------------- /src/docs/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin css-prefix($property, $value) { 2 | -webkit-#{$property}: #{$value}; 3 | -khtml-#{$property}: #{$value}; 4 | -moz-#{$property}: #{$value}; 5 | -ms-#{$property}: #{$value}; 6 | -o-#{$property}: #{$value}; 7 | #{$property}: #{$value}; 8 | } 9 | 10 | @mixin animation($str) { 11 | @include css-prefix(animation, $str); 12 | } 13 | 14 | @mixin background-gradient($direction, $startColor, $startColorAmount, $endColor, $endColorAmount) { 15 | background-color: $startColor; 16 | background: linear-gradient($direction, $startColor $startColorAmount, $endColor $endColorAmount); 17 | filter: progid:DXImageTransform.Microsoft.gradient(startColorStr= '#{$startColor}', endColorStr='#{$endColor}'); 18 | } 19 | 20 | @mixin hide-text { 21 | font: 0/0 a; 22 | text-shadow: none; 23 | color: transparent; 24 | } 25 | 26 | @mixin keyframes($animation-name) { 27 | @-webkit-keyframes #{$animation-name}{ 28 | @content; 29 | } 30 | @-moz-keyframes #{$animation-name} { 31 | @content; 32 | } 33 | @-ms-keyframes #{$animation-name} { 34 | @content; 35 | } 36 | @-o-keyframes #{$animation-name} { 37 | @content; 38 | } 39 | @keyframes #{$animation-name} { 40 | @content; 41 | } 42 | } 43 | 44 | @mixin placeholder { 45 | &::-webkit-input-placeholder { 46 | @content; 47 | } 48 | 49 | &:-moz-placeholder { 50 | opacity: 1; 51 | @content; 52 | } 53 | 54 | &::-moz-placeholder { 55 | @content; 56 | } 57 | 58 | &:-ms-input-placeholder { 59 | @content; 60 | } 61 | } 62 | 63 | @mixin pre-wrap { 64 | white-space: pre-wrap; 65 | white-space: -moz-pre-wrap; 66 | white-space: -pre-pre-wrap; 67 | white-space: -o-pre-wrap; 68 | word-wrap: break-word; 69 | } 70 | 71 | @mixin rotate($deg) { 72 | @include transform(rotate(#{$deg}deg)); 73 | } 74 | 75 | @mixin transform($transforms) { 76 | @include css-prefix(transform, $transforms); 77 | } 78 | -------------------------------------------------------------------------------- /dist/routers/base.router.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 | else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; 6 | return c > 3 && r && Object.defineProperty(target, key, r), r; 7 | }; 8 | Object.defineProperty(exports, "__esModule", { value: true }); 9 | const core_decorators_1 = require("core-decorators"); 10 | const express_1 = require("express"); 11 | const exception_1 = require("../core/exception"); 12 | // Base class for router implementations 13 | // Implements the routes that are served by the api 14 | class BaseRouter { 15 | constructor(app, service) { 16 | this.app = app; 17 | this.service = service; 18 | this.routesVersioning = require('express-routes-versioning')(); 19 | // Configure routes 20 | this.router = express_1.Router(); 21 | this.initRoutes(); 22 | } 23 | // Initialises the routes for this router implementation 24 | initRoutes() { 25 | throw new exception_1.NotImplementedException(); 26 | } 27 | // Adds a new route to this router implementation 28 | createRoute(verb, path, versionMappings) { 29 | this.router[verb](path, this.routesVersioning(versionMappings, this.unsupportedVersion)); 30 | } 31 | // Throws an error for when a requested api version is not supported 32 | unsupportedVersion() { 33 | throw new exception_1.UnsupportedVersionException(); 34 | } 35 | } 36 | __decorate([ 37 | core_decorators_1.autobind 38 | ], BaseRouter.prototype, "createRoute", null); 39 | exports.default = BaseRouter; 40 | //# sourceMappingURL=base.router.js.map -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 2 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 5 | const Path = require('path'); 6 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 7 | 8 | module.exports = { 9 | mode: 'production', 10 | entry: ['./src/docs/index.ts', './src/docs/styles.scss'], 11 | output: { 12 | filename: 'index.js', 13 | path: Path.resolve(__dirname, 'dist/docs') 14 | }, 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.html$/, 19 | use: [ 20 | { 21 | loader: 'html-loader', 22 | options: { minimize: true } 23 | } 24 | ] 25 | }, 26 | { 27 | test: /\.tsx?$/, 28 | use: 'ts-loader', 29 | exclude: /node_modules/ 30 | }, 31 | { 32 | test: /\.(sa|sc|c)ss$/, 33 | use: [ 34 | MiniCssExtractPlugin.loader, 35 | 'css-loader', 36 | 'sass-loader' 37 | ] 38 | }, 39 | { 40 | test: /\.(png|svg|jpg|gif)$/, 41 | use: 'file-loader' 42 | }, 43 | { 44 | test: /\.(woff|woff2|eot|ttf|otf)$/, 45 | use: 'file-loader' 46 | } 47 | ] 48 | }, 49 | optimization: { 50 | minimizer: [ 51 | new UglifyJsPlugin({ 52 | cache: true, 53 | parallel: true 54 | }), 55 | new OptimizeCSSAssetsPlugin({}) 56 | ] 57 | }, 58 | resolve: { 59 | extensions: ['.tsx', '.ts', '.js'] 60 | }, 61 | plugins: [ 62 | new CopyWebpackPlugin([{ 63 | from: 'src/docs/images' 64 | }]), 65 | new HtmlWebPackPlugin({ 66 | template: 'src/docs/index.html', 67 | filename: 'index.html' 68 | }), 69 | new MiniCssExtractPlugin({ 70 | filename: 'styles.css', 71 | }), 72 | ], 73 | devtool: 'source-map', 74 | stats: 'errors-only' 75 | }; -------------------------------------------------------------------------------- /src/core/db.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import * as mongojs from 'mongojs'; 3 | import * as mongoose from 'mongoose'; 4 | 5 | import Config from './config'; 6 | import { InvalidSyncIdException } from './exception'; 7 | import { LogLevel } from './server'; 8 | 9 | // Handles database interaction 10 | export default class DB { 11 | public static idIsValid(id): void { 12 | let binary; 13 | 14 | if (!id) { 15 | throw new InvalidSyncIdException(); 16 | } 17 | 18 | try { 19 | binary = mongojs.Binary(new Buffer(id, 'hex'), mongojs.Binary.SUBTYPE_UUID); 20 | } 21 | catch (err) { 22 | throw new InvalidSyncIdException(); 23 | } 24 | 25 | if (!binary || !binary.toJSON()) { 26 | throw new InvalidSyncIdException(); 27 | } 28 | } 29 | 30 | constructor(private log: (level: LogLevel, message: string, req?: Request, err?: Error) => void) { } 31 | 32 | // Closes the database connection 33 | public async closeConnection(): Promise { 34 | await mongoose.disconnect() 35 | } 36 | 37 | // Initialises the database connection using config settings 38 | public async openConnection(): Promise { 39 | // Set the db connection options from config settings 40 | const options: mongoose.ConnectionOptions = { 41 | connectTimeoutMS: Config.get().db.connTimeout, 42 | keepAlive: 1, 43 | pass: Config.get().db.password || process.env.XBROWSERSYNC_DB_PWD, 44 | useNewUrlParser: true, 45 | user: Config.get().db.username || process.env.XBROWSERSYNC_DB_USER 46 | }; 47 | 48 | // Connect to the host and db name defined in config settings 49 | const dbServerUrl = `mongodb://${Config.get().db.host}:${Config.get().db.port}/${Config.get().db.name}`; 50 | mongoose.connect(dbServerUrl, options); 51 | const dbConn = mongoose.connection; 52 | 53 | await new Promise((resolve, reject) => { 54 | dbConn.on('close', () => { 55 | dbConn.removeAllListeners(); 56 | }); 57 | 58 | dbConn.on('error', (err: mongoose.Error) => { 59 | this.log(LogLevel.Error, 'Uncaught exception occurred in database', null, err); 60 | reject(err); 61 | }); 62 | 63 | dbConn.once('open', resolve); 64 | }); 65 | } 66 | } -------------------------------------------------------------------------------- /dist/docs/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/docs/index.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/core/exception.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"exception.js","sourceRoot":"","sources":["../../src/core/exception.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/services/info.service.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | Object.defineProperty(exports, "__esModule", { value: true }); 11 | const config_1 = require("../core/config"); 12 | const server_1 = require("../core/server"); 13 | const base_service_1 = require("./base.service"); 14 | // Implementation of data service for service info operations 15 | class InfoService extends base_service_1.default { 16 | // Returns information describing the xBrowserSync service 17 | getInfo(req) { 18 | return __awaiter(this, void 0, void 0, function* () { 19 | // Create response object from config settings 20 | const serviceInfo = { 21 | maxSyncSize: config_1.default.get().maxSyncSize, 22 | message: this.stripScriptsFromHtml(config_1.default.get().status.message), 23 | status: server_1.ApiStatus.offline, 24 | version: config_1.default.get().version 25 | }; 26 | if (config_1.default.get().status.online) { 27 | try { 28 | // Call service method to check if accepting new syncs 29 | const acceptingNewSyncs = yield this.service.isAcceptingNewSyncs(); 30 | serviceInfo.status = acceptingNewSyncs ? server_1.ApiStatus.online : server_1.ApiStatus.noNewSyncs; 31 | } 32 | catch (err) { 33 | this.log(server_1.LogLevel.Error, 'Exception occurred in InfoService.getInfo', req, err); 34 | } 35 | } 36 | return serviceInfo; 37 | }); 38 | } 39 | // Removes script tags from a given HTML string 40 | stripScriptsFromHtml(html) { 41 | return !html ? '' : html.replace(/)<[^<]*)*<\/script>/gi, ''); 42 | } 43 | } 44 | exports.default = InfoService; 45 | //# sourceMappingURL=info.service.js.map -------------------------------------------------------------------------------- /dist/routers/info.router.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 | else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; 6 | return c > 3 && r && Object.defineProperty(target, key, r), r; 7 | }; 8 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 9 | return new (P || (P = Promise))(function (resolve, reject) { 10 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 11 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 12 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 13 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 14 | }); 15 | }; 16 | Object.defineProperty(exports, "__esModule", { value: true }); 17 | const core_decorators_1 = require("core-decorators"); 18 | const server_1 = require("../core/server"); 19 | const base_router_1 = require("../routers/base.router"); 20 | // Implementation of routes for service info operations 21 | class InfoRouter extends base_router_1.default { 22 | // Initialises the routes for this router implementation 23 | initRoutes() { 24 | this.app.use('/info', this.router); 25 | this.createRoute(server_1.ApiVerb.get, '/', { '^1.0.0': this.getInfo }); 26 | } 27 | // Gets service info such as status, version, etc 28 | getInfo(req, res, next) { 29 | return __awaiter(this, void 0, void 0, function* () { 30 | try { 31 | // Call service method to get service info and return response as json 32 | const serviceInfo = yield this.service.getInfo(req); 33 | res.send(serviceInfo); 34 | } 35 | catch (err) { 36 | next(err); 37 | } 38 | }); 39 | } 40 | } 41 | __decorate([ 42 | core_decorators_1.autobind 43 | ], InfoRouter.prototype, "getInfo", null); 44 | exports.default = InfoRouter; 45 | //# sourceMappingURL=info.router.js.map -------------------------------------------------------------------------------- /src/services/newSyncLogs.service.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | 3 | import Config from '../core/config'; 4 | import { ClientIpAddressEmptyException, UnspecifiedException } from '../core/exception'; 5 | import { LogLevel } from '../core/server'; 6 | import NewSyncLogsModel, { INewSyncLog } from '../models/newSyncLogs.model'; 7 | import BaseService from './base.service'; 8 | 9 | // Implementation of data service for new sync log operations 10 | export default class NewSyncLogsService extends BaseService { 11 | // Creates a new sync log entry with the supplied request data 12 | public async createLog(req: Request): Promise { 13 | // Get the client's ip address 14 | const clientIp = this.getClientIpAddress(req); 15 | if (!clientIp) { 16 | const err = new ClientIpAddressEmptyException(); 17 | this.log(LogLevel.Error, 'Exception occurred in NewSyncLogsService.createLog', req, err); 18 | throw err; 19 | } 20 | 21 | // Create new sync log payload 22 | const newLogPayload: INewSyncLog = { 23 | ipAddress: clientIp 24 | }; 25 | const newSyncLogsModel = new NewSyncLogsModel(newLogPayload); 26 | 27 | // Commit the payload to the db 28 | try { 29 | await newSyncLogsModel.save(); 30 | } 31 | catch (err) { 32 | this.log(LogLevel.Error, 'Exception occurred in NewSyncLogsService.createLog', req, err); 33 | throw err; 34 | } 35 | 36 | return newLogPayload; 37 | } 38 | 39 | // Returns true/false depending on whether a given request's ip address has hit the limit for daily new syncs created 40 | public async newSyncsLimitHit(req: Request): Promise { 41 | // Get the client's ip address 42 | const clientIp = this.getClientIpAddress(req); 43 | if (!clientIp) { 44 | const err = new ClientIpAddressEmptyException(); 45 | this.log(LogLevel.Error, 'Exception occurred in NewSyncLogsService.newSyncsLimitHit', req, err); 46 | throw err; 47 | } 48 | 49 | let newSyncsCreated = -1; 50 | 51 | // Query the newsynclogs collection for the total number of logs for the given ip address 52 | try { 53 | newSyncsCreated = await NewSyncLogsModel.count({ ipAddress: clientIp }).exec(); 54 | } 55 | catch (err) { 56 | this.log(LogLevel.Error, 'Exception occurred in NewSyncLogsService.newSyncsLimitHit', req, err); 57 | throw err; 58 | } 59 | 60 | // Ensure a valid count was returned 61 | if (newSyncsCreated < 0) { 62 | const err = new UnspecifiedException('New syncs created count cannot be less than zero'); 63 | this.log(LogLevel.Error, 'Exception occurred in NewSyncLogsService.newSyncsLimitHit', req, err); 64 | throw err; 65 | } 66 | 67 | // Check returned count against config setting 68 | return newSyncsCreated >= Config.get().dailyNewSyncsLimit; 69 | } 70 | 71 | // Extracts the client's ip address from a given request 72 | private getClientIpAddress(req: Request): string { 73 | if (!req || !req.ip) { 74 | return; 75 | } 76 | 77 | return req.ip; 78 | } 79 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xbrowsersync-api", 3 | "description": "The REST API service component of the xBrowserSync application.", 4 | "version": "1.1.4", 5 | "author": "xBrowserSync", 6 | "license": "MIT", 7 | "main": "dist/api.js", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/xBrowserSync/API.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/xBrowserSync/API/issues" 14 | }, 15 | "dependencies": { 16 | "bootstrap": "^3.3.7", 17 | "bunyan": "^1.8.12", 18 | "core-decorators": "^0.20.0", 19 | "cors": "^2.8.4", 20 | "deepmerge": "^2.1.1", 21 | "es6-promise": "^4.2.4", 22 | "express": "^4.16.3", 23 | "express-rate-limit": "^2.11.0", 24 | "express-routes-versioning": "^1.0.1", 25 | "helmet": "^3.12.1", 26 | "mkdirp": "^0.5.1", 27 | "moment": "^2.22.2", 28 | "mongojs": "^2.6.0", 29 | "mongoose": "^5.2.3", 30 | "mongoose-uuid2": "^2.1.0", 31 | "smooth-scroll": "^14.2.0", 32 | "typeface-roboto-condensed": "0.0.54", 33 | "uuid": "^3.3.2", 34 | "whatwg-fetch": "^2.0.4" 35 | }, 36 | "devDependencies": { 37 | "@types/bunyan": "^1.8.4", 38 | "@types/chai": "^4.1.4", 39 | "@types/chai-http": "^3.0.5", 40 | "@types/cors": "^2.8.4", 41 | "@types/deepmerge": "^2.1.0", 42 | "@types/express": "^4.16.0", 43 | "@types/express-rate-limit": "^2.9.3", 44 | "@types/helmet": "^0.0.38", 45 | "@types/mocha": "^5.2.4", 46 | "@types/mongoose": "^5.2.0", 47 | "@types/node": "^9.6.23", 48 | "@types/sinon": "^5.0.1", 49 | "@types/uuid": "^3.4.3", 50 | "chai": "^4.1.2", 51 | "chai-http": "^4.0.0", 52 | "copy-webpack-plugin": "^4.5.2", 53 | "css-loader": "^1.0.0", 54 | "decache": "^4.4.0", 55 | "file-loader": "^1.1.11", 56 | "html-loader": "^0.5.5", 57 | "html-webpack-plugin": "^3.2.0", 58 | "mini-css-extract-plugin": "^0.4.1", 59 | "mocha": "^5.2.0", 60 | "node-sass": "^4.9.2", 61 | "optimize-css-assets-webpack-plugin": "^5.0.0", 62 | "postcss-loader": "^2.1.6", 63 | "prettier": "^1.13.7", 64 | "sass-loader": "^7.0.3", 65 | "sinon": "^6.1.3", 66 | "ts-loader": "^4.4.2", 67 | "ts-node": "^7.0.0", 68 | "tslint": "^5.10.0", 69 | "tslint-config-prettier": "^1.13.0", 70 | "typescript": "^2.9.2", 71 | "uglifyjs-webpack-plugin": "^1.2.7", 72 | "webpack": "^4.16.1", 73 | "webpack-cli": "^3.0.8" 74 | }, 75 | "engines": { 76 | "node": ">=8.0.0" 77 | }, 78 | "scripts": { 79 | "build": "npm run tslint && npm run compilesrc && npm run builddocs && npm run buildcomplete", 80 | "compilesrc": "echo Compiling source files... & tsc --pretty", 81 | "tslint": "echo Running tslint... & tslint --project . -t stylish", 82 | "builddocs": "echo Building docs... & webpack --config webpack.config.js", 83 | "buildcomplete": "echo Done!", 84 | "test": "npm run unittests", 85 | "unittests": "echo Running unit tests... & mocha --require ts-node/register test/unit/*.test.ts", 86 | "integrationtests": "echo Running integration tests... & mocha --require ts-node/register test/integration/*.test.ts" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /dist/core/db.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | Object.defineProperty(exports, "__esModule", { value: true }); 11 | const mongojs = require("mongojs"); 12 | const mongoose = require("mongoose"); 13 | const config_1 = require("./config"); 14 | const exception_1 = require("./exception"); 15 | const server_1 = require("./server"); 16 | // Handles database interaction 17 | class DB { 18 | constructor(log) { 19 | this.log = log; 20 | } 21 | static idIsValid(id) { 22 | let binary; 23 | if (!id) { 24 | throw new exception_1.InvalidSyncIdException(); 25 | } 26 | try { 27 | binary = mongojs.Binary(new Buffer(id, 'hex'), mongojs.Binary.SUBTYPE_UUID); 28 | } 29 | catch (err) { 30 | throw new exception_1.InvalidSyncIdException(); 31 | } 32 | if (!binary || !binary.toJSON()) { 33 | throw new exception_1.InvalidSyncIdException(); 34 | } 35 | } 36 | // Closes the database connection 37 | closeConnection() { 38 | return __awaiter(this, void 0, void 0, function* () { 39 | yield mongoose.disconnect(); 40 | }); 41 | } 42 | // Initialises the database connection using config settings 43 | openConnection() { 44 | return __awaiter(this, void 0, void 0, function* () { 45 | // Set the db connection options from config settings 46 | const options = { 47 | connectTimeoutMS: config_1.default.get().db.connTimeout, 48 | keepAlive: 1, 49 | pass: config_1.default.get().db.password || process.env.XBROWSERSYNC_DB_PWD, 50 | useNewUrlParser: true, 51 | user: config_1.default.get().db.username || process.env.XBROWSERSYNC_DB_USER 52 | }; 53 | // Connect to the host and db name defined in config settings 54 | const dbServerUrl = `mongodb://${config_1.default.get().db.host}:${config_1.default.get().db.port}/${config_1.default.get().db.name}`; 55 | mongoose.connect(dbServerUrl, options); 56 | const dbConn = mongoose.connection; 57 | yield new Promise((resolve, reject) => { 58 | dbConn.on('close', () => { 59 | dbConn.removeAllListeners(); 60 | }); 61 | dbConn.on('error', (err) => { 62 | this.log(server_1.LogLevel.Error, 'Uncaught exception occurred in database', null, err); 63 | reject(err); 64 | }); 65 | dbConn.once('open', resolve); 66 | }); 67 | }); 68 | } 69 | } 70 | exports.default = DB; 71 | //# sourceMappingURL=db.js.map -------------------------------------------------------------------------------- /dist/docs/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/docs/images/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /dist/docs/c7ef06ad1c258635344d27f8c68093ed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/docs/index.ts: -------------------------------------------------------------------------------- 1 | import 'bootstrap/dist/css/bootstrap.min.css'; 2 | import { autobind } from 'core-decorators'; 3 | import es6promise = require('es6-promise'); 4 | import * as SmoothScroll from 'smooth-scroll'; 5 | import 'typeface-roboto-condensed'; 6 | import 'whatwg-fetch'; 7 | 8 | import { IGetInfoResponse } from '../services/info.service'; 9 | 10 | // API home page and documentation 11 | class DocsPage { 12 | constructor() { 13 | // Add support for promises pre-es6 14 | es6promise.polyfill(); 15 | } 16 | 17 | // Initialises the page once DOM is ready 18 | @autobind 19 | public async init(): Promise { 20 | // Enable resonsive menu 21 | this.enableMenu(); 22 | 23 | // Check service status 24 | this.checkStatus(); 25 | 26 | // Enable smooth scrolling of page links 27 | const scroll = new SmoothScroll('a[href*="#"]', { 28 | updateURL: false 29 | }); 30 | } 31 | 32 | private async checkStatus() { 33 | const versionEl = document.querySelector('#version'); 34 | const currentStatusEl = document.querySelector('#currentstatus'); 35 | 36 | // Display current status and version for this xBrowserSync service 37 | try { 38 | const response = await fetch(`${location.pathname}info`); 39 | if (!response.ok) { 40 | throw new Error(response.statusText); 41 | } 42 | 43 | const apiInfo: IGetInfoResponse = await response.json(); 44 | if (apiInfo) { 45 | versionEl.textContent = apiInfo.version; 46 | } 47 | 48 | switch (apiInfo.status) { 49 | case 1: 50 | currentStatusEl.textContent = 'Online'; 51 | currentStatusEl.className = 'success'; 52 | break; 53 | case 3: 54 | currentStatusEl.textContent = 'Not accepting new syncs'; 55 | currentStatusEl.className = 'warning'; 56 | break; 57 | default: 58 | case 2: 59 | currentStatusEl.textContent = 'Offline'; 60 | currentStatusEl.className = 'danger'; 61 | break; 62 | } 63 | } 64 | catch (err) { 65 | currentStatusEl.textContent = 'Offline'; 66 | currentStatusEl.className = 'danger'; 67 | } 68 | } 69 | 70 | private enableMenu() { 71 | const toggle = document.querySelector('.nav-menu-button'); 72 | const navbar = document.querySelector('nav'); 73 | 74 | const toggleMenu = () => { 75 | // Toggle menu display and menu button hide 76 | if (navbar.classList.contains('open')) { 77 | navbar.classList.remove('open'); 78 | toggle.classList.remove('hide'); 79 | document.body.classList.remove('noscroll'); 80 | } 81 | else { 82 | navbar.classList.add('open'); 83 | toggle.classList.add('hide'); 84 | document.body.classList.add('noscroll'); 85 | } 86 | }; 87 | 88 | // Enable menu button 89 | toggle.addEventListener('click', e => { 90 | toggleMenu(); 91 | }); 92 | 93 | // Hide menu when nav link is clicked 94 | const navbarLinks = navbar.querySelectorAll('a'); 95 | Array.from(navbarLinks).forEach(link => { 96 | link.addEventListener('click', e => { 97 | toggleMenu(); 98 | }); 99 | }); 100 | } 101 | } 102 | 103 | const docsPage = new DocsPage(); 104 | document.addEventListener('DOMContentLoaded', docsPage.init); -------------------------------------------------------------------------------- /test/unit/info.service.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from 'chai'; 2 | import decache = require('decache'); 3 | import { Request } from 'express'; 4 | import 'mocha'; 5 | import * as sinon from 'sinon'; 6 | 7 | import Config from '../../src/core/config'; 8 | import BookmarksService from '../../src/services/bookmarks.service'; 9 | import InfoService from '../../src/services/info.service'; 10 | import { ApiStatus } from '../../src/core/server'; 11 | 12 | describe('InfoService', () => { 13 | let bookmarksService: BookmarksService; 14 | let infoService: InfoService; 15 | let sandbox: sinon.SinonSandbox; 16 | let testConfig: any; 17 | 18 | beforeEach(() => { 19 | testConfig = Config.get(true); 20 | const log = () => { }; 21 | bookmarksService = new BookmarksService(null, log); 22 | infoService = new InfoService(bookmarksService as BookmarksService, log); 23 | sandbox = sinon.createSandbox(); 24 | sandbox.stub(bookmarksService, 'isAcceptingNewSyncs').returns(Promise.resolve(true)); 25 | }); 26 | 27 | afterEach(() => { 28 | sandbox.restore(); 29 | }); 30 | 31 | it('getInfo: should return max sync size config value', async () => { 32 | const req: Partial = {}; 33 | const maxSyncSizeTestVal = 1; 34 | testConfig.maxSyncSize = maxSyncSizeTestVal; 35 | sandbox.stub(Config, 'get').returns(testConfig); 36 | 37 | const response = await infoService.getInfo(req as Request); 38 | expect(response.maxSyncSize).to.equal(maxSyncSizeTestVal); 39 | }); 40 | 41 | it('getInfo: should return message config value', async () => { 42 | const req: Partial = {}; 43 | const messageTestVal = 'Test API message'; 44 | testConfig.status.message = messageTestVal; 45 | sandbox.stub(Config, 'get').returns(testConfig); 46 | 47 | const response = await infoService.getInfo(req as Request); 48 | expect(response.message).to.equal(messageTestVal); 49 | }); 50 | 51 | it('getInfo: should strip script tags from message config value', async () => { 52 | const req: Partial = {}; 53 | const messageTestVal = ``; 54 | testConfig.status.message = messageTestVal; 55 | sandbox.stub(Config, 'get').returns(testConfig); 56 | 57 | const response = await infoService.getInfo(req as Request); 58 | expect(response.message).to.equal(''); 59 | }); 60 | 61 | it('getInfo: should return correct API status when online', async () => { 62 | const req: Partial = {}; 63 | testConfig.status.online = true; 64 | sandbox.stub(Config, 'get').returns(testConfig); 65 | 66 | const response = await infoService.getInfo(req as Request); 67 | expect(response.status).to.equal(ApiStatus.online); 68 | }); 69 | 70 | it('getInfo: should return correct API status when offline', async () => { 71 | const req: Partial = {}; 72 | testConfig.status.online = false; 73 | sandbox.stub(Config, 'get').returns(testConfig); 74 | 75 | const response = await infoService.getInfo(req as Request); 76 | expect(response.status).to.equal(ApiStatus.offline); 77 | }); 78 | 79 | it('getInfo: should return version config value', async () => { 80 | const req: Partial = {}; 81 | const versionTestVal = '0.0.0'; 82 | testConfig.version = versionTestVal; 83 | sandbox.stub(Config, 'get').returns(testConfig); 84 | 85 | const response = await infoService.getInfo(req as Request); 86 | expect(response.version).to.equal(versionTestVal); 87 | }); 88 | }); -------------------------------------------------------------------------------- /test/unit/newSyncLogs.service.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from 'chai'; 2 | import decache = require('decache'); 3 | import { Request } from 'express'; 4 | import 'mocha'; 5 | import * as sinon from 'sinon'; 6 | 7 | import Config from '../../src/core/config'; 8 | import { ClientIpAddressEmptyException } from '../../src/core/exception'; 9 | import NewSyncLogsModel from '../../src/models/newSyncLogs.model'; 10 | import NewSyncLogsService from '../../src/services/newSyncLogs.service'; 11 | 12 | describe('NewSyncLogsService', () => { 13 | const testClientIPAddress = '123.456.789.0'; 14 | let newSyncLogsService: NewSyncLogsService; 15 | let sandbox: sinon.SinonSandbox; 16 | let testConfig: any; 17 | 18 | beforeEach(() => { 19 | testConfig = Config.get(true); 20 | const log = () => { }; 21 | newSyncLogsService = new NewSyncLogsService(null, log); 22 | sandbox = sinon.createSandbox(); 23 | }); 24 | 25 | afterEach(() => { 26 | sandbox.restore(); 27 | }); 28 | 29 | it('createLog: should create a new sync log using the request IP address', async () => { 30 | const req: Partial = { 31 | ip: testClientIPAddress 32 | }; 33 | const saveStub = sandbox.stub(NewSyncLogsModel.prototype, 'save'); 34 | const savedTestLog = await newSyncLogsService.createLog(req as Request); 35 | 36 | expect(saveStub.called).to.be.true; 37 | expect(savedTestLog.ipAddress).to.equal(testClientIPAddress); 38 | }); 39 | 40 | it('createLog: should throw a ClientIpAddressEmptyException if the request IP address could not be ascertained', async () => { 41 | const req: Partial = {}; 42 | 43 | try { 44 | await newSyncLogsService.createLog(req as Request); 45 | } 46 | catch (err) { 47 | expect(err).to.be.an.instanceOf(ClientIpAddressEmptyException); 48 | } 49 | }); 50 | 51 | it('newSyncsLimitHit: should return true if the request IP address has hit the limit for daily new syncs created', async () => { 52 | const req: Partial = { 53 | ip: testClientIPAddress 54 | }; 55 | const dailyNewSyncsLimitTestVal = 1; 56 | testConfig.dailyNewSyncsLimit = dailyNewSyncsLimitTestVal; 57 | sandbox.stub(Config, 'get').returns(testConfig); 58 | const countStub = sandbox.stub(NewSyncLogsModel, 'count').returns({ 59 | exec: () => Promise.resolve(dailyNewSyncsLimitTestVal) 60 | }); 61 | 62 | const limitHit = await newSyncLogsService.newSyncsLimitHit(req as Request); 63 | expect(countStub.called).to.be.true; 64 | expect(limitHit).to.equal(true); 65 | }); 66 | 67 | it('newSyncsLimitHit: should return false if the request IP address has not hit the limit for daily new syncs created', async () => { 68 | const req: Partial = { 69 | ip: testClientIPAddress 70 | }; 71 | testConfig.dailyNewSyncsLimit = 3; 72 | sandbox.stub(Config, 'get').returns(testConfig); 73 | const countStub = sandbox.stub(NewSyncLogsModel, 'count').returns({ 74 | exec: () => Promise.resolve(1) 75 | }); 76 | 77 | const limitHit = await newSyncLogsService.newSyncsLimitHit(req as Request); 78 | expect(countStub.called).to.be.true; 79 | expect(limitHit).to.equal(false); 80 | }); 81 | 82 | it('newSyncsLimitHit: should throw a ClientIpAddressEmptyException if the request IP address could not be ascertained', async () => { 83 | const req: Partial = {}; 84 | 85 | try { 86 | await newSyncLogsService.newSyncsLimitHit(req as Request); 87 | } 88 | catch (err) { 89 | expect(err).to.be.an.instanceOf(ClientIpAddressEmptyException); 90 | } 91 | }); 92 | }); -------------------------------------------------------------------------------- /src/core/exception.ts: -------------------------------------------------------------------------------- 1 | // Base class for custom api exceptions 2 | export class ExceptionBase extends Error { 3 | public message: string; 4 | public name: string; 5 | public status: number; 6 | 7 | constructor(message: string) { 8 | super(message); 9 | } 10 | 11 | public getResponseObject(): any { 12 | return { 13 | code: this.name, 14 | message: this.message 15 | }; 16 | } 17 | } 18 | 19 | export class ClientIpAddressEmptyException extends ExceptionBase { 20 | constructor(message?: string) { 21 | super(message || `Unable to determine client's IP address`); 22 | this.name = 'ClientIpAddressEmptyException'; 23 | this.status = 409; 24 | } 25 | } 26 | 27 | export class InvalidSyncIdException extends ExceptionBase { 28 | constructor(message?: string) { 29 | super(message || 'Invalid sync ID'); 30 | this.name = 'InvalidSyncIdException'; 31 | this.status = 409; 32 | } 33 | } 34 | 35 | export class NewSyncsForbiddenException extends ExceptionBase { 36 | constructor(message?: string) { 37 | super(message || 'The service is not accepting new syncs'); 38 | this.name = 'NewSyncsForbiddenException'; 39 | this.status = 405; 40 | } 41 | } 42 | 43 | export class NewSyncsLimitExceededException extends ExceptionBase { 44 | constructor(message?: string) { 45 | super(message || 'Client has exceeded the daily new syncs limit'); 46 | this.name = 'NewSyncsLimitExceededException'; 47 | this.status = 406; 48 | } 49 | } 50 | 51 | export class NotImplementedException extends ExceptionBase { 52 | constructor(message?: string) { 53 | super(message || 'The requested route has not been implemented'); 54 | this.name = 'NotImplementedException'; 55 | this.status = 404; 56 | } 57 | } 58 | 59 | export class OriginNotPermittedException extends ExceptionBase { 60 | constructor(message?: string) { 61 | super(message || 'Client not permitted to access this service'); 62 | this.name = 'OriginNotPermittedException'; 63 | this.status = 405; 64 | } 65 | } 66 | 67 | export class RequestThrottledException extends ExceptionBase { 68 | constructor(message?: string) { 69 | super(message || 'Too many requests'); 70 | this.name = 'RequestThrottledException'; 71 | this.status = 429; 72 | } 73 | } 74 | 75 | export class RequiredDataNotFoundException extends ExceptionBase { 76 | constructor(message?: string) { 77 | super(message || 'Unable to find required data'); 78 | this.name = 'RequiredDataNotFoundException'; 79 | this.status = 409; 80 | } 81 | } 82 | 83 | export class ServiceNotAvailableException extends ExceptionBase { 84 | constructor(message?: string) { 85 | super(message || 'The service is currently offline'); 86 | this.name = 'ServiceNotAvailableException'; 87 | this.status = 405; 88 | } 89 | } 90 | 91 | export class SyncDataLimitExceededException extends ExceptionBase { 92 | constructor(message?: string) { 93 | super(message || 'Sync data limit exceeded'); 94 | this.name = 'SyncDataLimitExceededException'; 95 | this.status = 413; 96 | } 97 | } 98 | 99 | export class UnspecifiedException extends ExceptionBase { 100 | constructor(message?: string) { 101 | super(message || 'An unspecified error has occurred'); 102 | this.name = 'UnspecifiedException'; 103 | this.status = 500; 104 | } 105 | } 106 | 107 | export class UnsupportedVersionException extends ExceptionBase { 108 | constructor(message?: string) { 109 | super(message || 'The requested API version is not supported'); 110 | this.name = 'UnsupportedVersionException'; 111 | this.status = 405; 112 | } 113 | } -------------------------------------------------------------------------------- /dist/routers/bookmarks.router.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"bookmarks.router.js","sourceRoot":"","sources":["../../src/routers/bookmarks.router.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/services/newSyncLogs.service.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | Object.defineProperty(exports, "__esModule", { value: true }); 11 | const config_1 = require("../core/config"); 12 | const exception_1 = require("../core/exception"); 13 | const server_1 = require("../core/server"); 14 | const newSyncLogs_model_1 = require("../models/newSyncLogs.model"); 15 | const base_service_1 = require("./base.service"); 16 | // Implementation of data service for new sync log operations 17 | class NewSyncLogsService extends base_service_1.default { 18 | // Creates a new sync log entry with the supplied request data 19 | createLog(req) { 20 | return __awaiter(this, void 0, void 0, function* () { 21 | // Get the client's ip address 22 | const clientIp = this.getClientIpAddress(req); 23 | if (!clientIp) { 24 | const err = new exception_1.ClientIpAddressEmptyException(); 25 | this.log(server_1.LogLevel.Error, 'Exception occurred in NewSyncLogsService.createLog', req, err); 26 | throw err; 27 | } 28 | // Create new sync log payload 29 | const newLogPayload = { 30 | ipAddress: clientIp 31 | }; 32 | const newSyncLogsModel = new newSyncLogs_model_1.default(newLogPayload); 33 | // Commit the payload to the db 34 | try { 35 | yield newSyncLogsModel.save(); 36 | } 37 | catch (err) { 38 | this.log(server_1.LogLevel.Error, 'Exception occurred in NewSyncLogsService.createLog', req, err); 39 | throw err; 40 | } 41 | return newLogPayload; 42 | }); 43 | } 44 | // Returns true/false depending on whether a given request's ip address has hit the limit for daily new syncs created 45 | newSyncsLimitHit(req) { 46 | return __awaiter(this, void 0, void 0, function* () { 47 | // Get the client's ip address 48 | const clientIp = this.getClientIpAddress(req); 49 | if (!clientIp) { 50 | const err = new exception_1.ClientIpAddressEmptyException(); 51 | this.log(server_1.LogLevel.Error, 'Exception occurred in NewSyncLogsService.newSyncsLimitHit', req, err); 52 | throw err; 53 | } 54 | let newSyncsCreated = -1; 55 | // Query the newsynclogs collection for the total number of logs for the given ip address 56 | try { 57 | newSyncsCreated = yield newSyncLogs_model_1.default.count({ ipAddress: clientIp }).exec(); 58 | } 59 | catch (err) { 60 | this.log(server_1.LogLevel.Error, 'Exception occurred in NewSyncLogsService.newSyncsLimitHit', req, err); 61 | throw err; 62 | } 63 | // Ensure a valid count was returned 64 | if (newSyncsCreated < 0) { 65 | const err = new exception_1.UnspecifiedException('New syncs created count cannot be less than zero'); 66 | this.log(server_1.LogLevel.Error, 'Exception occurred in NewSyncLogsService.newSyncsLimitHit', req, err); 67 | throw err; 68 | } 69 | // Check returned count against config setting 70 | return newSyncsCreated >= config_1.default.get().dailyNewSyncsLimit; 71 | }); 72 | } 73 | // Extracts the client's ip address from a given request 74 | getClientIpAddress(req) { 75 | if (!req || !req.ip) { 76 | return; 77 | } 78 | return req.ip; 79 | } 80 | } 81 | exports.default = NewSyncLogsService; 82 | //# sourceMappingURL=newSyncLogs.service.js.map -------------------------------------------------------------------------------- /dist/core/exception.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | // Base class for custom api exceptions 4 | class ExceptionBase extends Error { 5 | constructor(message) { 6 | super(message); 7 | } 8 | getResponseObject() { 9 | return { 10 | code: this.name, 11 | message: this.message 12 | }; 13 | } 14 | } 15 | exports.ExceptionBase = ExceptionBase; 16 | class ClientIpAddressEmptyException extends ExceptionBase { 17 | constructor(message) { 18 | super(message || `Unable to determine client's IP address`); 19 | this.name = 'ClientIpAddressEmptyException'; 20 | this.status = 409; 21 | } 22 | } 23 | exports.ClientIpAddressEmptyException = ClientIpAddressEmptyException; 24 | class InvalidSyncIdException extends ExceptionBase { 25 | constructor(message) { 26 | super(message || 'Invalid sync ID'); 27 | this.name = 'InvalidSyncIdException'; 28 | this.status = 409; 29 | } 30 | } 31 | exports.InvalidSyncIdException = InvalidSyncIdException; 32 | class NewSyncsForbiddenException extends ExceptionBase { 33 | constructor(message) { 34 | super(message || 'The service is not accepting new syncs'); 35 | this.name = 'NewSyncsForbiddenException'; 36 | this.status = 405; 37 | } 38 | } 39 | exports.NewSyncsForbiddenException = NewSyncsForbiddenException; 40 | class NewSyncsLimitExceededException extends ExceptionBase { 41 | constructor(message) { 42 | super(message || 'Client has exceeded the daily new syncs limit'); 43 | this.name = 'NewSyncsLimitExceededException'; 44 | this.status = 406; 45 | } 46 | } 47 | exports.NewSyncsLimitExceededException = NewSyncsLimitExceededException; 48 | class NotImplementedException extends ExceptionBase { 49 | constructor(message) { 50 | super(message || 'The requested route has not been implemented'); 51 | this.name = 'NotImplementedException'; 52 | this.status = 404; 53 | } 54 | } 55 | exports.NotImplementedException = NotImplementedException; 56 | class OriginNotPermittedException extends ExceptionBase { 57 | constructor(message) { 58 | super(message || 'Client not permitted to access this service'); 59 | this.name = 'OriginNotPermittedException'; 60 | this.status = 405; 61 | } 62 | } 63 | exports.OriginNotPermittedException = OriginNotPermittedException; 64 | class RequestThrottledException extends ExceptionBase { 65 | constructor(message) { 66 | super(message || 'Too many requests'); 67 | this.name = 'RequestThrottledException'; 68 | this.status = 429; 69 | } 70 | } 71 | exports.RequestThrottledException = RequestThrottledException; 72 | class RequiredDataNotFoundException extends ExceptionBase { 73 | constructor(message) { 74 | super(message || 'Unable to find required data'); 75 | this.name = 'RequiredDataNotFoundException'; 76 | this.status = 409; 77 | } 78 | } 79 | exports.RequiredDataNotFoundException = RequiredDataNotFoundException; 80 | class ServiceNotAvailableException extends ExceptionBase { 81 | constructor(message) { 82 | super(message || 'The service is currently offline'); 83 | this.name = 'ServiceNotAvailableException'; 84 | this.status = 405; 85 | } 86 | } 87 | exports.ServiceNotAvailableException = ServiceNotAvailableException; 88 | class SyncDataLimitExceededException extends ExceptionBase { 89 | constructor(message) { 90 | super(message || 'Sync data limit exceeded'); 91 | this.name = 'SyncDataLimitExceededException'; 92 | this.status = 413; 93 | } 94 | } 95 | exports.SyncDataLimitExceededException = SyncDataLimitExceededException; 96 | class UnspecifiedException extends ExceptionBase { 97 | constructor(message) { 98 | super(message || 'An unspecified error has occurred'); 99 | this.name = 'UnspecifiedException'; 100 | this.status = 500; 101 | } 102 | } 103 | exports.UnspecifiedException = UnspecifiedException; 104 | class UnsupportedVersionException extends ExceptionBase { 105 | constructor(message) { 106 | super(message || 'The requested API version is not supported'); 107 | this.name = 'UnsupportedVersionException'; 108 | this.status = 405; 109 | } 110 | } 111 | exports.UnsupportedVersionException = UnsupportedVersionException; 112 | //# sourceMappingURL=exception.js.map -------------------------------------------------------------------------------- /test/integration/server.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, request, use } from 'chai'; 2 | import chaiHttp = require('chai-http'); 3 | import decache = require('decache'); 4 | import 'mocha'; 5 | import * as sinon from 'sinon'; 6 | 7 | import Config from '../../src/core/config'; 8 | import { 9 | NotImplementedException, 10 | OriginNotPermittedException, 11 | RequestThrottledException, 12 | UnsupportedVersionException 13 | } from '../../src/core/exception'; 14 | import Server from '../../src/core/server'; 15 | 16 | before(() => { 17 | use(chaiHttp); 18 | }); 19 | 20 | after(() => { 21 | setTimeout(() => { 22 | process.exit(0); 23 | }, 100); 24 | }); 25 | 26 | describe('Server', () => { 27 | let sandbox: sinon.SinonSandbox; 28 | let server: Server; 29 | let testConfig: any; 30 | 31 | beforeEach(async () => { 32 | testConfig = Config.get(true); 33 | testConfig.log.enabled = false; 34 | testConfig.db.name = testConfig.tests.db; 35 | testConfig.server.port = testConfig.tests.port; 36 | sandbox = sinon.createSandbox(); 37 | 38 | server = new Server(); 39 | server.logToConsoleEnabled(false); 40 | await server.init(); 41 | await server.start(); 42 | }); 43 | 44 | afterEach(async () => { 45 | await server.stop(); 46 | sandbox.restore(); 47 | }); 48 | 49 | it('Should return a NotImplementedException error code for an invalid route', async () => { 50 | sandbox.stub(Config, 'get').returns(testConfig); 51 | 52 | await new Promise((resolve) => { 53 | request(server.Application) 54 | .get('/bookmarks') 55 | .set('content-type', 'application/json') 56 | .end((err, res) => { 57 | expect(res).to.have.status((new NotImplementedException()).status); 58 | expect(res).to.be.json; 59 | expect(res.body).to.be.an('object'); 60 | resolve(); 61 | }); 62 | }); 63 | }); 64 | 65 | it('Should return an OriginNotPermittedException error code when requested api version is not supported', async () => { 66 | await server.stop(); 67 | testConfig.allowedOrigins = ['http://test.com']; 68 | sandbox.stub(Config, 'get').returns(testConfig); 69 | server = new Server(); 70 | server.logToConsoleEnabled(false); 71 | await server.init(); 72 | await server.start(); 73 | 74 | await new Promise((resolve) => { 75 | request(server.Application) 76 | .get('/info') 77 | .set('content-type', 'application/json') 78 | .end((err, res) => { 79 | expect(res).to.have.status((new OriginNotPermittedException()).status); 80 | expect(res).to.be.json; 81 | expect(res.body).to.be.an('object'); 82 | resolve(); 83 | }); 84 | }); 85 | }); 86 | 87 | it('Should return a RequestThrottledException error code when request throttling is triggered', async () => { 88 | await server.stop(); 89 | testConfig.throttle.maxRequests = 1; 90 | sandbox.stub(Config, 'get').returns(testConfig); 91 | server = new Server(); 92 | server.logToConsoleEnabled(false); 93 | await server.init(); 94 | await server.start(); 95 | 96 | await new Promise((resolve) => { 97 | request(server.Application) 98 | .get('/info') 99 | .set('content-type', 'application/json') 100 | .end((err, res) => { 101 | resolve(); 102 | }); 103 | }); 104 | 105 | await new Promise((resolve) => { 106 | request(server.Application) 107 | .get('/info') 108 | .set('content-type', 'application/json') 109 | .end((err, res) => { 110 | expect(res).to.have.status((new RequestThrottledException()).status); 111 | expect(res).to.be.json; 112 | expect(res.body).to.be.an('object'); 113 | resolve(); 114 | }); 115 | }); 116 | }); 117 | 118 | it('Should return an UnsupportedVersionException error code when requested api version is not supported', async () => { 119 | sandbox.stub(Config, 'get').returns(testConfig); 120 | 121 | await new Promise((resolve) => { 122 | request(server.Application) 123 | .get('/info') 124 | .set('content-type', 'application/json') 125 | .set('accept-version', '0.0.0') 126 | .end((err, res) => { 127 | expect(res).to.have.status((new UnsupportedVersionException()).status); 128 | expect(res).to.be.json; 129 | expect(res.body).to.be.an('object'); 130 | resolve(); 131 | }); 132 | }); 133 | }); 134 | }); -------------------------------------------------------------------------------- /dist/docs/index.html: -------------------------------------------------------------------------------- 1 | xBrowserSync API

xBrowserSync

API service info

Status:

Version:

This xBrowserSync service provides a REST API that xBrowserSync clients can sync to. The available API methods are listed below.

For more information visit the xBrowserSync website or check out the API GitHub repo.

Bookmarks

Create Bookmarks

Post /bookmarks

Creates a new (empty) bookmark sync and returns the corresponding ID.

Post body example:

{
 2 |     "version":"1.0.0"
 3 | }
  • version: Version number of the xBrowserSync client used to create the sync.

Response example:

{
 4 |     "id":"52758cb942814faa9ab255208025ae59",
 5 |     "lastUpdated":"2016-07-06T12:43:16.866Z",
 6 |     "version":"1.0.0"
 7 | }
  • id: 32 character alphanumeric sync ID.
  • lastUpdated: Last updated timestamp for created bookmarks.
  • version: Version number of the xBrowserSync client used to create the sync.

Get Bookmarks

Get /bookmarks/{id}

Retrieves the bookmark sync corresponding to the provided sync ID.

Query params:

  • id: 32 character alphanumeric sync ID.

Response example:

{
 8 |     "bookmarks":"DWCx6wR9ggPqPRrhU4O4oLN5P09onApoAULX4Xt+ckxswtFNds...",
 9 |     "lastUpdated":"2016-07-06T12:43:16.866Z",
10 |     "version":"1.0.0"
11 | }
  • bookmarks: Encrypted bookmark data salted using secret value.
  • lastUpdated: Last updated timestamp for retrieved bookmarks.
  • version: Version number of the xBrowserSync client used to create the sync.

Update Bookmarks

Put /bookmarks/{id}

Updates the bookmark sync data corresponding to the provided sync ID with the provided encrypted bookmarks data.

Query params:

  • id: 32 character alphanumeric sync ID.

Post body example:

{
12 |     "bookmarks":"DWCx6wR9ggPqPRrhU4O4oLN5P09onApoAULX4Xt+ckxswtFNds..."
13 | }
  • bookmarks: Encrypted bookmark data salted using secret value.

Response example:

{
14 |     "lastUpdated":"2016-07-06T12:43:16.866Z"
15 | }
  • lastUpdated: Last updated timestamp for updated bookmarks.

Get Last Updated

Get /bookmarks/{id}/lastUpdated

Retrieves the bookmark sync last updated time stamp corresponding to the provided sync ID.

Query params:

  • id: 32 character alphanumeric sync ID.

Response example:

{
16 |     "lastUpdated":"2016-07-06T12:43:16.866Z"
17 | }
  • lastUpdated: Last updated timestamp for corresponding bookmarks.

Get Sync Version

Get /bookmarks/{id}/version

Retrieves the bookmark sync version number of the xBrowserSync client used to create the bookmarks sync corresponding to the provided sync ID.

Query params:

  • id: 32 character alphanumeric sync ID.

Response example:

{
18 |     "lastUpdated":"2016-07-06T12:43:16.866Z"
19 | }
  • lastUpdated: Last updated timestamp for corresponding bookmarks.

Service Information

Get Service Information

Get /info

Retrieves information describing the xBrowserSync service.

Response example:

{
20 |     "maxSyncSize":204800,
21 |     "message":"",
22 |     "status":1,
23 |     "version":"1.0.0"
24 | }
  • status: Current service status code. 1 = Online; 2 = Offline; 3 = Not accepting new syncs.
  • message: Service information message.
  • version: API version service is using.
  • maxSyncSize: Maximum sync size (in bytes) allowed by the service.
-------------------------------------------------------------------------------- /src/routers/bookmarks.router.ts: -------------------------------------------------------------------------------- 1 | import { autobind } from 'core-decorators'; 2 | import { NextFunction, Request, Response } from 'express'; 3 | 4 | import DB from '../core/db'; 5 | import { RequiredDataNotFoundException } from '../core/exception'; 6 | import { ApiVerb } from '../core/server'; 7 | import BaseRouter, { IApiRouter } from '../routers/base.router'; 8 | import BookmarksService from '../services/bookmarks.service'; 9 | 10 | // Implementation of routes for bookmarks operations 11 | export default class BookmarksRouter extends BaseRouter implements IApiRouter { 12 | // Initialises the routes for this router implementation 13 | public initRoutes(): void { 14 | this.app.use('/bookmarks', this.router); 15 | this.createRoute(ApiVerb.post, '/', { 16 | '~1.0.0': this.createBookmarks_v1, 17 | // tslint:disable-next-line:object-literal-sort-keys 18 | '^1.1.3': this.createBookmarks_v2 19 | }); 20 | this.createRoute(ApiVerb.get, '/:id', { '^1.0.0': this.getBookmarks }); 21 | this.createRoute(ApiVerb.put, '/:id', { 22 | '~1.0.0': this.updateBookmarks_v1, 23 | // tslint:disable-next-line:object-literal-sort-keys 24 | '^1.1.3': this.updateBookmarks_v2 25 | }); 26 | this.createRoute(ApiVerb.get, '/:id/lastUpdated', { '^1.0.0': this.getLastUpdated }); 27 | this.createRoute(ApiVerb.get, '/:id/version', { '^1.1.3': this.getVersion }); 28 | } 29 | 30 | // Creates a new bookmarks sync and returns new sync ID 31 | @autobind 32 | private async createBookmarks_v1(req: Request, res: Response, next: NextFunction): Promise { 33 | try { 34 | // Get posted bookmarks data 35 | const bookmarksData = this.getBookmarksData(req); 36 | if (bookmarksData === '') { 37 | throw new RequiredDataNotFoundException; 38 | } 39 | 40 | // Call service method to create new bookmarks sync and return response as json 41 | const newSync = await this.service.createBookmarks_v1(bookmarksData, req); 42 | res.json(newSync); 43 | } 44 | catch (err) { 45 | next(err); 46 | } 47 | } 48 | 49 | // Creates an empty sync using sync version and returns new sync ID 50 | @autobind 51 | private async createBookmarks_v2(req: Request, res: Response, next: NextFunction): Promise { 52 | try { 53 | // Get posted sync version 54 | const syncVersion = req.body.version; 55 | if (!syncVersion) { 56 | throw new RequiredDataNotFoundException; 57 | } 58 | 59 | // Call service method to create new sync and return response as json 60 | const newSync = await this.service.createBookmarks_v2(req.body.version, req); 61 | res.json(newSync); 62 | } 63 | catch (err) { 64 | next(err); 65 | } 66 | } 67 | 68 | // Retrieves an existing sync with a provided sync ID 69 | @autobind 70 | private async getBookmarks(req: Request, res: Response, next: NextFunction): Promise { 71 | try { 72 | // Check sync id has been provided 73 | const id = this.getSyncId(req); 74 | 75 | // Call service method to retrieve bookmarks data and return response as json 76 | const bookmarks = await this.service.getBookmarks(id, req); 77 | res.json(bookmarks); 78 | } 79 | catch (err) { 80 | next(err); 81 | } 82 | } 83 | 84 | // Retrieves posted bookmarks data from request body 85 | private getBookmarksData(req: Request): string { 86 | return req.body.bookmarks || ''; 87 | } 88 | 89 | // Retrieves last updated date for a given sync ID 90 | @autobind 91 | private async getLastUpdated(req: Request, res: Response, next: NextFunction): Promise { 92 | try { 93 | // Check sync id has been provided 94 | const id = this.getSyncId(req); 95 | 96 | // Call service method to get bookmarks last updated date and return response as json 97 | const lastUpdated = await this.service.getLastUpdated(id, req); 98 | res.json(lastUpdated); 99 | } 100 | catch (err) { 101 | next(err); 102 | } 103 | } 104 | 105 | // Retrieves the sync ID from the request query string parameters 106 | private getSyncId(req: Request): string { 107 | const id = req.params.id; 108 | 109 | // Check id is valid 110 | DB.idIsValid(id); 111 | 112 | return id; 113 | } 114 | 115 | // Retrieves sync version for a given sync ID 116 | @autobind 117 | private async getVersion(req: Request, res: Response, next: NextFunction): Promise { 118 | try { 119 | // Check sync id has been provided 120 | const id = this.getSyncId(req); 121 | 122 | // Call service method to get sync version and return response as json 123 | const syncVersion = await this.service.getVersion(id, req); 124 | res.json(syncVersion); 125 | } 126 | catch (err) { 127 | next(err); 128 | } 129 | } 130 | 131 | // Updates bookmarks data for a given bookmarks sync ID 132 | @autobind 133 | private async updateBookmarks_v1(req: Request, res: Response, next: NextFunction): Promise { 134 | try { 135 | // Check sync id has been provided 136 | const id = this.getSyncId(req); 137 | 138 | // Get posted bookmarks data 139 | const bookmarksData = this.getBookmarksData(req); 140 | if (bookmarksData === '') { 141 | throw new RequiredDataNotFoundException; 142 | } 143 | 144 | // Call service method to update bookmarks data and return response as json 145 | const bookmarksSync = await this.service.updateBookmarks_v1(id, bookmarksData, req); 146 | res.json(bookmarksSync); 147 | } 148 | catch (err) { 149 | next(err); 150 | } 151 | } 152 | 153 | // Updates bookmarks sync bookmarks data and sync version for a given bookmarks sync ID 154 | @autobind 155 | private async updateBookmarks_v2(req: Request, res: Response, next: NextFunction): Promise { 156 | try { 157 | // Check sync id has been provided 158 | const id = this.getSyncId(req); 159 | 160 | // Get posted bookmarks data 161 | const bookmarksData = this.getBookmarksData(req); 162 | if (bookmarksData === '') { 163 | throw new RequiredDataNotFoundException; 164 | } 165 | 166 | // Call service method to update bookmarks data and return response as json 167 | const bookmarksSync = await this.service.updateBookmarks_v2(id, bookmarksData, req.body.version, req); 168 | res.json(bookmarksSync); 169 | } 170 | catch (err) { 171 | next(err); 172 | } 173 | } 174 | } -------------------------------------------------------------------------------- /dist/services/bookmarks.service.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"bookmarks.service.js","sourceRoot":"","sources":["../../src/services/bookmarks.service.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/docs/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 63 | 68 | 73 | 76 | 82 | 84 | 89 | 95 | 97 | 99 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /src/docs/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 63 | 68 | 73 | 76 | 82 | 84 | 89 | 95 | 97 | 99 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /dist/docs/89e40751d0e7b7f69bd0e2dec3e98f18.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 63 | 68 | 73 | 76 | 82 | 84 | 89 | 95 | 97 | 99 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /dist/core/server.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/core/server.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/routers/bookmarks.router.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 | else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; 6 | return c > 3 && r && Object.defineProperty(target, key, r), r; 7 | }; 8 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 9 | return new (P || (P = Promise))(function (resolve, reject) { 10 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 11 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 12 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 13 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 14 | }); 15 | }; 16 | Object.defineProperty(exports, "__esModule", { value: true }); 17 | const core_decorators_1 = require("core-decorators"); 18 | const db_1 = require("../core/db"); 19 | const exception_1 = require("../core/exception"); 20 | const server_1 = require("../core/server"); 21 | const base_router_1 = require("../routers/base.router"); 22 | // Implementation of routes for bookmarks operations 23 | class BookmarksRouter extends base_router_1.default { 24 | // Initialises the routes for this router implementation 25 | initRoutes() { 26 | this.app.use('/bookmarks', this.router); 27 | this.createRoute(server_1.ApiVerb.post, '/', { 28 | '~1.0.0': this.createBookmarks_v1, 29 | // tslint:disable-next-line:object-literal-sort-keys 30 | '^1.1.3': this.createBookmarks_v2 31 | }); 32 | this.createRoute(server_1.ApiVerb.get, '/:id', { '^1.0.0': this.getBookmarks }); 33 | this.createRoute(server_1.ApiVerb.put, '/:id', { 34 | '~1.0.0': this.updateBookmarks_v1, 35 | // tslint:disable-next-line:object-literal-sort-keys 36 | '^1.1.3': this.updateBookmarks_v2 37 | }); 38 | this.createRoute(server_1.ApiVerb.get, '/:id/lastUpdated', { '^1.0.0': this.getLastUpdated }); 39 | this.createRoute(server_1.ApiVerb.get, '/:id/version', { '^1.1.3': this.getVersion }); 40 | } 41 | // Creates a new bookmarks sync and returns new sync ID 42 | createBookmarks_v1(req, res, next) { 43 | return __awaiter(this, void 0, void 0, function* () { 44 | try { 45 | // Get posted bookmarks data 46 | const bookmarksData = this.getBookmarksData(req); 47 | if (bookmarksData === '') { 48 | throw new exception_1.RequiredDataNotFoundException; 49 | } 50 | // Call service method to create new bookmarks sync and return response as json 51 | const newSync = yield this.service.createBookmarks_v1(bookmarksData, req); 52 | res.json(newSync); 53 | } 54 | catch (err) { 55 | next(err); 56 | } 57 | }); 58 | } 59 | // Creates an empty sync using sync version and returns new sync ID 60 | createBookmarks_v2(req, res, next) { 61 | return __awaiter(this, void 0, void 0, function* () { 62 | try { 63 | // Get posted sync version 64 | const syncVersion = req.body.version; 65 | if (!syncVersion) { 66 | throw new exception_1.RequiredDataNotFoundException; 67 | } 68 | // Call service method to create new sync and return response as json 69 | const newSync = yield this.service.createBookmarks_v2(req.body.version, req); 70 | res.json(newSync); 71 | } 72 | catch (err) { 73 | next(err); 74 | } 75 | }); 76 | } 77 | // Retrieves an existing sync with a provided sync ID 78 | getBookmarks(req, res, next) { 79 | return __awaiter(this, void 0, void 0, function* () { 80 | try { 81 | // Check sync id has been provided 82 | const id = this.getSyncId(req); 83 | // Call service method to retrieve bookmarks data and return response as json 84 | const bookmarks = yield this.service.getBookmarks(id, req); 85 | res.json(bookmarks); 86 | } 87 | catch (err) { 88 | next(err); 89 | } 90 | }); 91 | } 92 | // Retrieves posted bookmarks data from request body 93 | getBookmarksData(req) { 94 | return req.body.bookmarks || ''; 95 | } 96 | // Retrieves last updated date for a given sync ID 97 | getLastUpdated(req, res, next) { 98 | return __awaiter(this, void 0, void 0, function* () { 99 | try { 100 | // Check sync id has been provided 101 | const id = this.getSyncId(req); 102 | // Call service method to get bookmarks last updated date and return response as json 103 | const lastUpdated = yield this.service.getLastUpdated(id, req); 104 | res.json(lastUpdated); 105 | } 106 | catch (err) { 107 | next(err); 108 | } 109 | }); 110 | } 111 | // Retrieves the sync ID from the request query string parameters 112 | getSyncId(req) { 113 | const id = req.params.id; 114 | // Check id is valid 115 | db_1.default.idIsValid(id); 116 | return id; 117 | } 118 | // Retrieves sync version for a given sync ID 119 | getVersion(req, res, next) { 120 | return __awaiter(this, void 0, void 0, function* () { 121 | try { 122 | // Check sync id has been provided 123 | const id = this.getSyncId(req); 124 | // Call service method to get sync version and return response as json 125 | const syncVersion = yield this.service.getVersion(id, req); 126 | res.json(syncVersion); 127 | } 128 | catch (err) { 129 | next(err); 130 | } 131 | }); 132 | } 133 | // Updates bookmarks data for a given bookmarks sync ID 134 | updateBookmarks_v1(req, res, next) { 135 | return __awaiter(this, void 0, void 0, function* () { 136 | try { 137 | // Check sync id has been provided 138 | const id = this.getSyncId(req); 139 | // Get posted bookmarks data 140 | const bookmarksData = this.getBookmarksData(req); 141 | if (bookmarksData === '') { 142 | throw new exception_1.RequiredDataNotFoundException; 143 | } 144 | // Call service method to update bookmarks data and return response as json 145 | const bookmarksSync = yield this.service.updateBookmarks_v1(id, bookmarksData, req); 146 | res.json(bookmarksSync); 147 | } 148 | catch (err) { 149 | next(err); 150 | } 151 | }); 152 | } 153 | // Updates bookmarks sync bookmarks data and sync version for a given bookmarks sync ID 154 | updateBookmarks_v2(req, res, next) { 155 | return __awaiter(this, void 0, void 0, function* () { 156 | try { 157 | // Check sync id has been provided 158 | const id = this.getSyncId(req); 159 | // Get posted bookmarks data 160 | const bookmarksData = this.getBookmarksData(req); 161 | if (bookmarksData === '') { 162 | throw new exception_1.RequiredDataNotFoundException; 163 | } 164 | // Call service method to update bookmarks data and return response as json 165 | const bookmarksSync = yield this.service.updateBookmarks_v2(id, bookmarksData, req.body.version, req); 166 | res.json(bookmarksSync); 167 | } 168 | catch (err) { 169 | next(err); 170 | } 171 | }); 172 | } 173 | } 174 | __decorate([ 175 | core_decorators_1.autobind 176 | ], BookmarksRouter.prototype, "createBookmarks_v1", null); 177 | __decorate([ 178 | core_decorators_1.autobind 179 | ], BookmarksRouter.prototype, "createBookmarks_v2", null); 180 | __decorate([ 181 | core_decorators_1.autobind 182 | ], BookmarksRouter.prototype, "getBookmarks", null); 183 | __decorate([ 184 | core_decorators_1.autobind 185 | ], BookmarksRouter.prototype, "getLastUpdated", null); 186 | __decorate([ 187 | core_decorators_1.autobind 188 | ], BookmarksRouter.prototype, "getVersion", null); 189 | __decorate([ 190 | core_decorators_1.autobind 191 | ], BookmarksRouter.prototype, "updateBookmarks_v1", null); 192 | __decorate([ 193 | core_decorators_1.autobind 194 | ], BookmarksRouter.prototype, "updateBookmarks_v2", null); 195 | exports.default = BookmarksRouter; 196 | //# sourceMappingURL=bookmarks.router.js.map -------------------------------------------------------------------------------- /src/docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | xBrowserSync API 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 | 18 | 22 | 23 | 44 | 45 |
46 |
47 |
48 |
49 |

xBrowserSync

50 |
51 |
52 |

API service info

53 |
54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 |
62 |

63 | Status: 64 | 65 |

66 |
67 |
68 |

69 | Version: 70 | 71 |

72 |
73 |
74 |
75 |
76 |

This xBrowserSync service provides a REST API that xBrowserSync clients can sync to. 77 | The available API methods are listed below.

78 |

For more information visit the 79 | xBrowserSync website or check out the 80 | API GitHub repo.

81 |
82 |
83 |
84 |
85 | 86 |
87 |
88 |
89 |

Bookmarks

90 |
91 |

Create Bookmarks

92 |
93 | Post 94 | /bookmarks 95 |
96 |
97 |

Creates a new (empty) bookmark sync and returns the corresponding ID.

98 |
99 |

Post body example:

100 |
{
101 |     "version":"1.0.0"
102 | }
103 |
    104 |
  • 105 | version: Version number of the xBrowserSync client used to create the sync.
  • 106 |
107 |

Response example:

108 |
{
109 |     "id":"52758cb942814faa9ab255208025ae59",
110 |     "lastUpdated":"2016-07-06T12:43:16.866Z",
111 |     "version":"1.0.0"
112 | }
113 |
    114 |
  • 115 | id: 32 character alphanumeric sync ID.
  • 116 |
  • 117 | lastUpdated: Last updated timestamp for created bookmarks.
  • 118 |
  • 119 | version: Version number of the xBrowserSync client used to create the sync.
  • 120 |
121 |
122 | 123 |
124 |

Get Bookmarks

125 |
126 | Get 127 | /bookmarks/{id} 128 |
129 |
130 |

Retrieves the bookmark sync corresponding to the provided sync ID.

131 |
132 |

Query params:

133 |
    134 |
  • 135 | id: 32 character alphanumeric sync ID.
  • 136 |
137 |

Response example:

138 |
{
139 |     "bookmarks":"DWCx6wR9ggPqPRrhU4O4oLN5P09onApoAULX4Xt+ckxswtFNds...",
140 |     "lastUpdated":"2016-07-06T12:43:16.866Z",
141 |     "version":"1.0.0"
142 | }
143 |
    144 |
  • 145 | bookmarks: Encrypted bookmark data salted using secret value.
  • 146 |
  • 147 | lastUpdated: Last updated timestamp for retrieved bookmarks.
  • 148 |
  • 149 | version: Version number of the xBrowserSync client used to create the sync.
  • 150 |
151 |
152 | 153 |
154 |

Update Bookmarks

155 |
156 | Put 157 | /bookmarks/{id} 158 |
159 |
160 |

Updates the bookmark sync data corresponding to the provided sync ID with the provided encrypted bookmarks data.

161 |
162 |

Query params:

163 |
    164 |
  • 165 | id: 32 character alphanumeric sync ID.
  • 166 |
167 |

Post body example:

168 |
{
169 |     "bookmarks":"DWCx6wR9ggPqPRrhU4O4oLN5P09onApoAULX4Xt+ckxswtFNds..."
170 | }
171 |
    172 |
  • 173 | bookmarks: Encrypted bookmark data salted using secret value.
  • 174 |
175 |

Response example:

176 |
{
177 |     "lastUpdated":"2016-07-06T12:43:16.866Z"
178 | }
179 |
    180 |
  • 181 | lastUpdated: Last updated timestamp for updated bookmarks.
  • 182 |
183 |
184 | 185 |
186 |

Get Last Updated

187 |
188 | Get 189 | /bookmarks/{id}/lastUpdated 190 |
191 |
192 |

Retrieves the bookmark sync last updated time stamp corresponding to the provided sync ID.

193 |
194 |

Query params:

195 |
    196 |
  • 197 | id: 32 character alphanumeric sync ID.
  • 198 |
199 |

Response example:

200 |
{
201 |     "lastUpdated":"2016-07-06T12:43:16.866Z"
202 | }
203 |
    204 |
  • 205 | lastUpdated: Last updated timestamp for corresponding bookmarks.
  • 206 |
207 |
208 | 209 |
210 |

Get Sync Version

211 |
212 | Get 213 | /bookmarks/{id}/version 214 |
215 |
216 |

Retrieves the bookmark sync version number of the xBrowserSync client used to create the bookmarks sync corresponding 217 | to the provided sync ID.

218 |
219 |

Query params:

220 |
    221 |
  • 222 | id: 32 character alphanumeric sync ID.
  • 223 |
224 |

Response example:

225 |
{
226 |     "lastUpdated":"2016-07-06T12:43:16.866Z"
227 | }
228 |
    229 |
  • 230 | lastUpdated: Last updated timestamp for corresponding bookmarks.
  • 231 |
232 |
233 | 234 |
235 |
236 |
237 | 238 |
239 |
240 |
241 |

Service Information

242 |
243 |

Get Service Information

244 |
245 | Get 246 | /info 247 |
248 |
249 |

Retrieves information describing the xBrowserSync service.

250 |
251 |

Response example:

252 |
{
253 |     "maxSyncSize":204800,
254 |     "message":"",
255 |     "status":1,
256 |     "version":"1.0.0"
257 | }
258 |
    259 |
  • 260 | status: Current service status code. 1 = Online; 2 = Offline; 3 = Not accepting new syncs.
  • 261 |
  • 262 | message: Service information message.
  • 263 |
  • 264 | version: API version service is using.
  • 265 |
  • 266 | maxSyncSize: Maximum sync size (in bytes) allowed by the service.
  • 267 |
268 |
269 |
270 |
271 |
272 | 273 | 274 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xBrowserSync 2 | ## API service 3 | 4 | [![Build Status](https://travis-ci.org/xBrowserSync/API.svg)](https://travis-ci.org/xBrowserSync/API) [![Dependencies](https://david-dm.org/xBrowserSync/API/status.svg)](https://david-dm.org/xBrowserSync/API) [![Dev Dependencies](https://david-dm.org/xBrowserSync/API/dev-status.svg)](https://david-dm.org/xBrowserSync/API?type=dev) [![GitHub license](https://img.shields.io/github/license/xBrowserSync/API.svg)](https://github.com/xBrowserSync/API/blob/master/LICENSE.md) 5 | 6 | [![GitHub forks](https://img.shields.io/github/forks/xBrowserSync/API.svg?style=social&label=Fork)](https://github.com/xBrowserSync/API/fork) 7 | [![GitHub stars](https://img.shields.io/github/stars/xBrowserSync/API.svg?style=social&label=Star)](https://github.com/xBrowserSync/API) 8 | 9 | xBrowserSync is a free tool for syncing browser data between different browsers and devices, built for privacy and anonymity. For full details, see [www.xbrowsersync.org](https://www.xbrowsersync.org/). 10 | 11 | This repository contains the source code for the REST service API that client applications communicate with. If you'd like to run your own xBrowserSync service on your [Node.js](https://nodejs.org/) web server, follow the installation steps below. 12 | 13 | Once configured, you can begin syncing your browser data to your xBrowserSync service, and if you're feeling generous, [allow others to sync their data to your service](https://www.xbrowsersync.org/#getinvolved) also! 14 | 15 | ## Prerequisites 16 | 17 | - [Node.js v8 (LTS)](https://nodejs.org/) 18 | - [mongoDB v3](https://www.mongodb.com/) 19 | 20 | ## Upgrading from an earlier version 21 | 22 | If you are curently running v1.0.3 (or earlier) of the xBrowserSync API, you will need to export existing syncs and delete the xBrowserSync database before upgrading. 23 | 24 | To export existing syncs, run the following command: 25 | 26 | ``` 27 | mongoexport --db xBrowserSync -c bookmarks --out /path/to/export/file 28 | ``` 29 | 30 | Then to delete the database, run the following commands in the mongo shell: 31 | 32 | ``` 33 | use xBrowserSync 34 | db.dropAllUsers() 35 | db.dropDatabase() 36 | ``` 37 | 38 | Once you've upgraded and completed the installation steps below, you can import the syncs by running the following command: 39 | 40 | ``` 41 | mongoimport --db xbrowsersync -c bookmarks --file /path/to/export/file 42 | ``` 43 | 44 | ## Installation 45 | 46 | ### 1. Install and build xBrowserSync API package 47 | 48 | CD into the source directory and install dependencies (use `unsafe-perm` flag if you get any permissions issues whilst trying to install): 49 | 50 | $ npm install --unsafe-perm 51 | 52 | ### 2. Configure mongoDB database 53 | 54 | 1. Run the following commands in the mongo shell: 55 | 56 | (Replace `[password]` with a cleartext password of your choice) 57 | 58 | ``` 59 | use xbrowsersync 60 | db.createUser({ user: "xbrowsersyncdb", pwd: "[password]", roles: ["readWrite"] }) 61 | db.newsynclogs.createIndex( { "expiresAt": 1 }, { expireAfterSeconds: 0 } ) 62 | db.newsynclogs.createIndex({ "ipAddress": 1 }) 63 | ``` 64 | 65 | 2. Add the following environment variables to hold xBrowserSync DB account username and password: 66 | 67 | - `XBROWSERSYNC_DB_USER` 68 | - `XBROWSERSYNC_DB_PWD` 69 | 70 | On Windows, open a Command Prompt and type (replacing `[password]` with the password entered in the mongo shell): 71 | 72 | ``` 73 | setx XBROWSERSYNC_DB_USER "xbrowsersyncdb" 74 | setx XBROWSERSYNC_DB_PWD "[password]" 75 | ``` 76 | 77 | On Ubuntu/Debian Linux, open a terminal emulator and type: 78 | 79 | ``` 80 | $ pico ~/.profile 81 | ``` 82 | 83 | Add the lines (replacing `[password]` with the password entered in the mongo shell): 84 | 85 | ``` 86 | export XBROWSERSYNC_DB_USER=xbrowsersyncdb 87 | export XBROWSERSYNC_DB_PWD=[password] 88 | ``` 89 | 90 | Save and exit, then log out and back in again. 91 | 92 | #### If exposing your service to the public it is recommended you also perform the following steps: 93 | 94 | 3. Add a TTL index on `bookmarks.lastAccessed` to delete syncs that have not been accessed for 3 weeks: 95 | 96 | ``` 97 | use xbrowsersync 98 | db.bookmarks.createIndex( { "lastAccessed": 1 }, { expireAfterSeconds: 21*86400 } ) 99 | ``` 100 | 101 | ### 3. Modify configuration settings 102 | 103 | The file `config/settings.default.json` contains all of the default configuration settings. User configuration values should be stored in `config/settings.json` and will override the defaults. Should you wish to change any of the configuration settings, copy `settings.default.json` and rename the copy to `settings.json` before changing any values as required. Be sure to remove any settings that have not been changed so that any amendments to the default values in future versions are picked up. For example, a basic user configuration to modify the service status message could look like: 104 | 105 | ``` 106 | { 107 | "status": { 108 | "message": "Welcome to my xBrowserSync service!" 109 | } 110 | } 111 | ``` 112 | 113 | Any changes to the user configuration will require the service to be restarted before being picked up. The available configuration settings are: 114 | 115 | Config Setting | Description | Default Value 116 | -------------- | ----------- | ------------- 117 | `allowedOrigins` | Array of origins permitted to access the service. Each origin can be a `String` or a `RegExp`. For example `[ 'http://example1.com', /\.example2\.com$/ ]` will accept any request from `http://example1.com` or from a subdomain of `example2.com`. If the array is empty, all origins are permitted | `[]` (All origins permitted) 118 | `dailyNewSyncsLimit` | The maximum number of new syncs that a single IP address can create per day. If this setting is enabled, logs are created in newsynclogs collection to track IP addresses (cleared the following day). Set as `0` to disable. | `3` 119 | `db.connTimeout` | The connection timeout period to use for mongoDB. Using a high value helps prevent dropped connections in a hosted environment. | `30000` (30 secs) 120 | `db.host` | The mongoDB server address to connect to, either a hostname, IP address, or UNIX domain socket. | `127.0.0.1` 121 | `db.name` | Name of the mongoDB database to use. | `xbrowsersync` 122 | `db.username` | Username of the account used to access mongoDB. Set as empty string to use environment variable `XBROWSERSYNC_DB_USER`. | (Empty string, defers to environment variable) 123 | `db.password` | Password of the account used to access mongoDB. Set as empty string to use environment variable `XBROWSERSYNC_DB_PWD`. | (Empty string, defers to environment variable) 124 | `db.port` | The port to use to connect to mongoDB. | `27017` 125 | `log.enabled` | If set to true, [Bunyan](https://github.com/trentm/node-bunyan) will be used to capture minimal logging (service start/stop, new sync created, errors) to file. Logged messages are output to `log.path` and the log file is rotated automatically each period set by `log.rotationPeriod`, resulting in files "`log.path`.0", "`log.path`.1", etc. | `true` 126 | `log.level` | Bunyan log level to capture: `trace`, `debug`, `info`, `warn`, `error`, `fatal`. | `info` 127 | `log.path` | File path to log messages to (ensure the account node is running as has permission to write to this location). | `/var/log/xBrowserSync/api.log` 128 | `log.rotatedFilesToKeep` | Maximum number of rotated log files to retain. | `5` 129 | `log.rotationPeriod` | The period at which to rotate log files. This is a string of the format "$number$scope" where "$scope" is one of "ms" (milliseconds -- only useful for testing), "h" (hours), "d" (days), "w" (weeks), "m" (months), "y" (years). Or one of the following names can be used "hourly" (means 1h), "daily" (1d), "weekly" (1w), "monthly" (1m), "yearly" (1y). Rotation is done at the start of the scope: top of the hour (h), midnight (d), start of Sunday (w), start of the 1st of the month (m), start of Jan 1st (y). | `1d` 130 | `maxSyncs` | The maximum number of unique syncs to be stored on the service, once this limit is reached no more new syncs are permitted. Esers with an existing sync ID are able to get and update their sync data as normal. This value multiplied by the maxSyncSize will determine the maximum amount of disk space used by the xBrowserSync service. Set as `0` to disable. | `5242` 131 | `maxSyncSize` | The maximum sync size in bytes. Note this is not equivalent to the size/amount of bookmarks as data is compressed and encrypted client-side before being sent to the service. | `512000` (500kb) 132 | `server.behindProxy` | Set to `true` if service is behind a proxy, client IP address will be set from [X-Forwarded-For](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) header. Important: Do not set to `true` unless a proxy is present otherwise client IP address can easily be spoofed by malicious users. | `false` 133 | `server.host` | Host name or IP address to use for Node.js server for accepting incoming connections. | `127.0.0.1` 134 | `server.https.certPath` | Path to a valid SSL certificate. Required when HTTPS is enabled. | (Empty string, no path set) 135 | `server.https.enabled` | If enabled, the service is started using HTTPS. | `false` 136 | `server.https.keyPath` | Path to the SSL certificate's private key. Required when HTTPS is enabled. | (Empty string, no path set) 137 | `server.port` | Port to use for Node.js server for accepting incoming connections. | `8080` 138 | `status.allowNewSyncs` | Determines whether users will be allowed to create new syncs on this server. Note: if this setting is set to false, users who have already synced to this service and have a sync ID will still able to get and update their syncs. | `true` 139 | `status.message` | This message will be displayed in the service status panel of the client app when using this xBrowserSync service. Ideally the message should be 130 characters or less. | (Empty string, no message set) 140 | `status.online` | If set to true no clients will be able to connect to this service. | `true` 141 | `tests.db` | Name of the mongoDB database to use for integration tests. | `xbrowsersynctest` 142 | `tests.port` | Port to use for running tests. | `8081` 143 | `throttle.maxRequests` | Max number of connections during `throttle.timeWindow` milliseconds before sending a 429 response. Set as `0` to disable. | `1000` 144 | `throttle.timeWindow` | Amount of time (in milliseconds) before throttle counter is reset. | `300000` (5 mins) 145 | 146 | ### 4. Create log folder 147 | 148 | Ensure that the path set in the `log.path` config value exists, and that the account node will be running as can write to that location. 149 | 150 | ### 5. Run xBrowserSync service 151 | 152 | $ node dist/api.js 153 | 154 | ## Building 155 | 156 | If you've made code changes you can run a fresh build with the command: 157 | 158 | $ npm run build 159 | 160 | ## Testing 161 | 162 | To run unit tests, run either of the following commands: 163 | 164 | $ npm run test 165 | $ npm run unittests 166 | 167 | To run integration tests, you will need to create the test database first. Run the following commands in the mongo shell: 168 | 169 | (Replace `[dbname]` with your xBrowserSync database name and `[password]` with the xBrowserSync database user account password) 170 | 171 | ``` 172 | use [dbname]test 173 | db.createUser({ user: "xbrowsersyncdb", pwd: "[password]", roles: ["readWrite"] }) 174 | ``` 175 | 176 | You can then run the integration tests by running the following command: 177 | 178 | $ npm run integrationtests 179 | 180 | ## VS Code 181 | 182 | If you're using [VS Code](https://code.visualstudio.com/), you have the following launch configurations: 183 | 184 | 1. Debug API: Will compile and debug the main API service. 185 | 2. Debug docs: Will launch API home page in chrome and attach to debugger for debugging docs issues. 186 | 3. Run unit tests: Will run and debug all tests in `test/unit` folder. 187 | 4. Run integration tests: Will debug all tests in `test/integration` folder. 188 | 189 | ## Issues 190 | 191 | If you've found a bug or wish to request a new feature, please submit it [here](https://github.com/xBrowserSync/API/issues/). 192 | -------------------------------------------------------------------------------- /src/core/server.ts: -------------------------------------------------------------------------------- 1 | import * as bunyan from 'bunyan'; 2 | import { autobind } from 'core-decorators'; 3 | import * as cors from 'cors'; 4 | import * as express from 'express'; 5 | import * as fs from 'fs'; 6 | import * as helmet from 'helmet'; 7 | import * as http from 'http'; 8 | import * as https from 'https'; 9 | import * as mkdirp from 'mkdirp'; 10 | import * as path from 'path'; 11 | 12 | import Config from '../core/config'; 13 | import DB from '../core/db'; 14 | import { 15 | ExceptionBase, 16 | NotImplementedException, 17 | OriginNotPermittedException, 18 | RequestThrottledException, 19 | ServiceNotAvailableException, 20 | SyncDataLimitExceededException, 21 | UnspecifiedException 22 | } from '../core/exception'; 23 | import BookmarksRouter from '../routers/bookmarks.router'; 24 | import DocsRouter from '../routers/docs.router'; 25 | import InfoRouter from '../routers/info.router'; 26 | import BookmarksService from '../services/bookmarks.service'; 27 | import InfoService from '../services/info.service'; 28 | import NewSyncLogsService from '../services/newSyncLogs.service'; 29 | 30 | export enum ApiStatus { 31 | online = 1, 32 | offline = 2, 33 | noNewSyncs = 3 34 | } 35 | 36 | export enum ApiVerb { 37 | delete = 'delete', 38 | get = 'get', 39 | options = 'options', 40 | patch = 'patch', 41 | post = 'post', 42 | put = 'put' 43 | } 44 | 45 | export enum LogLevel { 46 | Error, 47 | Info 48 | } 49 | 50 | // Main class for the xBrowserSync api service 51 | export default class Server { 52 | // Throws an error if the service status is set to offline in config 53 | public static checkServiceAvailability(): void { 54 | if (!Config.get().status.online) { 55 | throw new ServiceNotAvailableException(); 56 | } 57 | } 58 | 59 | private app: express.Application; 60 | private bookmarksService: BookmarksService; 61 | private db: DB; 62 | private infoService: InfoService; 63 | private logger: bunyan; 64 | private logToConsole = true; 65 | private newSyncLogsService: NewSyncLogsService; 66 | private rateLimit = require('express-rate-limit'); 67 | private server: http.Server | https.Server; 68 | 69 | public get Application(): express.Application { 70 | return this.app; 71 | } 72 | 73 | // Initialises the xBrowserSync api service when a new instance is created 74 | public async init(): Promise { 75 | try { 76 | this.configureServer(); 77 | this.prepareDataServices(); 78 | this.prepareRoutes(); 79 | this.app.use(this.handleErrors); 80 | 81 | // Establish database connection 82 | await this.connectToDb(); 83 | } 84 | catch (err) { 85 | this.log(LogLevel.Error, `Service failed to start`, null, err); 86 | process.exit(1); 87 | } 88 | } 89 | 90 | // Logs messages and errors to console and to file (if enabled) 91 | @autobind 92 | public log(level: LogLevel, message: string, req?: express.Request, err?: Error): void { 93 | const writeToLogFile = (logAction: () => void) => { 94 | if (!Config.get().log.enabled || !logAction) { 95 | return; 96 | } 97 | 98 | if (!this.logger) { 99 | console.error('Unable to write to log as it has not been initialised.'); 100 | return; 101 | } 102 | 103 | logAction(); 104 | }; 105 | 106 | const writeToConsole = (logAction: () => void) => { 107 | if (!this.logToConsole) { 108 | return; 109 | } 110 | 111 | logAction(); 112 | }; 113 | 114 | switch (level) { 115 | case LogLevel.Error: 116 | writeToLogFile(() => { 117 | this.logger.error({ req, err }, message); 118 | }); 119 | 120 | writeToConsole(() => { 121 | console.error(err ? `${message}: ${err.message}` : message); 122 | }); 123 | break; 124 | case LogLevel.Info: 125 | writeToLogFile(() => { 126 | this.logger.info({ req }, message); 127 | }); 128 | 129 | writeToConsole(() => { 130 | console.log(message); 131 | }); 132 | break; 133 | } 134 | } 135 | 136 | // Enables/disables logging messages to the console 137 | public logToConsoleEnabled(logToConsole: boolean): void { 138 | this.logToConsole = logToConsole; 139 | } 140 | 141 | // Starts the api service 142 | @autobind 143 | public async start(): Promise { 144 | // Create https server if enabled in config, otherwise create http server 145 | if (Config.get().server.https.enabled) { 146 | const options: https.ServerOptions = { 147 | cert: fs.readFileSync(Config.get().server.https.certPath), 148 | key: fs.readFileSync(Config.get().server.https.keyPath) 149 | }; 150 | this.server = https.createServer(options, this.app); 151 | } 152 | else { 153 | this.server = http.createServer(this.app); 154 | } 155 | this.server.listen(Config.get().server.port); 156 | 157 | // Wait for server to start before continuing 158 | await new Promise((resolve, reject) => { 159 | this.server.on('error', (err: NodeJS.ErrnoException) => { 160 | this.log(LogLevel.Error, `Uncaught exception occurred`, null, err); 161 | this.server.close(async () => { 162 | await this.cleanupServer(); 163 | process.exit(1); 164 | }); 165 | }); 166 | 167 | this.server.on('listening', conn => { 168 | this.log(LogLevel.Info, `Service started on ${Config.get().server.host}:${Config.get().server.port}`); 169 | resolve(); 170 | }); 171 | }); 172 | 173 | // Catches ctrl+c event 174 | process.on('SIGINT', () => { 175 | this.log(LogLevel.Info, `Process terminated by SIGINT`, null, null); 176 | this.server.close(async () => { 177 | await this.cleanupServer(); 178 | process.exit(0); 179 | }); 180 | }); 181 | 182 | // Catches kill pid event 183 | process.on('SIGUSR1', () => { 184 | this.log(LogLevel.Info, `Process terminated by SIGUSR1`, null, null); 185 | this.server.close(async () => { 186 | await this.cleanupServer(); 187 | process.exit(0); 188 | }); 189 | }); 190 | 191 | // Catches kill pid event 192 | process.on('SIGUSR2', () => { 193 | this.log(LogLevel.Info, `Process terminated by SIGUSR2`, null, null); 194 | this.server.close(async () => { 195 | await this.cleanupServer(); 196 | process.exit(0); 197 | }); 198 | }); 199 | } 200 | 201 | // Stops the api service 202 | public stop(): Promise { 203 | return new Promise(resolve => { 204 | this.server.close(async () => { 205 | await this.cleanupServer(); 206 | resolve(); 207 | }); 208 | }); 209 | } 210 | 211 | // Cleans up server connections when stopping the service 212 | private async cleanupServer(): Promise { 213 | this.log(LogLevel.Info, `Service shutting down`); 214 | await this.db.closeConnection(); 215 | this.server.removeAllListeners(); 216 | process.removeAllListeners(); 217 | } 218 | 219 | // Initialises the express application and middleware 220 | private configureServer(): void { 221 | this.app = express(); 222 | 223 | // Add logging if required 224 | if (Config.get().log.enabled) { 225 | try { 226 | // Ensure log directory exists 227 | const logDirectory = Config.get().log.path.substring(0, Config.get().log.path.lastIndexOf('/')); 228 | if (!fs.existsSync(logDirectory)) { 229 | mkdirp.sync(logDirectory); 230 | } 231 | 232 | // Initialise bunyan logger 233 | this.logger = bunyan.createLogger({ 234 | level: Config.get().log.level, 235 | name: 'xBrowserSync_api', 236 | serializers: bunyan.stdSerializers, 237 | streams: [ 238 | { 239 | count: Config.get().log.rotatedFilesToKeep, 240 | level: Config.get().log.level, 241 | path: Config.get().log.path, 242 | period: Config.get().log.rotationPeriod, 243 | type: 'rotating-file' 244 | } 245 | ] 246 | }); 247 | } 248 | catch (err) { 249 | console.error(`Failed to initialise log file.`); 250 | throw err; 251 | } 252 | } 253 | 254 | // Set default config for helmet security hardening 255 | const helmetConfig: helmet.IHelmetConfiguration = { 256 | noCache: true 257 | }; 258 | 259 | this.app.use(helmet(helmetConfig)); 260 | 261 | // Add default version to request if not supplied 262 | this.app.use((req: any, res: express.Response, next: express.NextFunction) => { 263 | req.version = req.headers['accept-version'] || Config.get().version; 264 | next(); 265 | }); 266 | 267 | // If behind proxy use 'X-Forwarded-For' header for client ip address 268 | if (Config.get().server.behindProxy) { 269 | this.app.enable('trust proxy'); 270 | } 271 | 272 | // Process JSON-encoded bodies, set body size limit to config value or default to 500kb 273 | this.app.use(express.json({ 274 | limit: Config.get().maxSyncSize || 512000 275 | })); 276 | 277 | // Enable support for CORS 278 | const corsOptions: cors.CorsOptions = 279 | Config.get().allowedOrigins.length > 0 && { 280 | origin: (origin, callback) => { 281 | if (Config.get().allowedOrigins.indexOf(origin) !== -1) { 282 | callback(null, true); 283 | } else { 284 | const err = new OriginNotPermittedException(); 285 | callback(err); 286 | } 287 | } 288 | }; 289 | this.app.use(cors(corsOptions)); 290 | this.app.options('*', cors(corsOptions)); 291 | 292 | // Add thottling if enabled 293 | if (Config.get().throttle.maxRequests > 0) { 294 | this.app.use(new this.rateLimit({ 295 | delayMs: 0, 296 | handler: (req, res, next) => { 297 | next(new RequestThrottledException()); 298 | }, 299 | max: Config.get().throttle.maxRequests, 300 | windowMs: Config.get().throttle.timeWindow 301 | })); 302 | } 303 | } 304 | 305 | // Initialises and connects to mongodb 306 | private async connectToDb(): Promise { 307 | this.db = new DB(this.log); 308 | await this.db.openConnection(); 309 | } 310 | 311 | // Handles and logs api errors 312 | private handleErrors(err: any, req: express.Request, res: express.Response, next: express.NextFunction): void { 313 | if (err) { 314 | let responseObj: any; 315 | 316 | // Determine the response value based on the error thrown 317 | switch (true) { 318 | // If the error is one of our exceptions get the reponse object to return to the client 319 | case err instanceof ExceptionBase: 320 | responseObj = (err as ExceptionBase).getResponseObject(); 321 | break; 322 | // If the error is 413 Request Entity Too Large return a SyncDataLimitExceededException 323 | case err.status === 413: 324 | err = new SyncDataLimitExceededException(); 325 | responseObj = (err as SyncDataLimitExceededException).getResponseObject(); 326 | break; 327 | // Otherwise return an UnspecifiedException 328 | default: 329 | err = new UnspecifiedException(); 330 | responseObj = (err as UnspecifiedException).getResponseObject(); 331 | } 332 | 333 | res.status(err.status || 500); 334 | res.json(responseObj); 335 | } 336 | } 337 | 338 | // Initialise data services 339 | private prepareDataServices(): void { 340 | this.newSyncLogsService = new NewSyncLogsService(null, this.log); 341 | this.bookmarksService = new BookmarksService(this.newSyncLogsService, this.log); 342 | this.infoService = new InfoService(this.bookmarksService, this.log); 343 | } 344 | 345 | // Configures api routing 346 | private prepareRoutes(): void { 347 | const router = express.Router(); 348 | this.app.use('/', router); 349 | 350 | // Configure docs routing 351 | const docsRouter = new DocsRouter(this.app); 352 | 353 | // Configure bookmarks routing 354 | const bookmarksRouter = new BookmarksRouter(this.app, this.bookmarksService); 355 | 356 | // Configure info routing 357 | const infoRouter = new InfoRouter(this.app, this.infoService); 358 | 359 | // Handle all other routes with 404 error 360 | this.app.use((req, res, next) => { 361 | const err = new NotImplementedException(); 362 | next(err); 363 | }); 364 | } 365 | } -------------------------------------------------------------------------------- /src/docs/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'mixins.scss'; 2 | @import 'variables.scss'; 3 | @include keyframes(spin) { 4 | 100% { 5 | @include rotate(360); 6 | } 7 | } 8 | @include keyframes(slidein) { 9 | 0% { 10 | transform: translateX(100%); 11 | } 12 | 13 | 100% { 14 | transform: translateX(0%); 15 | } 16 | } 17 | 18 | body, 19 | head, 20 | html { 21 | height: 100%; 22 | } 23 | 24 | body { 25 | font-family: $font-stack-regular; 26 | font-size: $font-size; 27 | overflow-x: hidden; 28 | color: $colour-text1; 29 | display: flex; 30 | flex-direction: column; 31 | 32 | &.noscroll { 33 | @media (max-width: $width-small) { 34 | overflow: hidden; 35 | } 36 | } 37 | } 38 | 39 | h1 { 40 | @include hide-text; 41 | background: url("./images/logo.svg") no-repeat; 42 | width: 400px; 43 | height: 408px; 44 | padding: 0; 45 | margin: auto; 46 | @media (max-width: $width-small) { 47 | background-size: 300px; 48 | width: 300px; 49 | height: 305px; 50 | } 51 | } 52 | 53 | h2 { 54 | font-size: 2.2em; 55 | font-weight: 700; 56 | margin: 0 0 30px; 57 | @media (max-width: $width-small) { 58 | font-size: 2em; 59 | } 60 | } 61 | 62 | h3 { 63 | font-size: 2.4em; 64 | font-weight: 700; 65 | margin: 0 0 30px; 66 | text-align: center; 67 | @media (max-width: $width-small) { 68 | font-size: 2em; 69 | } 70 | } 71 | 72 | h4 { 73 | font-size: 1.4em; 74 | font-weight: 700; 75 | margin: 0; 76 | } 77 | 78 | p { 79 | margin: 0 0 1.5rem; 80 | 81 | &:last-child { 82 | margin-bottom: 0; 83 | } 84 | 85 | &.larger { 86 | font-size: 1.2em; 87 | } 88 | } 89 | 90 | a { 91 | color: $colour-text1; 92 | transition: color, ease-in, .2s; 93 | cursor: pointer; 94 | border-bottom: 1px dotted $colour-text1; 95 | 96 | &:active, 97 | &:focus, 98 | &:hover, 99 | .active { 100 | color: $colour-link-highlight; 101 | border-bottom-color: $colour-link-highlight; 102 | text-decoration: none; 103 | outline: 0; 104 | } 105 | } 106 | 107 | nav { 108 | &.navbar-custom { 109 | background: rgba($colour-text2, 0.9); 110 | font-weight: 400; 111 | border: none; 112 | outline: 0; 113 | padding: 10px 0; 114 | z-index: 10; 115 | 116 | &.open { 117 | @media (max-width: $width-small) { 118 | top: 0; 119 | left: 0; 120 | position: fixed; 121 | width: 100vw; 122 | height: 100vh; 123 | z-index: 100; 124 | font-size: 1.4em; 125 | padding: 8% 0 0 10%; 126 | @include animation(slidein 0.3s forwards); 127 | 128 | .nav, 129 | .navbar-collapse { 130 | display: block; 131 | 132 | &:before { 133 | display: table!important; 134 | content: " "!important; 135 | } 136 | } 137 | 138 | .navbar-brand { 139 | height: 6em; 140 | width: 6em; 141 | margin-bottom: 2rem; 142 | } 143 | 144 | .navbar-header { 145 | display: block; 146 | } 147 | 148 | .navbar-nav > li > a { 149 | line-height: 1.5em; 150 | display: inline-block; 151 | } 152 | } 153 | } 154 | 155 | .navbar-brand { 156 | text-indent: -9999px; 157 | display: block; 158 | background: url("./images/icon.svg") no-repeat; 159 | width: 40px; 160 | height: 50px; 161 | padding: 0; 162 | margin: 0; 163 | border-bottom: none; 164 | @media (max-width: $width-small) { 165 | margin-left: 15px; 166 | } 167 | } 168 | 169 | .navbar-header { 170 | @media (max-width: $width-small) { 171 | display: none; 172 | } 173 | } 174 | 175 | .navbar-nav { 176 | letter-spacing: 1px; 177 | 178 | li { 179 | a { 180 | color: $colour-text1; 181 | border-bottom: none!important; 182 | 183 | &:active, 184 | &:hover { 185 | color: $colour-link-highlight; 186 | outline: 0; 187 | } 188 | 189 | &:focus { 190 | @extend a; 191 | } 192 | 193 | &.active { 194 | color: $colour-link-highlight; 195 | background: transparent; 196 | 197 | &:active, 198 | &:focus, 199 | &:hover { 200 | @extend a; 201 | } 202 | } 203 | } 204 | } 205 | } 206 | 207 | .navbar-toggle { 208 | height: 50px; 209 | padding: 30px 0 0; 210 | margin: 0 15px; 211 | border: 0; 212 | font-weight: 400; 213 | color: $colour-link-highlight; 214 | 215 | span { 216 | display: block; 217 | width: 36px; 218 | height: 4px; 219 | background-color: $colour-link-highlight; 220 | border-radius: 5px; 221 | position: absolute; 222 | top: 5px; 223 | right: 0; 224 | 225 | &:after, 226 | &:before { 227 | content: ''; 228 | width: 100%; 229 | height: 4px; 230 | background-color: $colour-link-highlight; 231 | position: absolute; 232 | border-radius: 5px; 233 | } 234 | 235 | &:after { 236 | top: 10px; 237 | left: 0; 238 | } 239 | 240 | &:before { 241 | top: 20px; 242 | left: 0; 243 | } 244 | } 245 | 246 | &.collapsed { 247 | color: $colour-text1; 248 | background-color: transparent; 249 | 250 | &:focus, 251 | &:hover { 252 | background-color: transparent; 253 | } 254 | 255 | span { 256 | background-color: $colour-text1; 257 | 258 | &:after, 259 | &:before { 260 | background-color: $colour-text1; 261 | } 262 | } 263 | } 264 | 265 | &:focus, 266 | &:hover { 267 | background-color: transparent; 268 | } 269 | } 270 | 271 | .navbar-collapse { 272 | border: 0; 273 | box-shadow: none; 274 | } 275 | @media (max-width: $width-small) { 276 | min-height: auto; 277 | padding: 0; 278 | margin-bottom: 0; 279 | } 280 | } 281 | } 282 | 283 | pre { 284 | border: none; 285 | background: transparent; 286 | padding: 0; 287 | } 288 | 289 | code { 290 | font-family: $font-stack-code; 291 | color: $colour-link-highlight!important; 292 | background-color: rgba($colour-link-highlight, 0.1); 293 | font-size: .8em; 294 | } 295 | 296 | header { 297 | margin-top: 70px; 298 | padding: 10rem 0; 299 | 300 | & > .container > .row { 301 | @media (min-width: $width-medium + 1) { 302 | display: flex; 303 | 304 | .col-lg-7 { 305 | display: flex; 306 | flex-direction: column; 307 | justify-content: center; 308 | } 309 | } 310 | } 311 | 312 | img { 313 | display: block; 314 | margin: 0 auto 50px; 315 | } 316 | 317 | h2, 318 | p { 319 | @media (max-width: $width-medium) { 320 | text-align: center; 321 | } 322 | } 323 | 324 | h2 { 325 | line-height: 1.1em; 326 | padding-top: 5rem; 327 | margin-bottom: 20px; 328 | } 329 | 330 | p { 331 | font-weight: normal; 332 | font-size: 1.3em; 333 | line-height: 1.2em; 334 | margin-bottom: 10px; 335 | color: $colour-text2; 336 | } 337 | @media (max-width: $width-small) { 338 | margin-top: 0; 339 | padding: 5rem; 340 | } 341 | } 342 | 343 | section { 344 | padding: 10rem 0; 345 | 346 | & > .container > .panel { 347 | margin: 0 auto; 348 | width: 80rem; 349 | @media (max-width: $width-medium) { 350 | width: 100%; 351 | } 352 | 353 | a { 354 | color: $colour-text2; 355 | border-bottom-color: $colour-text2; 356 | 357 | &:active, 358 | &:focus, 359 | &:hover, 360 | .active { 361 | color: $colour-link-highlight; 362 | border-bottom-color: $colour-link-highlight; 363 | } 364 | } 365 | } 366 | 367 | &.dark { 368 | background-color: $colour-section1-bg; 369 | color: $colour-section1-text; 370 | 371 | a { 372 | color: $colour-section1-text; 373 | border-bottom-color: $colour-section1-text; 374 | 375 | &:active, 376 | &:focus, 377 | &:hover, 378 | .active { 379 | color: $colour-link-highlight; 380 | border-bottom-color: $colour-link-highlight; 381 | } 382 | } 383 | } 384 | 385 | .example { 386 | font-size: .9em; 387 | margin-bottom: 50px; 388 | 389 | p { 390 | margin-bottom: 10px; 391 | font-style: italic; 392 | } 393 | 394 | pre { 395 | code { 396 | font-size: 0.9em; 397 | } 398 | 399 | @media (min-width: $width-small + 1) { 400 | margin-left: 25px; 401 | font-size: 1.1em; 402 | } 403 | } 404 | 405 | ul { 406 | list-style: none; 407 | padding: 0; 408 | 409 | @media (min-width: $width-small + 1) { 410 | margin-left: 25px; 411 | } 412 | } 413 | } 414 | 415 | .header { 416 | display: flex; 417 | justify-content: space-between; 418 | margin-bottom: 15px; 419 | 420 | code { 421 | background: transparent; 422 | font-size: .9em; 423 | } 424 | 425 | .label { 426 | padding: 2px 5px; 427 | font-size: 65%; 428 | vertical-align: middle; 429 | 430 | &.label-info { 431 | background-color: $colour-bg2; 432 | } 433 | 434 | &.label-success { 435 | background-color: $colour-success; 436 | } 437 | 438 | &.label-warning { 439 | background-color: $colour-warning; 440 | } 441 | } 442 | 443 | @media (max-width: $width-small) { 444 | flex-direction: column; 445 | } 446 | } 447 | 448 | .panel { 449 | color: $colour-section2-text; 450 | background-color: $colour-section2-bg; 451 | border-radius: 30px; 452 | padding: 30px 35px; 453 | margin-bottom: 75px; 454 | box-shadow: .6875rem 1.3125rem 4.125rem rgba(0, 0, 0, 0.25); 455 | @media (max-width: $width-small) { 456 | margin: 0 15px 50px; 457 | } 458 | 459 | a { 460 | color: $colour-section2-text; 461 | border-bottom-color: $colour-section2-text; 462 | 463 | &:active, 464 | &:focus, 465 | &:hover, 466 | .active { 467 | color: $colour-link-highlight; 468 | border-bottom-color: $colour-link-highlight; 469 | } 470 | } 471 | } 472 | 473 | &#status { 474 | .serviceinfo { 475 | font-size: 1.2em; 476 | margin-bottom: 1.5rem; 477 | } 478 | } 479 | @media (max-width: $width-small) { 480 | padding: 5rem 0; 481 | } 482 | } 483 | 484 | footer { 485 | font-size: .7em; 486 | flex-grow: 1; 487 | background-color: $colour-section1-bg; 488 | color: $colour-section1-text; 489 | padding: 30px 0; 490 | } 491 | 492 | .bg-main { 493 | width: 100%; 494 | height: 100%; 495 | @include background-gradient(20deg, $colour-bg1, 0%, $colour-bg2, 100%); 496 | left: 0; 497 | top: 0; 498 | position: fixed; 499 | z-index: -1; 500 | 501 | .bg-main-img { 502 | display: block; 503 | width: 100%; 504 | height: 100%; 505 | background: url("./images/bg.svg"); 506 | background-size: 1100px; 507 | opacity: .3; 508 | @media (max-width: $width-small) { 509 | background-size: 650px; 510 | } 511 | } 512 | } 513 | 514 | .btn-outline { 515 | transition: background-color .2s, color .2s; 516 | transition-timing-function: ease-in; 517 | color: $colour-text1; 518 | font-weight: 700; 519 | border: 2px solid $colour-text1; 520 | background: 0 0; 521 | margin-top: 15px; 522 | border-radius: 25px; 523 | outline: 0; 524 | 525 | &.active, 526 | &:active, 527 | &:active:focus, 528 | &:focus { 529 | color: $colour-text1; 530 | border: 2px solid $colour-text1; 531 | outline: 0; 532 | box-shadow: none; 533 | 534 | } 535 | 536 | &:active:hover, 537 | &:focus:hover, 538 | &:hover { 539 | color: $colour-link-highlight; 540 | background: $colour-text1; 541 | border: 2px solid $colour-text1; 542 | outline: 0; 543 | box-shadow: none; 544 | 545 | } 546 | @media (max-width: $width-medium) { 547 | font-size: .95em; 548 | padding: 8px 15px; 549 | } 550 | } 551 | 552 | .form-control { 553 | color: $colour-text2; 554 | font-size: 1.2em; 555 | font-weight: 700; 556 | box-shadow: none!important; 557 | outline: none; 558 | resize: none; 559 | @include placeholder { 560 | color: rgba($colour-text2, 0.4)!important; 561 | } 562 | } 563 | 564 | .icon { 565 | height: 150px; 566 | 567 | & > img { 568 | height: 100%; 569 | } 570 | @media (max-width: $width-small) { 571 | height: 100px; 572 | margin-bottom: 0; 573 | } 574 | } 575 | 576 | .nav-menu-button { 577 | display: none; 578 | position: fixed; 579 | z-index: 20; 580 | top: 4%; 581 | right: 6%; 582 | height: 2em; 583 | width: 2em; 584 | padding: 0; 585 | background: rgba($colour-text2, 0.9); 586 | color: $colour-text1; 587 | border: none; 588 | border-radius: 5rem; 589 | font-size: 1.75em; 590 | line-height: 2.2em; 591 | outline: none; 592 | @media (max-width: $width-small) { 593 | display: block; 594 | } 595 | } 596 | --------------------------------------------------------------------------------