Added remote storage based on rest server

This commit is contained in:
Michaël Lemaire 2019-11-27 22:51:27 +01:00
parent b0fc58825e
commit e0307d4cbf
12 changed files with 586 additions and 8 deletions

2
.dockerignore Normal file
View file

@ -0,0 +1,2 @@
node_modules
.venv

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

9
Dockerfile Normal file
View file

@ -0,0 +1,9 @@
FROM node
ADD . /app
WORKDIR /app
RUN npm install
EXPOSE 5001
CMD npm run storageserver

View file

@ -42,9 +42,20 @@ const storage = tkStorage.getLocalStorage("myapp");
Use
---
To get a storage locally persistent (saved in browser data or on disk for Node.js):
```javascript
const storage = getLocalStorage("myapp");
await storage.get("key"); // => null
await storage.set("key", "value");
await storage.get("key"); // => "value"
```
To get a storage remotely persistent (saved on a compliant server):
```javascript
const storage = getRemoteStorage("myapp", "https://tk-storage.example.io/", { shared: true });
await storage.get("key"); // => null
await storage.set("key", "value");
await storage.get("key"); // => "value"
```

11
dist/index.html vendored Normal file
View file

@ -0,0 +1,11 @@
<html>
<head>
<meta charset="utf-8">
<script src="tk-storage.umd.js"></script>
</head>
<body>
</body>
</html>

342
package-lock.json generated
View file

