├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmignore ├── .nvmrc ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── generate.js └── template.md.ejs ├── jest.config.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── apis │ ├── billing.ts │ ├── expressions.ts │ ├── index.ts │ ├── memberships.ts │ ├── operations.ts │ ├── organizations.ts │ ├── render.ts │ ├── request.ts │ ├── sourceimages.alias.ts │ ├── sourceimages.meta.ts │ ├── sourceimages.ts │ ├── stackoptions.ts │ ├── stacks.ts │ ├── stats.ts │ ├── user.ts │ └── users.ts ├── index.ts ├── response.ts ├── transport.ts └── utils.ts ├── tests ├── apis │ ├── memberships.test.ts │ ├── operations.test.ts │ ├── organizations.test.ts │ ├── render.addStackVariables.test.ts │ ├── render.signUrl.test.ts │ ├── render.test.ts │ ├── sourceimages.metadata.test.ts │ ├── sourceimages.test.ts │ ├── stackoptions.test.ts │ ├── stacks.test.ts │ ├── stats.test.ts │ └── users.test.ts ├── fixtures │ ├── answers │ │ ├── memberships_create_with_array.json │ │ ├── memberships_create_with_new_user.json │ │ ├── memberships_delete.json │ │ ├── memberships_list.json │ │ ├── operations_get.json │ │ ├── organizations_create.json │ │ ├── organizations_get.json │ │ ├── sourceimages_copy.json │ │ ├── sourceimages_copy_no_overwrite.json │ │ ├── sourceimages_delete.json │ │ ├── sourceimages_delete_with_binary.json │ │ ├── sourceimages_download.json │ │ ├── sourceimages_get.json │ │ ├── sourceimages_get_binaryhash.json │ │ ├── sourceimages_list.json │ │ ├── sourceimages_list_with_args.json │ │ ├── sourceimages_metadata_add.json │ │ ├── sourceimages_metadata_delete.json │ │ ├── sourceimages_metadata_remove_subjectarea.json │ │ ├── sourceimages_metadata_remove_subjectarea_delete.json │ │ ├── sourceimages_metadata_replace.json │ │ ├── sourceimages_metadata_set_subjectarea.json │ │ ├── sourceimages_metadata_set_subjectarea_delete.json │ │ ├── sourceimages_restore.json │ │ ├── stackoptions_get.json │ │ ├── stacks_create.json │ │ ├── stacks_create_0_26.json │ │ ├── stacks_create_expressions.json │ │ ├── stacks_create_overwrite.json │ │ ├── stacks_create_overwrite_0_26.json │ │ ├── stacks_delete.json │ │ ├── stacks_get.json │ │ ├── stacks_list.json │ │ ├── stats_get.json │ │ ├── users_create.json │ │ └── users_getid.json │ ├── cartman.svg │ └── requests │ │ ├── memberships_create_with_array.json │ │ ├── memberships_create_with_new_user.json │ │ ├── memberships_delete.json │ │ ├── memberships_list.json │ │ ├── operations_get.json │ │ ├── organizations_create.json │ │ ├── organizations_get.json │ │ ├── sourceimages_copy.json │ │ ├── sourceimages_copy_no_overwrite.json │ │ ├── sourceimages_delete.json │ │ ├── sourceimages_delete_with_binary.json │ │ ├── sourceimages_download.json │ │ ├── sourceimages_get.json │ │ ├── sourceimages_get_binaryhash.json │ │ ├── sourceimages_list.json │ │ ├── sourceimages_list_with_args.json │ │ ├── sourceimages_metadata_add.json │ │ ├── sourceimages_metadata_delete.json │ │ ├── sourceimages_metadata_remove_subjectarea.json │ │ ├── sourceimages_metadata_remove_subjectarea_delete.json │ │ ├── sourceimages_metadata_replace.json │ │ ├── sourceimages_metadata_set_subjectarea.json │ │ ├── sourceimages_metadata_set_subjectarea_delete.json │ │ ├── sourceimages_restore.json │ │ ├── stackoptions_get.json │ │ ├── stacks_create.json │ │ ├── stacks_create_0_26.json │ │ ├── stacks_create_expressions.json │ │ ├── stacks_create_overwrite.json │ │ ├── stacks_create_overwrite_0_26.json │ │ ├── stacks_delete.json │ │ ├── stacks_get.json │ │ ├── stacks_list.json │ │ ├── stats_get.json │ │ ├── users_create.json │ │ └── users_getid.json ├── mockServer.ts └── options.test.ts └── tsconfig.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "targets": { 5 | "browsers": [">0.1%", "not op_mini all"], 6 | "node": "current" 7 | } 8 | }] 9 | ], 10 | "plugins": [ 11 | ["@babel/plugin-transform-runtime", { "corejs": 2 }] 12 | ] 13 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | dist/ 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 3 | extends: [ 4 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 5 | 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 6 | ], 7 | parserOptions: { 8 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 9 | sourceType: 'module', // Allows for the use of imports 10 | }, 11 | rules: { 12 | '@typescript-eslint/no-explicit-any': 'off', 13 | '@typescript-eslint/ban-types': 'off', 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | node: [ 16, 18, 20 ] 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: Use Node.js $${{ matrix.node }} 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: ${{ matrix.node }} 21 | cache: 'npm' 22 | 23 | - name: npm install and build 24 | run: | 25 | npm install 26 | npm run test 27 | npm run docs 28 | npm run compile 29 | 30 | 31 | coveralls: 32 | needs: test 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v3 36 | - name: Use Node.js 18 37 | uses: actions/setup-node@v3 38 | with: 39 | node-version: 18 40 | cache: 'npm' 41 | 42 | - name: npm install 43 | run: npm install 44 | 45 | - name: Coverage 46 | run: npm run coverage 47 | 48 | - name: Coveralls Parallel 49 | uses: coverallsapp/github-action@master 50 | with: 51 | github-token: ${{ secrets.github_token }} 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | npm-debug.log 4 | .nyc_output/ 5 | /dist 6 | /yarn.lock 7 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run docs && npm run lint 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .travis.yml 3 | .nvmrc 4 | .eslintrc 5 | test/ 6 | .envrc 7 | .eslintignore 8 | docs/ -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | semi: false 2 | singleQuote: true 3 | trailingComma: "all" 4 | tabWidth: 2 5 | arrowParens: "avoid" -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 3.16.0 - [02-01-2024] 2 | 3 | - Add meta_static interface and autodescription method to sourceimages API 4 | 5 | # 3.15.0 - [22-08-2023] 6 | 7 | - Add source image alias support. See [the Source Images Alias Guide](https://rokka.io/documentation/references/source-images-aliases.html) for details. 8 | 9 | # 3.14.1 - [30-03-2023] 10 | 11 | - Export StackOperation, StackOptions, Variables from rokka-render for BC reasons 12 | 13 | # 3.14.0 - [30-03-2023] 14 | 15 | - Use rokka-render.js 16 | - Add `variables` to `StackConfig` interface 17 | 18 | # 3.13.3 - [13-03-2023] 19 | 20 | - Fix url signing when it contains a filename 21 | 22 | 23 | # 3.13.2 - [06-03-2023] 24 | 25 | - Fix hostname replacement in `rokka.render.getUrlFromUrl` 26 | 27 | # 3.13.1 - [01-03-2023] 28 | 29 | - Put variables into ?v when long or have a space in it 30 | 31 | # 3.13.0 - [27-02-2023] 32 | 33 | - Added variables support to `rokka.render.getUrlFromUrl` and `rokka.render.getUrl` 34 | 35 | # 3.12.0 - [26-02-2023] 36 | 37 | - Added `rokka.render.getUrlFromUrl(rokkaUrl, stack, options)` to change a rokka render URL string to other stacks/options. 38 | 39 | # 3.11.0 - [31-10-2022] 40 | 41 | - Remove 3rd parameter in`rokka.sourceimages.downloadAsBuffer`, it's not used. 42 | - Add stackoptions option to getUrl 5th parameter 43 | 44 | # 3.10.0 - [24-09-2022] 45 | 46 | - Add support for downloading a list of images as Zip via `rokka.sourceimages.listDownload` 47 | 48 | # 3.9.1 - [19-09-2022] 49 | 50 | - Fix getting new Token, when apiKey is set via `rokka.user.getNewToken` 51 | 52 | # 3.9.0 - [08-09-2022] 53 | 54 | - Added support for getting a list of images in an album via `rokka.render.imagesByAlbum` 55 | 56 | # 3.8.0 - [16-08-2022] 57 | - Added support for locked sourceimages. 58 | See [the Source images Guide](https://rokka.io/documentation/references/source-images.html#lock-a-source-image-to-prevent-deletion) for details. 59 | 60 | # 3.7.0 - [30-05-2022] 61 | 62 | - Added support for API Tokens. 63 | See [the Authentication Guide](https://rokka.io/documentation/guides/authentication.html#using-rokka-with-a-jwt-token) for details 64 | - Added possibility to add a comment to new memberships 65 | 66 | # 3.6.0 [01-01-2022] 67 | 68 | - Export more typescript interfaces 69 | 70 | # 3.5.1 [28-12-2021] 71 | 72 | - Fix request interface 73 | 74 | # 3.5.0 [28-12-2021] 75 | 76 | - Added `rokka.sourceimages.downloadAsBuffer` to get a Buffer more easily 77 | - Added `rokka.render(path, method, payload)` to do any request against the rokka API 78 | - Fixed return type of body for non JSON responses. It's now always a Stream. 79 | 80 | # 3.4.0 [10-12-2021] 81 | 82 | - Added `rokka.sourceimages.addDynamicMetaData` and `rokka.sourceimages.deleteDynamicMetaData` method. 83 | 84 | # 3.3.0 [07-12-2021] 85 | 86 | - Added User Api Keys methods. 87 | See https://rokka.io/documentation/references/users-and-memberships.html#rotate-your-api-key for details. 88 | 89 | # 3.2.0 [23-11-2021] 90 | 91 | - Added agent transport option (for example for adding proxy options. See the README for an example) 92 | 93 | # 3.1.0 [20-10-2021] 94 | 95 | - Added `rokka.sourceimages.putName` method to change the name of an image. 96 | 97 | # 3.0.1 [17-09-2021] 98 | 99 | - Actually release the correct version 100 | 101 | # 3.0.0 [17-09-2021] 102 | 103 | - Converted to typescript. Even though it's supposed to be 100% backwards compatible, we decided to make this a major release. 104 | - Moved tests from ava to jest and nock. 105 | - Added `rokka.render.signUrl` method. 106 | - Added `rokka.render.addStackVariables` method. 107 | - Added `rokka.render.getUrlComponents` method. 108 | - Added `rokka.sourceimages.setProtected` method (and support for setting it during creation) 109 | - Added `rokka.organizations.setOption` method. 110 | 111 | # 2.0.1 [04-11-2019] 112 | 113 | - Small, non-code related fixes. 114 | - Use rollup instead of babel for packaging files. 115 | 116 | # 2.0.0 [25-10-2019] 117 | 118 | - Replace request package with cross-fetch. 119 | rokka.js uses now cross-fetch instead of "request" for making the actual http calls. It's much smaller as the main reason. 120 | The object returned as response in the Promises therefore also changed. We tried to make it as backwards compatible as possible with providing the relevant properties as before, but we can't guarantee 100% BC 121 | - Added deleted query support for `rokka.sourceimages.get` and `rokka.sourceimages.list` 122 | - Added `rokka.memberships.get(organization,userId)` 123 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015 by Liip AG 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /docs/generate.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const fs = require('fs') 3 | // eslint-disable-next-line @typescript-eslint/no-var-requires 4 | const markdox = require('markdox') 5 | 6 | const sources = [ 7 | '../src/index.ts', 8 | '../src/apis/users.ts', 9 | '../src/apis/user.ts', 10 | '../src/apis/billing.ts', 11 | '../src/apis/organizations.ts', 12 | '../src/apis/memberships.ts', 13 | '../src/apis/sourceimages.ts', 14 | '../src/apis/sourceimages.meta.ts', 15 | '../src/apis/sourceimages.alias.ts', 16 | '../src/apis/operations.ts', 17 | '../src/apis/stackoptions.ts', 18 | '../src/apis/stacks.ts', 19 | '../src/apis/render.ts', 20 | '../src/apis/stats.ts', 21 | '../src/apis/request.ts', 22 | ] 23 | const readme = '../README.md' 24 | const tmpFile = './API.md' 25 | 26 | const options = { 27 | template: './template.md.ejs', 28 | output: tmpFile, 29 | } 30 | 31 | markdox.process(sources, options, function () { 32 | let docsStr = fs.readFileSync(tmpFile, 'utf8') 33 | let readmeStr = fs.readFileSync(readme, 'utf8') 34 | 35 | docsStr = docsStr 36 | .replace(/</g, '<') 37 | .replace(/>/g, '>') 38 | .replace(/'/g, "'") 39 | .replace(/"/g, "'") 40 | .replace(/&/g, '&') 41 | 42 | readmeStr = readmeStr.replace( 43 | /()(?:\r|\n|.)+()/gm, 44 | '$1' + docsStr + '$2', 45 | ) 46 | 47 | fs.writeFileSync(readme, readmeStr) 48 | fs.unlinkSync(tmpFile) 49 | 50 | process.stdout.write('Documentation generated.\n') 51 | }) 52 | -------------------------------------------------------------------------------- /docs/template.md.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | #### rokka.. 9 | 10 | 0) { -?> → 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | **Deprecated** 21 | 22 | 23 | 24 | Author: 25 | 26 | 27 | 28 | Version: 29 | 30 | 31 | 32 | See: 33 | 34 | 35 | 36 | 37 | --- 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | roots: ['/tests'], 5 | transform: { 6 | '^.+\\.(ts)$': 'ts-jest' 7 | }, 8 | globals: { 9 | 'ts-jest': { 10 | diagnostics: true 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rokka", 3 | "version": "3.16.0", 4 | "description": "JavaScript client library for rokka.io", 5 | "main": "dist/index.js", 6 | "module": "dist/index.esm.js", 7 | "unpkg": "dist/index.umd.min.js", 8 | "jsdelivr": "dist/index.umd.min.js", 9 | "files": [ 10 | "/dist/*", 11 | "/src/*" 12 | ], 13 | "engines": { 14 | "node": ">=10" 15 | }, 16 | "scripts": { 17 | "test": "npm run lint && npm run jest", 18 | "lint": "eslint '{src,tests,docs}/**/*.{js,ts}'", 19 | "lint:fix": "eslint '{src,tests,docs}/**/*.{js,ts}' --fix", 20 | "jest": "jest", 21 | "coverage": "BABEL_ENV=coverage npm run jest -- --coverage", 22 | "docs": "cd ./docs; node generate.js", 23 | "compile": "rm -rf ./dist && rollup -c", 24 | "prepare": "husky install", 25 | "watch": "rollup -c -w" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/rokka-io/rokka.js.git" 30 | }, 31 | "keywords": [ 32 | "rokka", 33 | "client", 34 | "browser", 35 | "node.js" 36 | ], 37 | "author": "Patrick Stadler ", 38 | "license": "MIT", 39 | "bugs": { 40 | "url": "https://github.com/rokka-io/rokka.js/issues" 41 | }, 42 | "homepage": "https://github.com/rokka-io/rokka.js#readme", 43 | "dependencies": { 44 | "cross-fetch": "^3.1.5", 45 | "form-data": "^4.0.0", 46 | "jwt-decode": "^3.1.2", 47 | "query-string": "^7.1.1", 48 | "rokka-render": "^0.10.1", 49 | "simple-js-sha2-256": "^1.0.7" 50 | }, 51 | "devDependencies": { 52 | "@babel/core": "^7.17.10", 53 | "@babel/preset-env": "^7.17.10", 54 | "@rollup/plugin-commonjs": "^22.0.0", 55 | "@rollup/plugin-node-resolve": "^13.3.0", 56 | "@types/btoa": "^1.2.3", 57 | "@types/jest": "^27.5.1", 58 | "@typescript-eslint/eslint-plugin": "5.26.0", 59 | "@typescript-eslint/parser": "5.26.0", 60 | "coveralls": "^3.0.9", 61 | "eslint": "^8.15.0", 62 | "eslint-config-prettier": "^8.5.0", 63 | "eslint-plugin-prettier": "^4.0.0", 64 | "husky": "^8.0.1", 65 | "jest": "^28.1.0", 66 | "markdox": "^0.1.10", 67 | "nock": "^13.2.4", 68 | "prettier": "^2.6.2", 69 | "rollup": "^2.73.0", 70 | "rollup-plugin-terser": "^7.0.2", 71 | "rollup-plugin-typescript2": "^0.31.2", 72 | "ts-jest": "^28.0.2", 73 | "typescript": "^4.6.4" 74 | }, 75 | "overrides": { 76 | "dox": "0.9.1" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { terser } from 'rollup-plugin-terser' 2 | import resolve from '@rollup/plugin-node-resolve' 3 | import commonjs from '@rollup/plugin-commonjs' 4 | import typescript from 'rollup-plugin-typescript2' 5 | export default [ 6 | { 7 | input: 'src/index.ts', 8 | output: { 9 | file: 'dist/index.umd.min.js', 10 | format: 'umd', 11 | name: 'rokka', 12 | sourcemap: true, 13 | globals: { 14 | 'form-data': 'FormData', 15 | 'cross-fetch': 'fetch', 16 | }, 17 | }, 18 | 19 | plugins: [ 20 | typescript(), 21 | commonjs(), 22 | resolve(), 23 | terser({ 24 | // include: [/^.+\.min\.js$/], 25 | output: { 26 | comments: false, 27 | }, 28 | }), 29 | ], 30 | external: ['cross-fetch', 'form-data'], 31 | }, 32 | { 33 | input: 'src/index.ts', 34 | output: [ 35 | { 36 | file: 'dist/index.esm.js', 37 | format: 'esm', 38 | }, 39 | { 40 | file: 'dist/index.js', 41 | format: 'cjs', 42 | exports: 'auto', 43 | }, 44 | ], 45 | 46 | plugins: [typescript(), commonjs({}), resolve()], 47 | external: [ 48 | 'cross-fetch', 49 | 'form-data', 50 | 'query-string', 51 | 'simple-js-sha2-256', 52 | ], 53 | }, 54 | ] 55 | -------------------------------------------------------------------------------- /src/apis/billing.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ### Billing 3 | * 4 | * @module billing 5 | */ 6 | import { RokkaResponse } from '../response' 7 | import { State } from '../index' 8 | 9 | export interface Billing { 10 | get(organization: string, from?: string, to?: string): Promise 11 | } 12 | 13 | export default (state: State): { billing: Billing } => { 14 | const billing: Billing = { 15 | /** 16 | * Retrieve statistics about the billing of an organization 17 | * 18 | * If `from` and `to` are not specified, the API will return data for the last 30 days. 19 | * 20 | * ```js 21 | * rokka.billing.get('myorg', '2017-01-01', '2017-01-31') 22 | * .then(function(result) {}) 23 | * .catch(function(err) {}); 24 | * ``` 25 | * 26 | * @param {string} organization name 27 | * @param {string} [from=null] date in format YYYY-MM-DD 28 | * @param {string} [to=null] date in format YYYY-MM-DD 29 | * 30 | * @return {Promise} 31 | */ 32 | get: ( 33 | organization, 34 | from = undefined, 35 | to = undefined, 36 | ): Promise => { 37 | return state.request('GET', `billing/${organization}`, null, { 38 | from, 39 | to, 40 | }) 41 | }, 42 | } 43 | 44 | return { 45 | billing, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/apis/expressions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ### expressions 3 | * 4 | * @module expressions 5 | */ 6 | 7 | export interface Expressions { 8 | default(expression: string, options: Options): Expression 9 | } 10 | 11 | export interface Expression { 12 | expression: string 13 | overrides: { options: Options } 14 | } 15 | interface Options { 16 | [key: string]: string | number 17 | } 18 | 19 | export default (): { expressions: Expressions } => { 20 | const expressions = { 21 | default: (expression: string, options: Options): Expression => { 22 | return { expression, overrides: { options } } 23 | }, 24 | } 25 | return { 26 | expressions, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/apis/index.ts: -------------------------------------------------------------------------------- 1 | import memberships, { Memberships } from './memberships' 2 | import operations, { Operations } from './operations' 3 | import organizations, { Organizations } from './organizations' 4 | import expressions, { Expressions } from './expressions' 5 | import render, { Render } from './render' 6 | import sourceimages, { APISourceimages } from './sourceimages' 7 | import stackoptions, { StackOptions } from './stackoptions' 8 | import stacks, { Stacks } from './stacks' 9 | import stats, { Stats } from './stats' 10 | import billing, { Billing } from './billing' 11 | import users, { Users } from './users' 12 | import user, { User } from './user' 13 | import { State } from '../index' 14 | import request, { Request } from './request' 15 | 16 | export interface RokkaApi { 17 | billing: Billing 18 | expressions: Expressions 19 | memberships: Memberships 20 | operations: Operations 21 | organizations: Organizations 22 | render: Render 23 | sourceimages: APISourceimages 24 | stackoptions: StackOptions 25 | stacks: Stacks 26 | stats: Stats 27 | users: Users 28 | user: User 29 | request: Request 30 | } 31 | 32 | export default (state: State): RokkaApi => { 33 | return Object.assign( 34 | {}, 35 | memberships(state), 36 | operations(state), 37 | organizations(state), 38 | expressions(), 39 | render(state), 40 | sourceimages(state), 41 | stackoptions(state), 42 | stacks(state), 43 | stats(state), 44 | users(state), 45 | billing(state), 46 | user(state), 47 | request(state), 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /src/apis/memberships.ts: -------------------------------------------------------------------------------- 1 | import { RokkaResponse } from '../response' 2 | import { State } from '../index' 3 | 4 | /** 5 | * ### Memberships 6 | * 7 | * #### Roles 8 | * 9 | * - `rokka.memberships.ROLES.READ` - read-only access 10 | * - `rokka.memberships.ROLES.WRITE` - read-write access 11 | * - `rokka.memberships.ROLES.UPLOAD` - upload-only access 12 | * - `rokka.memberships.ROLES.ADMIN` - administrative access 13 | * 14 | * @module memberships 15 | */ 16 | 17 | export interface Memberships { 18 | ROLES: { READ: Role; WRITE: Role; UPLOAD: Role; ADMIN: Role } 19 | create( 20 | organization: string, 21 | userId: string, 22 | roles: Role | Role[], 23 | comment?: string | null | undefined, 24 | ): Promise 25 | delete(organization: string, userId: string): Promise 26 | createWithNewUser( 27 | organization: string, 28 | roles: Role[], 29 | comment?: string | null | undefined, 30 | ): Promise 31 | list(organization: string): Promise 32 | get(organization: string, userId: string): Promise 33 | } 34 | export enum Role { 35 | ADMIN = 'admin', 36 | READ = 'read', 37 | WRITE = 'write', 38 | UPLOAD = 'upload', 39 | SOURCEIMAGE_READ = 'sourceimages:read', 40 | SOURCEIMAGE_WRITE = 'sourceimages:write', 41 | SOURCEIMAGE_UNLOCK = 'sourceimages:unlock', 42 | SOURCEIMAGES_DOWNLOAD_PROTECTED = 'sourceimages:download:protected', 43 | } 44 | 45 | export default (state: State): { memberships: Memberships } => { 46 | const ROLES: { 47 | [key: string]: Role 48 | READ: Role 49 | WRITE: Role 50 | UPLOAD: Role 51 | ADMIN: Role 52 | SOURCEIMAGE_READ: Role 53 | SOURCEIMAGE_WRITE: Role 54 | SOURCEIMAGE_UNLOCK: Role 55 | SOURCEIMAGES_DOWNLOAD_PROTECTED: Role 56 | } = { 57 | READ: Role.READ, 58 | WRITE: Role.WRITE, 59 | UPLOAD: Role.UPLOAD, 60 | ADMIN: Role.ADMIN, 61 | SOURCEIMAGE_READ: Role.SOURCEIMAGE_READ, 62 | SOURCEIMAGE_WRITE: Role.SOURCEIMAGE_WRITE, 63 | SOURCEIMAGE_UNLOCK: Role.SOURCEIMAGE_UNLOCK, 64 | SOURCEIMAGES_DOWNLOAD_PROTECTED: Role.SOURCEIMAGES_DOWNLOAD_PROTECTED, 65 | } 66 | const memberships: Memberships = { 67 | ROLES, 68 | 69 | /** 70 | * Add a member to an organization. 71 | * 72 | * ```js 73 | * rokka.memberships.create('myorg', '613547f8-e26d-48f6-8a6a-552c18b1a290', [rokka.memberships.ROLES.WRITE], "An optional comment") 74 | * .then(function(result) {}) 75 | * .catch(function(err) {}); 76 | * ``` 77 | * 78 | * @authenticated 79 | * @param {string} organization name 80 | * @param {string} userId UUID of user to add to the organization 81 | * @param {string|array} roles user roles (`rokka.memberships.ROLES`) 82 | * @param {string} comment optional comment 83 | * @return {Promise} 84 | */ 85 | create: ( 86 | organization: string, 87 | userId: string, 88 | roles: Role | Role[], 89 | comment?: string | null | undefined, 90 | ): Promise => { 91 | if (typeof roles === 'string') { 92 | roles = [roles] 93 | } 94 | 95 | roles.forEach(role => { 96 | if ( 97 | Object.keys(ROLES) 98 | .map(key => ROLES[key]) 99 | .indexOf(role) === -1 100 | ) { 101 | return Promise.reject(new Error(`Invalid role "${role}"`)) 102 | } 103 | }) 104 | 105 | const path = `organizations/${organization}/memberships/${userId}` 106 | 107 | return state.request('PUT', path, { roles: roles, comment }) 108 | }, 109 | 110 | /** 111 | * Delete a member in an organization. 112 | * 113 | * ```js 114 | * rokka.memberships.delete('myorg', '613547f8-e26d-48f6-8a6a-552c18b1a290') 115 | * .then(function(result) {}) 116 | * .catch(function(err) {}); 117 | * ``` 118 | * 119 | * @authenticated 120 | * @param {string} organization name 121 | * @param {string} userId UUID of user to add to the organization 122 | * @return {Promise} 123 | */ 124 | delete: (organization, userId): Promise => { 125 | const path = `organizations/${organization}/memberships/${userId}` 126 | 127 | return state.request('DELETE', path) 128 | }, 129 | 130 | /** 131 | * Create a user and membership associated to this organization. 132 | * 133 | * ```js 134 | * rokka.memberships.createWithNewUser('myorg', [rokka.memberships.ROLES.READ], "New user for something") 135 | * .then(function(result) {}) 136 | * .catch(function(err) {}); 137 | * ``` 138 | * 139 | * @authenticated 140 | * @param {string} organization name 141 | * @param {array} roles user roles (`rokka.memberships.ROLES`) 142 | * @param {string} comment optional comment 143 | * @return {Promise} 144 | */ 145 | createWithNewUser: ( 146 | organization: string, 147 | roles: Role[], 148 | comment?: string | null | undefined, 149 | ): Promise => { 150 | roles.forEach(role => { 151 | if ( 152 | Object.keys(ROLES) 153 | .map(key => ROLES[key]) 154 | .indexOf(role) === -1 155 | ) { 156 | return Promise.reject(new Error(`Invalid role "${role}"`)) 157 | } 158 | }) 159 | 160 | const path = `organizations/${organization}/memberships` 161 | 162 | return state.request('POST', path, { roles: roles, comment }) 163 | }, 164 | 165 | /** 166 | * Lists members in an organization. 167 | * 168 | * ```js 169 | * rokka.memberships.list('myorg') 170 | * .then(function(result) {}) 171 | * .catch(function(err) {}); 172 | * ``` 173 | * 174 | * @authenticated 175 | * @param {string} organization name 176 | * @return {Promise} 177 | */ 178 | list: (organization: string): Promise => { 179 | const path = `organizations/${organization}/memberships` 180 | 181 | return state.request('GET', path) 182 | }, 183 | 184 | /** 185 | * Get info of a member in an organization. 186 | * 187 | * ```js 188 | * rokka.memberships.get('myorg',userId) 189 | * .then(function(result) {}) 190 | * .catch(function(err) {}); 191 | * ``` 192 | * 193 | * @authenticated 194 | * @param {string} organization name 195 | * @param {string} userId 196 | * @return {Promise} 197 | */ 198 | get: (organization: string, userId: string): Promise => { 199 | const path = `organizations/${organization}/memberships/${userId}` 200 | 201 | return state.request('GET', path) 202 | }, 203 | } 204 | 205 | return { 206 | memberships, 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/apis/operations.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ### Operations 3 | * 4 | * #### Available operations 5 | * 6 | * - `rokka.operations.resize(width, height, options = {})` 7 | * - `rokka.operations.autorotate(options = {})` 8 | * - `rokka.operations.rotate(angle, options = {})` 9 | * - `rokka.operations.dropshadow(options = {})` 10 | * - `rokka.operations.trim(options = {})` 11 | * - `rokka.operations.crop(width, height, options = {})` 12 | * - `rokka.operations.noop()` 13 | * - `rokka.operations.composition(width, height, mode, options = {})` 14 | * - `rokka.operations.blur(sigma, radius)` 15 | * 16 | * Please refer to the 17 | * [rokka API documentation](https://rokka.io/documentation/references/operations.html) 18 | * 19 | * @module operations 20 | */ 21 | import { State } from '../index' 22 | import { RokkaResponse } from '../response' 23 | import { StackOperation } from 'rokka-render' 24 | 25 | export interface StackOperationOptions { 26 | [key: string]: string | number | boolean | undefined | null 27 | enabled?: boolean 28 | } 29 | export enum ResizeMode { 30 | Box = 'box', 31 | Fill = 'fill', 32 | Absolute = 'absolute', 33 | } 34 | 35 | export interface ResizeOperationsOptions extends StackOperationOptions { 36 | width?: number 37 | height?: number 38 | mode?: ResizeMode 39 | upscale?: boolean 40 | upscale_dpr?: boolean 41 | } 42 | 43 | export interface CropOperationsOptions extends StackOperationOptions { 44 | width?: number 45 | height?: number 46 | anchor?: string 47 | fallback?: string 48 | mode?: string 49 | area?: string 50 | scale?: number 51 | } 52 | 53 | export enum CompositionMode { 54 | Foreground = 'foreground', 55 | Background = 'background', 56 | } 57 | 58 | export interface CompositionOperationsOptions extends StackOperationOptions { 59 | width?: number 60 | height?: number 61 | mode?: CompositionMode 62 | resize_mode?: ResizeMode 63 | anchor?: string 64 | resize_to_primary?: boolean 65 | secondary_color?: string 66 | secondary_opacity?: number 67 | secondary_image?: string 68 | } 69 | 70 | export interface Operations { 71 | [key: string]: Function 72 | resize( 73 | width: number, 74 | height: number, 75 | options?: ResizeOperationsOptions, 76 | ): StackOperation 77 | autorotate(options?: StackOperationOptions): StackOperation 78 | rotate(angle: number, options?: StackOperationOptions): StackOperation 79 | dropshadow(options?: StackOperationOptions): StackOperation 80 | trim(options?: StackOperationOptions): StackOperation 81 | noop(): StackOperation 82 | crop( 83 | width: number, 84 | height: number, 85 | options?: CropOperationsOptions, 86 | ): StackOperation 87 | composition( 88 | width: number, 89 | height: number, 90 | mode: string, 91 | options?: CompositionOperationsOptions, 92 | ): StackOperation 93 | blur(sigma: number, radius?: number): StackOperation 94 | list(): Promise 95 | } 96 | 97 | export default (state: State): { operations: Operations } => { 98 | const operations: Operations = { 99 | resize: ( 100 | width: number, 101 | height: number, 102 | options: ResizeOperationsOptions = {}, 103 | ): StackOperation => { 104 | options.width = width 105 | options.height = height 106 | 107 | return { 108 | name: 'resize', 109 | options, 110 | } 111 | }, 112 | autorotate: ( 113 | options: StackOperationOptions | undefined = {}, 114 | ): StackOperation => { 115 | return { 116 | name: 'autorotate', 117 | options, 118 | } 119 | }, 120 | rotate: ( 121 | angle: number, 122 | options: StackOperationOptions = {}, 123 | ): StackOperation => { 124 | options.angle = angle 125 | 126 | return { 127 | name: 'rotate', 128 | options, 129 | } 130 | }, 131 | dropshadow: (options: StackOperationOptions = {}): StackOperation => { 132 | return { 133 | name: 'dropshadow', 134 | options, 135 | } 136 | }, 137 | trim: (options: StackOperationOptions = {}): StackOperation => { 138 | return { 139 | name: 'trim', 140 | options, 141 | } 142 | }, 143 | crop: ( 144 | width: number, 145 | height: number, 146 | options: CropOperationsOptions = {}, 147 | ): StackOperation => { 148 | options.width = width 149 | options.height = height 150 | 151 | return { 152 | name: 'crop', 153 | options, 154 | } 155 | }, 156 | noop: (): StackOperation => { 157 | return { 158 | name: 'noop', 159 | } 160 | }, 161 | composition: ( 162 | width: number, 163 | height: number, 164 | mode: string, 165 | options: StackOperationOptions = {}, 166 | ): StackOperation => { 167 | options.width = width 168 | options.height = height 169 | options.mode = mode 170 | 171 | return { 172 | name: 'composition', 173 | options, 174 | } 175 | }, 176 | blur: (sigma: number, radius: number): StackOperation => { 177 | const options = { sigma, radius } 178 | 179 | return { 180 | name: 'blur', 181 | options, 182 | } 183 | }, 184 | 185 | /** 186 | * Get a list of available stack operations. 187 | * 188 | * ```js 189 | * rokka.operations.list() 190 | * .then(function(result) {}) 191 | * .catch(function(err) {}); 192 | * ``` 193 | * 194 | * @return {Promise} 195 | */ 196 | list: (): Promise => { 197 | return state.request('GET', 'operations', null, null, { 198 | noAuthHeaders: true, 199 | }) 200 | }, 201 | } 202 | return { 203 | operations, 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/apis/organizations.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ### Organizations 3 | * 4 | * @module organizations 5 | */ 6 | import { RokkaResponse } from '../response' 7 | import { State } from '../index' 8 | 9 | export interface Organizations { 10 | OPTION_PROTECT_DYNAMIC_STACK: string 11 | get(name: string): Promise 12 | 13 | create( 14 | name: string, 15 | billingEmail: string, 16 | displayName: string, 17 | ): Promise 18 | 19 | setOption( 20 | organizationName: string, 21 | name: string, 22 | value: boolean | string, 23 | ): Promise 24 | } 25 | 26 | export default (state: State): { organizations: Organizations } => { 27 | const organizations: Organizations = { 28 | OPTION_PROTECT_DYNAMIC_STACK: 'protect_dynamic_stack', 29 | /** 30 | * Get a list of organizations. 31 | * 32 | * ```js 33 | * rokka.organizations.get('myorg') 34 | * .then(function(result) {}) 35 | * .catch(function(err) {}); 36 | * ``` 37 | * 38 | * @authenticated 39 | * @param {string} name organization 40 | * @return {Promise} 41 | */ 42 | get: name => { 43 | return state.request('GET', `organizations/${name}`) 44 | }, 45 | 46 | /** 47 | * Create an organization. 48 | * 49 | * ```js 50 | * rokka.organizations.create('myorg', 'billing@example.org', 'Organization Inc.') 51 | * .then(function(result) {}) 52 | * .catch(function(err) {}); 53 | * ``` 54 | * 55 | * @authenticated 56 | * @param {string} name organization 57 | * @param {string} billingEmail email used for billing 58 | * @param {string} displayName pretty name 59 | * @return {Promise} 60 | */ 61 | create: (name, billingEmail, displayName) => { 62 | return state.request('PUT', `organizations/${name}`, { 63 | billing_email: billingEmail, 64 | display_name: displayName, 65 | }) 66 | }, 67 | 68 | setOption: (organizationName, name, value) => { 69 | return state.request( 70 | 'PUT', 71 | `organizations/${organizationName}/options/${name}`, 72 | value, 73 | ) 74 | }, 75 | } 76 | 77 | return { 78 | organizations, 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/apis/render.ts: -------------------------------------------------------------------------------- 1 | import { State } from '../index' 2 | import sha2_256 from 'simple-js-sha2-256' 3 | import { RokkaResponse } from '../response' 4 | import { 5 | getUrlFromUrl, 6 | getUrl, 7 | AddStackVariablesType, 8 | GetUrlOptions, 9 | GetUrlFromUrlOptions, 10 | GetUrlComponentsType, 11 | addStackVariables, 12 | } from 'rokka-render' 13 | 14 | type SignUrlWithOptionsType = ( 15 | url: string, 16 | signKey: string, 17 | options?: { until?: string } | null, 18 | ) => string 19 | 20 | export interface SignUrlOptions { 21 | until?: Date | null 22 | roundDateUpTo?: number 23 | } 24 | 25 | export type SignUrlType = ( 26 | url: string, 27 | signKey: string, 28 | options?: SignUrlOptions, 29 | ) => string 30 | 31 | interface ImagesByAlbumOptions { 32 | favorites?: boolean 33 | } 34 | 35 | export interface Render { 36 | getUrl( 37 | organization: string, 38 | hash: string, 39 | format: string, 40 | stack: string | object, 41 | options?: GetUrlOptions, 42 | ): string 43 | getUrlFromUrl( 44 | rokkaUrl: string, 45 | stack: string | object, 46 | options?: GetUrlFromUrlOptions, 47 | ): string 48 | imagesByAlbum: ( 49 | organization: string, 50 | album: string, 51 | options?: ImagesByAlbumOptions | undefined, 52 | ) => Promise 53 | signUrl: SignUrlType 54 | signUrlWithOptions: SignUrlWithOptionsType 55 | addStackVariables: AddStackVariablesType 56 | getUrlComponents: GetUrlComponentsType 57 | } 58 | 59 | // currently only gets stack variables 60 | 61 | const getUrlComponents: GetUrlComponentsType = (urlObject: URL) => { 62 | const stackPattern = '(?.*([^-]|--)|-*)' 63 | const hashPattern = '(?[0-9a-f]{6,40})' 64 | const filenamePattern = '(?[^\\/^.]+)' 65 | const formatPattern = '(?.{2,4})' 66 | const pathPattern = '(?-.+-)' 67 | 68 | const regExes = [ 69 | new RegExp( 70 | `/${stackPattern}/${hashPattern}/${filenamePattern}\.${formatPattern}`, 71 | ), 72 | new RegExp(`/${stackPattern}/${hashPattern}\.${formatPattern}`), 73 | new RegExp( 74 | `/${stackPattern}/${pathPattern}/${filenamePattern}\.${formatPattern}`, 75 | ), 76 | 77 | new RegExp(`/${stackPattern}/${pathPattern}\.${formatPattern}`), 78 | ] 79 | 80 | const path = urlObject.pathname 81 | let matches = null 82 | for (let i = 0; i < regExes.length; i++) { 83 | matches = path.match(regExes[i]) 84 | if (matches) { 85 | break 86 | } 87 | } 88 | 89 | if (matches !== null && matches.groups?.['stack']) { 90 | return { 91 | stack: matches.groups['stack'], 92 | hash: matches.groups['hash'], 93 | format: matches.groups['format'], 94 | filename: matches.groups['filename'], 95 | } 96 | } 97 | return false 98 | } 99 | 100 | /** 101 | * ### Render 102 | * 103 | * @module render 104 | */ 105 | export default (state: State): { render: Render } => { 106 | const render: Render = { 107 | /** 108 | * Get URL for rendering an image. 109 | * 110 | * If you just need this function in a browser, you can also use [rokka-render.js](https://github.com/rokka-io/rokka-render.js) 111 | * 112 | * ```js 113 | * rokka.render.getUrl('myorg', 'c421f4e8cefe0fd3aab22832f51e85bacda0a47a', 'png', 'mystack') 114 | * ``` 115 | * 116 | * @param {string} organization name 117 | * @param {string} hash image hash 118 | * @param {string} format image format: `jpg`, `png` or `gif` 119 | * @param {string|array} [stack] optional stack name or an array of stack operation objects 120 | * @param {{filename:string|undefined, stackoptions: StackOptions|undefined, variables: VariablesInterface|undefined, clearVariables:boolean|undefined, removeSafeUrlFromQuery: boolean|undefined}} options Optional. filename: Adds the filename to the URL, stackoptions: Adds stackoptions to the URL 121 | * @return {string} 122 | */ 123 | getUrl: (organization, hash, format, stack, options) => { 124 | return getUrl( 125 | organization, 126 | hash, 127 | format, 128 | stack, 129 | options, 130 | state.renderHost, 131 | ) 132 | }, 133 | 134 | /** 135 | * Get URL for rendering an image from a rokka render URL. 136 | * 137 | * If you just need this function in a browser, you can also use [rokka-render.js](https://github.com/rokka-io/rokka-render.js) 138 | * 139 | * ```js 140 | * rokka.render.getUrlFromUrl('https://myorg.rokka.io/dynamic/c421f4e8cefe0fd3aab22832f51e85bacda0a47a.png', 'mystack') 141 | * ``` 142 | * 143 | * @param {string} rokkaUrl rokka render URL 144 | * @param {string|array} stack stack name or an array of stack operation objects 145 | * @param {{filename:string|undefined, stackoptions: StackOptions|undefined, format: string|undefined, variables: VariablesInterface|undefined, clearVariables:boolean|undefined, removeSafeUrlFromQuery: boolean|undefined}} options Optional. filename: Adds or changes the filename to the URL, stackoptions: Adds stackoptions to the URL, format: Changes the format 146 | * @return {string} 147 | */ 148 | getUrlFromUrl: ( 149 | rokkaUrl: string, 150 | stack: string | object, 151 | options: GetUrlFromUrlOptions = {}, 152 | ): string => { 153 | return getUrlFromUrl(rokkaUrl, stack, options, state.renderHost) 154 | }, 155 | 156 | /** 157 | * Get image hashes and some other info belonging to a album (from metadata: user:array:albums) 158 | * ```js 159 | * rokka.render.imagesByAlbum('myorg', 'Albumname', { favorites }) 160 | * ``` 161 | * 162 | * @param {string} organization name 163 | * @param {string } album albumname 164 | * @param {{favorites:boolean}} options Optional options 165 | */ 166 | imagesByAlbum: (organization, album, options): Promise => { 167 | const host = state.renderHost.replace('{organization}', organization) 168 | 169 | let filename = 'all' 170 | if (options?.favorites) { 171 | filename = 'favorites' 172 | } 173 | return state.request( 174 | 'GET', 175 | `_albums/${album}/${filename}.json`, 176 | null, 177 | undefined, 178 | { 179 | host, 180 | noAuthHeaders: true, 181 | noTokenRefresh: true, 182 | }, 183 | ) 184 | }, 185 | 186 | /** 187 | * Signs a Rokka URL with an option valid until date. 188 | * 189 | * It also rounds up the date to the next 5 minutes (300 seconds) to 190 | * improve CDN caching, can be changed 191 | * 192 | * @param {string} url The Url to be signed 193 | * @param {string} signKey The organinzation's sign key 194 | * @param {SignUrlOptions} [{until:Date = null, roundDateUpTo:number = 300}] optional options. 195 | * until: Valid until, 196 | * roundDateUpTo: For improved caching, the date can be rounded up by so many seconds (default: 300) 197 | * @return {string} 198 | */ 199 | signUrl: (url, signKey, { until = null, roundDateUpTo = 300 } = {}) => { 200 | let options = null 201 | if (until) { 202 | let until2 = until 203 | 204 | if (roundDateUpTo > 1) { 205 | until2 = new Date() 206 | until2.setTime( 207 | Math.ceil(until.getTime() / (roundDateUpTo * 1000)) * 208 | (roundDateUpTo * 1000), 209 | ) 210 | } 211 | options = { until: until2.toISOString() } 212 | } 213 | return render.signUrlWithOptions(url, signKey, options) 214 | }, 215 | /** 216 | * Signs a rokka URL with a sign key and optional signature options. 217 | * 218 | */ 219 | signUrlWithOptions: (url, signKey, options) => { 220 | const urlObject = new URL(url) 221 | 222 | // generate sigopts 223 | if (options) { 224 | urlObject.searchParams.set('sigopts', JSON.stringify(options)) 225 | } else { 226 | urlObject.searchParams.delete('sigopts') 227 | } 228 | 229 | // remove sig 230 | urlObject.searchParams.delete('sig') 231 | 232 | // remove filename if one exists, not needed for signature 233 | const components = getUrlComponents(urlObject) 234 | if (components && components.filename) { 235 | const regex = new RegExp( 236 | `/${components.filename}\.${components.format}$`, 237 | ) 238 | urlObject.pathname = urlObject.pathname.replace( 239 | regex, 240 | `.${components.format}`, 241 | ) 242 | } 243 | const urlPath = urlObject.pathname + urlObject.search 244 | const sigString = urlPath + ':' + signKey 245 | const hash = sha2_256(sigString) 246 | 247 | // append new sig 248 | urlObject.searchParams.append('sig', hash.substring(0, 16)) 249 | return urlObject.toString() 250 | }, 251 | 252 | /** 253 | * Adds stack variables to a rokka URL in a safe way 254 | * 255 | * Uses the v query parameter, if a variable shouldn't be in the path 256 | * 257 | * If you just need this function in a browser, you can also use [rokka-render.js](https://github.com/rokka-io/rokka-render.js) 258 | * 259 | * @param {string} url The url the stack variables are added to 260 | * @param {object} variables The variables to add 261 | * @param {boolean} [removeSafeUrlFromQuery=false] If true, removes some safe characters from the query 262 | * @return {string} 263 | */ 264 | addStackVariables: ( 265 | url, 266 | variables, 267 | removeSafeUrlFromQuery = false, 268 | ): string => { 269 | return addStackVariables(url, variables, removeSafeUrlFromQuery) 270 | }, 271 | /** 272 | * Get rokka components from an URL object. 273 | * 274 | * Returns false, if it could not parse it as rokka URL. 275 | * 276 | * @param {URL} urlObject 277 | * @return {UrlComponents|false} 278 | */ 279 | getUrlComponents: getUrlComponents, 280 | } 281 | 282 | return { 283 | render, 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /src/apis/request.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ### request 3 | * 4 | * @module request 5 | */ 6 | 7 | import { RokkaResponse } from '../response' 8 | import { State } from '../index' 9 | 10 | export interface Request { 11 | (path: string, method?: string, body?: any | null): Promise 12 | } 13 | export default (state: State): { request: Request } => { 14 | /** 15 | * Does an authenticated request for any path to the Rokka API 16 | * @param path {string} The path (without leading slash) 17 | * @param method {string} HTTP method, Default GET 18 | * @param body {any|null} The body payload. Default: null 19 | */ 20 | const request = (path: string, method = 'GET', body: any | null = null) => { 21 | return state.request(method, path, body) 22 | } 23 | return { 24 | request, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/apis/sourceimages.alias.ts: -------------------------------------------------------------------------------- 1 | import { State } from '../index' 2 | import { APISourceimagesAlias } from './sourceimages' 3 | import { RokkaResponse } from '../response' 4 | 5 | /** 6 | * ### Source Images alias 7 | * 8 | * @module sourceimages.alias 9 | */ 10 | export default (state: State): APISourceimagesAlias => { 11 | return { 12 | /** 13 | * ### Source Images alias 14 | * 15 | * See [the usource image alias documentation](https://rokka.io/documentation/references/source-images-aliases.html) 16 | * for more information. 17 | */ 18 | 19 | /** 20 | * Adds an alias to a source image. 21 | * 22 | * See [the source image alias documentation](https://rokka.io/documentation/references/source-images-aliases.html) 23 | * for an explanation. 24 | * 25 | * ```js 26 | * rokka.sourceimages.alias.create('myorg', 'myalias', { 27 | * hash: 'somehash', 28 | * }).then(function(result) {}) 29 | * .catch(function(err) {}); 30 | * ``` 31 | * 32 | * @authenticated 33 | * @param {string} organization name 34 | * @param {string} alias alias name 35 | * @param {object} data object with "hash" key 36 | * @param {{overwrite: bool}} [params={}] params query params, only {overwrite: true|false} is currently supported 37 | * @return {Promise} 38 | */ 39 | create: ( 40 | organization: string, 41 | alias: string, 42 | data: { hash: string }, 43 | params: { overwrite?: boolean } = {}, 44 | ): Promise => { 45 | const queryParams = Object.assign({}, params) 46 | 47 | return state.request( 48 | 'PUT', 49 | `sourceimages/${organization}/alias/${alias}`, 50 | data, 51 | queryParams, 52 | ) 53 | }, 54 | 55 | /** 56 | * Get an alias. 57 | * @param organization 58 | * @param alias 59 | */ 60 | get(organization: string, alias: string): Promise { 61 | return state.request('GET', `sourceimages/${organization}/alias/${alias}`) 62 | }, 63 | 64 | /** 65 | * Delete an alias. 66 | * 67 | * @param organization 68 | * @param alias 69 | */ 70 | delete(organization: string, alias: string): Promise { 71 | return state.request( 72 | 'DELETE', 73 | `sourceimages/${organization}/alias/${alias}`, 74 | ) 75 | }, 76 | /** 77 | * Invalidate the CDN cache for an alias. 78 | * 79 | * @param organization 80 | * @param alias 81 | */ 82 | invalidateCache( 83 | organization: string, 84 | alias: string, 85 | ): Promise { 86 | return state.request( 87 | 'DELETE', 88 | `sourceimages/${organization}/alias/${alias}/cache`, 89 | ) 90 | }, 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/apis/sourceimages.meta.ts: -------------------------------------------------------------------------------- 1 | import { State } from '../index' 2 | import { APISourceimagesMeta } from './sourceimages' 3 | import { RokkaResponse } from '../response' 4 | 5 | /** 6 | * ### Source Images 7 | * 8 | * @module sourceimages.meta 9 | */ 10 | export default (state: State): APISourceimagesMeta => { 11 | return { 12 | /** 13 | * ### User metadata 14 | * 15 | * See [the user metadata documentation](https://rokka.io/documentation/references/user-metadata.html) 16 | * for more information. 17 | */ 18 | 19 | /** 20 | * Add user metadata to a source image. 21 | * 22 | * See [the user metadata documentation](https://rokka.io/documentation/references/user-metadata.html) 23 | * for an explanation. 24 | * 25 | * ```js 26 | * rokka.sourceimages.meta.add('myorg', 'c421f4e8cefe0fd3aab22832f51e85bacda0a47a', { 27 | * somefield: 'somevalue', 28 | * 'int:some_number': 0, 29 | * 'delete_this': null 30 | * }).then(function(result) {}) 31 | * .catch(function(err) {}); 32 | * ``` 33 | * 34 | * @authenticated 35 | * @param {string} organization name 36 | * @param {string} hash image hash 37 | * @param {object} data metadata to add to the image 38 | * @return {Promise} 39 | */ 40 | add: ( 41 | organization: string, 42 | hash: string, 43 | data: { [key: string]: any }, 44 | ): Promise => { 45 | return state.request( 46 | 'PATCH', 47 | `sourceimages/${organization}/${hash}/meta/user`, 48 | data, 49 | ) 50 | }, 51 | 52 | /** 53 | * Replace user metadata of a source image with the passed data. 54 | * 55 | * See [the user metadata documentation](https://rokka.io/documentation/references/user-metadata.html) 56 | * for an explanation. 57 | * 58 | * ```js 59 | * rokka.sourceimages.meta.replace('myorg', 'c421f4e8cefe0fd3aab22832f51e85bacda0a47a', { 60 | * somefield: 'somevalue', 61 | * 'int:some_number': 0 62 | * }).then(function(result) {}) 63 | * .catch(function(err) {}); 64 | * ``` 65 | * 66 | * @authenticated 67 | * @param {string} organization name 68 | * @param {string} hash image hash 69 | * @param {object} data new metadata 70 | * @return {Promise} 71 | */ 72 | replace: ( 73 | organization: string, 74 | hash: string, 75 | data: { [key: string]: any }, 76 | ): Promise => { 77 | return state.request( 78 | 'PUT', 79 | `sourceimages/${organization}/${hash}/meta/user`, 80 | data, 81 | ) 82 | }, 83 | 84 | /** 85 | * Replace user metadata of a source image with the passed data. 86 | * 87 | * See [the user metadata documentation](https://rokka.io/documentation/references/user-metadata.html) 88 | * for an explanation. 89 | * 90 | * ```js 91 | * rokka.sourceimages.meta.delete('myorg', 'c421f4e8cefe0fd3aab22832f51e85bacda0a47a') 92 | * .then(function(result) {}) 93 | * .catch(function(err) {}); 94 | * ``` 95 | * 96 | * If the third parameter (field) is specified, it will just delete this field. 97 | * 98 | * @authenticated 99 | * @param {string} organization name 100 | * @param {string} hash image hash 101 | * @param {string} [field=null] optional field to delete 102 | * @return {Promise} 103 | */ 104 | delete: ( 105 | organization: string, 106 | hash: string, 107 | field: string | null = null, 108 | ): Promise => { 109 | const fieldpath = field ? `/${field}` : '' 110 | return state.request( 111 | 'DELETE', 112 | `sourceimages/${organization}/${hash}/meta/user${fieldpath}`, 113 | ) 114 | }, 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/apis/stackoptions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ### Stack options 3 | * 4 | * @module stackoptions 5 | */ 6 | 7 | import { RokkaResponse } from '../response' 8 | import { State } from '../index' 9 | 10 | export interface StackOptions { 11 | get(): Promise 12 | } 13 | 14 | export default (state: State): { stackoptions: StackOptions } => { 15 | const stackoptions: StackOptions = { 16 | /** 17 | * Returns a json-schema like definition of options which can be set on a stack. 18 | * 19 | * ```js 20 | * rokka.stackoptions.get() 21 | * .then(function(result) {}) 22 | * .catch(function(err) {}); 23 | * ``` 24 | * 25 | * @return {Promise} 26 | */ 27 | get: (): Promise => { 28 | return state.request('GET', 'stackoptions', null, null, { 29 | noAuthHeaders: true, 30 | }) 31 | }, 32 | } 33 | return { 34 | stackoptions, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/apis/stacks.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ### Stacks 3 | * 4 | * @module stacks 5 | */ 6 | 7 | import { RokkaResponse } from '../response' 8 | import { Expression } from './expressions' 9 | import { State } from '../index' 10 | import { StackOperation, StackOptions, Variables } from 'rokka-render' 11 | export { StackOperation, StackOptions, Variables } from 'rokka-render' 12 | 13 | export interface Stacks { 14 | delete(organization: string, name: string): Promise 15 | get(organization: string, name: string): Promise 16 | create( 17 | organization: string, 18 | name: string, 19 | stackConfig: StackConfig | StackOperation[], 20 | params?: { overwrite?: boolean } | StackOptions, 21 | ...rest: boolean[] 22 | ): Promise 23 | 24 | list( 25 | organization: string, 26 | limit?: number | null, 27 | offset?: string | null, 28 | ): Promise 29 | } 30 | 31 | export interface StackConfig { 32 | operations?: StackOperation[] 33 | options?: StackOptions 34 | expressions?: Expression[] 35 | variables?: Variables 36 | } 37 | 38 | export default (state: State): { stacks: Stacks } => { 39 | const stacks = { 40 | /** 41 | * Get a list of available stacks. 42 | * 43 | * ```js 44 | * rokka.stacks.list('myorg') 45 | * .then(function(result) {}) 46 | * .catch(function(err) {}); 47 | * ``` 48 | * 49 | * @authenticated 50 | * @param {string} organization name 51 | * @param {number} [limit=null] 52 | * @param {string} [offset=null] cursor 53 | * @return {Promise} 54 | */ 55 | list: ( 56 | organization: string, 57 | limit: number | null = null, 58 | offset: string | null = null, 59 | ): Promise => { 60 | const queryParams: { limit?: number; offset?: string } = {} 61 | 62 | if (limit !== null) { 63 | queryParams.limit = limit 64 | } 65 | if (offset !== null) { 66 | queryParams.offset = offset 67 | } 68 | 69 | return state.request('GET', `stacks/${organization}`, null, queryParams) 70 | }, 71 | 72 | /** 73 | * Get details about a stack. 74 | * 75 | * ```js 76 | * rokka.stacks.get('myorg', 'mystack') 77 | * .then(function(result) {}) 78 | * .catch(function(result) {}); 79 | * ``` 80 | * 81 | * @authenticated 82 | * @param {string} organization name 83 | * @param {string} name stack name 84 | * @return {Promise} 85 | */ 86 | 87 | get: (organization: string, name: string): Promise => { 88 | return state.request('GET', `stacks/${organization}/${name}`) 89 | }, 90 | 91 | /** 92 | * Create a new stack. 93 | * 94 | * The signature of this method changed in 0.27. 95 | * 96 | * Using a single stack operation object (without an enclosing array) as the 3rd parameter (stackConfig) is 97 | * since version 0.27.0 not supported anymore. 98 | * 99 | * ```js 100 | * const operations = [ 101 | * rokka.operations.rotate(45), 102 | * rokka.operations.resize(100, 100) 103 | * ] 104 | * 105 | * // stack options are optional 106 | * const options = { 107 | * 'jpg.quality': 80, 108 | * 'webp.quality': 80 109 | * } 110 | * 111 | * // stack expressions are optional 112 | * const expressions = [ 113 | * rokka.expressions.default('options.dpr > 2', { 'jpg.quality': 60, 'webp.quality': 60 }) 114 | * ] 115 | * 116 | * // query params are optional 117 | * var queryParams = { overwrite: true } 118 | * rokka.stacks.create( 119 | * 'test', 120 | * 'mystack', 121 | * { operations, options, expressions }, 122 | * queryParams 123 | * ).then(function(result) {}) 124 | * .catch(function(err) {}) 125 | * ``` 126 | * 127 | * @authenticated 128 | * @param {string} organization name 129 | * @param {string} name stack name 130 | * @param {Object} stackConfig object with the stack config of stack operations, options and expressions. 131 | * @param {{overwrite: bool}} [params={}] params query params, only {overwrite: true|false} is currently supported 132 | * @return {Promise} 133 | */ 134 | 135 | create: ( 136 | organization: string, 137 | name: string, 138 | stackConfig: StackConfig | StackOperation[], 139 | params: { overwrite?: boolean } | StackOptions = {}, 140 | ...rest: boolean[] 141 | ): Promise => { 142 | let queryParams = Object.assign({}, params) 143 | let body: StackConfig = {} 144 | 145 | // backwards compatibility for previous signature: 146 | // create(organization, name, operations, options = null, overwrite = false) 147 | if (Array.isArray(stackConfig)) { 148 | body.operations = stackConfig 149 | body.options = params as StackOptions 150 | const _overwrite = rest.length > 0 ? rest[0] : false 151 | queryParams = {} 152 | if (_overwrite) { 153 | queryParams.overwrite = _overwrite 154 | } 155 | } else { 156 | body = stackConfig 157 | } 158 | 159 | return state.request( 160 | 'PUT', 161 | `stacks/${organization}/${name}`, 162 | body, 163 | queryParams, 164 | ) 165 | }, 166 | 167 | /** 168 | * Delete a stack. 169 | * 170 | * ```js 171 | * rokka.stacks.delete('myorg', 'mystack') 172 | * .then(function(result) {}) 173 | * .catch(function(err) {}); 174 | * ``` 175 | * 176 | * @authenticated 177 | * @param {string} organization name 178 | * @param {string} name stack name 179 | * @return {Promise} 180 | */ 181 | delete: (organization: string, name: string): Promise => { 182 | return state.request('DELETE', `stacks/${organization}/${name}`) 183 | }, 184 | } 185 | 186 | return { 187 | stacks, 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/apis/stats.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ### Stats 3 | * 4 | * @module stats 5 | */ 6 | 7 | import { RokkaResponse } from '../response' 8 | import { State } from '../index' 9 | 10 | export interface Stats { 11 | get( 12 | organization: string, 13 | from?: string | null, 14 | to?: string | null, 15 | ): Promise 16 | } 17 | export default (state: State): { stats: Stats } => { 18 | const stats: Stats = { 19 | /** 20 | * Retrieve statistics about an organization. 21 | * 22 | * If `from` and `to` are not specified, the API will return data for the last 30 days. 23 | * 24 | * ```js 25 | * rokka.stats.get('myorg', '2017-01-01', '2017-01-31') 26 | * .then(function(result) {}) 27 | * .catch(function(err) {}); 28 | * ``` 29 | * 30 | * @param {string} organization name 31 | * @param {string} [from=null] date in format YYYY-MM-DD 32 | * @param {string} [to=null] date in format YYYY-MM-DD 33 | * 34 | * @return {Promise} 35 | */ 36 | get: ( 37 | organization: string, 38 | from: string | null = null, 39 | to: string | null = null, 40 | ): Promise => { 41 | return state.request('GET', `stats/${organization}`, null, { 42 | from, 43 | to, 44 | }) 45 | }, 46 | } 47 | 48 | return { 49 | stats, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/apis/user.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ### User 3 | * 4 | * @module user 5 | */ 6 | 7 | import { RokkaResponse } from '../response' 8 | import { RequestQueryParams, State } from '../index' 9 | import { _getTokenPayload, _isTokenExpiring, _tokenValidFor } from '../utils' 10 | 11 | export interface UserApiKey { 12 | id: string 13 | accessed?: string 14 | created?: string 15 | comment?: string 16 | api_key?: string 17 | } 18 | 19 | export interface UserApiKeyResponse extends RokkaResponse { 20 | body: UserApiKey 21 | } 22 | 23 | export type ApiToken = string | null 24 | 25 | export interface UserKeyTokenBody extends RokkaResponse { 26 | token: ApiToken 27 | payload: ApiTokenPayload 28 | } 29 | 30 | export interface ApiTokenPayload { 31 | [key: string]: string[] | string | number | undefined | null | boolean 32 | exp: number 33 | ip?: string 34 | expt?: number 35 | nr?: boolean 36 | ips?: string[] 37 | } 38 | 39 | export type ApiTokenGetCallback = (() => ApiToken) | null | undefined 40 | export type ApiTokenSetCallback = 41 | | ((token: ApiToken, payload?: ApiTokenPayload | null) => void) 42 | | null 43 | 44 | export interface UserKeyTokenResponse extends RokkaResponse { 45 | body: UserKeyTokenBody 46 | } 47 | 48 | export interface UserApiKeyListResponse extends RokkaResponse { 49 | body: UserApiKey[] 50 | } 51 | 52 | export interface UserResponse extends RokkaResponse { 53 | body: { user_id: string; email?: string; api_keys: UserApiKey[] } 54 | } 55 | 56 | export interface RequestQueryParamsNewToken extends RequestQueryParams { 57 | renewable?: boolean 58 | no_ip_protection?: boolean 59 | ips?: string 60 | expires_in?: number 61 | } 62 | 63 | export interface User { 64 | getId(): Promise 65 | get(): Promise 66 | addApiKey(comment: string | null): Promise 67 | deleteApiKey(id: string): Promise 68 | listApiKeys(): Promise 69 | getCurrentApiKey(): Promise 70 | getNewToken( 71 | apiKey?: string, 72 | queryParams?: RequestQueryParamsNewToken | null, 73 | ): Promise 74 | getToken(): ApiToken | null 75 | setToken(token: ApiToken | null): void 76 | isTokenExpiring(atLeastNotForSeconds?: number): boolean 77 | getTokenIsValidFor(): number 78 | } 79 | 80 | export default (state: State): { user: User } => { 81 | const user: User = { 82 | /** 83 | * Get user_id for current user 84 | * 85 | * ```js 86 | * rokka.users.getId() 87 | * .then(function(result) {}) 88 | * .catch(function(err) {}); 89 | * ``` 90 | * @since 3.3.0 91 | * @authenticated 92 | * 93 | * @return {Promise} 94 | */ 95 | getId: (): Promise => { 96 | return state.request('GET', 'user').then(result => result.body.user_id) 97 | }, 98 | 99 | /** 100 | * Get user object for current user 101 | * 102 | * ```js 103 | * rokka.user.get() 104 | * .then(function(result) {}) 105 | * .catch(function(err) {}); 106 | * ``` 107 | * 108 | * @since 3.3.0 109 | * @authenticated 110 | * 111 | * @return {Promise} 112 | */ 113 | get: (): Promise => { 114 | return state.request('GET', 'user') 115 | }, 116 | 117 | /** 118 | * List Api Keys of the current user 119 | 120 | * 121 | * ```js 122 | * rokka.user.listApiKeys() 123 | * .then(function(result) {}) 124 | * .catch(function(err) {}); 125 | * ``` 126 | * 127 | * @since 3.3.0 128 | * @authenticated 129 | * 130 | * @return {Promise} 131 | */ 132 | listApiKeys: (): Promise => { 133 | return state.request('GET', 'user/apikeys') 134 | }, 135 | 136 | /** 137 | * Add Api Key to the current user 138 | * 139 | * @param {string} comment optional comment 140 | * 141 | * ```js 142 | * rokka.user.addApiKey('some comment') 143 | * .then(function(result) {}) 144 | * .catch(function(err) {}); 145 | * ``` 146 | * 147 | * @since 3.3.0 148 | * @authenticated 149 | * 150 | * @return {Promise} 151 | */ 152 | addApiKey: (comment: string | null = null): Promise => { 153 | return state.request('POST', 'user/apikeys', { comment }) 154 | }, 155 | 156 | /** 157 | * Delete Api Key from the current user 158 | 159 | * 160 | * @param {string} id the id of the Api Key 161 | * 162 | * ```js 163 | * rokka.user.deleteApiKey(id) 164 | * .then(function(result) {}) 165 | * .catch(function(err) {}); 166 | * ``` 167 | * @since 3.3.0 168 | * @authenticated 169 | * 170 | * @return {Promise} 171 | */ 172 | deleteApiKey: (id: string): Promise => { 173 | return state.request('DELETE', `user/apikeys/${id}`) 174 | }, 175 | 176 | /** 177 | * Get currently used Api Key 178 | * 179 | * ```js 180 | * rokka.user.getCurrentApiKey() 181 | * .then(function(result) {}) 182 | * .catch(function(err) {}); 183 | * ``` 184 | * 185 | * @since 3.3.0 186 | * @authenticated 187 | * 188 | * @return {Promise} 189 | */ 190 | getCurrentApiKey: (): Promise => { 191 | return state.request('GET', 'user/apikeys/current') 192 | }, 193 | 194 | /** 195 | * Gets a new JWT token from the API. 196 | * 197 | * You either provide an API Key or there's a valid JWT token registered to get a new JWT token. 198 | * 199 | * ```js 200 | * rokka.user.getNowToken(apiKey, {expires_in: 48 * 3600, renewable: true}) 201 | * .then(function(result) {}) 202 | * .catch(function(err) {}); 203 | * ``` 204 | * 205 | * @since 3.7.0 206 | * @authenticated 207 | * 208 | * 209 | * @param {string} apiKey (optional) If you don't have a valid JWT token, we need an API key to retrieve a new one 210 | * @param {RequestQueryParamsNewToken} queryParams (optional) The query parameters used for generating a new JWT token. 211 | * 212 | * @return {Promise} 213 | */ 214 | getNewToken( 215 | apiKey?: string, 216 | queryParams: RequestQueryParamsNewToken | null = {}, 217 | ): Promise { 218 | if (apiKey) { 219 | state.apiKey = apiKey 220 | } 221 | if (!!queryParams) { 222 | queryParams = {} 223 | } 224 | return state 225 | .request( 226 | 'GET', 227 | 'user/apikeys/token', 228 | undefined, 229 | { ...state.apiTokenOptions, ...queryParams }, 230 | { 231 | forceUseApiKey: !!apiKey && this.getTokenIsValidFor() < 10, 232 | noTokenRefresh: true, 233 | }, 234 | ) 235 | .then(response => { 236 | this.setToken(response.body.token) 237 | return response 238 | }) 239 | }, 240 | 241 | /** 242 | * Gets the currently registered JWT Token from the `apiTokenGetCallback` config function or null 243 | * 244 | * @since 3.7.0 245 | * 246 | * @return {string|null} 247 | */ 248 | getToken: (): ApiToken => { 249 | return state.apiTokenGetCallback ? state.apiTokenGetCallback() : null 250 | }, 251 | 252 | /** 253 | * Sets a new JWT token with the `apiTokenSetCallback` function 254 | * 255 | * @since 3.7.0 256 | * 257 | * @param {string} token 258 | */ 259 | setToken: (token: ApiToken) => { 260 | if (state.apiTokenSetCallback) { 261 | state.apiTokenPayload = _getTokenPayload(token) 262 | state.apiTokenSetCallback(token, state.apiTokenPayload) 263 | } 264 | }, 265 | 266 | /** 267 | * Check if the registered JWT token is expiring within these amount of seconds (default: 3600) 268 | * 269 | * @since 3.7.0 270 | * 271 | * @param {number} withinNextSeconds Does it expire in these seconds (default: 3600) 272 | * 273 | * @return {boolean} 274 | */ 275 | isTokenExpiring(withinNextSeconds = 3600): boolean { 276 | return _isTokenExpiring( 277 | state.apiTokenPayload?.exp, 278 | state.apiTokenGetCallback, 279 | withinNextSeconds, 280 | ) 281 | }, 282 | 283 | /** 284 | * How long a token is still valid for (just checking for expiration time 285 | * 286 | * @since 3.7.0 287 | * 288 | * @return {number} The amount of seconds it's still valid for, -1 if it doesn't exist 289 | */ 290 | getTokenIsValidFor(): number { 291 | return _tokenValidFor( 292 | state.apiTokenPayload?.exp, 293 | state.apiTokenGetCallback, 294 | ) 295 | }, 296 | } 297 | 298 | return { 299 | user, 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/apis/users.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ### Users 3 | * 4 | * @module users 5 | */ 6 | 7 | import { RokkaResponse } from '../response' 8 | import { State } from '../index' 9 | 10 | export interface Users { 11 | create(email: string, organization: string | null): Promise 12 | getId(): Promise 13 | } 14 | 15 | export default (state: State): { users: Users } => { 16 | const users: Users = { 17 | /** 18 | * Register a new user for the rokka service. 19 | * 20 | * ```js 21 | * rokka.users.create('user@example.org') 22 | * .then(function(result) {}) 23 | * .catch(function(err) {}); 24 | * ``` 25 | * 26 | * @param {string} email address of a user 27 | * @param {string} [organization=null] to create 28 | * @return {Promise} 29 | */ 30 | create: ( 31 | email: string, 32 | organization: string | null = null, 33 | ): Promise => { 34 | return state.request('POST', 'users', { email, organization }, null, { 35 | noAuthHeaders: true, 36 | }) 37 | }, 38 | 39 | /** 40 | * Get user_id for current user 41 | * 42 | * ```js 43 | * rokka.users.getId() 44 | * .then(function(result) {}) 45 | * .catch(function(err) {}); 46 | * ``` 47 | * 48 | * @return {Promise} 49 | */ 50 | getId: (): Promise => { 51 | return state.request('GET', 'user').then(result => result.body.user_id) 52 | }, 53 | } 54 | 55 | return { 56 | users, 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import transport from './transport' 2 | import modules, { RokkaApi } from './apis' 3 | import RokkaResponse, { 4 | RokkaResponse as RokkaResponseInterface, 5 | } from './response' 6 | import { stringify } from 'query-string' 7 | import FormData from 'form-data' 8 | import user, { 9 | ApiTokenGetCallback, 10 | ApiTokenPayload, 11 | ApiTokenSetCallback, 12 | RequestQueryParamsNewToken, 13 | } from './apis/user' 14 | import { _getTokenPayload, _isTokenExpiring, _tokenValidFor } from './utils' 15 | 16 | export interface Config { 17 | apiKey?: string 18 | apiHost?: string // default: https://api.rokka.io 19 | apiVersion?: number | string // default: 1 20 | apiTokenGetCallback?: ApiTokenGetCallback 21 | apiTokenSetCallback?: ApiTokenSetCallback 22 | apiTokenRefreshTime?: number 23 | apiTokenOptions?: RequestQueryParamsNewToken | null 24 | renderHost?: string // default: https://{organization}.rokka.io 25 | debug?: boolean // default: false 26 | transport?: { 27 | requestTimeout?: number // milliseconds to wait for rokka server response (default: 30000) 28 | retries?: number // number of retries when API response is 429 (default: 10) 29 | minTimeout?: number // minimum milliseconds between retries (default: 1000) 30 | maxTimeout?: number // maximum milliseconds between retries (default: 10000) 31 | randomize?: boolean // randomize time between retries (default: true) 32 | agent?: any 33 | } 34 | } 35 | 36 | interface RequestOptions { 37 | headers?: object 38 | noAuthHeaders?: boolean 39 | fallBackToText?: boolean 40 | form?: boolean 41 | multipart?: boolean 42 | forceUseApiKey?: boolean 43 | noTokenRefresh?: boolean 44 | host?: string 45 | } 46 | 47 | const defaults = { 48 | apiHost: 'https://api.rokka.io', 49 | renderHost: 'https://{organization}.rokka.io', 50 | apiVersion: 1, 51 | transport: { 52 | requestTimeout: 30000, 53 | retries: 10, 54 | minTimeout: 1000, 55 | maxTimeout: 10000, 56 | randomize: true, 57 | factor: 2, 58 | debug: false, 59 | }, 60 | } 61 | 62 | const getResponseBody = async (response: Response, fallbackToText = false) => { 63 | if (response.headers && response.json) { 64 | if (response.headers.get('content-type') === 'application/json') { 65 | return response.json() 66 | } 67 | 68 | if (response.status === 204 || response.status === 201 || fallbackToText) { 69 | return response.text() 70 | } 71 | return response.body 72 | } 73 | return response.body 74 | } 75 | 76 | interface Request { 77 | method: string 78 | headers: { 'Api-Version'?: string | number; 'Api-Key'?: string } 79 | timeout: number | undefined 80 | retries: number | undefined | any 81 | retryDelay: (attempt: number) => number 82 | form: {} 83 | json: boolean 84 | body: any 85 | agent?: any 86 | } 87 | 88 | export interface RequestQueryParams { 89 | [key: string]: string | number | boolean | undefined | null 90 | } 91 | 92 | export interface State { 93 | apiKey: string | undefined 94 | apiHost: string 95 | apiVersion: number | string 96 | renderHost: string 97 | transportOptions: any 98 | apiTokenGetCallback?: ApiTokenGetCallback 99 | apiTokenSetCallback?: ApiTokenSetCallback 100 | apiTokenPayload: ApiTokenPayload | null 101 | apiTokenOptions?: RequestQueryParamsNewToken | null 102 | apiTokenRefreshTime: number 103 | 104 | request( 105 | method: string, 106 | path: string, 107 | payload?: any | null | undefined, 108 | queryParams?: RequestQueryParams | null, 109 | options?: RequestOptions | undefined | null, 110 | ): Promise 111 | } 112 | 113 | /** 114 | * Initializing the rokka client. 115 | * 116 | * ```js 117 | * const rokka = require('rokka')({ 118 | * apiKey: 'apikey', // required for certain operations 119 | * apiTokenGetCallback?: <() => string> // return JWT token instead of API Key 120 | * apiTokenSetCallback?: <((token: string, payload?: object|null) => void)> // Stores a newly retrieved JWT token 121 | * apiTokenOptions?: // The rokka.user.getNewToken query parameter options, default: {} 122 | * apiTokenRefreshTime?: // how many seconds before the token is expiring, it should be refreshed, default: 3600 123 | * apiHost: '', // default: https://api.rokka.io 124 | * apiVersion: , // default: 1 125 | * renderHost: '', // default: https://{organization}.rokka.io 126 | * debug: true, // default: false 127 | * transport: { 128 | * requestTimeout: , // milliseconds to wait for rokka server response (default: 30000) 129 | * retries: , // number of retries when API response is 429 (default: 10) 130 | * minTimeout: , // minimum milliseconds between retries (default: 1000) 131 | * maxTimeout: , // maximum milliseconds between retries (default: 10000) 132 | * randomize: // randomize time between retries (default: true) 133 | * agent?: // an agent to be used with node-fetch, eg. if you need a proxy (default: undefined) 134 | * } 135 | * }); 136 | * ``` 137 | * 138 | * All properties are optional since certain calls don't require credentials. 139 | * 140 | * If you need to use a proxy, you can do the following 141 | * 142 | * ```js 143 | * import { HttpsProxyAgent } from 'https-proxy-agent' 144 | * 145 | * const rokka = require('rokka')({ 146 | * apiKey: 'apikey' 147 | * transport: {agent: new HttpsProxyAgent(proxy)} 148 | * }); 149 | * ``` 150 | * 151 | * @param {Object} [config={}] configuration properties 152 | * @return {Object} 153 | * 154 | * @module rokka 155 | */ 156 | export default (config: Config = {}): RokkaApi => { 157 | const state: State = { 158 | // config 159 | apiKey: config.apiKey, 160 | apiHost: config.apiHost || defaults.apiHost, 161 | apiTokenGetCallback: config.apiTokenGetCallback || null, 162 | apiTokenSetCallback: config.apiTokenSetCallback || null, 163 | apiTokenPayload: null, 164 | apiTokenOptions: config.apiTokenOptions || {}, 165 | apiTokenRefreshTime: config.apiTokenRefreshTime || 3600, 166 | apiVersion: config.apiVersion || defaults.apiVersion, 167 | renderHost: config.renderHost || defaults.renderHost, 168 | transportOptions: Object.assign(defaults.transport, config.transport), 169 | 170 | // functions 171 | async request( 172 | method: string, 173 | path: string, 174 | payload: any | null = null, 175 | queryParams: { 176 | [key: string]: string | number | boolean 177 | } | null = null, 178 | options: RequestOptions = { 179 | noAuthHeaders: false, 180 | fallBackToText: false, 181 | forceUseApiKey: false, 182 | noTokenRefresh: false, 183 | host: undefined, 184 | }, 185 | ): Promise { 186 | let uri = [options.host || state.apiHost, path].join('/') 187 | if ( 188 | queryParams && 189 | !( 190 | Object.entries(queryParams).length === 0 && 191 | queryParams.constructor === Object 192 | ) 193 | ) { 194 | uri += '?' + stringify(queryParams) 195 | } 196 | 197 | const headers: { 198 | 'Api-Version'?: string | number 199 | 'Api-Key'?: string 200 | Authorization?: string 201 | } = options.headers || {} 202 | 203 | headers['Api-Version'] = state.apiVersion 204 | 205 | if (options.noAuthHeaders !== true) { 206 | if (!options.forceUseApiKey && state.apiTokenGetCallback) { 207 | let apiToken = state.apiTokenGetCallback() 208 | // fill apiTokenPayload, if not set, this happens when you load a page, for example 209 | if (!state.apiTokenPayload) { 210 | state.apiTokenPayload = _getTokenPayload(apiToken) 211 | } 212 | // get a new token, when it's somehow almost expired, but should still be valid 213 | const isTokenValid = 214 | apiToken && 215 | state.apiTokenPayload?.rn === true && 216 | _tokenValidFor(state.apiTokenPayload?.exp, apiToken) > 0 217 | 218 | // if it's not valid, it's also not expiring... 219 | const isTokenExpiring = 220 | isTokenValid && 221 | _isTokenExpiring( 222 | state.apiTokenPayload?.exp, 223 | apiToken, 224 | state.apiTokenRefreshTime, 225 | ) 226 | if ( 227 | (!options.noTokenRefresh && isTokenValid && isTokenExpiring) || 228 | (!isTokenValid && state.apiKey) //or do we have an apiKey 229 | ) { 230 | try { 231 | apiToken = (await user(state).user.getNewToken(state.apiKey)).body 232 | .token 233 | } catch (e: any) { 234 | // clear the api token so that we can enforce a new login usually 235 | // a 403 means that we couldn't get a new token (trying to get a longer expiry time for example) 236 | if (e && e.statusCode === 403 && state.apiTokenSetCallback) { 237 | state.apiTokenSetCallback('', null) 238 | } 239 | } 240 | } 241 | if (!apiToken) { 242 | const code = 401 243 | throw { 244 | error: { 245 | code, 246 | message: 'No API token (or renewing it did not work correctly)', 247 | }, 248 | status: code, 249 | } 250 | } 251 | 252 | // set apiTokenExpiry, if not set, to avoid to having to decode it all the time 253 | 254 | headers['Authorization'] = `Bearer ${apiToken}` 255 | } else { 256 | if (!state.apiKey) { 257 | return Promise.reject( 258 | new Error('Missing required property `apiKey`'), 259 | ) 260 | } 261 | headers['Api-Key'] = state.apiKey 262 | } 263 | } 264 | 265 | const retryDelay = (attempt: number) => { 266 | // from https://github.com/tim-kos/node-retry/blob/master/lib/retry.js 267 | const random = state.transportOptions.randomize ? Math.random() + 1 : 1 268 | 269 | const timeout = Math.round( 270 | random * 271 | state.transportOptions.minTimeout * 272 | Math.pow(state.transportOptions.factor, attempt), 273 | ) 274 | return Math.min(timeout, state.transportOptions.maxTimeout) 275 | } 276 | 277 | const requestOptions: Request = { 278 | method: method, 279 | headers: headers, 280 | timeout: state.transportOptions.requestTimeout, 281 | retries: state.transportOptions.retries, 282 | retryDelay, 283 | form: {}, 284 | json: false, 285 | body: undefined, 286 | agent: state.transportOptions.agent, 287 | } 288 | if (options.form === true) { 289 | const formData = payload || {} 290 | const requestData = new FormData() 291 | Object.keys(formData).forEach(function (meta) { 292 | requestData.append(meta, formData[meta]) 293 | }) 294 | requestOptions.body = requestData 295 | } else if (options.multipart !== true) { 296 | requestOptions.json = true 297 | requestOptions.body = payload 298 | } else { 299 | const formData = payload.formData || {} 300 | const requestData = new FormData() 301 | 302 | requestData.append(payload.name, payload.contents, payload.filename) 303 | 304 | Object.keys(formData).forEach(function (meta) { 305 | requestData.append(meta, JSON.stringify(formData[meta])) 306 | }) 307 | 308 | requestOptions.body = requestData 309 | } 310 | 311 | if ( 312 | requestOptions.json && 313 | requestOptions.body && 314 | typeof requestOptions.body === 'object' 315 | ) { 316 | requestOptions.body = JSON.stringify(requestOptions.body) 317 | } 318 | 319 | const t = transport(uri, requestOptions) 320 | return t.then( 321 | async (response: Response): Promise => { 322 | const rokkaResponse = RokkaResponse(response) 323 | rokkaResponse.body = await getResponseBody( 324 | response, 325 | options.fallBackToText, 326 | ) 327 | if (response.status >= 400) { 328 | rokkaResponse.error = rokkaResponse.body 329 | rokkaResponse.message = 330 | response.status + ' - ' + JSON.stringify(rokkaResponse.body) 331 | // if response is a 401 and we have apiTokenSetCallback, clear the token 332 | if ( 333 | response.status === 401 && 334 | state.apiTokenSetCallback && 335 | state.apiTokenGetCallback 336 | ) { 337 | // but not when the authorization header changed in the meantime 338 | if ( 339 | headers['Authorization'] === 340 | 'Bearer ' + state.apiTokenGetCallback() 341 | ) { 342 | state.apiTokenSetCallback('', null) 343 | state.apiTokenPayload = null 344 | } 345 | } 346 | throw rokkaResponse 347 | } 348 | return rokkaResponse 349 | }, 350 | ) 351 | }, 352 | } 353 | 354 | return Object.assign({}, modules(state)) 355 | } 356 | -------------------------------------------------------------------------------- /src/response.ts: -------------------------------------------------------------------------------- 1 | export interface RokkaResponse { 2 | response: Response 3 | body: any 4 | statusCode: number 5 | statusMessage: string 6 | error?: any 7 | message?: string 8 | } 9 | 10 | export interface RokkaListResponseBody { 11 | total: number 12 | cursor?: string 13 | links?: { 14 | next?: { href: string } 15 | prev?: { href: string } 16 | } 17 | items: any[] 18 | } 19 | 20 | export interface RokkaListResponse extends RokkaResponse { 21 | body: RokkaListResponseBody 22 | } 23 | 24 | export default (originalResponse: Response): RokkaResponse => { 25 | return { 26 | response: originalResponse, 27 | body: null, 28 | get statusCode() { 29 | return this.response.status 30 | }, 31 | get statusMessage() { 32 | return this.response.statusText 33 | }, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/transport.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'cross-fetch' 2 | // from https://github.com/jonbern/fetch-retry 3 | // and https://github.com/jonbern/fetch-retry/pull/27 4 | 5 | function isPositiveInteger(value: any) { 6 | return Number.isInteger(value) && value >= 0 7 | } 8 | 9 | class ArgumentError { 10 | [x: string]: string 11 | constructor(message: string) { 12 | this.name = 'ArgumentError' 13 | this.message = message 14 | } 15 | } 16 | 17 | interface Options { 18 | retryDelay?: Function | number 19 | retries?: number 20 | retryOn?: Function | number[] 21 | agent?: any 22 | } 23 | 24 | export default (url: string, options: Options): Promise => { 25 | let retries = 3 26 | let retryDelay: Function | number = 1000 27 | let retryOn: Function | number[] = [429, 502, 503, 504] 28 | if (options && options.retries !== undefined) { 29 | if (isPositiveInteger(options.retries)) { 30 | retries = options.retries 31 | } else { 32 | throw new ArgumentError('retries must be a positive integer') 33 | } 34 | } 35 | 36 | if (options && options.retryDelay !== undefined) { 37 | if ( 38 | isPositiveInteger(options.retryDelay) || 39 | typeof options.retryDelay === 'function' 40 | ) { 41 | retryDelay = options.retryDelay 42 | } else { 43 | throw new ArgumentError( 44 | 'retryDelay must be a positive integer or a function returning a positive integer', 45 | ) 46 | } 47 | } 48 | 49 | if (options && options.retryOn) { 50 | if ( 51 | Array.isArray(options.retryOn) || 52 | typeof options.retryOn === 'function' 53 | ) { 54 | retryOn = options.retryOn 55 | } else { 56 | throw new ArgumentError('retryOn property expects an array or function') 57 | } 58 | } 59 | 60 | return new Promise(function (resolve, reject) { 61 | function retry(attempt: number, error: string | null, response: any) { 62 | const delay = 63 | typeof retryDelay === 'function' 64 | ? retryDelay(attempt, error, response) 65 | : retryDelay 66 | setTimeout(function () { 67 | //eslint-disable-next-line @typescript-eslint/no-use-before-define 68 | wrappedFetch(++attempt) 69 | }, delay) 70 | } 71 | const wrappedFetch = function (attempt: number) { 72 | fetch(url, options as any) 73 | .then(function (response) { 74 | if ( 75 | Array.isArray(retryOn) && 76 | retryOn.indexOf(response.status) === -1 77 | ) { 78 | resolve(response) 79 | return 80 | } 81 | if (typeof retryOn === 'function') { 82 | if (retryOn(attempt, null, response)) { 83 | retry(attempt, null, response) 84 | return 85 | } 86 | resolve(response) 87 | return 88 | } 89 | if (attempt < retries) { 90 | retry(attempt, null, response) 91 | return 92 | } 93 | resolve(response) 94 | }) 95 | .catch(function (error) { 96 | if (typeof retryOn === 'function') { 97 | if (retryOn(attempt, error, null)) { 98 | retry(attempt, error, null) 99 | return 100 | } 101 | reject(error) 102 | return 103 | } 104 | if (attempt < retries) { 105 | retry(attempt, error, null) 106 | return 107 | } 108 | reject(error) 109 | }) 110 | } 111 | wrappedFetch(0) 112 | }) 113 | } 114 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import jwtDecode from 'jwt-decode' 2 | import { ApiTokenGetCallback, ApiTokenPayload } from './apis/user' 3 | 4 | export function isStream(stream: { pipe: Function } | null): boolean { 5 | return ( 6 | stream !== null && 7 | typeof stream === 'object' && 8 | typeof stream.pipe === 'function' 9 | ) 10 | } 11 | 12 | export function _isTokenExpiring( 13 | exp?: number | null, 14 | apiTokenGetCallback?: string | ApiTokenGetCallback, 15 | withinNextSeconds = 3600, 16 | ): boolean { 17 | const validFor = _tokenValidFor(exp, apiTokenGetCallback) 18 | return withinNextSeconds > validFor 19 | } 20 | 21 | export function _tokenValidFor( 22 | exp?: number | null, 23 | apiTokenGetCallback?: string | ApiTokenGetCallback, 24 | ): number { 25 | if (!exp) { 26 | const payload = _getTokenPayload(apiTokenGetCallback) 27 | exp = payload?.exp 28 | } 29 | if (!exp) { 30 | return -1 31 | } 32 | const now = new Date().getTime() / 1000 33 | return exp - now 34 | } 35 | 36 | export function _getTokenPayload( 37 | apiTokenGetCallback?: string | ApiTokenGetCallback, 38 | ) { 39 | if (!apiTokenGetCallback) { 40 | return null 41 | } 42 | let token: string | null 43 | if (typeof apiTokenGetCallback === 'string') { 44 | token = apiTokenGetCallback 45 | } else { 46 | token = apiTokenGetCallback() 47 | } 48 | if (!token) { 49 | return null 50 | } 51 | try { 52 | return jwtDecode(token) 53 | } catch { 54 | return null 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/apis/memberships.test.ts: -------------------------------------------------------------------------------- 1 | import { rokka, queryAndCheckAnswer } from '../mockServer' 2 | 3 | describe('memberships', () => { 4 | it('memberships.ROLES', async () => { 5 | expect(rokka().memberships.ROLES).toEqual({ 6 | READ: 'read', 7 | WRITE: 'write', 8 | UPLOAD: 'upload', 9 | ADMIN: 'admin', 10 | SOURCEIMAGES_DOWNLOAD_PROTECTED: 'sourceimages:download:protected', 11 | SOURCEIMAGE_READ: 'sourceimages:read', 12 | SOURCEIMAGE_UNLOCK: 'sourceimages:unlock', 13 | SOURCEIMAGE_WRITE: 'sourceimages:write', 14 | }) 15 | }) 16 | 17 | it('memberships.createWithNewUser', async () => { 18 | await queryAndCheckAnswer( 19 | async () => { 20 | return rokka().memberships.createWithNewUser('rokka-js-tests', [ 21 | rokka().memberships.ROLES.UPLOAD, 22 | rokka().memberships.ROLES.READ, 23 | ]) 24 | }, 25 | { 26 | mockFile: 'memberships_create_with_new_user.json', 27 | }, 28 | ) 29 | }) 30 | 31 | it('memberships.createWithArray', async () => { 32 | await queryAndCheckAnswer( 33 | async () => { 34 | return rokka().memberships.create( 35 | 'rokka-js-tests', 36 | '679cd7aa-5445-4d6a-8d56-930557a2a77e', 37 | [rokka().memberships.ROLES.UPLOAD, rokka().memberships.ROLES.WRITE], 38 | ) 39 | }, 40 | { 41 | mockFile: 'memberships_create_with_array.json', 42 | }, 43 | ) 44 | }) 45 | 46 | it('memberships.list', async () => { 47 | await queryAndCheckAnswer( 48 | async () => rokka().memberships.list('rokka-js-tests'), 49 | { 50 | mockFile: 'memberships_list.json', 51 | }, 52 | ) 53 | }) 54 | 55 | it('memberships.delete', async () => { 56 | await queryAndCheckAnswer( 57 | async () => 58 | rokka().memberships.delete( 59 | 'rokka-js-tests', 60 | '679cd7aa-5445-4d6a-8d56-930557a2a77e', 61 | ), 62 | { 63 | mockFile: 'memberships_delete.json', 64 | }, 65 | ) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /tests/apis/operations.test.ts: -------------------------------------------------------------------------------- 1 | import { rokka, queryAndCheckAnswer } from '../mockServer' 2 | 3 | describe('operations', () => { 4 | it('operations get', async () => { 5 | await queryAndCheckAnswer( 6 | async () => rokka({ noAuth: true }).operations.list(), 7 | { 8 | mockFile: 'operations_get.json', 9 | }, 10 | ) 11 | }) 12 | 13 | it('known operations functions exists', async () => { 14 | const knownOperations: string[] = [ 15 | 'resize', 16 | 'autorotate', 17 | 'rotate', 18 | 'dropshadow', 19 | 'trim', 20 | 'crop', 21 | 'noop', 22 | 'composition', 23 | 'blur', 24 | ] 25 | 26 | const rka = rokka() 27 | 28 | knownOperations.forEach(key => { 29 | expect(typeof rka.operations[key] === 'function').toBe(true) 30 | }) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /tests/apis/organizations.test.ts: -------------------------------------------------------------------------------- 1 | import { rokka, queryAndCheckAnswer } from '../mockServer' 2 | describe('organizations', () => { 3 | it('organizations.get', async () => { 4 | await queryAndCheckAnswer( 5 | async () => rokka().organizations.get('rokka-js-tests'), 6 | { 7 | mockFile: 'organizations_get.json', 8 | }, 9 | ) 10 | }) 11 | 12 | it('organizations.create', async () => { 13 | await queryAndCheckAnswer( 14 | async () => 15 | rokka().organizations.create( 16 | 'rokka-js-test-new', 17 | 'billing@example.org', 18 | 'Organization Inc.', 19 | ), 20 | { 21 | mockFile: 'organizations_create.json', 22 | }, 23 | ) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /tests/apis/render.addStackVariables.test.ts: -------------------------------------------------------------------------------- 1 | import { rokka } from '../mockServer' 2 | const rootUrl = 'http://test.rokka.test/template3/' 3 | const hash = 'abcdef.png' 4 | describe('addStackVariables', () => { 5 | const rka = rokka() 6 | 7 | test.each` 8 | description | url | variables | cleanerUrl | expectedResult 9 | ${'Add url variable'} | ${hash} | ${{ a: 'b' }} | ${false} | ${`v-a-b/${hash}`} 10 | ${'Replace url variable'} | ${`v-a-b/${hash}`} | ${{ a: 'c' }} | ${false} | ${`v-a-c/${hash}`} 11 | ${'Variable special char'} | ${`v-a-b/${hash}`} | ${{ a: '#' }} | ${false} | ${`${hash}?v=%7B%22a%22%3A%22%23%22%7D`} 12 | ${'Variable special char cleaner url'} | ${`v-a-b/${hash}`} | ${{ a: '#' }} | ${true} | ${`${hash}?v={"a":"%23"}`} 13 | ${'Double variables definition'} | ${`v-a-b--v-d-e/${hash}`} | ${{ a: 'c' }} | ${false} | ${`v-a-c-d-e/${hash}`} 14 | ${'With Seo'} | ${`v-a-b/abcdef/foo.png`} | ${{ a: 'c' }} | ${false} | ${`v-a-c/abcdef/foo.png`} 15 | ${'With options'} | ${`v-a-b/o-af-1/${hash}`} | ${{ a: 'c' }} | ${false} | ${`o-af-1/v-a-c/${hash}`} 16 | ${'With options and empty vars'} | ${`v-a-b/o-af-1/${hash}`} | ${{}} | ${false} | ${`o-af-1/v-a-b/${hash}`} 17 | ${'With existing query '} | ${`${hash}?foo=bar`} | ${{ a: 'd' }} | ${false} | ${`v-a-d/${hash}?foo=bar`} 18 | ${'With existing and special chars '} | ${`${hash}?foo=bar`} | ${{ a: '\\k' }} | ${false} | ${`${hash}?foo=bar&v=%7B%22a%22%3A%22%5C%5Ck%22%7D`} 19 | ${'From special to not'} | ${`${hash}?v={"a":"%23"}`} | ${{ a: 'q a' }} | ${false} | ${`${hash}?v=%7B%22a%22%3A%22q+a%22%7D`} 20 | `( 21 | '$description: converts $url to $expectedResult with $variables', 22 | ({ url, variables, cleanerUrl, expectedResult }) => { 23 | expect( 24 | rka.render.addStackVariables(`${rootUrl}${url}`, variables, cleanerUrl), 25 | ).toBe(`${rootUrl}${expectedResult}`) 26 | }, 27 | ) 28 | }) 29 | -------------------------------------------------------------------------------- /tests/apis/render.signUrl.test.ts: -------------------------------------------------------------------------------- 1 | import { rokka } from '../mockServer' 2 | 3 | const signPath = 'https://myorg.rokka.io/dynamic/c1b110.jpg' 4 | const signKey = 'OCOuisGe30QyocYkQN1SPErGGKunyuhZ' 5 | 6 | describe('render', () => { 7 | it('render.signUrl', async () => { 8 | const rka = rokka() 9 | expect(rka.render.signUrl(signPath, signKey)).toBe( 10 | 'https://myorg.rokka.io/dynamic/c1b110.jpg?sig=62e7a9ccd3dea053', 11 | ) 12 | 13 | expect( 14 | rka.render.signUrl( 15 | 'https://myorg.rokka.io/dynamic/c1b110/foo.jpg', 16 | signKey, 17 | ), 18 | ).toBe('https://myorg.rokka.io/dynamic/c1b110.jpg?sig=62e7a9ccd3dea053') 19 | 20 | // time limited 21 | expect( 22 | rka.render.signUrl(signPath, signKey, { 23 | until: new Date('2050-02-08T08:03:00+01:00'), 24 | }), 25 | ).toBe( 26 | 'https://myorg.rokka.io/dynamic/c1b110.jpg?sigopts=%7B%22until%22%3A%222050-02-08T07%3A05%3A00.000Z%22%7D&sig=0f6370995020ce81', 27 | ) 28 | 29 | //same sig a minute later 30 | expect( 31 | rka.render.signUrl(signPath, signKey, { 32 | until: new Date('2050-02-08T08:04:00+01:00'), 33 | }), 34 | ).toBe( 35 | 'https://myorg.rokka.io/dynamic/c1b110.jpg?sigopts=%7B%22until%22%3A%222050-02-08T07%3A05%3A00.000Z%22%7D&sig=0f6370995020ce81', 36 | ) 37 | 38 | // different sig 2 minutes later 39 | expect( 40 | rka.render.signUrl(signPath, signKey, { 41 | until: new Date('2050-02-08T08:06:00+01:00'), 42 | }), 43 | ).toBe( 44 | 'https://myorg.rokka.io/dynamic/c1b110.jpg?sigopts=%7B%22until%22%3A%222050-02-08T07%3A10%3A00.000Z%22%7D&sig=1b4ef0fce0a76d00', 45 | ) 46 | 47 | // keep v querey 48 | expect(rka.render.signUrl(signPath + '?v={"a":"b"}', signKey)).toBe( 49 | 'https://myorg.rokka.io/dynamic/c1b110.jpg?v=%7B%22a%22%3A%22b%22%7D&sig=9259f07089f76a05', 50 | ) 51 | 52 | // keep any query query 53 | expect( 54 | rka.render.signUrl(signPath + '?foo=bar&lala=hello&soso', signKey), 55 | ).toBe( 56 | 'https://myorg.rokka.io/dynamic/c1b110.jpg?foo=bar&lala=hello&soso=&sig=4d6173249ba045f0', 57 | ) 58 | 59 | //remove existing sig 60 | expect( 61 | rka.render.signUrl( 62 | signPath + '?foo=bar&lala=hello&soso&sig=lala', 63 | signKey, 64 | ), 65 | ).toBe( 66 | 'https://myorg.rokka.io/dynamic/c1b110.jpg?foo=bar&lala=hello&soso=&sig=4d6173249ba045f0', 67 | ) 68 | 69 | // remove existing sigopts & sig 70 | expect( 71 | rka.render.signUrl( 72 | signPath + '?foo=bar&lala=hello&soso&sig=lala&sigopts=84989', 73 | signKey, 74 | ), 75 | ).toBe( 76 | 'https://myorg.rokka.io/dynamic/c1b110.jpg?foo=bar&lala=hello&soso=&sig=4d6173249ba045f0', 77 | ) 78 | 79 | //remove existing sigopts & sig with date 80 | expect( 81 | rka.render.signUrl( 82 | signPath + '?foo=bar&lala=hello&soso&sig=lala&sigopts=84989', 83 | signKey, 84 | { until: new Date('2050-02-08T08:03:00+01:00') }, 85 | ), 86 | ).toBe( 87 | 'https://myorg.rokka.io/dynamic/c1b110.jpg?foo=bar&lala=hello&soso=&sigopts=%7B%22until%22%3A%222050-02-08T07%3A05%3A00.000Z%22%7D&sig=6f3e929c4a310e27', 88 | ) 89 | }) 90 | }) 91 | -------------------------------------------------------------------------------- /tests/apis/render.test.ts: -------------------------------------------------------------------------------- 1 | import { rokka } from '../mockServer' 2 | 3 | describe('render', () => { 4 | it('get URL', async () => { 5 | const url = rokka().render.getUrl( 6 | 'myorg', 7 | 'c421f4e8cefe0fd3aab22832f51e85bacda0a47a', 8 | 'png', 9 | 'mystack', 10 | ) 11 | 12 | expect(url).toBe( 13 | 'https://myorg.rokka.io/mystack/c421f4e8cefe0fd3aab22832f51e85bacda0a47a.png', 14 | ) 15 | }) 16 | 17 | it('get URL from Url', async () => { 18 | const url = rokka().render.getUrlFromUrl( 19 | 'https://myorg.rokka.io/dynamic/c421f4e8cefe0fd3aab22832f51e85bacda0a47a.png', 20 | 'mystack', 21 | ) 22 | 23 | expect(url).toBe( 24 | 'https://myorg.rokka.io/mystack/c421f4e8cefe0fd3aab22832f51e85bacda0a47a.png', 25 | ) 26 | 27 | const url2 = rokka().render.getUrlFromUrl( 28 | 'https://myorg.rokka.io/dynamic/c421f4e8cefe0fd3aab22832f51e85bacda0a47a/foo.png', 29 | [{ name: 'resize', options: { width: '100' } }], 30 | { format: 'jpg' }, 31 | ) 32 | 33 | expect(url2).toBe( 34 | 'https://myorg.rokka.io/dynamic/resize-width-100/c421f4e8cefe0fd3aab22832f51e85bacda0a47a/foo.jpg', 35 | ) 36 | 37 | const url3 = rokka().render.getUrlFromUrl( 38 | 'https://myorg.rokka.io/dynamic/c421f4e8cefe0fd3aab22832f51e85bacda0a47a/foo.png', 39 | [{ name: 'resize', options: { width: '200' } }], 40 | { format: 'jpg', filename: 'bar', stackoptions: { af: '1' } }, 41 | ) 42 | 43 | expect(url3).toBe( 44 | 'https://myorg.rokka.io/dynamic/resize-width-200/o-af-1/c421f4e8cefe0fd3aab22832f51e85bacda0a47a/bar.jpg', 45 | ) 46 | const url4 = rokka().render.getUrlFromUrl( 47 | 'https://myorg.rokka.io/dynamic/resize-width-100/v-foo-bar/c421f4e8cefe0fd3aab22832f51e85bacda0a47a/foo.jpg?v={"baz":"hello-world"}', 48 | 'newStack', 49 | { 50 | removeSafeUrlFromQuery: true, 51 | clearVariables: false, 52 | variables: { hi: 'ho' }, 53 | }, 54 | ) 55 | expect(url4).toBe( 56 | 'https://myorg.rokka.io/newStack/v-foo-bar-hi-ho/c421f4e8cefe0fd3aab22832f51e85bacda0a47a/foo.jpg?v={"baz":"hello-world"}', 57 | ) 58 | 59 | const url5 = rokka().render.getUrlFromUrl( 60 | 'https://myorg.rokka.io/dynamic/resize-width-100/v-foo-bar/c421f4e8cefe0fd3aab22832f51e85bacda0a47a/foo.jpg?v={"baz":"hello-world"}', 61 | 'newStack', 62 | { 63 | removeSafeUrlFromQuery: true, 64 | variables: { hi: 'ho' }, 65 | }, 66 | ) 67 | expect(url5).toBe( 68 | 'https://myorg.rokka.io/newStack/v-hi-ho/c421f4e8cefe0fd3aab22832f51e85bacda0a47a/foo.jpg', 69 | ) 70 | }) 71 | it('render.addStackVariables', async () => { 72 | const url = rokka().render.addStackVariables( 73 | 'https://myorg.rokka.io/dynamic/resize-width-100/c421f4e8cefe0fd3aab22832f51e85bacda0a47a/foo.jpg', 74 | { foo: 'bar', baz: 'hello-world' }, 75 | true, 76 | ) 77 | expect(url).toBe( 78 | 'https://myorg.rokka.io/dynamic/resize-width-100/v-foo-bar/c421f4e8cefe0fd3aab22832f51e85bacda0a47a/foo.jpg?v={"baz":"hello-world"}', 79 | ) 80 | const url2 = rokka().render.addStackVariables( 81 | 'https://myorg.rokka.io/dynamic/resize-width-100/v-foo-bar/c421f4e8cefe0fd3aab22832f51e85bacda0a47a/foo.jpg?v={"baz":"hello-world"}', 82 | { lal: 'lol' }, 83 | true, 84 | ) 85 | expect(url2).toBe( 86 | 'https://myorg.rokka.io/dynamic/resize-width-100/v-foo-bar-lal-lol/c421f4e8cefe0fd3aab22832f51e85bacda0a47a/foo.jpg?v={"baz":"hello-world"}', 87 | ) 88 | }) 89 | it('render.getUrl using custom operations', async () => { 90 | const rka = rokka() 91 | const operations = [ 92 | rka.operations.rotate(45), 93 | rka.operations.resize(100, 100), 94 | ] 95 | 96 | const url = rka.render.getUrl( 97 | 'myorg', 98 | 'c421f4e8cefe0fd3aab22832f51e85bacda0a47a', 99 | 'png', 100 | operations, 101 | ) 102 | expect(url).toBe( 103 | 'https://myorg.rokka.io/dynamic/rotate-angle-45--resize-width-100-height-100/c421f4e8cefe0fd3aab22832f51e85bacda0a47a.png', 104 | ) 105 | }) 106 | }) 107 | -------------------------------------------------------------------------------- /tests/apis/sourceimages.metadata.test.ts: -------------------------------------------------------------------------------- 1 | import { rokka, queryAndCheckAnswer } from '../mockServer' 2 | 3 | describe('sourceimages.metadata', () => { 4 | it('sourceimages.setSubjectArea', async () => { 5 | const subjectArea = { x: 100, y: 100, width: 50, height: 50 } 6 | 7 | await queryAndCheckAnswer( 8 | async () => 9 | rokka().sourceimages.setSubjectArea( 10 | 'rokka-js-tests', 11 | 'fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2', 12 | subjectArea, 13 | ), 14 | { 15 | mockFile: 'sourceimages_metadata_set_subjectarea.json', 16 | }, 17 | ) 18 | }) 19 | 20 | it('sourceimages.setSubjectArea.deletePrevious', async () => { 21 | const subjectArea = { x: 120, y: 120, width: 40, height: 40 } 22 | 23 | await queryAndCheckAnswer( 24 | async () => 25 | rokka().sourceimages.setSubjectArea( 26 | 'rokka-js-tests', 27 | 'fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2', 28 | subjectArea, 29 | { deletePrevious: true }, 30 | ), 31 | { 32 | mockFile: 'sourceimages_metadata_set_subjectarea_delete.json', 33 | }, 34 | ) 35 | }) 36 | 37 | it('sourceimages.removeSubjectArea', async () => { 38 | await queryAndCheckAnswer( 39 | async () => 40 | rokka().sourceimages.removeSubjectArea( 41 | 'rokka-js-tests', 42 | '76114710a44fb15fb37ecf66bbad250643373990', 43 | ), 44 | { 45 | mockFile: 'sourceimages_metadata_remove_subjectarea.json', 46 | }, 47 | ) 48 | }) 49 | 50 | it('sourceimages.removeSubjectArea.deletePrevious', async () => { 51 | await queryAndCheckAnswer( 52 | async () => 53 | rokka().sourceimages.removeSubjectArea( 54 | 'rokka-js-tests', 55 | 'e551164763cdbabcd0b75b144f3f08112844a81f', 56 | { deletePrevious: true }, 57 | ), 58 | { 59 | mockFile: 'sourceimages_metadata_remove_subjectarea_delete.json', 60 | }, 61 | ) 62 | }) 63 | 64 | it('sourceimages.meta.add', async () => { 65 | const userData = { 66 | somefield: 'somevalue', 67 | 'int:some_number': 0, 68 | delete_this: null, 69 | } 70 | await queryAndCheckAnswer( 71 | async () => 72 | rokka().sourceimages.meta.add( 73 | 'rokka-js-tests', 74 | 'fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2', 75 | userData, 76 | ), 77 | { 78 | mockFile: 'sourceimages_metadata_add.json', 79 | }, 80 | ) 81 | }) 82 | 83 | it('sourceimages.meta.replace', async () => { 84 | const userData = { somefield: 'somevalue', 'int:another_number': 23 } 85 | 86 | await queryAndCheckAnswer( 87 | async () => 88 | rokka().sourceimages.meta.add( 89 | 'rokka-js-tests', 90 | 'fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2', 91 | userData, 92 | ), 93 | { 94 | mockFile: 'sourceimages_metadata_replace.json', 95 | }, 96 | ) 97 | }) 98 | 99 | it('sourceimages.meta.delete', async () => { 100 | await queryAndCheckAnswer( 101 | async () => 102 | rokka().sourceimages.meta.delete( 103 | 'rokka-js-tests', 104 | 'fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2', 105 | ), 106 | { 107 | mockFile: 'sourceimages_metadata_delete.json', 108 | }, 109 | ) 110 | }) 111 | }) 112 | -------------------------------------------------------------------------------- /tests/apis/sourceimages.test.ts: -------------------------------------------------------------------------------- 1 | import { rokka, queryAndCheckAnswer } from '../mockServer' 2 | import fs from 'fs' 3 | import nock from 'nock' 4 | 5 | describe('sourceimages', () => { 6 | it('sourceimages.list', async () => { 7 | await queryAndCheckAnswer( 8 | async () => rokka().sourceimages.list('rokka-js-tests'), 9 | { 10 | mockFile: 'sourceimages_list.json', 11 | }, 12 | ) 13 | }) 14 | 15 | it('sourceimages.list with args', async () => { 16 | await queryAndCheckAnswer( 17 | async () => { 18 | const search = { 19 | 'user:int:id': '42', 20 | height: '64', 21 | } 22 | return rokka().sourceimages.list('rokka-js-tests', { 23 | search, 24 | limit: 2, 25 | offset: 1, 26 | }) 27 | }, 28 | 29 | { 30 | mockFile: 'sourceimages_list_with_args.json', 31 | }, 32 | ) 33 | }) 34 | 35 | it('sourceimages.get', async () => { 36 | await queryAndCheckAnswer( 37 | async () => 38 | rokka().sourceimages.get( 39 | 'rokka-js-tests', 40 | '063c3dce6a528d8944a63185bd4c7b161454ee4f', 41 | ), 42 | { 43 | mockFile: 'sourceimages_get.json', 44 | }, 45 | ) 46 | }) 47 | 48 | it('sourceimages.getWithBinaryHash', async () => { 49 | await queryAndCheckAnswer( 50 | async () => 51 | rokka().sourceimages.getWithBinaryHash( 52 | 'rokka-js-tests', 53 | '498fed612f01199cd6702f5effe5fa7bb67e44f4', 54 | ), 55 | 56 | { 57 | mockFile: 'sourceimages_get_binaryhash.json', 58 | }, 59 | ) 60 | }) 61 | 62 | it('sourceimages.download', async () => { 63 | await queryAndCheckAnswer( 64 | async () => 65 | rokka().sourceimages.download( 66 | 'rokka-js-tests', 67 | '063c3dce6a528d8944a63185bd4c7b161454ee4f', 68 | ), 69 | { 70 | mockFile: 'sourceimages_download.json', 71 | }, 72 | ) 73 | }) 74 | 75 | it('sourceimages.create', async () => { 76 | nock('https://api.rokka.io') 77 | .post( 78 | '/sourceimages/rokka-js-tests', 79 | body => 80 | body.includes('filename="cartman3.svg"') && 81 | !body.includes('Content-Disposition: form-data; name="meta_user[0]'), 82 | ) 83 | .reply(200, { 84 | total: 1, 85 | items: [ 86 | { 87 | hash: 'fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2', 88 | short_hash: 'fe5d9a', 89 | binary_hash: '63a65da81cae86964e3369b4431b80252f9404b0', 90 | created: '2019-12-10T10:45:32+00:00', 91 | name: 'cartman3.svg', 92 | mimetype: 'image/svg+xml', 93 | format: 'svg', 94 | size: 1202, 95 | width: 104, 96 | height: 97, 97 | organization: 'rokka-js-tests', 98 | link: '/sourceimages/rokka-js-tests/fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2', 99 | deleted: false, 100 | opaque: false, 101 | }, 102 | ], 103 | }) 104 | 105 | const resp = await rokka().sourceimages.create( 106 | 'rokka-js-tests', 107 | 'cartman3.svg', 108 | fs.createReadStream('tests/fixtures/cartman.svg'), 109 | ) 110 | 111 | expect(resp.body.items[0].hash).toBe( 112 | 'fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2', 113 | ) 114 | }) 115 | 116 | it('sourceimages.create with metadata', async () => { 117 | nock('https://api.rokka.io') 118 | .post( 119 | '/sourceimages/rokka-js-tests', 120 | body => 121 | body.includes('filename="cartman2.svg"') && 122 | body.includes('Content-Disposition: form-data; name="meta_user[0]'), 123 | ) 124 | .reply(200, { 125 | total: 1, 126 | items: [ 127 | { 128 | hash: '063c3dce6a528d8944a63185bd4c7b161454ee4f', 129 | short_hash: '063c3d', 130 | binary_hash: '498fed612f01199cd6702f5effe5fa7bb67e44f4', 131 | created: '2019-12-10T11:13:24+00:00', 132 | name: 'cartman2.svg', 133 | mimetype: 'image/svg+xml', 134 | format: 'svg', 135 | size: 1203, 136 | width: 104, 137 | height: 97, 138 | organization: 'rokka-js-tests', 139 | link: '/sourceimages/rokka-js-tests/063c3dce6a528d8944a63185bd4c7b161454ee4f', 140 | user_metadata: { 141 | foo: 'bar', 142 | }, 143 | deleted: false, 144 | opaque: false, 145 | }, 146 | ], 147 | }) 148 | 149 | const resp = await rokka().sourceimages.create( 150 | 'rokka-js-tests', 151 | 'cartman2.svg', 152 | fs.createReadStream('tests/fixtures/cartman.svg'), 153 | { meta_user: { foo: 'bar' } }, 154 | ) 155 | 156 | expect(resp.body.items[0].hash).toBe( 157 | '063c3dce6a528d8944a63185bd4c7b161454ee4f', 158 | ) 159 | expect(resp.body.items[0].user_metadata?.foo).toBe('bar') 160 | }) 161 | 162 | it('sourceimages.delete', async () => { 163 | await queryAndCheckAnswer( 164 | async () => 165 | rokka().sourceimages.delete( 166 | 'rokka-js-tests', 167 | '063c3dce6a528d8944a63185bd4c7b161454ee4f', 168 | ), 169 | { 170 | mockFile: 'sourceimages_delete.json', 171 | }, 172 | ) 173 | }) 174 | 175 | it('sourceimages.restore', async () => { 176 | await queryAndCheckAnswer( 177 | async () => 178 | rokka().sourceimages.restore( 179 | 'rokka-js-tests', 180 | '063c3dce6a528d8944a63185bd4c7b161454ee4f', 181 | ), 182 | { 183 | mockFile: 'sourceimages_restore.json', 184 | }, 185 | ) 186 | }) 187 | 188 | it('sourceimages.copy', async () => { 189 | await queryAndCheckAnswer( 190 | async () => 191 | rokka().sourceimages.copy( 192 | 'rokka-js-tests', 193 | '063c3dce6a528d8944a63185bd4c7b161454ee4f', 194 | 'rokka-js-tests-new2', 195 | ), 196 | { 197 | mockFile: 'sourceimages_copy.json', 198 | }, 199 | ) 200 | }) 201 | 202 | it('sourceimages.copy with no overwrite', async () => { 203 | await queryAndCheckAnswer( 204 | async () => 205 | rokka().sourceimages.copy( 206 | 'rokka-js-tests', 207 | '063c3dce6a528d8944a63185bd4c7b161454ee4f', 208 | 'rokka-js-tests-new2', 209 | false, 210 | ), 211 | { 212 | mockFile: 'sourceimages_copy_no_overwrite.json', 213 | returnError: true, 214 | }, 215 | ) 216 | }) 217 | 218 | it('sourceimages.deleteWithBinaryHash', async () => { 219 | await queryAndCheckAnswer( 220 | async () => 221 | rokka().sourceimages.deleteWithBinaryHash( 222 | 'rokka-js-tests', 223 | '498fed612f01199cd6702f5effe5fa7bb67e44f4', 224 | ), 225 | { 226 | mockFile: 'sourceimages_delete_with_binary.json', 227 | returnError: true, 228 | }, 229 | ) 230 | }) 231 | }) 232 | -------------------------------------------------------------------------------- /tests/apis/stackoptions.test.ts: -------------------------------------------------------------------------------- 1 | import { rokka, queryAndCheckAnswer } from '../mockServer' 2 | 3 | describe('stackoptions', () => { 4 | it('stackoptions get', async () => { 5 | await queryAndCheckAnswer( 6 | async () => rokka({ noAuth: true }).stackoptions.get(), 7 | { 8 | mockFile: 'stackoptions_get.json', 9 | }, 10 | ) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /tests/apis/stacks.test.ts: -------------------------------------------------------------------------------- 1 | import { queryAndCheckAnswer, rokka } from '../mockServer' 2 | 3 | describe('stacks', () => { 4 | it('stacks.list', async () => { 5 | await queryAndCheckAnswer( 6 | async () => rokka().stacks.list('rokka-js-tests'), 7 | { 8 | mockFile: 'stacks_list.json', 9 | }, 10 | ) 11 | }) 12 | 13 | it('stacks.get', async () => { 14 | await queryAndCheckAnswer( 15 | async () => rokka().stacks.get('rokka-js-tests', 'mystack'), 16 | { 17 | mockFile: 'stacks_get.json', 18 | }, 19 | ) 20 | }) 21 | 22 | it('stacks.create', async () => { 23 | await queryAndCheckAnswer( 24 | async () => { 25 | const options = { 'jpg.quality': 77 } 26 | const operations = [ 27 | rokka().operations.rotate(45), 28 | rokka().operations.resize(100, 100), 29 | ] 30 | 31 | return rokka().stacks.create('rokka-js-tests', 'mystack', { 32 | operations: operations, 33 | options: options, 34 | }) 35 | }, 36 | { 37 | mockFile: 'stacks_create.json', 38 | }, 39 | ) 40 | }) 41 | 42 | it('stacks.create (version <=0.26)', async () => { 43 | await queryAndCheckAnswer( 44 | async () => { 45 | const options = { 'jpg.quality': 76 } 46 | const operations = [ 47 | rokka().operations.rotate(47), 48 | rokka().operations.resize(120, 120), 49 | ] 50 | 51 | return rokka().stacks.create( 52 | 'rokka-js-tests', 53 | 'mystack026', 54 | operations, 55 | options, 56 | ) 57 | }, 58 | { 59 | mockFile: 'stacks_create_0_26.json', 60 | }, 61 | ) 62 | }) 63 | 64 | it('stacks.create (with expressions)', async () => { 65 | await queryAndCheckAnswer( 66 | async () => { 67 | const options = { 'jpg.quality': 78 } 68 | const operations = [ 69 | rokka().operations.rotate(45), 70 | rokka().operations.resize(100, 100), 71 | ] 72 | const expressions = [ 73 | rokka().expressions.default('options.dpr >= 2', { 74 | 'jpg.quality': 60, 75 | 'webp.quality': 60, 76 | }), 77 | ] 78 | return rokka().stacks.create('rokka-js-tests', 'mystack_expressions', { 79 | operations, 80 | options, 81 | expressions, 82 | }) 83 | }, 84 | { 85 | mockFile: 'stacks_create_expressions.json', 86 | }, 87 | ) 88 | }) 89 | it('stacks.createOverwrite', async () => { 90 | await queryAndCheckAnswer( 91 | async () => { 92 | const options = { 'jpg.quality': 76 } 93 | const operations = [ 94 | rokka().operations.rotate(45), 95 | rokka().operations.resize(100, 100), 96 | ] 97 | 98 | return rokka().stacks.create( 99 | 'rokka-js-tests', 100 | 'mystack', 101 | { operations, options }, 102 | { overwrite: true }, 103 | ) 104 | }, 105 | { 106 | mockFile: 'stacks_create_overwrite.json', 107 | }, 108 | ) 109 | }) 110 | 111 | it('stacks.createOverwrite (version <=0.26)', async () => { 112 | await queryAndCheckAnswer( 113 | async () => { 114 | const options = { 'jpg.quality': 77 } 115 | const operations = [ 116 | rokka().operations.rotate(48), 117 | rokka().operations.resize(120, 120), 118 | ] 119 | 120 | return rokka().stacks.create( 121 | 'rokka-js-tests', 122 | 'mystack', 123 | operations, 124 | options, 125 | true, 126 | ) 127 | }, 128 | { 129 | mockFile: 'stacks_create_overwrite_0_26.json', 130 | }, 131 | ) 132 | }) 133 | 134 | it('stacks.delete', async () => { 135 | await queryAndCheckAnswer( 136 | async () => rokka().stacks.delete('rokka-js-tests', 'mystack'), 137 | { 138 | mockFile: 'stacks_delete.json', 139 | }, 140 | ) 141 | }) 142 | }) 143 | -------------------------------------------------------------------------------- /tests/apis/stats.test.ts: -------------------------------------------------------------------------------- 1 | import { rokka, queryAndCheckAnswer } from '../mockServer' 2 | 3 | describe('stats', () => { 4 | it('stats get', async () => { 5 | await queryAndCheckAnswer( 6 | async () => { 7 | return rokka().stats.get('rokka-js-tests', '2019-11-01', '2019-11-30') 8 | }, 9 | { 10 | mockFile: 'stats_get.json', 11 | }, 12 | ) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /tests/apis/users.test.ts: -------------------------------------------------------------------------------- 1 | import { rokka, queryAndCheckAnswer } from '../mockServer' 2 | describe('users', () => { 3 | it('users.create', async () => { 4 | await queryAndCheckAnswer( 5 | async () => 6 | rokka({ noAuth: true }).users.create( 7 | 'user@example.org', 8 | 'rokka-js-tests-new2', 9 | ), 10 | { 11 | mockFile: 'users_create.json', 12 | }, 13 | ) 14 | }) 15 | 16 | it('users.getId', async () => { 17 | await queryAndCheckAnswer(async () => rokka().users.getId(), { 18 | mockFile: 'users_getid.json', 19 | }) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /tests/fixtures/answers/memberships_create_with_array.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "user_id": "679cd7aa-5445-4d6a-8d56-930557a2a77e", 8 | "email": "rokka-test@example.org", 9 | "roles": [ 10 | "upload", 11 | "write" 12 | ], 13 | "organization_id": "4eebf31f-6afa-412c-8b94-3458922896a4", 14 | "active": true, 15 | "last_access": "2019-12-10T09:34:57+00:00" 16 | }, 17 | "statusCode": 200, 18 | "statusMessage": "OK" 19 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/memberships_create_with_new_user.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "user_id": "679cd7aa-5445-4d6a-8d56-930557a2a77e", 8 | "email": "rokka-test@example.org", 9 | "roles": [ 10 | "upload", 11 | "read" 12 | ], 13 | "api_key": "NEW_APIKEY", 14 | "organization_id": "4eebf31f-6afa-412c-8b94-3458922896a4", 15 | "active": true 16 | }, 17 | "statusCode": 201, 18 | "statusMessage": "Created" 19 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/memberships_delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": "", 7 | "statusCode": 204, 8 | "statusMessage": "No Content" 9 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/memberships_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "total": 2, 8 | "items": [ 9 | { 10 | "user_id": "679cd7aa-5445-4d6a-8d56-930557a2a77e", 11 | "email": "rokka-test@example.org", 12 | "roles": [ 13 | "upload", 14 | "write" 15 | ], 16 | "organization_id": "4eebf31f-6afa-412c-8b94-3458922896a4", 17 | "active": true, 18 | "last_access": "2019-12-10T09:34:57+00:00" 19 | }, 20 | { 21 | "user_id": "9727f161-d236-4e89-8a03-b0da1a68efdc", 22 | "email": "rokka-test@example.org", 23 | "roles": [ 24 | "admin" 25 | ], 26 | "organization_id": "4eebf31f-6afa-412c-8b94-3458922896a4", 27 | "active": true, 28 | "last_access": "2019-12-10T07:11:27+00:00" 29 | } 30 | ] 31 | }, 32 | "statusCode": 200, 33 | "statusMessage": "OK" 34 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/organizations_create.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "id": "2bcced39-5e64-4c7c-b0c0-49e2b9c93507", 8 | "display_name": "Organization Inc.", 9 | "name": "rokka-js-test-new", 10 | "billing_email": "billing@example.org", 11 | "created": "2019-12-10T08:51:36+00:00", 12 | "master_organization": "rokka-js-test-new" 13 | }, 14 | "statusCode": 200, 15 | "statusMessage": "OK" 16 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/organizations_get.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "id": "4eebf31f-6afa-412c-8b94-3458922896a4", 8 | "display_name": "rokka-js-tests", 9 | "name": "rokka-js-tests", 10 | "billing_email": "rokka-test@example.org", 11 | "created": "2019-12-10T07:11:24+00:00", 12 | "master_organization": "rokka-js-tests" 13 | }, 14 | "statusCode": 200, 15 | "statusMessage": "OK" 16 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/sourceimages_copy.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": {}, 7 | "statusCode": 201, 8 | "statusMessage": "Created" 9 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/sourceimages_copy_no_overwrite.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": {}, 7 | "statusCode": 412, 8 | "statusMessage": "Precondition Failed", 9 | "error": {}, 10 | "message": "412 - {}" 11 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/sourceimages_delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": "", 7 | "statusCode": 204, 8 | "statusMessage": "No Content" 9 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/sourceimages_delete_with_binary.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": "", 7 | "statusCode": 204, 8 | "statusMessage": "No Content" 9 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/sourceimages_download.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": "\n\n \n \n \n \n \n \n \n \n \n\n", 7 | "statusCode": 200, 8 | "statusMessage": "OK" 9 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/sourceimages_get.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "hash": "063c3dce6a528d8944a63185bd4c7b161454ee4f", 8 | "short_hash": "063c3d", 9 | "binary_hash": "498fed612f01199cd6702f5effe5fa7bb67e44f4", 10 | "created": "2019-12-10T11:13:24+00:00", 11 | "name": "cartman.svg", 12 | "mimetype": "image/svg+xml", 13 | "format": "svg", 14 | "size": 1203, 15 | "width": 104, 16 | "height": 97, 17 | "organization": "rokka-js-tests", 18 | "link": "/sourceimages/rokka-js-tests/063c3dce6a528d8944a63185bd4c7b161454ee4f", 19 | "user_metadata": { 20 | "foo": "bar" 21 | }, 22 | "opaque": false 23 | }, 24 | "statusCode": 200, 25 | "statusMessage": "OK" 26 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/sourceimages_get_binaryhash.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "total": 1, 8 | "items": [ 9 | { 10 | "hash": "063c3dce6a528d8944a63185bd4c7b161454ee4f", 11 | "short_hash": "063c3d", 12 | "binary_hash": "498fed612f01199cd6702f5effe5fa7bb67e44f4", 13 | "created": "2019-12-10T11:13:24+00:00", 14 | "name": "cartman.svg", 15 | "mimetype": "image/svg+xml", 16 | "format": "svg", 17 | "size": 1203, 18 | "width": 104, 19 | "height": 97, 20 | "organization": "rokka-js-tests", 21 | "link": "/sourceimages/rokka-js-tests/063c3dce6a528d8944a63185bd4c7b161454ee4f", 22 | "user_metadata": { 23 | "foo": "bar" 24 | }, 25 | "opaque": false 26 | } 27 | ] 28 | }, 29 | "statusCode": 200, 30 | "statusMessage": "OK" 31 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/sourceimages_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "total": 3, 8 | "items": [ 9 | { 10 | "hash": "063c3dce6a528d8944a63185bd4c7b161454ee4f", 11 | "short_hash": "063c3d", 12 | "binary_hash": "498fed612f01199cd6702f5effe5fa7bb67e44f4", 13 | "created": "2019-12-10T11:13:24+00:00", 14 | "name": "cartman.svg", 15 | "mimetype": "image/svg+xml", 16 | "format": "svg", 17 | "size": 1203, 18 | "width": 104, 19 | "height": 97, 20 | "organization": "rokka-js-tests", 21 | "link": "/sourceimages/rokka-js-tests/063c3dce6a528d8944a63185bd4c7b161454ee4f", 22 | "user_metadata": { 23 | "foo": "bar" 24 | }, 25 | "opaque": false 26 | }, 27 | { 28 | "hash": "fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2", 29 | "short_hash": "fe5d9a", 30 | "binary_hash": "63a65da81cae86964e3369b4431b80252f9404b0", 31 | "created": "2019-12-10T10:45:32+00:00", 32 | "name": "schweiz.jpg", 33 | "mimetype": "image/svg+xml", 34 | "format": "svg", 35 | "size": 1202, 36 | "width": 104, 37 | "height": 97, 38 | "organization": "rokka-js-tests", 39 | "link": "/sourceimages/rokka-js-tests/fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2", 40 | "opaque": false 41 | }, 42 | { 43 | "hash": "b32fcb212a2a13220c5ad7f8c71bd6ef5dfd0027", 44 | "short_hash": "b32fcb", 45 | "binary_hash": "1215617542b2926a80396eecf97980359f772aad", 46 | "created": "2019-12-10T10:30:59+00:00", 47 | "name": "schweiz.jpg", 48 | "mimetype": "image/jpeg", 49 | "format": "jpg", 50 | "size": 2190, 51 | "width": 68, 52 | "height": 68, 53 | "organization": "rokka-js-tests", 54 | "link": "/sourceimages/rokka-js-tests/b32fcb212a2a13220c5ad7f8c71bd6ef5dfd0027", 55 | "opaque": true 56 | } 57 | ], 58 | "cursor": "Vc6zPbFoQW9KNDY1WFg5KzRDUHlKd2NtOWtMV2RzYjJKaGJHSXpNbVpqWWpJeE1tRXlZVEV6TWpJd1l6VmhaRGRtT0dNM01XSmtObVZtTldSbVpEQXdNamR5YjJ0cllTMXFjeTEwWlhOMGN3PT0D", 59 | "links": {} 60 | }, 61 | "statusCode": 200, 62 | "statusMessage": "OK" 63 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/sourceimages_list_with_args.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "total": 3, 8 | "items": [ 9 | { 10 | "hash": "fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2", 11 | "short_hash": "fe5d9a", 12 | "binary_hash": "63a65da81cae86964e3369b4431b80252f9404b0", 13 | "created": "2019-12-10T10:45:32+00:00", 14 | "name": "schweiz.jpg", 15 | "mimetype": "image/svg+xml", 16 | "format": "svg", 17 | "size": 1202, 18 | "width": 104, 19 | "height": 97, 20 | "organization": "rokka-js-tests", 21 | "link": "/sourceimages/rokka-js-tests/fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2", 22 | "opaque": false 23 | }, 24 | { 25 | "hash": "b32fcb212a2a13220c5ad7f8c71bd6ef5dfd0027", 26 | "short_hash": "b32fcb", 27 | "binary_hash": "1215617542b2926a80396eecf97980359f772aad", 28 | "created": "2019-12-10T10:30:59+00:00", 29 | "name": "schweiz.jpg", 30 | "mimetype": "image/jpeg", 31 | "format": "jpg", 32 | "size": 2190, 33 | "width": 68, 34 | "height": 68, 35 | "organization": "rokka-js-tests", 36 | "link": "/sourceimages/rokka-js-tests/b32fcb212a2a13220c5ad7f8c71bd6ef5dfd0027", 37 | "opaque": true 38 | } 39 | ], 40 | "links": { 41 | "prev": { 42 | "href": "/sourceimages/rokka-js-tests?limit=2" 43 | } 44 | } 45 | }, 46 | "statusCode": 200, 47 | "statusMessage": "OK" 48 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/sourceimages_metadata_add.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": "", 7 | "statusCode": 204, 8 | "statusMessage": "No Content" 9 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/sourceimages_metadata_delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": "", 7 | "statusCode": 204, 8 | "statusMessage": "No Content" 9 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/sourceimages_metadata_remove_subjectarea.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "hash": "fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2", 8 | "short_hash": "fe5d9a", 9 | "binary_hash": "63a65da81cae86964e3369b4431b80252f9404b0", 10 | "created": "2019-12-10T10:45:32+00:00", 11 | "name": "schweiz.jpg", 12 | "mimetype": "image/svg+xml", 13 | "format": "svg", 14 | "size": 1202, 15 | "width": 104, 16 | "height": 97, 17 | "organization": "rokka-js-tests", 18 | "opaque": false 19 | }, 20 | "statusCode": 201, 21 | "statusMessage": "Created" 22 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/sourceimages_metadata_remove_subjectarea_delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "hash": "fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2", 8 | "short_hash": "fe5d9a", 9 | "binary_hash": "63a65da81cae86964e3369b4431b80252f9404b0", 10 | "created": "2019-12-10T10:45:32+00:00", 11 | "name": "schweiz.jpg", 12 | "mimetype": "image/svg+xml", 13 | "format": "svg", 14 | "size": 1202, 15 | "width": 104, 16 | "height": 97, 17 | "organization": "rokka-js-tests", 18 | "opaque": false 19 | }, 20 | "statusCode": 201, 21 | "statusMessage": "Created" 22 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/sourceimages_metadata_replace.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": "", 7 | "statusCode": 204, 8 | "statusMessage": "No Content" 9 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/sourceimages_metadata_set_subjectarea.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "hash": "e551164763cdbabcd0b75b144f3f08112844a81f", 8 | "short_hash": "e55116", 9 | "binary_hash": "63a65da81cae86964e3369b4431b80252f9404b0", 10 | "created": "2019-12-10T10:45:32+00:00", 11 | "name": "schweiz.jpg", 12 | "mimetype": "image/svg+xml", 13 | "format": "svg", 14 | "size": 1202, 15 | "width": 104, 16 | "height": 97, 17 | "organization": "rokka-js-tests", 18 | "dynamic_metadata": { 19 | "subject_area": { 20 | "width": 50, 21 | "height": 50, 22 | "x": 100, 23 | "y": 100 24 | } 25 | }, 26 | "opaque": false 27 | }, 28 | "statusCode": 201, 29 | "statusMessage": "Created" 30 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/sourceimages_metadata_set_subjectarea_delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "hash": "76114710a44fb15fb37ecf66bbad250643373990", 8 | "short_hash": "761147", 9 | "binary_hash": "63a65da81cae86964e3369b4431b80252f9404b0", 10 | "created": "2019-12-10T10:45:32+00:00", 11 | "name": "schweiz.jpg", 12 | "mimetype": "image/svg+xml", 13 | "format": "svg", 14 | "size": 1202, 15 | "width": 104, 16 | "height": 97, 17 | "organization": "rokka-js-tests", 18 | "dynamic_metadata": { 19 | "subject_area": { 20 | "width": 40, 21 | "height": 40, 22 | "x": 120, 23 | "y": 120 24 | } 25 | }, 26 | "opaque": false 27 | }, 28 | "statusCode": 201, 29 | "statusMessage": "Created" 30 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/sourceimages_restore.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "hash": "063c3dce6a528d8944a63185bd4c7b161454ee4f", 8 | "short_hash": "063c3d", 9 | "binary_hash": "498fed612f01199cd6702f5effe5fa7bb67e44f4", 10 | "created": "2019-12-10T11:13:24+00:00", 11 | "name": "cartman.svg", 12 | "mimetype": "image/svg+xml", 13 | "format": "svg", 14 | "size": 1203, 15 | "width": 104, 16 | "height": 97, 17 | "organization": "rokka-js-tests", 18 | "user_metadata": { 19 | "foo": "bar" 20 | }, 21 | "deleted": false, 22 | "opaque": false 23 | }, 24 | "statusCode": 200, 25 | "statusMessage": "OK" 26 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/stackoptions_get.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "properties": { 8 | "basestack": { 9 | "type": [ 10 | "string", 11 | "null" 12 | ], 13 | "default": null, 14 | "pattern": "[a-z0-9\\-_]+" 15 | }, 16 | "jpg.quality": { 17 | "type": "integer", 18 | "minimum": 1, 19 | "maximum": 100, 20 | "default": 80 21 | }, 22 | "pngquant.quality": { 23 | "type": "integer", 24 | "minimum": 10, 25 | "maximum": 100, 26 | "default": 98 27 | }, 28 | "webp.quality": { 29 | "type": "integer", 30 | "minimum": 1, 31 | "maximum": 100, 32 | "default": 80 33 | }, 34 | "heif.quality": { 35 | "type": "integer", 36 | "minimum": 1, 37 | "maximum": 100, 38 | "default": 40 39 | }, 40 | "av1.quality": { 41 | "type": "integer", 42 | "minimum": 1, 43 | "maximum": 100, 44 | "default": 50 45 | }, 46 | "webm.quality": { 47 | "type": "integer", 48 | "minimum": 1, 49 | "maximum": 100, 50 | "default": 50 51 | }, 52 | "mp4.quality": { 53 | "type": "integer", 54 | "minimum": 1, 55 | "maximum": 100, 56 | "default": 50 57 | }, 58 | "gif.quality": { 59 | "type": "integer", 60 | "minimum": 1, 61 | "maximum": 100, 62 | "default": 60 63 | }, 64 | "jp2.quality": { 65 | "type": "integer", 66 | "minimum": 1, 67 | "maximum": 100, 68 | "default": 40 69 | }, 70 | "optim.quality": { 71 | "type": "integer", 72 | "minimum": 0, 73 | "maximum": 10, 74 | "default": 4 75 | }, 76 | "png.compression_level": { 77 | "type": "integer", 78 | "minimum": 0, 79 | "maximum": 9, 80 | "default": 7 81 | }, 82 | "dpr": { 83 | "type": "number", 84 | "minimum": 1, 85 | "maximum": 10, 86 | "default": 1 87 | }, 88 | "optim.ttl_shared": { 89 | "type": "boolean", 90 | "default": false 91 | }, 92 | "autoformat": { 93 | "type": "boolean", 94 | "default": false 95 | }, 96 | "autoformat.exclude": { 97 | "type": [ 98 | "string", 99 | "null" 100 | ], 101 | "default": null 102 | }, 103 | "jpg.transparency.convert": { 104 | "type": "boolean", 105 | "default": false 106 | }, 107 | "source_file": { 108 | "type": "boolean", 109 | "default": false 110 | }, 111 | "optim.disable_all": { 112 | "type": "boolean", 113 | "default": false 114 | }, 115 | "optim.immediate": { 116 | "type": "boolean", 117 | "default": false 118 | }, 119 | "remote_fullurl_allow": { 120 | "type": [ 121 | "boolean", 122 | "null" 123 | ], 124 | "default": null 125 | }, 126 | "remote_basepath": { 127 | "type": [ 128 | "string", 129 | "null" 130 | ], 131 | "default": null, 132 | "pattern": "^https?://" 133 | }, 134 | "jpg.transparency.autoformat": { 135 | "type": "string", 136 | "default": "false", 137 | "values": [ 138 | "true", 139 | "false", 140 | "png", 141 | "webp" 142 | ] 143 | }, 144 | "jpg.transparency.color": { 145 | "type": "string", 146 | "default": "FFFFFF" 147 | }, 148 | "content_disposition": { 149 | "type": "string", 150 | "default": "inline", 151 | "values": [ 152 | "inline", 153 | "attachment" 154 | ] 155 | }, 156 | "timing_origin": { 157 | "type": [ 158 | "boolean", 159 | "null" 160 | ], 161 | "default": null 162 | }, 163 | "interlacing.mode": { 164 | "type": "string", 165 | "default": "plane", 166 | "values": [ 167 | "none", 168 | "line", 169 | "plane", 170 | "partition" 171 | ] 172 | }, 173 | "remote_fullurl_whitelist": { 174 | "type": "array", 175 | "items": { 176 | "type": "string" 177 | }, 178 | "default": null 179 | }, 180 | "timestamp": { 181 | "type": [ 182 | "string", 183 | "null" 184 | ], 185 | "default": null 186 | }, 187 | "fps": { 188 | "type": "integer", 189 | "minimum": 1, 190 | "maximum": 25, 191 | "default": 10 192 | } 193 | } 194 | }, 195 | "statusCode": 200, 196 | "statusMessage": "OK" 197 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/stacks_create.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "name": "mystack", 8 | "organization": "rokka-js-tests", 9 | "created": "2019-12-10T09:13:53+00:00", 10 | "stack_operations": [ 11 | { 12 | "name": "rotate", 13 | "options": { 14 | "angle": 45 15 | } 16 | }, 17 | { 18 | "name": "resize", 19 | "options": { 20 | "width": 100, 21 | "height": 100 22 | } 23 | } 24 | ], 25 | "stack_options": { 26 | "jpg.quality": 77 27 | }, 28 | "stack_expressions": [], 29 | "stack_variables": {} 30 | }, 31 | "statusCode": 201, 32 | "statusMessage": "Created" 33 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/stacks_create_0_26.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "name": "mystack026", 8 | "organization": "rokka-js-tests", 9 | "created": "2019-12-10T09:04:22+00:00", 10 | "stack_operations": [ 11 | { 12 | "name": "rotate", 13 | "options": { 14 | "angle": 47 15 | } 16 | }, 17 | { 18 | "name": "resize", 19 | "options": { 20 | "width": 120, 21 | "height": 120 22 | } 23 | } 24 | ], 25 | "stack_options": { 26 | "jpg.quality": 76 27 | }, 28 | "stack_expressions": [], 29 | "stack_variables": {} 30 | }, 31 | "statusCode": 201, 32 | "statusMessage": "Created" 33 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/stacks_create_expressions.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "name": "mystack_expressions", 8 | "organization": "rokka-js-tests", 9 | "created": "2019-12-10T09:07:00+00:00", 10 | "stack_operations": [ 11 | { 12 | "name": "rotate", 13 | "options": { 14 | "angle": 45 15 | } 16 | }, 17 | { 18 | "name": "resize", 19 | "options": { 20 | "width": 100, 21 | "height": 100 22 | } 23 | } 24 | ], 25 | "stack_options": { 26 | "jpg.quality": 78 27 | }, 28 | "stack_expressions": [ 29 | { 30 | "expression": "options.dpr >= 2", 31 | "overrides": { 32 | "options": { 33 | "jpg.quality": 60, 34 | "webp.quality": 60 35 | } 36 | } 37 | } 38 | ], 39 | "stack_variables": {} 40 | }, 41 | "statusCode": 201, 42 | "statusMessage": "Created" 43 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/stacks_create_overwrite.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "name": "mystack", 8 | "organization": "rokka-js-tests", 9 | "created": "2019-12-10T09:08:34+00:00", 10 | "stack_operations": [ 11 | { 12 | "name": "rotate", 13 | "options": { 14 | "angle": 45 15 | } 16 | }, 17 | { 18 | "name": "resize", 19 | "options": { 20 | "width": 100, 21 | "height": 100 22 | } 23 | } 24 | ], 25 | "stack_options": { 26 | "jpg.quality": 76 27 | }, 28 | "stack_expressions": [], 29 | "stack_variables": {} 30 | }, 31 | "statusCode": 200, 32 | "statusMessage": "OK" 33 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/stacks_create_overwrite_0_26.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "name": "mystack", 8 | "organization": "rokka-js-tests", 9 | "created": "2019-12-10T09:11:32+00:00", 10 | "stack_operations": [ 11 | { 12 | "name": "rotate", 13 | "options": { 14 | "angle": 48 15 | } 16 | }, 17 | { 18 | "name": "resize", 19 | "options": { 20 | "width": 120, 21 | "height": 120 22 | } 23 | } 24 | ], 25 | "stack_options": { 26 | "jpg.quality": 77 27 | }, 28 | "stack_expressions": [], 29 | "stack_variables": {} 30 | }, 31 | "statusCode": 200, 32 | "statusMessage": "OK" 33 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/stacks_delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": "", 7 | "statusCode": 204, 8 | "statusMessage": "No Content" 9 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/stacks_get.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "name": "mystack", 8 | "organization": "rokka-js-tests", 9 | "created": "2019-12-10T09:04:22+00:00", 10 | "stack_operations": [ 11 | { 12 | "name": "rotate", 13 | "options": { 14 | "angle": 45 15 | } 16 | }, 17 | { 18 | "name": "resize", 19 | "options": { 20 | "width": 100, 21 | "height": 100 22 | } 23 | } 24 | ], 25 | "stack_options": { 26 | "jpg.quality": 77 27 | }, 28 | "stack_expressions": [], 29 | "link": "/stacks/rokka-js-tests/mystack", 30 | "stack_variables": {} 31 | }, 32 | "statusCode": 200, 33 | "statusMessage": "OK" 34 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/stacks_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "items": [ 8 | { 9 | "name": "mystack", 10 | "organization": "rokka-js-tests", 11 | "created": "2019-12-10T09:08:34+00:00", 12 | "stack_operations": [ 13 | { 14 | "name": "rotate", 15 | "options": { 16 | "angle": 45 17 | } 18 | }, 19 | { 20 | "name": "resize", 21 | "options": { 22 | "width": 100, 23 | "height": 100 24 | } 25 | } 26 | ], 27 | "stack_options": { 28 | "jpg.quality": 75 29 | }, 30 | "stack_expressions": [], 31 | "stack_variables": {} 32 | }, 33 | { 34 | "name": "mystack026", 35 | "organization": "rokka-js-tests", 36 | "created": "2019-12-10T09:04:22+00:00", 37 | "stack_operations": [ 38 | { 39 | "name": "rotate", 40 | "options": { 41 | "angle": 47 42 | } 43 | }, 44 | { 45 | "name": "resize", 46 | "options": { 47 | "width": 120, 48 | "height": 120 49 | } 50 | } 51 | ], 52 | "stack_options": { 53 | "jpg.quality": 76 54 | }, 55 | "stack_expressions": [], 56 | "stack_variables": {} 57 | }, 58 | { 59 | "name": "mystack_expressions", 60 | "organization": "rokka-js-tests", 61 | "created": "2019-12-10T09:07:00+00:00", 62 | "stack_operations": [ 63 | { 64 | "name": "rotate", 65 | "options": { 66 | "angle": 45 67 | } 68 | }, 69 | { 70 | "name": "resize", 71 | "options": { 72 | "width": 100, 73 | "height": 100 74 | } 75 | } 76 | ], 77 | "stack_options": { 78 | "jpg.quality": 78 79 | }, 80 | "stack_expressions": [ 81 | { 82 | "expression": "options.dpr >= 2", 83 | "overrides": { 84 | "options": { 85 | "jpg.quality": 60, 86 | "webp.quality": 60 87 | } 88 | } 89 | } 90 | ], 91 | "stack_variables": {} 92 | } 93 | ] 94 | }, 95 | "statusCode": 200, 96 | "statusMessage": "OK" 97 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/stats_get.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "space_in_bytes": [ 8 | { 9 | "timestamp": "2019-11-30T00:00:00+00:00", 10 | "value": 5231255208.7761, 11 | "unit": "Bytes" 12 | }, 13 | { 14 | "timestamp": "2019-11-29T00:00:00+00:00", 15 | "value": 5231255208.7761, 16 | "unit": "Bytes" 17 | }, 18 | { 19 | "timestamp": "2019-11-28T00:00:00+00:00", 20 | "value": 5231255208.7761, 21 | "unit": "Bytes" 22 | }, 23 | { 24 | "timestamp": "2019-11-27T00:00:00+00:00", 25 | "value": 5231255208.7761, 26 | "unit": "Bytes" 27 | }, 28 | { 29 | "timestamp": "2019-11-26T00:00:00+00:00", 30 | "value": 5226143878.2304, 31 | "unit": "Bytes" 32 | }, 33 | { 34 | "timestamp": "2019-11-25T00:00:00+00:00", 35 | "value": 5226143878.2304, 36 | "unit": "Bytes" 37 | }, 38 | { 39 | "timestamp": "2019-11-24T00:00:00+00:00", 40 | "value": 5226143878.2304, 41 | "unit": "Bytes" 42 | }, 43 | { 44 | "timestamp": "2019-11-23T00:00:00+00:00", 45 | "value": 5226143878.2304, 46 | "unit": "Bytes" 47 | }, 48 | { 49 | "timestamp": "2019-11-22T00:00:00+00:00", 50 | "value": 5226143878.2304, 51 | "unit": "Bytes" 52 | }, 53 | { 54 | "timestamp": "2019-11-21T00:00:00+00:00", 55 | "value": 5226143878.2304, 56 | "unit": "Bytes" 57 | }, 58 | { 59 | "timestamp": "2019-11-20T00:00:00+00:00", 60 | "value": 5196235309.3372, 61 | "unit": "Bytes" 62 | }, 63 | { 64 | "timestamp": "2019-11-19T00:00:00+00:00", 65 | "value": 5196235309.3372, 66 | "unit": "Bytes" 67 | }, 68 | { 69 | "timestamp": "2019-11-18T00:00:00+00:00", 70 | "value": 5196235309.3372, 71 | "unit": "Bytes" 72 | }, 73 | { 74 | "timestamp": "2019-11-17T00:00:00+00:00", 75 | "value": 5196235309.3372, 76 | "unit": "Bytes" 77 | }, 78 | { 79 | "timestamp": "2019-11-16T00:00:00+00:00", 80 | "value": 5196235309.3372, 81 | "unit": "Bytes" 82 | }, 83 | { 84 | "timestamp": "2019-11-15T00:00:00+00:00", 85 | "value": 5196235309.3372, 86 | "unit": "Bytes" 87 | }, 88 | { 89 | "timestamp": "2019-11-14T00:00:00+00:00", 90 | "value": 5196235309.3372, 91 | "unit": "Bytes" 92 | }, 93 | { 94 | "timestamp": "2019-11-13T00:00:00+00:00", 95 | "value": 5196235309.3372, 96 | "unit": "Bytes" 97 | }, 98 | { 99 | "timestamp": "2019-11-12T00:00:00+00:00", 100 | "value": 5196235309.3372, 101 | "unit": "Bytes" 102 | }, 103 | { 104 | "timestamp": "2019-11-11T00:00:00+00:00", 105 | "value": 5196235309.3372, 106 | "unit": "Bytes" 107 | }, 108 | { 109 | "timestamp": "2019-11-10T00:00:00+00:00", 110 | "value": 5196235309.3372, 111 | "unit": "Bytes" 112 | }, 113 | { 114 | "timestamp": "2019-11-09T00:00:00+00:00", 115 | "value": 5196235309.3372, 116 | "unit": "Bytes" 117 | }, 118 | { 119 | "timestamp": "2019-11-08T00:00:00+00:00", 120 | "value": 5194651548.7133, 121 | "unit": "Bytes" 122 | }, 123 | { 124 | "timestamp": "2019-11-07T00:00:00+00:00", 125 | "value": 5192201888.71, 126 | "unit": "Bytes" 127 | }, 128 | { 129 | "timestamp": "2019-11-06T00:00:00+00:00", 130 | "value": 5192201888.71, 131 | "unit": "Bytes" 132 | }, 133 | { 134 | "timestamp": "2019-11-05T00:00:00+00:00", 135 | "value": 5192201888.71, 136 | "unit": "Bytes" 137 | }, 138 | { 139 | "timestamp": "2019-11-04T00:00:00+00:00", 140 | "value": 5192201888.71, 141 | "unit": "Bytes" 142 | }, 143 | { 144 | "timestamp": "2019-11-03T00:00:00+00:00", 145 | "value": 5192201888.71, 146 | "unit": "Bytes" 147 | }, 148 | { 149 | "timestamp": "2019-11-02T00:00:00+00:00", 150 | "value": 5164421208.285, 151 | "unit": "Bytes" 152 | }, 153 | { 154 | "timestamp": "2019-11-01T00:00:00+00:00", 155 | "value": 5164421208.285, 156 | "unit": "Bytes" 157 | } 158 | ], 159 | "number_of_files": [ 160 | { 161 | "timestamp": "2019-11-30T00:00:00+00:00", 162 | "value": 3201, 163 | "unit": null 164 | }, 165 | { 166 | "timestamp": "2019-11-29T00:00:00+00:00", 167 | "value": 3201, 168 | "unit": null 169 | }, 170 | { 171 | "timestamp": "2019-11-28T00:00:00+00:00", 172 | "value": 3201, 173 | "unit": null 174 | }, 175 | { 176 | "timestamp": "2019-11-27T00:00:00+00:00", 177 | "value": 3201, 178 | "unit": null 179 | }, 180 | { 181 | "timestamp": "2019-11-26T00:00:00+00:00", 182 | "value": 3200, 183 | "unit": null 184 | }, 185 | { 186 | "timestamp": "2019-11-25T00:00:00+00:00", 187 | "value": 3200, 188 | "unit": null 189 | }, 190 | { 191 | "timestamp": "2019-11-24T00:00:00+00:00", 192 | "value": 3200, 193 | "unit": null 194 | }, 195 | { 196 | "timestamp": "2019-11-23T00:00:00+00:00", 197 | "value": 3200, 198 | "unit": null 199 | }, 200 | { 201 | "timestamp": "2019-11-22T00:00:00+00:00", 202 | "value": 3200, 203 | "unit": null 204 | }, 205 | { 206 | "timestamp": "2019-11-21T00:00:00+00:00", 207 | "value": 3200, 208 | "unit": null 209 | }, 210 | { 211 | "timestamp": "2019-11-20T00:00:00+00:00", 212 | "value": 3194, 213 | "unit": null 214 | }, 215 | { 216 | "timestamp": "2019-11-19T00:00:00+00:00", 217 | "value": 3194, 218 | "unit": null 219 | }, 220 | { 221 | "timestamp": "2019-11-18T00:00:00+00:00", 222 | "value": 3194, 223 | "unit": null 224 | }, 225 | { 226 | "timestamp": "2019-11-17T00:00:00+00:00", 227 | "value": 3194, 228 | "unit": null 229 | }, 230 | { 231 | "timestamp": "2019-11-16T00:00:00+00:00", 232 | "value": 3194, 233 | "unit": null 234 | }, 235 | { 236 | "timestamp": "2019-11-15T00:00:00+00:00", 237 | "value": 3194, 238 | "unit": null 239 | }, 240 | { 241 | "timestamp": "2019-11-14T00:00:00+00:00", 242 | "value": 3194, 243 | "unit": null 244 | }, 245 | { 246 | "timestamp": "2019-11-13T00:00:00+00:00", 247 | "value": 3194, 248 | "unit": null 249 | }, 250 | { 251 | "timestamp": "2019-11-12T00:00:00+00:00", 252 | "value": 3194, 253 | "unit": null 254 | }, 255 | { 256 | "timestamp": "2019-11-11T00:00:00+00:00", 257 | "value": 3194, 258 | "unit": null 259 | }, 260 | { 261 | "timestamp": "2019-11-10T00:00:00+00:00", 262 | "value": 3194, 263 | "unit": null 264 | }, 265 | { 266 | "timestamp": "2019-11-09T00:00:00+00:00", 267 | "value": 3194, 268 | "unit": null 269 | }, 270 | { 271 | "timestamp": "2019-11-08T00:00:00+00:00", 272 | "value": 3192, 273 | "unit": null 274 | }, 275 | { 276 | "timestamp": "2019-11-07T00:00:00+00:00", 277 | "value": 3191, 278 | "unit": null 279 | }, 280 | { 281 | "timestamp": "2019-11-06T00:00:00+00:00", 282 | "value": 3191, 283 | "unit": null 284 | }, 285 | { 286 | "timestamp": "2019-11-05T00:00:00+00:00", 287 | "value": 3191, 288 | "unit": null 289 | }, 290 | { 291 | "timestamp": "2019-11-04T00:00:00+00:00", 292 | "value": 3191, 293 | "unit": null 294 | }, 295 | { 296 | "timestamp": "2019-11-03T00:00:00+00:00", 297 | "value": 3191, 298 | "unit": null 299 | }, 300 | { 301 | "timestamp": "2019-11-02T00:00:00+00:00", 302 | "value": 3189, 303 | "unit": null 304 | }, 305 | { 306 | "timestamp": "2019-11-01T00:00:00+00:00", 307 | "value": 3189, 308 | "unit": null 309 | } 310 | ], 311 | "bytes_downloaded": [ 312 | { 313 | "timestamp": "2019-11-26T00:00:00+00:00", 314 | "value": 47900253, 315 | "unit": null 316 | }, 317 | { 318 | "timestamp": "2019-11-21T00:00:00+00:00", 319 | "value": 75417344, 320 | "unit": null 321 | }, 322 | { 323 | "timestamp": "2019-11-12T00:00:00+00:00", 324 | "value": 31803438, 325 | "unit": null 326 | }, 327 | { 328 | "timestamp": "2019-11-11T00:00:00+00:00", 329 | "value": 74472, 330 | "unit": null 331 | }, 332 | { 333 | "timestamp": "2019-11-09T00:00:00+00:00", 334 | "value": 29535842, 335 | "unit": null 336 | }, 337 | { 338 | "timestamp": "2019-11-08T00:00:00+00:00", 339 | "value": 755410, 340 | "unit": null 341 | }, 342 | { 343 | "timestamp": "2019-11-07T00:00:00+00:00", 344 | "value": 3464679, 345 | "unit": null 346 | }, 347 | { 348 | "timestamp": "2019-11-06T00:00:00+00:00", 349 | "value": 20072058, 350 | "unit": null 351 | }, 352 | { 353 | "timestamp": "2019-11-05T00:00:00+00:00", 354 | "value": 6265633, 355 | "unit": null 356 | }, 357 | { 358 | "timestamp": "2019-11-04T00:00:00+00:00", 359 | "value": 4209520, 360 | "unit": null 361 | }, 362 | { 363 | "timestamp": "2019-11-03T00:00:00+00:00", 364 | "value": 58485234, 365 | "unit": null 366 | }, 367 | { 368 | "timestamp": "2019-11-02T00:00:00+00:00", 369 | "value": 424304, 370 | "unit": null 371 | }, 372 | { 373 | "timestamp": "2019-11-01T00:00:00+00:00", 374 | "value": 113534, 375 | "unit": null 376 | } 377 | ], 378 | "bytes_downloaded_external": [] 379 | }, 380 | "statusCode": 200, 381 | "statusMessage": "OK" 382 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/users_create.json: -------------------------------------------------------------------------------- 1 | { 2 | "response": { 3 | "size": 0, 4 | "timeout": 30000 5 | }, 6 | "body": { 7 | "id": "c6b56803-e6f7-4798-a3ee-6fd761548d89", 8 | "email": "user@example.org", 9 | "api_key": "ANOTHER_API_KEY" 10 | }, 11 | "statusCode": 200, 12 | "statusMessage": "OK" 13 | } -------------------------------------------------------------------------------- /tests/fixtures/answers/users_getid.json: -------------------------------------------------------------------------------- 1 | "9727f161-d236-4e89-8a03-b0da1a68efdc" -------------------------------------------------------------------------------- /tests/fixtures/cartman.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/fixtures/requests/memberships_create_with_array.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "PUT", 5 | "path": "/organizations/rokka-js-tests/memberships/679cd7aa-5445-4d6a-8d56-930557a2a77e", 6 | "body": { 7 | "roles": [ 8 | "upload", 9 | "write" 10 | ] 11 | }, 12 | "status": 200, 13 | "response": { 14 | "user_id": "679cd7aa-5445-4d6a-8d56-930557a2a77e", 15 | "email": "rokka-test@example.org", 16 | "roles": [ 17 | "upload", 18 | "write" 19 | ], 20 | "organization_id": "4eebf31f-6afa-412c-8b94-3458922896a4", 21 | "active": true, 22 | "last_access": "2019-12-10T09:34:57+00:00" 23 | }, 24 | "responseIsBinary": false 25 | } 26 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/memberships_create_with_new_user.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "POST", 5 | "path": "/organizations/rokka-js-tests/memberships", 6 | "body": { 7 | "roles": [ 8 | "upload", 9 | "read" 10 | ] 11 | }, 12 | "status": 201, 13 | "response": { 14 | "user_id": "679cd7aa-5445-4d6a-8d56-930557a2a77e", 15 | "email": "rokka-test@example.org", 16 | "roles": [ 17 | "upload", 18 | "read" 19 | ], 20 | "api_key": "NEW_APIKEY", 21 | "organization_id": "4eebf31f-6afa-412c-8b94-3458922896a4", 22 | "active": true 23 | }, 24 | "responseIsBinary": false 25 | } 26 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/memberships_delete.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "DELETE", 5 | "path": "/organizations/rokka-js-tests/memberships/679cd7aa-5445-4d6a-8d56-930557a2a77e", 6 | "body": "", 7 | "status": 204, 8 | "response": "", 9 | "responseIsBinary": false 10 | } 11 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/memberships_list.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "GET", 5 | "path": "/organizations/rokka-js-tests/memberships", 6 | "body": "", 7 | "status": 200, 8 | "response": { 9 | "total": 2, 10 | "items": [ 11 | { 12 | "user_id": "679cd7aa-5445-4d6a-8d56-930557a2a77e", 13 | "email": "rokka-test@example.org", 14 | "roles": [ 15 | "upload", 16 | "write" 17 | ], 18 | "organization_id": "4eebf31f-6afa-412c-8b94-3458922896a4", 19 | "active": true, 20 | "last_access": "2019-12-10T09:34:57+00:00" 21 | }, 22 | { 23 | "user_id": "9727f161-d236-4e89-8a03-b0da1a68efdc", 24 | "email": "rokka-test@example.org", 25 | "roles": [ 26 | "admin" 27 | ], 28 | "organization_id": "4eebf31f-6afa-412c-8b94-3458922896a4", 29 | "active": true, 30 | "last_access": "2019-12-10T07:11:27+00:00" 31 | } 32 | ] 33 | }, 34 | "responseIsBinary": false 35 | } 36 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/organizations_create.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "PUT", 5 | "path": "/organizations/rokka-js-test-new", 6 | "body": { 7 | "billing_email": "billing@example.org", 8 | "display_name": "Organization Inc." 9 | }, 10 | "status": 200, 11 | "response": { 12 | "id": "2bcced39-5e64-4c7c-b0c0-49e2b9c93507", 13 | "display_name": "Organization Inc.", 14 | "name": "rokka-js-test-new", 15 | "billing_email": "billing@example.org", 16 | "created": "2019-12-10T08:51:36+00:00", 17 | "master_organization": "rokka-js-test-new" 18 | }, 19 | "responseIsBinary": false 20 | } 21 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/organizations_get.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "GET", 5 | "path": "/organizations/rokka-js-tests", 6 | "body": "", 7 | "status": 200, 8 | "response": { 9 | "id": "4eebf31f-6afa-412c-8b94-3458922896a4", 10 | "display_name": "rokka-js-tests", 11 | "name": "rokka-js-tests", 12 | "billing_email": "rokka-test@example.org", 13 | "created": "2019-12-10T07:11:24+00:00", 14 | "master_organization": "rokka-js-tests" 15 | }, 16 | "responseIsBinary": false 17 | } 18 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/sourceimages_copy.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "COPY", 5 | "path": "/sourceimages/rokka-js-tests/063c3dce6a528d8944a63185bd4c7b161454ee4f", 6 | "body": "", 7 | "status": 201, 8 | "response": {}, 9 | "reqheaders": { 10 | "destination": [ 11 | "rokka-js-tests-new2" 12 | ], 13 | "api-version": [ 14 | "1" 15 | ] 16 | }, 17 | "responseIsBinary": false 18 | } 19 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/sourceimages_copy_no_overwrite.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "COPY", 5 | "path": "/sourceimages/rokka-js-tests/063c3dce6a528d8944a63185bd4c7b161454ee4f", 6 | "body": "", 7 | "status": 412, 8 | "response": {}, 9 | "reqheaders": { 10 | "destination": [ 11 | "rokka-js-tests-new2" 12 | ], 13 | "overwrite": [ 14 | "F" 15 | ], 16 | "api-version": [ 17 | "1" 18 | ] 19 | }, 20 | "responseIsBinary": false 21 | } 22 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/sourceimages_delete.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "DELETE", 5 | "path": "/sourceimages/rokka-js-tests/063c3dce6a528d8944a63185bd4c7b161454ee4f", 6 | "body": "", 7 | "status": 204, 8 | "response": "", 9 | "responseIsBinary": false 10 | } 11 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/sourceimages_delete_with_binary.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "DELETE", 5 | "path": "/sourceimages/rokka-js-tests?binaryHash=498fed612f01199cd6702f5effe5fa7bb67e44f4", 6 | "body": "", 7 | "status": 204, 8 | "response": "", 9 | "responseIsBinary": false 10 | } 11 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/sourceimages_download.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "GET", 5 | "path": "/sourceimages/rokka-js-tests/063c3dce6a528d8944a63185bd4c7b161454ee4f/download", 6 | "body": "", 7 | "status": 200, 8 | "response": "\n\n \n \n \n \n \n \n \n \n \n\n", 9 | "responseIsBinary": false 10 | } 11 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/sourceimages_get.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "GET", 5 | "path": "/sourceimages/rokka-js-tests/063c3dce6a528d8944a63185bd4c7b161454ee4f", 6 | "body": "", 7 | "status": 200, 8 | "response": { 9 | "hash": "063c3dce6a528d8944a63185bd4c7b161454ee4f", 10 | "short_hash": "063c3d", 11 | "binary_hash": "498fed612f01199cd6702f5effe5fa7bb67e44f4", 12 | "created": "2019-12-10T11:13:24+00:00", 13 | "name": "cartman.svg", 14 | "mimetype": "image/svg+xml", 15 | "format": "svg", 16 | "size": 1203, 17 | "width": 104, 18 | "height": 97, 19 | "organization": "rokka-js-tests", 20 | "link": "/sourceimages/rokka-js-tests/063c3dce6a528d8944a63185bd4c7b161454ee4f", 21 | "user_metadata": { 22 | "foo": "bar" 23 | }, 24 | "opaque": false 25 | }, 26 | "responseIsBinary": false 27 | } 28 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/sourceimages_get_binaryhash.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "GET", 5 | "path": "/sourceimages/rokka-js-tests?binaryHash=498fed612f01199cd6702f5effe5fa7bb67e44f4", 6 | "body": "", 7 | "status": 200, 8 | "response": { 9 | "total": 1, 10 | "items": [ 11 | { 12 | "hash": "063c3dce6a528d8944a63185bd4c7b161454ee4f", 13 | "short_hash": "063c3d", 14 | "binary_hash": "498fed612f01199cd6702f5effe5fa7bb67e44f4", 15 | "created": "2019-12-10T11:13:24+00:00", 16 | "name": "cartman.svg", 17 | "mimetype": "image/svg+xml", 18 | "format": "svg", 19 | "size": 1203, 20 | "width": 104, 21 | "height": 97, 22 | "organization": "rokka-js-tests", 23 | "link": "/sourceimages/rokka-js-tests/063c3dce6a528d8944a63185bd4c7b161454ee4f", 24 | "user_metadata": { 25 | "foo": "bar" 26 | }, 27 | "opaque": false 28 | } 29 | ] 30 | }, 31 | "responseIsBinary": false 32 | } 33 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/sourceimages_list.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "GET", 5 | "path": "/sourceimages/rokka-js-tests", 6 | "body": "", 7 | "status": 200, 8 | "response": { 9 | "total": 3, 10 | "items": [ 11 | { 12 | "hash": "063c3dce6a528d8944a63185bd4c7b161454ee4f", 13 | "short_hash": "063c3d", 14 | "binary_hash": "498fed612f01199cd6702f5effe5fa7bb67e44f4", 15 | "created": "2019-12-10T11:13:24+00:00", 16 | "name": "cartman.svg", 17 | "mimetype": "image/svg+xml", 18 | "format": "svg", 19 | "size": 1203, 20 | "width": 104, 21 | "height": 97, 22 | "organization": "rokka-js-tests", 23 | "link": "/sourceimages/rokka-js-tests/063c3dce6a528d8944a63185bd4c7b161454ee4f", 24 | "user_metadata": { 25 | "foo": "bar" 26 | }, 27 | "opaque": false 28 | }, 29 | { 30 | "hash": "fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2", 31 | "short_hash": "fe5d9a", 32 | "binary_hash": "63a65da81cae86964e3369b4431b80252f9404b0", 33 | "created": "2019-12-10T10:45:32+00:00", 34 | "name": "schweiz.jpg", 35 | "mimetype": "image/svg+xml", 36 | "format": "svg", 37 | "size": 1202, 38 | "width": 104, 39 | "height": 97, 40 | "organization": "rokka-js-tests", 41 | "link": "/sourceimages/rokka-js-tests/fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2", 42 | "opaque": false 43 | }, 44 | { 45 | "hash": "b32fcb212a2a13220c5ad7f8c71bd6ef5dfd0027", 46 | "short_hash": "b32fcb", 47 | "binary_hash": "1215617542b2926a80396eecf97980359f772aad", 48 | "created": "2019-12-10T10:30:59+00:00", 49 | "name": "schweiz.jpg", 50 | "mimetype": "image/jpeg", 51 | "format": "jpg", 52 | "size": 2190, 53 | "width": 68, 54 | "height": 68, 55 | "organization": "rokka-js-tests", 56 | "link": "/sourceimages/rokka-js-tests/b32fcb212a2a13220c5ad7f8c71bd6ef5dfd0027", 57 | "opaque": true 58 | } 59 | ], 60 | "cursor": "Vc6zPbFoQW9KNDY1WFg5KzRDUHlKd2NtOWtMV2RzYjJKaGJHSXpNbVpqWWpJeE1tRXlZVEV6TWpJd1l6VmhaRGRtT0dNM01XSmtObVZtTldSbVpEQXdNamR5YjJ0cllTMXFjeTEwWlhOMGN3PT0D", 61 | "links": {} 62 | }, 63 | "responseIsBinary": false 64 | } 65 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/sourceimages_list_with_args.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "GET", 5 | "path": "/sourceimages/rokka-js-tests?height=64&limit=2&offset=1&user%3Aint%3Aid=42", 6 | "body": "", 7 | "status": 200, 8 | "response": { 9 | "total": 3, 10 | "items": [ 11 | { 12 | "hash": "fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2", 13 | "short_hash": "fe5d9a", 14 | "binary_hash": "63a65da81cae86964e3369b4431b80252f9404b0", 15 | "created": "2019-12-10T10:45:32+00:00", 16 | "name": "schweiz.jpg", 17 | "mimetype": "image/svg+xml", 18 | "format": "svg", 19 | "size": 1202, 20 | "width": 104, 21 | "height": 97, 22 | "organization": "rokka-js-tests", 23 | "link": "/sourceimages/rokka-js-tests/fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2", 24 | "opaque": false 25 | }, 26 | { 27 | "hash": "b32fcb212a2a13220c5ad7f8c71bd6ef5dfd0027", 28 | "short_hash": "b32fcb", 29 | "binary_hash": "1215617542b2926a80396eecf97980359f772aad", 30 | "created": "2019-12-10T10:30:59+00:00", 31 | "name": "schweiz.jpg", 32 | "mimetype": "image/jpeg", 33 | "format": "jpg", 34 | "size": 2190, 35 | "width": 68, 36 | "height": 68, 37 | "organization": "rokka-js-tests", 38 | "link": "/sourceimages/rokka-js-tests/b32fcb212a2a13220c5ad7f8c71bd6ef5dfd0027", 39 | "opaque": true 40 | } 41 | ], 42 | "links": { 43 | "prev": { 44 | "href": "/sourceimages/rokka-js-tests?limit=2" 45 | } 46 | } 47 | }, 48 | "responseIsBinary": false 49 | } 50 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/sourceimages_metadata_add.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "PATCH", 5 | "path": "/sourceimages/rokka-js-tests/fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2/meta/user", 6 | "body": { 7 | "somefield": "somevalue", 8 | "int:some_number": 0, 9 | "delete_this": null 10 | }, 11 | "status": 204, 12 | "response": "", 13 | "responseIsBinary": false 14 | } 15 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/sourceimages_metadata_delete.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "DELETE", 5 | "path": "/sourceimages/rokka-js-tests/fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2/meta/user", 6 | "body": "", 7 | "status": 204, 8 | "response": "", 9 | "responseIsBinary": false 10 | } 11 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/sourceimages_metadata_remove_subjectarea.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "DELETE", 5 | "path": "/sourceimages/rokka-js-tests/76114710a44fb15fb37ecf66bbad250643373990/meta/dynamic/subject_area?deletePrevious=false", 6 | "body": "", 7 | "status": 201, 8 | "response": { 9 | "hash": "fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2", 10 | "short_hash": "fe5d9a", 11 | "binary_hash": "63a65da81cae86964e3369b4431b80252f9404b0", 12 | "created": "2019-12-10T10:45:32+00:00", 13 | "name": "schweiz.jpg", 14 | "mimetype": "image/svg+xml", 15 | "format": "svg", 16 | "size": 1202, 17 | "width": 104, 18 | "height": 97, 19 | "organization": "rokka-js-tests", 20 | "opaque": false 21 | }, 22 | "responseIsBinary": false 23 | } 24 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/sourceimages_metadata_remove_subjectarea_delete.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "DELETE", 5 | "path": "/sourceimages/rokka-js-tests/e551164763cdbabcd0b75b144f3f08112844a81f/meta/dynamic/subject_area?deletePrevious=true", 6 | "body": "", 7 | "status": 201, 8 | "response": { 9 | "hash": "fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2", 10 | "short_hash": "fe5d9a", 11 | "binary_hash": "63a65da81cae86964e3369b4431b80252f9404b0", 12 | "created": "2019-12-10T10:45:32+00:00", 13 | "name": "schweiz.jpg", 14 | "mimetype": "image/svg+xml", 15 | "format": "svg", 16 | "size": 1202, 17 | "width": 104, 18 | "height": 97, 19 | "organization": "rokka-js-tests", 20 | "opaque": false 21 | }, 22 | "responseIsBinary": false 23 | } 24 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/sourceimages_metadata_replace.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "PATCH", 5 | "path": "/sourceimages/rokka-js-tests/fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2/meta/user", 6 | "body": { 7 | "somefield": "somevalue", 8 | "int:another_number": 23 9 | }, 10 | "status": 204, 11 | "response": "", 12 | "responseIsBinary": false 13 | } 14 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/sourceimages_metadata_set_subjectarea.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "PUT", 5 | "path": "/sourceimages/rokka-js-tests/fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2/meta/dynamic/subject_area?deletePrevious=false", 6 | "body": { 7 | "x": 100, 8 | "y": 100, 9 | "width": 50, 10 | "height": 50 11 | }, 12 | "status": 201, 13 | "response": { 14 | "hash": "e551164763cdbabcd0b75b144f3f08112844a81f", 15 | "short_hash": "e55116", 16 | "binary_hash": "63a65da81cae86964e3369b4431b80252f9404b0", 17 | "created": "2019-12-10T10:45:32+00:00", 18 | "name": "schweiz.jpg", 19 | "mimetype": "image/svg+xml", 20 | "format": "svg", 21 | "size": 1202, 22 | "width": 104, 23 | "height": 97, 24 | "organization": "rokka-js-tests", 25 | "dynamic_metadata": { 26 | "subject_area": { 27 | "width": 50, 28 | "height": 50, 29 | "x": 100, 30 | "y": 100 31 | } 32 | }, 33 | "opaque": false 34 | }, 35 | "responseIsBinary": false 36 | } 37 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/sourceimages_metadata_set_subjectarea_delete.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "PUT", 5 | "path": "/sourceimages/rokka-js-tests/fe5d9a097df0e93cf9570cbdb0386b137b4c9ed2/meta/dynamic/subject_area?deletePrevious=true", 6 | "body": { 7 | "x": 120, 8 | "y": 120, 9 | "width": 40, 10 | "height": 40 11 | }, 12 | "status": 201, 13 | "response": { 14 | "hash": "76114710a44fb15fb37ecf66bbad250643373990", 15 | "short_hash": "761147", 16 | "binary_hash": "63a65da81cae86964e3369b4431b80252f9404b0", 17 | "created": "2019-12-10T10:45:32+00:00", 18 | "name": "schweiz.jpg", 19 | "mimetype": "image/svg+xml", 20 | "format": "svg", 21 | "size": 1202, 22 | "width": 104, 23 | "height": 97, 24 | "organization": "rokka-js-tests", 25 | "dynamic_metadata": { 26 | "subject_area": { 27 | "width": 40, 28 | "height": 40, 29 | "x": 120, 30 | "y": 120 31 | } 32 | }, 33 | "opaque": false 34 | }, 35 | "responseIsBinary": false 36 | } 37 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/sourceimages_restore.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "POST", 5 | "path": "/sourceimages/rokka-js-tests/063c3dce6a528d8944a63185bd4c7b161454ee4f/restore", 6 | "body": "", 7 | "status": 200, 8 | "response": { 9 | "hash": "063c3dce6a528d8944a63185bd4c7b161454ee4f", 10 | "short_hash": "063c3d", 11 | "binary_hash": "498fed612f01199cd6702f5effe5fa7bb67e44f4", 12 | "created": "2019-12-10T11:13:24+00:00", 13 | "name": "cartman.svg", 14 | "mimetype": "image/svg+xml", 15 | "format": "svg", 16 | "size": 1203, 17 | "width": 104, 18 | "height": 97, 19 | "organization": "rokka-js-tests", 20 | "user_metadata": { 21 | "foo": "bar" 22 | }, 23 | "deleted": false, 24 | "opaque": false 25 | }, 26 | "responseIsBinary": false 27 | } 28 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/stackoptions_get.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "GET", 5 | "path": "/stackoptions", 6 | "body": "", 7 | "status": 200, 8 | "response": { 9 | "properties": { 10 | "basestack": { 11 | "type": [ 12 | "string", 13 | "null" 14 | ], 15 | "default": null, 16 | "pattern": "[a-z0-9\\-_]+" 17 | }, 18 | "jpg.quality": { 19 | "type": "integer", 20 | "minimum": 1, 21 | "maximum": 100, 22 | "default": 80 23 | }, 24 | "pngquant.quality": { 25 | "type": "integer", 26 | "minimum": 10, 27 | "maximum": 100, 28 | "default": 98 29 | }, 30 | "webp.quality": { 31 | "type": "integer", 32 | "minimum": 1, 33 | "maximum": 100, 34 | "default": 80 35 | }, 36 | "heif.quality": { 37 | "type": "integer", 38 | "minimum": 1, 39 | "maximum": 100, 40 | "default": 40 41 | }, 42 | "av1.quality": { 43 | "type": "integer", 44 | "minimum": 1, 45 | "maximum": 100, 46 | "default": 50 47 | }, 48 | "webm.quality": { 49 | "type": "integer", 50 | "minimum": 1, 51 | "maximum": 100, 52 | "default": 50 53 | }, 54 | "mp4.quality": { 55 | "type": "integer", 56 | "minimum": 1, 57 | "maximum": 100, 58 | "default": 50 59 | }, 60 | "gif.quality": { 61 | "type": "integer", 62 | "minimum": 1, 63 | "maximum": 100, 64 | "default": 60 65 | }, 66 | "jp2.quality": { 67 | "type": "integer", 68 | "minimum": 1, 69 | "maximum": 100, 70 | "default": 40 71 | }, 72 | "optim.quality": { 73 | "type": "integer", 74 | "minimum": 0, 75 | "maximum": 10, 76 | "default": 4 77 | }, 78 | "png.compression_level": { 79 | "type": "integer", 80 | "minimum": 0, 81 | "maximum": 9, 82 | "default": 7 83 | }, 84 | "dpr": { 85 | "type": "number", 86 | "minimum": 1, 87 | "maximum": 10, 88 | "default": 1 89 | }, 90 | "optim.ttl_shared": { 91 | "type": "boolean", 92 | "default": false 93 | }, 94 | "autoformat": { 95 | "type": "boolean", 96 | "default": false 97 | }, 98 | "autoformat.exclude": { 99 | "type": [ 100 | "string", 101 | "null" 102 | ], 103 | "default": null 104 | }, 105 | "jpg.transparency.convert": { 106 | "type": "boolean", 107 | "default": false 108 | }, 109 | "source_file": { 110 | "type": "boolean", 111 | "default": false 112 | }, 113 | "optim.disable_all": { 114 | "type": "boolean", 115 | "default": false 116 | }, 117 | "optim.immediate": { 118 | "type": "boolean", 119 | "default": false 120 | }, 121 | "remote_fullurl_allow": { 122 | "type": [ 123 | "boolean", 124 | "null" 125 | ], 126 | "default": null 127 | }, 128 | "remote_basepath": { 129 | "type": [ 130 | "string", 131 | "null" 132 | ], 133 | "default": null, 134 | "pattern": "^https?://" 135 | }, 136 | "jpg.transparency.autoformat": { 137 | "type": "string", 138 | "default": "false", 139 | "values": [ 140 | "true", 141 | "false", 142 | "png", 143 | "webp" 144 | ] 145 | }, 146 | "jpg.transparency.color": { 147 | "type": "string", 148 | "default": "FFFFFF" 149 | }, 150 | "content_disposition": { 151 | "type": "string", 152 | "default": "inline", 153 | "values": [ 154 | "inline", 155 | "attachment" 156 | ] 157 | }, 158 | "timing_origin": { 159 | "type": [ 160 | "boolean", 161 | "null" 162 | ], 163 | "default": null 164 | }, 165 | "interlacing.mode": { 166 | "type": "string", 167 | "default": "plane", 168 | "values": [ 169 | "none", 170 | "line", 171 | "plane", 172 | "partition" 173 | ] 174 | }, 175 | "remote_fullurl_whitelist": { 176 | "type": "array", 177 | "items": { 178 | "type": "string" 179 | }, 180 | "default": null 181 | }, 182 | "timestamp": { 183 | "type": [ 184 | "string", 185 | "null" 186 | ], 187 | "default": null 188 | }, 189 | "fps": { 190 | "type": "integer", 191 | "minimum": 1, 192 | "maximum": 25, 193 | "default": 10 194 | } 195 | } 196 | }, 197 | "responseIsBinary": false 198 | } 199 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/stacks_create.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "PUT", 5 | "path": "/stacks/rokka-js-tests/mystack", 6 | "body": { 7 | "operations": [ 8 | { 9 | "name": "rotate", 10 | "options": { 11 | "angle": 45 12 | } 13 | }, 14 | { 15 | "name": "resize", 16 | "options": { 17 | "width": 100, 18 | "height": 100 19 | } 20 | } 21 | ], 22 | "options": { 23 | "jpg.quality": 77 24 | } 25 | }, 26 | "status": 201, 27 | "response": { 28 | "name": "mystack", 29 | "organization": "rokka-js-tests", 30 | "created": "2019-12-10T09:13:53+00:00", 31 | "stack_operations": [ 32 | { 33 | "name": "rotate", 34 | "options": { 35 | "angle": 45 36 | } 37 | }, 38 | { 39 | "name": "resize", 40 | "options": { 41 | "width": 100, 42 | "height": 100 43 | } 44 | } 45 | ], 46 | "stack_options": { 47 | "jpg.quality": 77 48 | }, 49 | "stack_expressions": [], 50 | "stack_variables": {} 51 | }, 52 | "responseIsBinary": false 53 | } 54 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/stacks_create_0_26.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "PUT", 5 | "path": "/stacks/rokka-js-tests/mystack026", 6 | "body": { 7 | "operations": [ 8 | { 9 | "name": "rotate", 10 | "options": { 11 | "angle": 47 12 | } 13 | }, 14 | { 15 | "name": "resize", 16 | "options": { 17 | "width": 120, 18 | "height": 120 19 | } 20 | } 21 | ], 22 | "options": { 23 | "jpg.quality": 76 24 | } 25 | }, 26 | "status": 201, 27 | "response": { 28 | "name": "mystack026", 29 | "organization": "rokka-js-tests", 30 | "created": "2019-12-10T09:04:22+00:00", 31 | "stack_operations": [ 32 | { 33 | "name": "rotate", 34 | "options": { 35 | "angle": 47 36 | } 37 | }, 38 | { 39 | "name": "resize", 40 | "options": { 41 | "width": 120, 42 | "height": 120 43 | } 44 | } 45 | ], 46 | "stack_options": { 47 | "jpg.quality": 76 48 | }, 49 | "stack_expressions": [], 50 | "stack_variables": {} 51 | }, 52 | "responseIsBinary": false 53 | } 54 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/stacks_create_expressions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "PUT", 5 | "path": "/stacks/rokka-js-tests/mystack_expressions", 6 | "body": { 7 | "operations": [ 8 | { 9 | "name": "rotate", 10 | "options": { 11 | "angle": 45 12 | } 13 | }, 14 | { 15 | "name": "resize", 16 | "options": { 17 | "width": 100, 18 | "height": 100 19 | } 20 | } 21 | ], 22 | "options": { 23 | "jpg.quality": 78 24 | }, 25 | "expressions": [ 26 | { 27 | "expression": "options.dpr >= 2", 28 | "overrides": { 29 | "options": { 30 | "jpg.quality": 60, 31 | "webp.quality": 60 32 | } 33 | } 34 | } 35 | ] 36 | }, 37 | "status": 201, 38 | "response": { 39 | "name": "mystack_expressions", 40 | "organization": "rokka-js-tests", 41 | "created": "2019-12-10T09:07:00+00:00", 42 | "stack_operations": [ 43 | { 44 | "name": "rotate", 45 | "options": { 46 | "angle": 45 47 | } 48 | }, 49 | { 50 | "name": "resize", 51 | "options": { 52 | "width": 100, 53 | "height": 100 54 | } 55 | } 56 | ], 57 | "stack_options": { 58 | "jpg.quality": 78 59 | }, 60 | "stack_expressions": [ 61 | { 62 | "expression": "options.dpr >= 2", 63 | "overrides": { 64 | "options": { 65 | "jpg.quality": 60, 66 | "webp.quality": 60 67 | } 68 | } 69 | } 70 | ], 71 | "stack_variables": {} 72 | }, 73 | "responseIsBinary": false 74 | } 75 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/stacks_create_overwrite.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "PUT", 5 | "path": "/stacks/rokka-js-tests/mystack?overwrite=true", 6 | "body": { 7 | "operations": [ 8 | { 9 | "name": "rotate", 10 | "options": { 11 | "angle": 45 12 | } 13 | }, 14 | { 15 | "name": "resize", 16 | "options": { 17 | "width": 100, 18 | "height": 100 19 | } 20 | } 21 | ], 22 | "options": { 23 | "jpg.quality": 76 24 | } 25 | }, 26 | "status": 200, 27 | "response": { 28 | "name": "mystack", 29 | "organization": "rokka-js-tests", 30 | "created": "2019-12-10T09:08:34+00:00", 31 | "stack_operations": [ 32 | { 33 | "name": "rotate", 34 | "options": { 35 | "angle": 45 36 | } 37 | }, 38 | { 39 | "name": "resize", 40 | "options": { 41 | "width": 100, 42 | "height": 100 43 | } 44 | } 45 | ], 46 | "stack_options": { 47 | "jpg.quality": 76 48 | }, 49 | "stack_expressions": [], 50 | "stack_variables": {} 51 | }, 52 | "responseIsBinary": false 53 | } 54 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/stacks_create_overwrite_0_26.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "PUT", 5 | "path": "/stacks/rokka-js-tests/mystack?overwrite=true", 6 | "body": { 7 | "operations": [ 8 | { 9 | "name": "rotate", 10 | "options": { 11 | "angle": 48 12 | } 13 | }, 14 | { 15 | "name": "resize", 16 | "options": { 17 | "width": 120, 18 | "height": 120 19 | } 20 | } 21 | ], 22 | "options": { 23 | "jpg.quality": 77 24 | } 25 | }, 26 | "status": 200, 27 | "response": { 28 | "name": "mystack", 29 | "organization": "rokka-js-tests", 30 | "created": "2019-12-10T09:11:32+00:00", 31 | "stack_operations": [ 32 | { 33 | "name": "rotate", 34 | "options": { 35 | "angle": 48 36 | } 37 | }, 38 | { 39 | "name": "resize", 40 | "options": { 41 | "width": 120, 42 | "height": 120 43 | } 44 | } 45 | ], 46 | "stack_options": { 47 | "jpg.quality": 77 48 | }, 49 | "stack_expressions": [], 50 | "stack_variables": {} 51 | }, 52 | "responseIsBinary": false 53 | } 54 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/stacks_delete.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "DELETE", 5 | "path": "/stacks/rokka-js-tests/mystack", 6 | "body": "", 7 | "status": 204, 8 | "response": "", 9 | "responseIsBinary": false 10 | } 11 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/stacks_get.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "GET", 5 | "path": "/stacks/rokka-js-tests/mystack", 6 | "body": "", 7 | "status": 200, 8 | "response": { 9 | "name": "mystack", 10 | "organization": "rokka-js-tests", 11 | "created": "2019-12-10T09:04:22+00:00", 12 | "stack_operations": [ 13 | { 14 | "name": "rotate", 15 | "options": { 16 | "angle": 45 17 | } 18 | }, 19 | { 20 | "name": "resize", 21 | "options": { 22 | "width": 100, 23 | "height": 100 24 | } 25 | } 26 | ], 27 | "stack_options": { 28 | "jpg.quality": 77 29 | }, 30 | "stack_expressions": [], 31 | "link": "/stacks/rokka-js-tests/mystack", 32 | "stack_variables": {} 33 | }, 34 | "responseIsBinary": false 35 | } 36 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/stacks_list.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "GET", 5 | "path": "/stacks/rokka-js-tests", 6 | "body": "", 7 | "status": 200, 8 | "response": { 9 | "items": [ 10 | { 11 | "name": "mystack", 12 | "organization": "rokka-js-tests", 13 | "created": "2019-12-10T09:08:34+00:00", 14 | "stack_operations": [ 15 | { 16 | "name": "rotate", 17 | "options": { 18 | "angle": 45 19 | } 20 | }, 21 | { 22 | "name": "resize", 23 | "options": { 24 | "width": 100, 25 | "height": 100 26 | } 27 | } 28 | ], 29 | "stack_options": { 30 | "jpg.quality": 75 31 | }, 32 | "stack_expressions": [], 33 | "stack_variables": {} 34 | }, 35 | { 36 | "name": "mystack026", 37 | "organization": "rokka-js-tests", 38 | "created": "2019-12-10T09:04:22+00:00", 39 | "stack_operations": [ 40 | { 41 | "name": "rotate", 42 | "options": { 43 | "angle": 47 44 | } 45 | }, 46 | { 47 | "name": "resize", 48 | "options": { 49 | "width": 120, 50 | "height": 120 51 | } 52 | } 53 | ], 54 | "stack_options": { 55 | "jpg.quality": 76 56 | }, 57 | "stack_expressions": [], 58 | "stack_variables": {} 59 | }, 60 | { 61 | "name": "mystack_expressions", 62 | "organization": "rokka-js-tests", 63 | "created": "2019-12-10T09:07:00+00:00", 64 | "stack_operations": [ 65 | { 66 | "name": "rotate", 67 | "options": { 68 | "angle": 45 69 | } 70 | }, 71 | { 72 | "name": "resize", 73 | "options": { 74 | "width": 100, 75 | "height": 100 76 | } 77 | } 78 | ], 79 | "stack_options": { 80 | "jpg.quality": 78 81 | }, 82 | "stack_expressions": [ 83 | { 84 | "expression": "options.dpr >= 2", 85 | "overrides": { 86 | "options": { 87 | "jpg.quality": 60, 88 | "webp.quality": 60 89 | } 90 | } 91 | } 92 | ], 93 | "stack_variables": {} 94 | } 95 | ] 96 | }, 97 | "responseIsBinary": false 98 | } 99 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/users_create.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "POST", 5 | "path": "/users", 6 | "body": { 7 | "email": "user@example.org", 8 | "organization": "rokka-js-tests-new2" 9 | }, 10 | "status": 200, 11 | "response": { 12 | "id": "c6b56803-e6f7-4798-a3ee-6fd761548d89", 13 | "email": "user@example.org", 14 | "api_key": "ANOTHER_API_KEY" 15 | }, 16 | "responseIsBinary": false 17 | } 18 | ] -------------------------------------------------------------------------------- /tests/fixtures/requests/users_getid.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "scope": "https://api.rokka.io:443", 4 | "method": "GET", 5 | "path": "/user", 6 | "body": "", 7 | "status": 200, 8 | "response": { 9 | "user_id": "9727f161-d236-4e89-8a03-b0da1a68efdc" 10 | }, 11 | "responseIsBinary": false 12 | } 13 | ] -------------------------------------------------------------------------------- /tests/mockServer.ts: -------------------------------------------------------------------------------- 1 | import nock from 'nock' 2 | import { join } from 'path' 3 | import fs from 'fs' 4 | import rka, { Config } from '../src' 5 | import { RokkaApi } from '../src/apis' 6 | 7 | const nockBack = nock.back 8 | nockBack.setMode('record') 9 | nockBack.fixtures = join(__dirname, 'fixtures/requests/') 10 | const responseDir = join(__dirname, 'fixtures/answers/') 11 | // don't save rawHeaders into fixtures. not needed and change too often 12 | const afterRecord = (scopes: object[]) => { 13 | return ( 14 | scopes 15 | // don't store local requests to fixtures 16 | .filter((scope: any) => !scope.scope.match(/127.0.0.1/)) 17 | .map((scope: any) => { 18 | if (scope.rawHeaders) { 19 | delete scope.rawHeaders 20 | } 21 | return scope 22 | }) 23 | // sort by path to have less variability in fixtures 24 | .sort((a, b) => { 25 | return a.path < b.path ? -1 : 1 26 | }) 27 | ) 28 | } 29 | 30 | export const rokka = ({ noAuth = false } = {}): RokkaApi => { 31 | const config: Config = { 32 | transport: { retries: 0 }, 33 | } 34 | if (noAuth !== true) { 35 | config.apiKey = process.env.API_KEY || 'APIKEY' 36 | } 37 | return rka(config) 38 | } 39 | 40 | // set to true, in case you need that for some requests 41 | const recorder = { enable_reqheaders_recording: false } 42 | 43 | interface Options { 44 | mockFile?: string 45 | returnError?: boolean 46 | } 47 | 48 | export const query = async ( 49 | call: () => void, 50 | { mockFile, returnError }: Options = {}, 51 | ): Promise => { 52 | let nockRes = null 53 | if (mockFile) { 54 | // load fixture 55 | nockRes = await nockBack(mockFile, { afterRecord, recorder }) 56 | } 57 | nock.disableNetConnect() 58 | let response 59 | try { 60 | response = await call() 61 | } catch (e) { 62 | if (!returnError) { 63 | throw e 64 | } 65 | response = e 66 | } 67 | if (mockFile && nockRes) { 68 | nockRes.nockDone() 69 | } 70 | nock.cleanAll() 71 | return response 72 | } 73 | 74 | /* eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types */ 75 | export const checkAnswer = (response: any, file: string): any => { 76 | const filePath = join(responseDir, file) 77 | let json 78 | if (fs.existsSync(filePath)) { 79 | json = JSON.parse(fs.readFileSync(join(responseDir, file)).toString()) 80 | // normalize response 81 | response = JSON.parse(JSON.stringify(response)) 82 | } else { 83 | fs.writeFileSync(filePath, JSON.stringify(response, null, 2)) 84 | json = response 85 | } 86 | expect(response).toEqual(json) 87 | return json 88 | } 89 | 90 | export const queryAndCheckAnswer = async ( 91 | call: () => void, 92 | args: Options = {}, 93 | ): Promise => { 94 | return query(call, args).then(queryResponse => { 95 | if (args.mockFile) { 96 | return checkAnswer(queryResponse, args.mockFile) 97 | } else { 98 | throw new Error('No mockFile given, needed for this method') 99 | } 100 | }) 101 | } 102 | -------------------------------------------------------------------------------- /tests/options.test.ts: -------------------------------------------------------------------------------- 1 | import rokka from '../src' 2 | import apis from '../src/apis' 3 | jest.mock('../src/apis') 4 | 5 | describe('options', () => { 6 | it('default options', () => { 7 | const expectedState = { 8 | apiKey: undefined, 9 | apiHost: 'https://api.rokka.io', 10 | renderHost: 'https://{organization}.rokka.io', 11 | apiVersion: 1, 12 | transportOptions: { 13 | factor: 2, 14 | maxTimeout: 10000, 15 | minTimeout: 1000, 16 | randomize: true, 17 | requestTimeout: 30000, 18 | retries: 10, 19 | }, 20 | } 21 | rokka() 22 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 23 | // @ts-ignore 24 | expect(apis.mock.calls[0][0]).toMatchObject(expectedState) 25 | }) 26 | 27 | it('custom options', () => { 28 | const customOptions = { 29 | apiKey: 'APIKEY', 30 | apiHost: 'https://api.example.org', 31 | renderHost: 'https://{organization}.example.org', 32 | apiVersion: 2, 33 | } 34 | 35 | rokka(customOptions) 36 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 37 | // @ts-ignore 38 | expect(apis.mock.calls[1][0]).toMatchObject(customOptions) 39 | }) 40 | 41 | it('custom transport options', () => { 42 | const transportOptions = { 43 | retries: 23, 44 | minTimeout: 2323, 45 | maxTimeout: 232323, 46 | randomize: false, 47 | } 48 | 49 | rokka({ transport: transportOptions }) 50 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 51 | // @ts-ignore 52 | expect(apis.mock.calls[2][0].transportOptions).toMatchObject( 53 | transportOptions, 54 | ) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./built", 4 | "allowJs": true, 5 | "target": "es5", 6 | "allowSyntheticDefaultImports": true, 7 | "declaration": true, 8 | "esModuleInterop": true, 9 | "strict": true 10 | }, 11 | "include": ["./src/**/*"] 12 | } 13 | --------------------------------------------------------------------------------