@webority-technologies/mobile-core 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -1
- package/lib/commonjs/api/baseUrl.js +34 -0
- package/lib/commonjs/api/curl.js +109 -0
- package/lib/commonjs/api/publicClient.js +11 -20
- package/lib/commonjs/api/retry.js +75 -0
- package/lib/commonjs/api/userClient.js +46 -22
- package/lib/commonjs/auth/authStore.js +101 -33
- package/lib/commonjs/auth/jwt.js +32 -8
- package/lib/commonjs/config/index.js +2 -1
- package/lib/commonjs/deepLink/index.js +286 -0
- package/lib/commonjs/download/adapters/expoFs.js +100 -0
- package/lib/commonjs/download/adapters/shared.js +13 -0
- package/lib/commonjs/download/index.js +272 -0
- package/lib/commonjs/formatters/date.js +1 -1
- package/lib/commonjs/formatters/index.js +6 -0
- package/lib/commonjs/formatters/phone.js +103 -15
- package/lib/commonjs/geolocation/adapters/expoLocation.js +126 -0
- package/lib/commonjs/geolocation/index.js +339 -0
- package/lib/commonjs/index.js +133 -48
- package/lib/commonjs/initSDK.js +23 -2
- package/lib/commonjs/initUserAuth.js +2 -1
- package/lib/commonjs/logger/index.js +159 -5
- package/lib/commonjs/network/index.js +6 -0
- package/lib/commonjs/network/networkStatus.js +50 -15
- package/lib/commonjs/permissions/index.js +24 -1
- package/lib/commonjs/sqlite/adapters/expo.js +53 -0
- package/lib/commonjs/sqlite/adapters/op.js +43 -0
- package/lib/commonjs/sqlite/adapters/rows.js +33 -0
- package/lib/commonjs/sqlite/adapters/storage.js +50 -0
- package/lib/commonjs/sqlite/adapters/sync.js +45 -0
- package/lib/commonjs/sqlite/index.js +610 -0
- package/lib/commonjs/sqlite/nitro.js +17 -0
- package/lib/commonjs/sqlite/op.js +18 -0
- package/lib/commonjs/sqlite/quick.js +14 -0
- package/lib/commonjs/sqlite/storage.js +14 -0
- package/lib/commonjs/storage/index.js +40 -19
- package/lib/commonjs/types/globals.d.js +6 -0
- package/lib/commonjs/utils/imageCompression.js +51 -14
- package/lib/commonjs/utils/index.js +6 -0
- package/lib/commonjs/versionCheck/index.js +28 -4
- package/lib/module/api/baseUrl.js +30 -0
- package/lib/module/api/curl.js +104 -0
- package/lib/module/api/publicClient.js +11 -20
- package/lib/module/api/retry.js +69 -0
- package/lib/module/api/userClient.js +45 -21
- package/lib/module/auth/authStore.js +99 -32
- package/lib/module/auth/jwt.js +32 -8
- package/lib/module/config/index.js +2 -1
- package/lib/module/deepLink/index.js +280 -0
- package/lib/module/download/adapters/expoFs.js +95 -0
- package/lib/module/download/adapters/shared.js +8 -0
- package/lib/module/download/index.js +264 -0
- package/lib/module/formatters/date.js +1 -1
- package/lib/module/formatters/index.js +1 -1
- package/lib/module/formatters/phone.js +99 -13
- package/lib/module/geolocation/adapters/expoLocation.js +121 -0
- package/lib/module/geolocation/index.js +332 -0
- package/lib/module/index.js +28 -11
- package/lib/module/initSDK.js +23 -2
- package/lib/module/initUserAuth.js +2 -1
- package/lib/module/logger/index.js +156 -4
- package/lib/module/network/index.js +1 -1
- package/lib/module/network/networkStatus.js +47 -14
- package/lib/module/permissions/index.js +22 -0
- package/lib/module/sqlite/adapters/expo.js +48 -0
- package/lib/module/sqlite/adapters/op.js +38 -0
- package/lib/module/sqlite/adapters/rows.js +28 -0
- package/lib/module/sqlite/adapters/storage.js +46 -0
- package/lib/module/sqlite/adapters/sync.js +41 -0
- package/lib/module/sqlite/index.js +600 -0
- package/lib/module/sqlite/nitro.js +12 -0
- package/lib/module/sqlite/op.js +13 -0
- package/lib/module/sqlite/quick.js +9 -0
- package/lib/module/sqlite/storage.js +9 -0
- package/lib/module/storage/index.js +37 -18
- package/lib/module/types/globals.d.js +10 -0
- package/lib/module/utils/imageCompression.js +49 -13
- package/lib/module/utils/index.js +1 -1
- package/lib/module/versionCheck/index.js +27 -4
- package/lib/typescript/commonjs/api/baseUrl.d.ts +20 -0
- package/lib/typescript/commonjs/api/curl.d.ts +18 -0
- package/lib/typescript/commonjs/api/retry.d.ts +27 -0
- package/lib/typescript/commonjs/api/userClient.d.ts +11 -0
- package/lib/typescript/commonjs/auth/authStore.d.ts +29 -6
- package/lib/typescript/commonjs/config/index.d.ts +6 -0
- package/lib/typescript/commonjs/deepLink/index.d.ts +54 -0
- package/lib/typescript/commonjs/download/adapters/expoFs.d.ts +45 -0
- package/lib/typescript/commonjs/download/adapters/shared.d.ts +3 -0
- package/lib/typescript/commonjs/download/index.d.ts +76 -0
- package/lib/typescript/commonjs/formatters/index.d.ts +2 -1
- package/lib/typescript/commonjs/formatters/phone.d.ts +16 -1
- package/lib/typescript/commonjs/geolocation/adapters/expoLocation.d.ts +42 -0
- package/lib/typescript/commonjs/geolocation/index.d.ts +89 -0
- package/lib/typescript/commonjs/index.d.ts +30 -13
- package/lib/typescript/commonjs/initSDK.d.ts +32 -0
- package/lib/typescript/commonjs/logger/index.d.ts +39 -0
- package/lib/typescript/commonjs/network/index.d.ts +2 -2
- package/lib/typescript/commonjs/network/networkStatus.d.ts +16 -0
- package/lib/typescript/commonjs/permissions/index.d.ts +35 -0
- package/lib/typescript/commonjs/sqlite/adapters/expo.d.ts +30 -0
- package/lib/typescript/commonjs/sqlite/adapters/op.d.ts +27 -0
- package/lib/typescript/commonjs/sqlite/adapters/rows.d.ts +7 -0
- package/lib/typescript/commonjs/sqlite/adapters/storage.d.ts +30 -0
- package/lib/typescript/commonjs/sqlite/adapters/sync.d.ts +31 -0
- package/lib/typescript/commonjs/sqlite/index.d.ts +121 -0
- package/lib/typescript/commonjs/sqlite/nitro.d.ts +7 -0
- package/lib/typescript/commonjs/sqlite/op.d.ts +8 -0
- package/lib/typescript/commonjs/sqlite/quick.d.ts +4 -0
- package/lib/typescript/commonjs/sqlite/storage.d.ts +4 -0
- package/lib/typescript/commonjs/storage/index.d.ts +24 -2
- package/lib/typescript/commonjs/utils/imageCompression.d.ts +17 -0
- package/lib/typescript/commonjs/utils/index.d.ts +2 -2
- package/lib/typescript/module/api/baseUrl.d.ts +20 -0
- package/lib/typescript/module/api/curl.d.ts +18 -0
- package/lib/typescript/module/api/retry.d.ts +27 -0
- package/lib/typescript/module/api/userClient.d.ts +11 -0
- package/lib/typescript/module/auth/authStore.d.ts +29 -6
- package/lib/typescript/module/config/index.d.ts +6 -0
- package/lib/typescript/module/deepLink/index.d.ts +54 -0
- package/lib/typescript/module/download/adapters/expoFs.d.ts +45 -0
- package/lib/typescript/module/download/adapters/shared.d.ts +3 -0
- package/lib/typescript/module/download/index.d.ts +76 -0
- package/lib/typescript/module/formatters/index.d.ts +2 -1
- package/lib/typescript/module/formatters/phone.d.ts +16 -1
- package/lib/typescript/module/geolocation/adapters/expoLocation.d.ts +42 -0
- package/lib/typescript/module/geolocation/index.d.ts +89 -0
- package/lib/typescript/module/index.d.ts +30 -13
- package/lib/typescript/module/initSDK.d.ts +32 -0
- package/lib/typescript/module/logger/index.d.ts +39 -0
- package/lib/typescript/module/network/index.d.ts +2 -2
- package/lib/typescript/module/network/networkStatus.d.ts +16 -0
- package/lib/typescript/module/permissions/index.d.ts +35 -0
- package/lib/typescript/module/sqlite/adapters/expo.d.ts +30 -0
- package/lib/typescript/module/sqlite/adapters/op.d.ts +27 -0
- package/lib/typescript/module/sqlite/adapters/rows.d.ts +7 -0
- package/lib/typescript/module/sqlite/adapters/storage.d.ts +30 -0
- package/lib/typescript/module/sqlite/adapters/sync.d.ts +31 -0
- package/lib/typescript/module/sqlite/index.d.ts +121 -0
- package/lib/typescript/module/sqlite/nitro.d.ts +7 -0
- package/lib/typescript/module/sqlite/op.d.ts +8 -0
- package/lib/typescript/module/sqlite/quick.d.ts +4 -0
- package/lib/typescript/module/sqlite/storage.d.ts +4 -0
- package/lib/typescript/module/storage/index.d.ts +24 -2
- package/lib/typescript/module/utils/imageCompression.d.ts +17 -0
- package/lib/typescript/module/utils/index.d.ts +2 -2
- package/package.json +102 -14
|
@@ -3,32 +3,41 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.setStorageImplementation = exports.Storage = void 0;
|
|
6
|
+
exports.setStorageImplementation = exports.isStorageAvailable = exports.Storage = void 0;
|
|
7
|
+
var _kvStore = _interopRequireDefault(require("expo-sqlite/kv-store"));
|
|
7
8
|
var _index = require("../logger/index.js");
|
|
9
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
10
|
/**
|
|
9
11
|
* Minimal AsyncStorage shape we depend on.
|
|
10
12
|
* Lets us avoid a hard import on `@react-native-async-storage/async-storage`.
|
|
11
13
|
*/
|
|
12
14
|
|
|
15
|
+
/** Only what a test or the showcase injected; null means use expo-sqlite/kv-store. */
|
|
13
16
|
let backend = null;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* expo-sqlite's key/value store, imported directly.
|
|
20
|
+
*
|
|
21
|
+
* It deliberately implements the AsyncStorage v1 surface — `getItem`,
|
|
22
|
+
* `setItem`, `removeItem`, `multiGet`, `multiSet`, `multiRemove`, `clear`,
|
|
23
|
+
* `getAllKeys`, with v1's shapes — which is exactly what this module already
|
|
24
|
+
* speaks, so there is nothing to convert. Verified against expo-sqlite 57.0.2.
|
|
25
|
+
*/
|
|
26
|
+
const resolveBackend = () => backend ?? _kvStore.default;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Always true now that the backend ships with the library. Kept because callers
|
|
30
|
+
* whose use of storage is an OPTIMISATION branch on it, and removing it would
|
|
31
|
+
* be a breaking change for no gain.
|
|
32
|
+
*/
|
|
33
|
+
const isStorageAvailable = () => resolveBackend() !== null;
|
|
34
|
+
exports.isStorageAvailable = isStorageAvailable;
|
|
28
35
|
const requireBackend = op => {
|
|
29
36
|
const b = resolveBackend();
|
|
30
37
|
if (!b) {
|
|
31
|
-
|
|
38
|
+
// Only reachable if a caller injected something falsy; the default backend
|
|
39
|
+
// ships with the library, so there is no "not installed" case any more.
|
|
40
|
+
const err = new Error(`[Storage] no storage backend available; cannot ${op}.`);
|
|
32
41
|
_index.Logger.error(err.message);
|
|
33
42
|
throw err;
|
|
34
43
|
}
|
|
@@ -36,12 +45,11 @@ const requireBackend = op => {
|
|
|
36
45
|
};
|
|
37
46
|
|
|
38
47
|
/**
|
|
39
|
-
*
|
|
40
|
-
* Pass `null` to
|
|
48
|
+
* Replace the storage backend. Intended for TESTS and for the showcase; an app
|
|
49
|
+
* gets expo-sqlite/kv-store without wiring anything. Pass `null` to restore it.
|
|
41
50
|
*/
|
|
42
51
|
const setStorageImplementation = impl => {
|
|
43
52
|
backend = impl;
|
|
44
|
-
resolved = true;
|
|
45
53
|
};
|
|
46
54
|
exports.setStorageImplementation = setStorageImplementation;
|
|
47
55
|
const safeJsonParse = raw => {
|
|
@@ -96,6 +104,10 @@ const setJson = async (key, value) => {
|
|
|
96
104
|
const multiGet = async keys => {
|
|
97
105
|
try {
|
|
98
106
|
const b = requireBackend('multiGet');
|
|
107
|
+
// v3 first: its Record is already this function's return shape.
|
|
108
|
+
if (b.getMany) {
|
|
109
|
+
return await b.getMany([...keys]);
|
|
110
|
+
}
|
|
99
111
|
if (!b.multiGet) {
|
|
100
112
|
const out = {};
|
|
101
113
|
await Promise.all(keys.map(async k => {
|
|
@@ -117,6 +129,11 @@ const multiGet = async keys => {
|
|
|
117
129
|
const multiSet = async entries => {
|
|
118
130
|
try {
|
|
119
131
|
const b = requireBackend('multiSet');
|
|
132
|
+
// v3 first: it takes the Record this function already receives.
|
|
133
|
+
if (b.setMany) {
|
|
134
|
+
await b.setMany(entries);
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
120
137
|
const pairs = Object.entries(entries);
|
|
121
138
|
if (!b.multiSet) {
|
|
122
139
|
await Promise.all(pairs.map(([k, v]) => b.setItem(k, v)));
|
|
@@ -132,6 +149,10 @@ const multiSet = async entries => {
|
|
|
132
149
|
const multiRemove = async keys => {
|
|
133
150
|
try {
|
|
134
151
|
const b = requireBackend('multiRemove');
|
|
152
|
+
if (b.removeMany) {
|
|
153
|
+
await b.removeMany([...keys]);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
135
156
|
if (!b.multiRemove) {
|
|
136
157
|
await Promise.all(keys.map(k => b.removeItem(k)));
|
|
137
158
|
return true;
|
|
@@ -3,23 +3,60 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.qualityForFileSize = exports.compressImage = void 0;
|
|
6
|
+
exports.setImageCompressorImplementation = exports.qualityForFileSize = exports.compressImage = void 0;
|
|
7
|
+
var _expoImageManipulator = require("expo-image-manipulator");
|
|
7
8
|
var _index = require("../logger/index.js");
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
/** expo's SaveFormat values, inlined to keep this file free of enum imports. */
|
|
10
|
+
const EXPO_FORMAT = {
|
|
11
|
+
jpg: 'jpeg',
|
|
12
|
+
png: 'png',
|
|
13
|
+
webp: 'webp'
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* expo-image-manipulator, adapted to the compressor shape this module speaks.
|
|
18
|
+
*
|
|
19
|
+
* Written against the CONTEXTUAL api rather than `manipulateAsync`, which 57
|
|
20
|
+
* marks deprecated. Only ONE edge is constrained: expo treats a given width and
|
|
21
|
+
* height as targets and would distort the image, where maxWidth/maxHeight here
|
|
22
|
+
* mean a box to fit inside. The smaller bound is used so the result fits
|
|
23
|
+
* whichever way round the image is.
|
|
24
|
+
*/
|
|
25
|
+
const expoCompressorBackend = {
|
|
26
|
+
Image: {
|
|
27
|
+
compress: async (uri, options) => {
|
|
28
|
+
const context = _expoImageManipulator.ImageManipulator.manipulate(uri);
|
|
29
|
+
const {
|
|
30
|
+
maxWidth,
|
|
31
|
+
maxHeight
|
|
32
|
+
} = options;
|
|
33
|
+
if (typeof maxWidth === 'number' || typeof maxHeight === 'number') {
|
|
34
|
+
context.resize({
|
|
35
|
+
width: Math.min(maxWidth ?? Number.POSITIVE_INFINITY, maxHeight ?? Number.POSITIVE_INFINITY)
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const image = await context.renderAsync();
|
|
39
|
+
const result = await image.saveAsync({
|
|
40
|
+
compress: options.quality,
|
|
41
|
+
format: EXPO_FORMAT[options.output ?? 'jpg'] ?? 'jpeg'
|
|
42
|
+
});
|
|
43
|
+
return result.uri;
|
|
44
|
+
}
|
|
20
45
|
}
|
|
21
|
-
return compressor;
|
|
22
46
|
};
|
|
47
|
+
|
|
48
|
+
/** Only what a test or the showcase injected; null means use expo-image-manipulator. */
|
|
49
|
+
let compressor = null;
|
|
50
|
+
const resolveCompressor = () => compressor ?? expoCompressorBackend;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Replace the compressor. Intended for TESTS and for the showcase; an app gets
|
|
54
|
+
* expo-image-manipulator without wiring anything. Pass `null` to restore it.
|
|
55
|
+
*/
|
|
56
|
+
const setImageCompressorImplementation = impl => {
|
|
57
|
+
compressor = impl;
|
|
58
|
+
};
|
|
59
|
+
exports.setImageCompressorImplementation = setImageCompressorImplementation;
|
|
23
60
|
const EIGHT_MB = 8 * 1024 * 1024;
|
|
24
61
|
const THREE_MB = 3 * 1024 * 1024;
|
|
25
62
|
|
|
@@ -9,5 +9,11 @@ Object.defineProperty(exports, "compressImage", {
|
|
|
9
9
|
return _imageCompression.compressImage;
|
|
10
10
|
}
|
|
11
11
|
});
|
|
12
|
+
Object.defineProperty(exports, "setImageCompressorImplementation", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _imageCompression.setImageCompressorImplementation;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
12
18
|
var _imageCompression = require("./imageCompression.js");
|
|
13
19
|
//# sourceMappingURL=index.js.map
|
|
@@ -4,9 +4,11 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.useVersionCheck = exports.setDeviceInfoImplementation = exports.default = exports.VersionCheck = void 0;
|
|
7
|
+
var Application = _interopRequireWildcard(require("expo-application"));
|
|
7
8
|
var _react = require("react");
|
|
8
9
|
var _index = require("../config/index.js");
|
|
9
10
|
var _index2 = require("../logger/index.js");
|
|
11
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
10
12
|
// Type-only — erased at compile time; react-native stays lazily required below.
|
|
11
13
|
|
|
12
14
|
/**
|
|
@@ -20,6 +22,26 @@ var _index2 = require("../logger/index.js");
|
|
|
20
22
|
* wire two config keys.
|
|
21
23
|
*/
|
|
22
24
|
|
|
25
|
+
/**
|
|
26
|
+
* expo-application, read at call time.
|
|
27
|
+
*
|
|
28
|
+
* `applicationId` and `nativeApplicationVersion` are module CONSTANTS rather
|
|
29
|
+
* than functions, so they are read inside the accessors rather than captured
|
|
30
|
+
* here — capturing would snapshot them before the native module has
|
|
31
|
+
* initialised.
|
|
32
|
+
*
|
|
33
|
+
* `setConfig({ appId, appVersion })` still WINS over this: a Webority app
|
|
34
|
+
* already carries both in appsettings.Compiled.json, and config is checked
|
|
35
|
+
* first. This is the fallback for an app that would rather read its identity
|
|
36
|
+
* from the binary it is actually running, which is the one place the two can
|
|
37
|
+
* disagree.
|
|
38
|
+
*/
|
|
39
|
+
const expoApplicationBackend = {
|
|
40
|
+
getBundleId: () => Application.applicationId ?? '',
|
|
41
|
+
getVersion: () => Application.nativeApplicationVersion ?? ''
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** Only what a test or the showcase injected; null means use expo-application. */
|
|
23
45
|
let deviceInfo = null;
|
|
24
46
|
|
|
25
47
|
/**
|
|
@@ -50,8 +72,9 @@ const getBundleId = () => {
|
|
|
50
72
|
if (fromConfig) {
|
|
51
73
|
return fromConfig;
|
|
52
74
|
}
|
|
53
|
-
|
|
54
|
-
|
|
75
|
+
const fromDevice = (deviceInfo ?? expoApplicationBackend).getBundleId();
|
|
76
|
+
if (fromDevice) {
|
|
77
|
+
return fromDevice;
|
|
55
78
|
}
|
|
56
79
|
throw missing('store identifier', 'appId');
|
|
57
80
|
};
|
|
@@ -60,8 +83,9 @@ const getVersion = () => {
|
|
|
60
83
|
if (fromConfig) {
|
|
61
84
|
return fromConfig;
|
|
62
85
|
}
|
|
63
|
-
|
|
64
|
-
|
|
86
|
+
const fromDevice = (deviceInfo ?? expoApplicationBackend).getVersion();
|
|
87
|
+
if (fromDevice) {
|
|
88
|
+
return fromDevice;
|
|
65
89
|
}
|
|
66
90
|
throw missing('version', 'appVersion');
|
|
67
91
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { getConfigValue } from "../config/index.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolves `baseURL` PER REQUEST rather than at module evaluation.
|
|
7
|
+
*
|
|
8
|
+
* `axios.create({ baseURL: getConfigValue('baseUrl') })` reads the value once,
|
|
9
|
+
* when the module is first imported — which happens the moment anything touches
|
|
10
|
+
* the package barrel, long before an app's startup code can call `setConfig`.
|
|
11
|
+
* The result was a client permanently pinned to `undefined` and a `setConfig`
|
|
12
|
+
* that appeared to do nothing, with no error anywhere: requests just went to a
|
|
13
|
+
* relative URL.
|
|
14
|
+
*
|
|
15
|
+
* A consumer cannot fix that by ordering its own imports, because the import
|
|
16
|
+
* that loses the race is usually one it never wrote. So the clients stop
|
|
17
|
+
* capturing the value and ask for it per request instead.
|
|
18
|
+
*
|
|
19
|
+
* An explicit per-request `baseURL` still wins, which is what keeps a one-off
|
|
20
|
+
* call to a different host possible.
|
|
21
|
+
*/
|
|
22
|
+
export const applyConfiguredBaseUrl = client => {
|
|
23
|
+
client.interceptors.request.use(config => {
|
|
24
|
+
if (!config.baseURL) {
|
|
25
|
+
config.baseURL = getConfigValue('baseUrl') ?? undefined;
|
|
26
|
+
}
|
|
27
|
+
return config;
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=baseUrl.js.map
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { Logger } from "../logger/index.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Header names whose VALUE is a credential. Matched case-insensitively against
|
|
7
|
+
* the whole name, since `Authorization` and `authorization` are the same header
|
|
8
|
+
* and axios does not normalise the case of headers a caller sets by hand.
|
|
9
|
+
*/
|
|
10
|
+
const SECRET_HEADERS = new Set(['authorization', 'proxy-authorization', 'cookie', 'set-cookie', 'x-api-key', 'x-auth-token', 'x-access-token', 'x-refresh-token', 'api-key']);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Body fields whose value is a credential. Matched on the key alone, so this
|
|
14
|
+
* catches `password`, `newPassword` and `password_confirmation` alike.
|
|
15
|
+
*/
|
|
16
|
+
const SECRET_BODY_PATTERN = /pass(word|code)|secret|token|otp|pin|cvv|authoriz/i;
|
|
17
|
+
|
|
18
|
+
/** The axios per-method header buckets, which are config plumbing, not headers. */
|
|
19
|
+
const AXIOS_METHOD_KEYS = new Set(['common', 'delete', 'get', 'head', 'post', 'put', 'patch']);
|
|
20
|
+
const REDACTED = '<redacted>';
|
|
21
|
+
const redactBody = (value, seen, depth = 0) => {
|
|
22
|
+
if (value === null || typeof value !== 'object') {
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
// Both guards are needed and they catch different shapes. `seen` breaks a
|
|
26
|
+
// true cycle, which would otherwise recurse forever AND make the eventual
|
|
27
|
+
// JSON.stringify throw. The depth bound catches a body that is merely
|
|
28
|
+
// enormous. A curl line is a debugging aid, and it must never be able to
|
|
29
|
+
// hang or fail the request it is describing.
|
|
30
|
+
if (seen.has(value)) {
|
|
31
|
+
return '<circular>';
|
|
32
|
+
}
|
|
33
|
+
if (depth > 6) {
|
|
34
|
+
return '<truncated>';
|
|
35
|
+
}
|
|
36
|
+
seen.add(value);
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
return value.map(entry => redactBody(entry, seen, depth + 1));
|
|
39
|
+
}
|
|
40
|
+
const out = {};
|
|
41
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
42
|
+
out[key] = SECRET_BODY_PATTERN.test(key) ? REDACTED : redactBody(entry, seen, depth + 1);
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
};
|
|
46
|
+
const buildUrl = config => {
|
|
47
|
+
const url = config.url ?? '';
|
|
48
|
+
const base = config.baseURL ? config.baseURL.replace(/\/$/, '') : '';
|
|
49
|
+
let complete = url.startsWith('http') ? url : `${base}/${url.replace(/^\//, '')}`;
|
|
50
|
+
if (config.params) {
|
|
51
|
+
const query = new URLSearchParams(config.params).toString();
|
|
52
|
+
if (query) {
|
|
53
|
+
complete += (complete.includes('?') ? '&' : '?') + query;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return complete;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Builds a copy-pasteable curl command for a request, with every credential
|
|
61
|
+
* replaced by a placeholder.
|
|
62
|
+
*
|
|
63
|
+
* The redaction is the point, not a nicety. An unredacted version of this in a
|
|
64
|
+
* consumer app put `Authorization: Bearer <token>` and the login request's
|
|
65
|
+
* plaintext password into the device console of a *release* staging build.
|
|
66
|
+
* A curl line is worth having; a curl line that has to be trusted not to be
|
|
67
|
+
* enabled in the wrong build is not.
|
|
68
|
+
*/
|
|
69
|
+
export const buildCurl = config => {
|
|
70
|
+
const method = (config.method ?? 'get').toUpperCase();
|
|
71
|
+
let command = `curl -X ${method} "${buildUrl(config)}"`;
|
|
72
|
+
const headers = config.headers;
|
|
73
|
+
if (headers) {
|
|
74
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
75
|
+
if (AXIOS_METHOD_KEYS.has(key)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (typeof value !== 'string' && typeof value !== 'number') {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const shown = SECRET_HEADERS.has(key.toLowerCase()) ? REDACTED : value;
|
|
82
|
+
command += ` -H "${key}: ${shown}"`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (config.data !== undefined && config.data !== null) {
|
|
86
|
+
const redacted = typeof config.data === 'object' ? redactBody(config.data, new WeakSet()) : config.data;
|
|
87
|
+
const body = typeof redacted === 'object' ? JSON.stringify(redacted) : String(redacted);
|
|
88
|
+
command += ` -d '${body.replace(/'/g, "'\\''")}'`;
|
|
89
|
+
}
|
|
90
|
+
return command;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Logs the curl equivalent of a request. Never throws: a failure to describe a
|
|
95
|
+
* request must not be able to fail the request itself.
|
|
96
|
+
*/
|
|
97
|
+
export const logCurl = config => {
|
|
98
|
+
try {
|
|
99
|
+
Logger.info('CURL Command:', buildCurl(config));
|
|
100
|
+
} catch (error) {
|
|
101
|
+
Logger.warn('[api] Failed to build a curl command for the request', error);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=curl.js.map
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import axios from 'axios';
|
|
4
|
-
import axiosRetry from 'axios-retry';
|
|
5
4
|
import { getConfigValue } from "../config/index.js";
|
|
6
5
|
import { Logger } from "../logger/index.js";
|
|
6
|
+
import { applyConfiguredBaseUrl } from "./baseUrl.js";
|
|
7
|
+
import { logCurl } from "./curl.js";
|
|
8
|
+
import { attachRetry } from "./retry.js";
|
|
7
9
|
|
|
8
10
|
// Define timeout for requests
|
|
9
11
|
const REQUEST_TIMEOUT = 30000; // 30 seconds
|
|
@@ -16,7 +18,6 @@ const REQUEST_TIMEOUT = 30000; // 30 seconds
|
|
|
16
18
|
* - Handles specific HTTP status codes.
|
|
17
19
|
*/
|
|
18
20
|
const publicClient = axios.create({
|
|
19
|
-
baseURL: getConfigValue('baseUrl') ?? undefined,
|
|
20
21
|
headers: {
|
|
21
22
|
'Content-Type': 'application/json',
|
|
22
23
|
Accept: 'application/json',
|
|
@@ -24,29 +25,19 @@ const publicClient = axios.create({
|
|
|
24
25
|
},
|
|
25
26
|
timeout: REQUEST_TIMEOUT
|
|
26
27
|
});
|
|
28
|
+
applyConfiguredBaseUrl(publicClient);
|
|
27
29
|
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// Immediate retry for the first attempt
|
|
33
|
-
retryCondition: error => {
|
|
34
|
-
// Status-based ONLY, deliberately: a bare network error must never be
|
|
35
|
-
// retried here. This client carries login and OTP, which are not
|
|
36
|
-
// idempotent — the request may well have reached the server before the
|
|
37
|
-
// connection broke, so a retry can create a second session the user never
|
|
38
|
-
// sees. axios-retry's isNetworkOrIdempotentRequestError does NOT protect
|
|
39
|
-
// against this: its isNetworkError branch never looks at the HTTP method,
|
|
40
|
-
// so it retried POSTs too.
|
|
41
|
-
const status = error.response?.status;
|
|
42
|
-
return typeof status === 'number' && (status === 429 || status >= 500 && status <= 599);
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
axiosRetry(publicClient, retryConfig);
|
|
30
|
+
// Registered before the logging interceptors below so they only ever observe
|
|
31
|
+
// the final outcome of a request, not its intermediate retries. The policy
|
|
32
|
+
// itself, and why a bare network error is never retried, lives in ./retry.
|
|
33
|
+
attachRetry(publicClient);
|
|
46
34
|
|
|
47
35
|
// Add request interceptor for logging
|
|
48
36
|
publicClient.interceptors.request.use(config => {
|
|
49
37
|
Logger.info(`Request: ${config.method?.toUpperCase()} ${config.url}`);
|
|
38
|
+
if (getConfigValue('logCurl')) {
|
|
39
|
+
logCurl(config);
|
|
40
|
+
}
|
|
50
41
|
return config;
|
|
51
42
|
}, error => {
|
|
52
43
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Status-based ONLY, deliberately: a bare network error (no `error.response`)
|
|
5
|
+
* is never retryable. Both clients carry non-idempotent requests, login and OTP
|
|
6
|
+
* on the public one and every write on the user one, and a broken connection
|
|
7
|
+
* says nothing about whether the request reached the server. Retrying can
|
|
8
|
+
* therefore create a second session, or a duplicate record, that the user never
|
|
9
|
+
* sees. A generic network-error retry rule is not a substitute: it decides on
|
|
10
|
+
* the transport alone and never looks at the HTTP method, so it retries POSTs
|
|
11
|
+
* as readily as GETs.
|
|
12
|
+
*/
|
|
13
|
+
export const isRetryableStatus = status => typeof status === 'number' && (status === 429 || status >= 500 && status <= 599);
|
|
14
|
+
const DEFAULT_POLICY = {
|
|
15
|
+
retries: 3,
|
|
16
|
+
retryDelayMs: attempt => attempt <= 1 ? 0 : 2 ** attempt * 1000,
|
|
17
|
+
shouldRetry: error => isRetryableStatus(error.response?.status),
|
|
18
|
+
onRetry: () => undefined
|
|
19
|
+
};
|
|
20
|
+
const waitOrAbort = (delay, signal) => new Promise(resolve => {
|
|
21
|
+
const onAbort = () => {
|
|
22
|
+
clearTimeout(timer);
|
|
23
|
+
resolve(false);
|
|
24
|
+
};
|
|
25
|
+
const timer = setTimeout(() => {
|
|
26
|
+
signal?.removeEventListener?.('abort', onAbort);
|
|
27
|
+
resolve(true);
|
|
28
|
+
}, delay);
|
|
29
|
+
signal?.addEventListener?.('abort', onAbort, {
|
|
30
|
+
once: true
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
export const attachRetry = (client, policy = {}) => {
|
|
34
|
+
client.interceptors.response.use(undefined, async error => {
|
|
35
|
+
const axiosError = error;
|
|
36
|
+
const config = axiosError?.config;
|
|
37
|
+
if (!config || config.retry === false) {
|
|
38
|
+
return Promise.reject(error);
|
|
39
|
+
}
|
|
40
|
+
const perRequest = typeof config.retry === 'object' ? config.retry : {};
|
|
41
|
+
const effective = {
|
|
42
|
+
...DEFAULT_POLICY,
|
|
43
|
+
...policy,
|
|
44
|
+
...perRequest
|
|
45
|
+
};
|
|
46
|
+
const attempt = (config.__retryAttempt ?? 0) + 1;
|
|
47
|
+
if (attempt > effective.retries || !effective.shouldRetry(axiosError)) {
|
|
48
|
+
return Promise.reject(error);
|
|
49
|
+
}
|
|
50
|
+
if (config.signal?.aborted) {
|
|
51
|
+
return Promise.reject(error);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// The attempt count rides on the config, which axios copies onto the config
|
|
55
|
+
// of the retried request, so nested attempts continue the same count and
|
|
56
|
+
// separate requests never share one.
|
|
57
|
+
config.__retryAttempt = attempt;
|
|
58
|
+
// The body was serialised by the first pass; transforming it again would
|
|
59
|
+
// JSON-encode the already-encoded string.
|
|
60
|
+
config.transformRequest = [data => data];
|
|
61
|
+
effective.onRetry(attempt, axiosError);
|
|
62
|
+
const proceed = await waitOrAbort(effective.retryDelayMs(attempt), config.signal);
|
|
63
|
+
if (!proceed) {
|
|
64
|
+
return Promise.reject(error);
|
|
65
|
+
}
|
|
66
|
+
return client.request(config);
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import axios, { AxiosHeaders } from 'axios';
|
|
4
|
-
import
|
|
5
|
-
import { getToken } from "../auth/authStore.js";
|
|
4
|
+
import { ACCESS_TOKEN_KEY, getToken, REFRESH_TOKEN_KEY } from "../auth/authStore.js";
|
|
6
5
|
import { getConfigValue } from "../config/index.js";
|
|
7
6
|
import { Logger } from "../logger/index.js";
|
|
8
7
|
import { getNetworkStatus } from "../network/networkStatus.js";
|
|
8
|
+
import { applyConfiguredBaseUrl } from "./baseUrl.js";
|
|
9
|
+
import { logCurl } from "./curl.js";
|
|
10
|
+
import { attachRetry } from "./retry.js";
|
|
9
11
|
|
|
10
12
|
// Define timeout for requests
|
|
11
13
|
const REQUEST_TIMEOUT = 30000; // 30 seconds
|
|
@@ -79,7 +81,6 @@ const getSessionTerminationCode = data => {
|
|
|
79
81
|
*/
|
|
80
82
|
|
|
81
83
|
const userClient = axios.create({
|
|
82
|
-
baseURL: getConfigValue('baseUrl') ?? undefined,
|
|
83
84
|
headers: {
|
|
84
85
|
'Content-Type': 'application/json',
|
|
85
86
|
'Accept-Encoding': 'gzip, deflate, br'
|
|
@@ -87,19 +88,10 @@ const userClient = axios.create({
|
|
|
87
88
|
timeout: REQUEST_TIMEOUT // Set timeout for all requests
|
|
88
89
|
});
|
|
89
90
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// Immediate retry for the first attempt
|
|
95
|
-
retryCondition: error => {
|
|
96
|
-
// Status-based ONLY — see publicClient for the reasoning. A bare network
|
|
97
|
-
// error carries no response, so it is not retried: the write may already
|
|
98
|
-
// have landed server-side, and this client issues non-idempotent requests.
|
|
99
|
-
const status = error.response?.status;
|
|
100
|
-
return typeof status === 'number' && (status === 429 || status >= 500 && status <= 599);
|
|
101
|
-
}
|
|
102
|
-
});
|
|
91
|
+
// Registered before the auth interceptors below, so a retryable 5xx is retried
|
|
92
|
+
// before anything else sees it while a 401 still falls through to the refresh
|
|
93
|
+
// flow. Policy and reasoning live in ./retry.
|
|
94
|
+
attachRetry(userClient);
|
|
103
95
|
let store;
|
|
104
96
|
let authActions = {
|
|
105
97
|
logout: () => undefined,
|
|
@@ -137,6 +129,21 @@ const processQueue = (error, token = null) => {
|
|
|
137
129
|
});
|
|
138
130
|
failedQueue = [];
|
|
139
131
|
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Clear any in-flight token refresh and reject everything queued behind it.
|
|
135
|
+
* Call this on logout.
|
|
136
|
+
*
|
|
137
|
+
* Without it, a refresh that was already in flight when the user logged out
|
|
138
|
+
* resolves afterwards and replays every queued request with a token for a
|
|
139
|
+
* session that has ended. The queue is module state, so it survives the logout
|
|
140
|
+
* entirely: nothing else clears it. A consumer app hit this and hand-wrote the
|
|
141
|
+
* same function locally, which is how it was found.
|
|
142
|
+
*/
|
|
143
|
+
export const resetRefreshState = () => {
|
|
144
|
+
refreshTokenPromise = null;
|
|
145
|
+
processQueue(new Error('[@webority-technologies/mobile-core] Session ended'));
|
|
146
|
+
};
|
|
140
147
|
const getErrorMessage = error => {
|
|
141
148
|
if (error instanceof Error) {
|
|
142
149
|
return error.message;
|
|
@@ -167,6 +174,23 @@ const requireStore = () => {
|
|
|
167
174
|
/**
|
|
168
175
|
* Attach Authorization Token Before Requests
|
|
169
176
|
*/
|
|
177
|
+
// Registered after the curl logger and before the auth interceptor, so that it
|
|
178
|
+
// RUNS between them: axios unshifts its request chain, making the first
|
|
179
|
+
// registered run last. The curl line therefore describes a request that already
|
|
180
|
+
// has both its baseURL and its Authorization header.
|
|
181
|
+
applyConfiguredBaseUrl(userClient);
|
|
182
|
+
|
|
183
|
+
// Registered BEFORE the auth interceptor below deliberately. Axios builds its
|
|
184
|
+
// request chain by unshifting, so the FIRST-registered request interceptor runs
|
|
185
|
+
// LAST — closest to dispatch. This one therefore observes the final config,
|
|
186
|
+
// with the Authorization header already attached, which is the whole point of
|
|
187
|
+
// a curl line. Registering it after would log the request before it is signed.
|
|
188
|
+
userClient.interceptors.request.use(config => {
|
|
189
|
+
if (getConfigValue('logCurl')) {
|
|
190
|
+
logCurl(config);
|
|
191
|
+
}
|
|
192
|
+
return config;
|
|
193
|
+
});
|
|
170
194
|
userClient.interceptors.request.use(async config => {
|
|
171
195
|
// Reject before any network call when the device is known to be offline.
|
|
172
196
|
// Unknown connectivity (netinfo not installed, or undetermined) is treated
|
|
@@ -181,7 +205,7 @@ userClient.interceptors.request.use(async config => {
|
|
|
181
205
|
if (config.url?.includes('/Account/RefreshToken')) {
|
|
182
206
|
return config;
|
|
183
207
|
}
|
|
184
|
-
const token = await getToken(
|
|
208
|
+
const token = await getToken(ACCESS_TOKEN_KEY);
|
|
185
209
|
|
|
186
210
|
// Check if token exists
|
|
187
211
|
if (!token?.token) {
|
|
@@ -213,7 +237,7 @@ userClient.interceptors.request.use(async config => {
|
|
|
213
237
|
// Start a new token refresh process
|
|
214
238
|
refreshTokenPromise = (async () => {
|
|
215
239
|
try {
|
|
216
|
-
const refreshToken = await getToken(
|
|
240
|
+
const refreshToken = await getToken(REFRESH_TOKEN_KEY);
|
|
217
241
|
if (!refreshToken?.token) {
|
|
218
242
|
Logger.error('No refresh token available');
|
|
219
243
|
throw new Error('No refresh token available');
|
|
@@ -231,7 +255,7 @@ userClient.interceptors.request.use(async config => {
|
|
|
231
255
|
if (refreshResult.error) {
|
|
232
256
|
throw new Error(`Token refresh failed: ${refreshResult.error.message || 'Unknown error'}`);
|
|
233
257
|
}
|
|
234
|
-
const newAccessToken = await getToken(
|
|
258
|
+
const newAccessToken = await getToken(ACCESS_TOKEN_KEY);
|
|
235
259
|
if (!newAccessToken?.token) {
|
|
236
260
|
throw new Error('Failed to retrieve new access token after refresh');
|
|
237
261
|
}
|
|
@@ -314,7 +338,7 @@ userClient.interceptors.response.use(response => response, async error => {
|
|
|
314
338
|
refreshTokenPromise = (async () => {
|
|
315
339
|
try {
|
|
316
340
|
Logger.info('User Client: Refreshing token...');
|
|
317
|
-
const refreshToken = await getToken(
|
|
341
|
+
const refreshToken = await getToken(REFRESH_TOKEN_KEY);
|
|
318
342
|
if (!refreshToken || !refreshToken.token) {
|
|
319
343
|
Logger.error('No refresh token available or token is invalid');
|
|
320
344
|
throw new Error('No refresh token available');
|
|
@@ -327,7 +351,7 @@ userClient.interceptors.response.use(response => response, async error => {
|
|
|
327
351
|
throw new Error(`Token refresh failed: ${refreshResult.error.message || 'Unknown error'}`);
|
|
328
352
|
}
|
|
329
353
|
Logger.info('Getting new access token after refresh');
|
|
330
|
-
const newAccessToken = await getToken(
|
|
354
|
+
const newAccessToken = await getToken(ACCESS_TOKEN_KEY);
|
|
331
355
|
if (!newAccessToken?.token) {
|
|
332
356
|
throw new Error('Failed to retrieve new access token');
|
|
333
357
|
}
|