@ -1155,12 +1155,61 @@
"@babel/types": "^7.3.0"
}
},
"@types/body-parser": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz",
"integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==",
"dev": true,
"requires": {
"@types/connect": "*",
"@types/node": "*"
}
},
"@types/connect": {
"version": "3.4.32",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz",
"integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/cors": {
"version": "2.8.6",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.6.tgz",
"integrity": "sha512-invOmosX0DqbpA+cE2yoHGUlF/blyf7nB0OGYBBiH27crcVm5NmFaZkLP4Ta1hGaesckCi5lVLlydNJCxkTOSg==",
"dev": true,
"requires": {
"@types/express": "*"
}
},
"@types/estree": {
"version": "0.0.39",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
"dev": true
},
"@types/express": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz",
"integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==",
"dev": true,
"requires": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "*",
"@types/serve-static": "*"
}
},
"@types/express-serve-static-core": {
"version": "4.17.0",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.0.tgz",
"integrity": "sha512-Xnub7w57uvcBqFdIGoRg1KhNOeEj0vB6ykUM7uFWyxvbdE89GFyqgmUcanAriMr4YOxNFZBAWkfcWIb4WBPt3g==",
"dev": true,
"requires": {
"@types/node": "*",
"@types/range-parser": "*"
}
},
"@types/istanbul-lib-coverage": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz",
@ -1201,6 +1250,12 @@
"integrity": "sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA==",
"dev": true
},
"@types/mime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz",
"integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==",
"dev": true
},
"@types/node": {
"version": "12.12.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.6.tgz",
@ -1213,6 +1268,12 @@
"integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==",
"dev": true
},
"@types/range-parser": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
"dev": true
},
"@types/resolve": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz",
@ -1222,6 +1283,16 @@
"@types/node": "*"
}
},
"@types/serve-static": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz",
"integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==",
"dev": true,
"requires": {
"@types/express-serve-static-core": "*",
"@types/mime": "*"
}
},
"@types/stack-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
@ -1401,6 +1472,12 @@
"integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
"dev": true
},
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
"dev": true
},
"array-unique": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
@ -1675,6 +1752,51 @@
"integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
"dev": true
},
"body-parser": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
"dev": true,
"requires": {
"bytes": "3.1.0",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "~1.1.2",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"on-finished": "~2.3.0",
"qs": "6.7.0",
"raw-body": "2.4.0",
"type-is": "~1.6.17"
},
"dependencies": {
"http-errors": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
"dev": true,
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
"dev": true
}
}
},
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@ -1793,6 +1915,12 @@
"integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==",
"dev": true
},
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
"dev": true
},
"cache-base": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
@ -2076,6 +2204,21 @@
"utils-merge": "1.0.1"
}
},
"content-disposition": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
"dev": true,
"requires": {
"safe-buffer": "5.1.2"
}
},
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
"dev": true
},
"convert-source-map": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
@ -2085,6 +2228,18 @@
"safe-buffer": "~5.1.1"
}
},
"cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
"dev": true
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
"dev": true
},
"copy-descriptor": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
@ -2903,6 +3058,52 @@
"jest-regex-util": "^24.9.0"
}
},
"express": {
"version": "4.17.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
"dev": true,
"requires": {
"accepts": "~1.3.7",
"array-flatten": "1.1.1",
"body-parser": "1.19.0",
"content-disposition": "0.5.3",
"content-type": "~1.0.4",
"cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~1.1.2",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.1.2",
"fresh": "0.5.2",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.5",
"qs": "6.7.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.1.2",
"send": "0.17.1",
"serve-static": "1.14.1",
"setprototypeof": "1.1.1",
"statuses": "~1.5.0",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"dependencies": {
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
"dev": true
}
}
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@ -3129,6 +3330,12 @@
"mime-types": "^2.1.12"
}
},
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=",
"dev": true
},
"fragment-cache": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
@ -3736,6 +3943,15 @@
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
"get-port": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/get-port/-/get-port-5.0.0.tgz",
"integrity": "sha512-imzMU0FjsZqNa6BqOjbbW6w5BivHIuQKopjpPqcnx0AVHJQKCxK1O+Ab3OrVXhrekqfVMjwA9ZYu062R+KcIsQ==",
"dev": true,
"requires": {
"type-fest": "^0.3.0"
}
},
"get-stream": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
@ -3844,9 +4060,9 @@
}
},
"handlebars": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz",
"integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==",
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz",
"integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==",
"dev": true,
"requires": {
"neo-async": "^2.6.0",
@ -4112,6 +4328,12 @@
"loose-envify": "^1.0.0"
}
},
"ipaddr.js": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
"integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==",
"dev": true
},
"is-absolute-url": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz",
@ -5102,6 +5324,14 @@
"proxy-middleware": "^0.15.0",
"send": "^0.17.1",
"serve-index": "^1.9.1"
},
"dependencies": {
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true
}
}
},
"load-json-file": {
@ -5332,18 +5562,36 @@
"integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==",
"dev": true
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
"dev": true
},
"memorystream": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
"integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=",
"dev": true
},
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=",
"dev": true
},
"merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
"dev": true
},
"microbundle": {
"version": "0.12.0-next.6",
"resolved": "https://registry.npmjs.org/microbundle/-/microbundle-0.12.0-next.6.tgz",
@ -5974,6 +6222,12 @@
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
"dev": true
},
"path-type": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
@ -6717,6 +6971,16 @@
"sisteransi": "^1.0.3"
}
},
"proxy-addr": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
"integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
"dev": true,
"requires": {
"forwarded": "~0.1.2",
"ipaddr.js": "1.9.0"
}
},
"proxy-middleware": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz",
@ -6763,6 +7027,39 @@
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"dev": true
},
"raw-body": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
"dev": true,
"requires": {
"bytes": "3.1.0",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
},
"dependencies": {
"http-errors": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
"dev": true,
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
}
}
},
"react-is": {
"version": "16.11.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.11.0.tgz",
@ -7431,6 +7728,18 @@
}
}
},
"serve-static": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
"dev": true,
"requires": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.17.1"
}
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@ -8205,6 +8514,22 @@
"prelude-ls": "~1.1.2"
}
},
"type-fest": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz",
"integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==",
"dev": true
},
"type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"dev": true,
"requires": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
}
},
"typescript": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz",
@ -8212,9 +8537,9 @@
"dev": true
},
"uglify-js": {
"version": "3.6.8",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.8.tgz",
"integrity": "sha512-XhHJ3S3ZyMwP8kY1Gkugqx3CJh2C3O0y8NPiSxtm1tyD/pktLAkFZsFGpuNfTZddKDQ/bbDBLAd2YyA1pbi8HQ==",
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.0.tgz",
"integrity": "sha512-PC/ee458NEMITe1OufAjal65i6lB58R1HWMRcxwvdz1UopW0DYqlRL3xdu3IcTvTXsB02CRHykidkTRL+A3hQA==",
"dev": true,
"optional": true,
"requires": {
@ -8569,6 +8894,11 @@
"integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==",
"dev": true
},
"xmlhttprequest": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz",
"integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw="
},
"y18n": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",

View file

@ -13,12 +13,21 @@
"dev": "run-p dev:*",
"dev:test": "jest --watchAll",
"dev:build": "microbundle watch -f modern,umd",
"storageserver": "ts-node src/server.ts",
"prepare": "npm run build",
"prepublishOnly": "npm test",
"dev:serve": "live-server --host=localhost --port=5000 --no-browser --ignorePattern='.*\\.d\\.ts' dist"
},
"dependencies": {
"xmlhttprequest": "^1.8.0"
},
"devDependencies": {
"@types/cors": "^2.8.6",
"@types/express": "^4.17.2",
"@types/uuid": "^3.4.6",
"cors": "^2.8.5",
"express": "^4.17.1",
"get-port": "^5.0.0",
"tk-base": "^0.2.5",
"uuid": "^3.3.3"
},

View file

@ -1,8 +1,9 @@
import { getLocalStorage, getMemoryStorage } from ".";
import { getLocalStorage, getMemoryStorage, getRemoteStorage } from ".";
import { MemoryStorage, ScopedStorage } from "./basic";
import { BrowserLocalStorage } from "./browser";
import { NodeDirectoryStorage } from "./node";
import { forceNodeStoragesInTempDir } from "./node.test";
import { startTestServer } from "./remote.test";
const localStorage = (window as any).localStorage;
@ -42,6 +43,27 @@ describe(getLocalStorage, () => {
});
});
describe(getRemoteStorage, () => {
const server = startTestServer();
it("returns a scoped storage", async () => {
let storage = getRemoteStorage("app1", `http://localhost:${server.port()}`);
await storage.set("testkey", "testvalue");
expect(await storage.get("testkey")).toBe("testvalue");
storage = getRemoteStorage("app1", `http://localhost:${server.port()}`);
expect(await storage.get("testkey")).toBe("testvalue");
storage = getRemoteStorage("app2", `http://localhost:${server.port()}`);
expect(await storage.get("testkey")).toBeNull();
});
it("scopes in a private namespace on shared mode", async () => {
});
});
describe(getMemoryStorage, () => {
it("returns a memory storage", () => {
const storage = getMemoryStorage();

View file

@ -1,12 +1,18 @@
import { KeyValueStorage, MemoryStorage, ScopedStorage } from "./basic";
import { KeyValueStorage, MemoryStorage, RefScopedStorage, ScopedStorage } from "./basic";
import { BrowserLocalStorage } from "./browser";
import { NodeDirectoryStorage } from "./node";
import { RestRemoteStorage } from "./remote";
/**
* Base type for storage usage
*/
export type TKStorage = KeyValueStorage
/**
* Exposed classes
*/
export { MemoryStorage, ScopedStorage, RefScopedStorage, BrowserLocalStorage, NodeDirectoryStorage, RestRemoteStorage };
/**
* Get the best "local" storage available
*/
@ -23,6 +29,20 @@ export function getLocalStorage(appname: string): TKStorage {
}
}
/**
* Get a remote storage, based on an URI
*
* If *shared* is set to true, a namespace will be used to avoid collisions, using getLocalStorage to persist it
*/
export function getRemoteStorage(appname: string, uri: string, options = { shared: false }): TKStorage {
let storage: TKStorage = new RestRemoteStorage(appname, uri);
storage = new ScopedStorage(storage, appname);
if (options.shared) {
storage = new RefScopedStorage(getLocalStorage(appname), storage);
}
return storage;
}
/**
* Get a in-memory volatile storage
*/

33
src/remote.test.ts Normal file
View file

@ -0,0 +1,33 @@
import getPort from "get-port";
import { MemoryStorage } from "./basic";
import { basicCheck } from "./basic.test";
import { RestRemoteStorage } from "./remote";
import { startRestServer } from "./server";
export function startTestServer(): { port: () => number } {
let app: { close: Function } | undefined;
let port: number;
beforeEach(async () => {
port = await getPort();
app = await startRestServer(port, new MemoryStorage());
});
afterEach(async () => {
if (app) {
await app.close();
app = undefined;
}
});
return { port: () => port };
}
describe(RestRemoteStorage, () => {
const server = startTestServer();
it("communicates with the server", async () => {
const storage = new RestRemoteStorage("test", `http://localhost:${server.port()}`);
await basicCheck(storage);
});
});

64
src/remote.ts Normal file
View file

@ -0,0 +1,64 @@
import { KeyValueStorage } from "./basic";
function protectKey(key: string): string {
return encodeURIComponent(key);
}
type RestClient = (method: "GET" | "POST" | "PUT" | "DELETE", uri: string, body?: string) => Promise<string | null>
export const HEADER_REQUESTER = "X-TK-Storage-App"
export const HEADER_REPLYIER = "X-TK-Storage-Control"
/**
* Storage on a remote server, directly usable using HTTP REST verbs
*/
export class RestRemoteStorage implements KeyValueStorage {
readonly appname: string
readonly base_url: string
readonly client: RestClient
constructor(appname: string, url: string) {
this.appname = appname;
this.base_url = (url[url.length - 1] != "/") ? url + "/" : url;
this.client = this.createClient();
}
async get(key: string): Promise<string | null> {
key = protectKey(key);
return await this.client("GET", key);
}
async set(key: string, value: string | null): Promise<void> {
key = protectKey(key);
if (value === null) {
await this.client("DELETE", key);
} else {
await this.client("PUT", key, value);
}
}
private createClient(): RestClient {
const Request: typeof XMLHttpRequest = (typeof XMLHttpRequest === "undefined") ? require("xmlhttprequest").XMLHttpRequest : XMLHttpRequest;
const client: RestClient = (method, path, body?) => new Promise((resolve, reject) => {
const xhr = new Request();
xhr.open(method, `${this.base_url}${path}`);
xhr.responseType = 'text';
xhr.setRequestHeader(HEADER_REQUESTER, this.appname);
xhr.onload = function () {
if (xhr.getResponseHeader(HEADER_REPLYIER) != "ok") {
reject("storage not compatible with tk-storage");
} else if (xhr.status == 200) {
resolve(xhr.responseText);
} else if (xhr.status == 404) {
resolve(null);
} else {
reject(`http error ${xhr.status}: ${xhr.statusText}`);
}
};
xhr.onerror = function () {
reject("unknown error");
};
xhr.send(body);
});
return client;
}
}

64
src/server.ts Normal file
View file

@ -0,0 +1,64 @@
import bodyParser from "body-parser";
import cors from "cors";
import express from "express";
import { getLocalStorage } from ".";
import { KeyValueStorage } from "./basic";
import { HEADER_REPLYIER, HEADER_REQUESTER } from "./remote";
type Server = {
close: () => Promise<void>
}
/**
* Start a server compliant with RestRemoteStorage
*/
export function startRestServer(port: number, storage: KeyValueStorage): Promise<Server> {
const app = express();
app.use(
cors({ exposedHeaders: [HEADER_REPLYIER] }),
bodyParser.text(),
(req, res, next) => {
if (req.header(HEADER_REQUESTER)) {
res.set(HEADER_REPLYIER, "ok");
next();
} else {
res.status(400).send();
}
},
);
app.get("*", (req, res, next) => {
storage.get(req.path).then(result => {
if (result === null) {
res.status(404).send();
} else {
res.send(result);
}
}).catch(next);
});
app.put("*", (req, res, next) => {
storage.set(req.path, req.body).then(() => {
res.send();
}).catch(next);
});
app.delete("*", (req, res, next) => {
storage.set(req.path, null).then(() => {
res.send();
}).catch(next);
});
return new Promise((resolve) => {
const server = app.listen(port, () => {
resolve({
close: () => new Promise((resolve, reject) => {
server.close((err) => err ? reject(err) : resolve());
})
})
});
});
}
if (typeof require !== "undefined" && require.main === module) {
startRestServer(5001, getLocalStorage("tk-storage-server")).then(() => {
console.log(`Server running on port 5001`);
});
}