@webority-technologies/mobile-core 0.0.1
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 +15 -0
- package/lib/commonjs/api/publicClient.js +80 -0
- package/lib/commonjs/api/userClient.js +368 -0
- package/lib/commonjs/auth/authStore.js +155 -0
- package/lib/commonjs/auth/index.js +44 -0
- package/lib/commonjs/auth/jwt.js +88 -0
- package/lib/commonjs/config/index.js +65 -0
- package/lib/commonjs/formatters/currency.js +69 -0
- package/lib/commonjs/formatters/date.js +168 -0
- package/lib/commonjs/formatters/index.js +77 -0
- package/lib/commonjs/formatters/initials.js +32 -0
- package/lib/commonjs/formatters/number.js +88 -0
- package/lib/commonjs/formatters/phone.js +67 -0
- package/lib/commonjs/index.js +383 -0
- package/lib/commonjs/initSDK.js +45 -0
- package/lib/commonjs/initUserAuth.js +38 -0
- package/lib/commonjs/logger/index.js +137 -0
- package/lib/commonjs/network/index.js +25 -0
- package/lib/commonjs/network/networkStatus.js +98 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/permissions/index.js +200 -0
- package/lib/commonjs/storage/index.js +193 -0
- package/lib/commonjs/utils/imageCompression.js +75 -0
- package/lib/commonjs/utils/index.js +13 -0
- package/lib/commonjs/validators/index.js +193 -0
- package/lib/commonjs/versionCheck/index.js +395 -0
- package/lib/module/api/publicClient.js +76 -0
- package/lib/module/api/userClient.js +360 -0
- package/lib/module/auth/authStore.js +148 -0
- package/lib/module/auth/index.js +5 -0
- package/lib/module/auth/jwt.js +82 -0
- package/lib/module/config/index.js +57 -0
- package/lib/module/formatters/currency.js +64 -0
- package/lib/module/formatters/date.js +160 -0
- package/lib/module/formatters/index.js +8 -0
- package/lib/module/formatters/initials.js +27 -0
- package/lib/module/formatters/number.js +81 -0
- package/lib/module/formatters/phone.js +61 -0
- package/lib/module/index.js +30 -0
- package/lib/module/initSDK.js +40 -0
- package/lib/module/initUserAuth.js +34 -0
- package/lib/module/logger/index.js +133 -0
- package/lib/module/network/index.js +4 -0
- package/lib/module/network/networkStatus.js +91 -0
- package/lib/module/package.json +1 -0
- package/lib/module/permissions/index.js +197 -0
- package/lib/module/storage/index.js +189 -0
- package/lib/module/utils/imageCompression.js +69 -0
- package/lib/module/utils/index.js +4 -0
- package/lib/module/validators/index.js +171 -0
- package/lib/module/versionCheck/index.js +390 -0
- package/lib/typescript/commonjs/api/publicClient.d.ts +11 -0
- package/lib/typescript/commonjs/api/userClient.d.ts +49 -0
- package/lib/typescript/commonjs/auth/authStore.d.ts +43 -0
- package/lib/typescript/commonjs/auth/index.d.ts +5 -0
- package/lib/typescript/commonjs/auth/jwt.d.ts +31 -0
- package/lib/typescript/commonjs/config/index.d.ts +49 -0
- package/lib/typescript/commonjs/formatters/currency.d.ts +24 -0
- package/lib/typescript/commonjs/formatters/date.d.ts +22 -0
- package/lib/typescript/commonjs/formatters/index.d.ts +8 -0
- package/lib/typescript/commonjs/formatters/initials.d.ts +11 -0
- package/lib/typescript/commonjs/formatters/number.d.ts +15 -0
- package/lib/typescript/commonjs/formatters/phone.d.ts +13 -0
- package/lib/typescript/commonjs/index.d.ts +30 -0
- package/lib/typescript/commonjs/initSDK.d.ts +40 -0
- package/lib/typescript/commonjs/initUserAuth.d.ts +22 -0
- package/lib/typescript/commonjs/logger/index.d.ts +42 -0
- package/lib/typescript/commonjs/network/index.d.ts +3 -0
- package/lib/typescript/commonjs/network/networkStatus.d.ts +27 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/permissions/index.d.ts +20 -0
- package/lib/typescript/commonjs/storage/index.d.ts +42 -0
- package/lib/typescript/commonjs/utils/imageCompression.d.ts +31 -0
- package/lib/typescript/commonjs/utils/index.d.ts +3 -0
- package/lib/typescript/commonjs/validators/index.d.ts +46 -0
- package/lib/typescript/commonjs/versionCheck/index.d.ts +80 -0
- package/lib/typescript/module/api/publicClient.d.ts +11 -0
- package/lib/typescript/module/api/userClient.d.ts +49 -0
- package/lib/typescript/module/auth/authStore.d.ts +43 -0
- package/lib/typescript/module/auth/index.d.ts +5 -0
- package/lib/typescript/module/auth/jwt.d.ts +31 -0
- package/lib/typescript/module/config/index.d.ts +49 -0
- package/lib/typescript/module/formatters/currency.d.ts +24 -0
- package/lib/typescript/module/formatters/date.d.ts +22 -0
- package/lib/typescript/module/formatters/index.d.ts +8 -0
- package/lib/typescript/module/formatters/initials.d.ts +11 -0
- package/lib/typescript/module/formatters/number.d.ts +15 -0
- package/lib/typescript/module/formatters/phone.d.ts +13 -0
- package/lib/typescript/module/index.d.ts +30 -0
- package/lib/typescript/module/initSDK.d.ts +40 -0
- package/lib/typescript/module/initUserAuth.d.ts +22 -0
- package/lib/typescript/module/logger/index.d.ts +42 -0
- package/lib/typescript/module/network/index.d.ts +3 -0
- package/lib/typescript/module/network/networkStatus.d.ts +27 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/permissions/index.d.ts +20 -0
- package/lib/typescript/module/storage/index.d.ts +42 -0
- package/lib/typescript/module/utils/imageCompression.d.ts +31 -0
- package/lib/typescript/module/utils/index.d.ts +3 -0
- package/lib/typescript/module/validators/index.d.ts +46 -0
- package/lib/typescript/module/versionCheck/index.d.ts +80 -0
- package/package.json +112 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generic validators returning `boolean`. All accept `null`/`undefined` safely.
|
|
5
|
+
*
|
|
6
|
+
* Tradeoff: regexes are pragmatic, not RFC-strict. They reject the things real users
|
|
7
|
+
* typo and accept the long tail of legitimate inputs. For RFC-grade validation use
|
|
8
|
+
* a dedicated library at the boundary.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const RE_EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
12
|
+
const RE_INDIAN_MOBILE = /^[6-9]\d{9}$/;
|
|
13
|
+
const RE_NUMERIC = /^-?\d+(?:\.\d+)?$/;
|
|
14
|
+
const RE_INTEGER = /^-?\d+$/;
|
|
15
|
+
const RE_ALPHANUMERIC = /^[a-zA-Z0-9]+$/;
|
|
16
|
+
const RE_PINCODE_IN = /^[1-9]\d{5}$/;
|
|
17
|
+
const RE_PAN = /^[A-Z]{5}\d{4}[A-Z]$/;
|
|
18
|
+
const RE_GSTIN = /^\d{2}[A-Z]{5}\d{4}[A-Z][1-9A-Z]Z[0-9A-Z]$/;
|
|
19
|
+
const RE_AADHAAR = /^\d{12}$/;
|
|
20
|
+
const RE_IFSC = /^[A-Z]{4}0[A-Z0-9]{6}$/;
|
|
21
|
+
const RE_HEX_COLOR = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
22
|
+
const asString = v => {
|
|
23
|
+
if (typeof v === 'string') {
|
|
24
|
+
return v.trim();
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
};
|
|
28
|
+
export const isNonEmpty = value => {
|
|
29
|
+
const s = asString(value);
|
|
30
|
+
return !!s && s.length > 0;
|
|
31
|
+
};
|
|
32
|
+
export const isEmail = value => {
|
|
33
|
+
const s = asString(value);
|
|
34
|
+
return !!s && RE_EMAIL.test(s);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Validate an Indian mobile number. Accepts:
|
|
39
|
+
* - 10-digit number starting with 6-9
|
|
40
|
+
* - Optional `+91` or `91` country code prefix
|
|
41
|
+
*/
|
|
42
|
+
export const isIndianMobile = value => {
|
|
43
|
+
const s = asString(value);
|
|
44
|
+
if (!s) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const digits = s.replace(/\D/g, '');
|
|
48
|
+
const tail = digits.startsWith('91') && digits.length === 12 ? digits.slice(2) : digits;
|
|
49
|
+
return RE_INDIAN_MOBILE.test(tail);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Generic phone validator: 7–15 digits, optional `+` prefix.
|
|
54
|
+
* Use `isIndianMobile` when you specifically need an Indian mobile number.
|
|
55
|
+
*/
|
|
56
|
+
export const isPhone = value => {
|
|
57
|
+
const s = asString(value);
|
|
58
|
+
if (!s) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const digits = s.replace(/[^\d+]/g, '').replace(/^\+/, '');
|
|
62
|
+
return digits.length >= 7 && digits.length <= 15 && /^\d+$/.test(digits);
|
|
63
|
+
};
|
|
64
|
+
export const isUrl = value => {
|
|
65
|
+
const s = asString(value);
|
|
66
|
+
if (!s) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
// URL constructor is available on Hermes & JSC.
|
|
71
|
+
|
|
72
|
+
new URL(s);
|
|
73
|
+
return /^https?:\/\//i.test(s) || /^[a-z][a-z0-9+\-.]*:\/\//i.test(s);
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
export const isNumeric = value => {
|
|
79
|
+
if (typeof value === 'number') {
|
|
80
|
+
return Number.isFinite(value);
|
|
81
|
+
}
|
|
82
|
+
const s = asString(value);
|
|
83
|
+
return !!s && RE_NUMERIC.test(s);
|
|
84
|
+
};
|
|
85
|
+
export const isInteger = value => {
|
|
86
|
+
if (typeof value === 'number') {
|
|
87
|
+
return Number.isInteger(value);
|
|
88
|
+
}
|
|
89
|
+
const s = asString(value);
|
|
90
|
+
return !!s && RE_INTEGER.test(s);
|
|
91
|
+
};
|
|
92
|
+
export const isAlphanumeric = value => {
|
|
93
|
+
const s = asString(value);
|
|
94
|
+
return !!s && RE_ALPHANUMERIC.test(s);
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Common password complexity check. Defaults to 8+ chars,
|
|
98
|
+
* one uppercase, one lowercase, one digit.
|
|
99
|
+
*/
|
|
100
|
+
export const isStrongPassword = (value, rules = {}) => {
|
|
101
|
+
if (typeof value !== 'string') {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
const {
|
|
105
|
+
minLength = 8,
|
|
106
|
+
requireUppercase = true,
|
|
107
|
+
requireLowercase = true,
|
|
108
|
+
requireNumber = true,
|
|
109
|
+
requireSymbol = false
|
|
110
|
+
} = rules;
|
|
111
|
+
if (value.length < minLength) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
if (requireUppercase && !/[A-Z]/.test(value)) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
if (requireLowercase && !/[a-z]/.test(value)) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
if (requireNumber && !/\d/.test(value)) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
if (requireSymbol && !/[^A-Za-z0-9]/.test(value)) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
};
|
|
128
|
+
export const isIndianPincode = value => {
|
|
129
|
+
const s = asString(value);
|
|
130
|
+
return !!s && RE_PINCODE_IN.test(s);
|
|
131
|
+
};
|
|
132
|
+
export const isPan = value => {
|
|
133
|
+
const s = asString(value);
|
|
134
|
+
return !!s && RE_PAN.test(s.toUpperCase());
|
|
135
|
+
};
|
|
136
|
+
export const isGstin = value => {
|
|
137
|
+
const s = asString(value);
|
|
138
|
+
return !!s && RE_GSTIN.test(s.toUpperCase());
|
|
139
|
+
};
|
|
140
|
+
export const isAadhaar = value => {
|
|
141
|
+
const s = asString(value);
|
|
142
|
+
if (!s) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
const digits = s.replace(/\s/g, '');
|
|
146
|
+
return RE_AADHAAR.test(digits);
|
|
147
|
+
};
|
|
148
|
+
export const isIfsc = value => {
|
|
149
|
+
const s = asString(value);
|
|
150
|
+
return !!s && RE_IFSC.test(s.toUpperCase());
|
|
151
|
+
};
|
|
152
|
+
export const isHexColor = value => {
|
|
153
|
+
const s = asString(value);
|
|
154
|
+
return !!s && RE_HEX_COLOR.test(s);
|
|
155
|
+
};
|
|
156
|
+
export const isBetween = (value, min, max, inclusive = true) => {
|
|
157
|
+
const n = typeof value === 'number' ? value : Number(asString(value));
|
|
158
|
+
if (!Number.isFinite(n)) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
return inclusive ? n >= min && n <= max : n > min && n < max;
|
|
162
|
+
};
|
|
163
|
+
export const minLength = (value, length) => {
|
|
164
|
+
const s = asString(value);
|
|
165
|
+
return !!s && s.length >= length;
|
|
166
|
+
};
|
|
167
|
+
export const maxLength = (value, length) => {
|
|
168
|
+
const s = asString(value);
|
|
169
|
+
return s == null ? true : s.length <= length;
|
|
170
|
+
};
|
|
171
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
// Type-only — erased at compile time; react-native stays lazily required below.
|
|
5
|
+
|
|
6
|
+
import { getConfigValue } from "../config/index.js";
|
|
7
|
+
import { Logger } from "../logger/index.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Minimal device-info shape we depend on.
|
|
11
|
+
*
|
|
12
|
+
* This is a LAST RESORT, not the primary path. `Platform.OS` already gives the
|
|
13
|
+
* platform, and every Webority app carries its own store id and version in
|
|
14
|
+
* `appsettings.Compiled.json` — so `setConfig({ appId, appVersion })` covers
|
|
15
|
+
* both remaining values without a native module. The injector below exists for
|
|
16
|
+
* apps that would rather hand us a `react-native-device-info` instance than
|
|
17
|
+
* wire two config keys.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
let deviceInfo = null;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Supply the app identity from a native module instead of config.
|
|
24
|
+
* Prefer `setConfig({ appId, appVersion })` — it needs no native dependency.
|
|
25
|
+
* Pass `null` to clear.
|
|
26
|
+
*/
|
|
27
|
+
export const setDeviceInfoImplementation = impl => {
|
|
28
|
+
deviceInfo = impl;
|
|
29
|
+
};
|
|
30
|
+
const missing = (what, configKey) => {
|
|
31
|
+
const err = new Error(`[VersionCheck] the app's ${what} is not available. Call ` + `setConfig({ ${configKey}: '...' }) — every Webority app already has this value in ` + 'appsettings.Compiled.json — or setDeviceInfoImplementation() with a native provider.');
|
|
32
|
+
Logger.error(err.message);
|
|
33
|
+
return err;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Platform comes from React Native itself; there has never been a reason to
|
|
37
|
+
// cross a native module boundary for it.
|
|
38
|
+
const getSystemName = () => {
|
|
39
|
+
const {
|
|
40
|
+
Platform
|
|
41
|
+
} = require('react-native');
|
|
42
|
+
return Platform.OS;
|
|
43
|
+
};
|
|
44
|
+
const getBundleId = () => {
|
|
45
|
+
const fromConfig = getConfigValue('appId');
|
|
46
|
+
if (fromConfig) {
|
|
47
|
+
return fromConfig;
|
|
48
|
+
}
|
|
49
|
+
if (deviceInfo) {
|
|
50
|
+
return deviceInfo.getBundleId();
|
|
51
|
+
}
|
|
52
|
+
throw missing('store identifier', 'appId');
|
|
53
|
+
};
|
|
54
|
+
const getVersion = () => {
|
|
55
|
+
const fromConfig = getConfigValue('appVersion');
|
|
56
|
+
if (fromConfig) {
|
|
57
|
+
return fromConfig;
|
|
58
|
+
}
|
|
59
|
+
if (deviceInfo) {
|
|
60
|
+
return deviceInfo.getVersion();
|
|
61
|
+
}
|
|
62
|
+
throw missing('version', 'appVersion');
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Constants
|
|
66
|
+
const MAX_RETRIES = 3;
|
|
67
|
+
const RETRY_DELAY = 3000; // 3 seconds
|
|
68
|
+
|
|
69
|
+
const getCountry = () => getConfigValue('country', 'in').toLowerCase();
|
|
70
|
+
// Utility function to parse and coerce version to a valid format
|
|
71
|
+
const parseVersion = version => {
|
|
72
|
+
Logger.info(`Parsing version: ${version}`);
|
|
73
|
+
const coercedVersion = version.replace(/[^0-9.]/g, '');
|
|
74
|
+
const versionParts = coercedVersion.split('.').map(Number);
|
|
75
|
+
return {
|
|
76
|
+
major: versionParts[0] || 0,
|
|
77
|
+
minor: versionParts[1] || 0,
|
|
78
|
+
patch: versionParts[2] || 0,
|
|
79
|
+
prerelease: version.includes('-') ? version.split('-')[1] ?? '' : ''
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Utility function to compare two versions loosely
|
|
84
|
+
const diffLoose = (version1, version2) => {
|
|
85
|
+
Logger.info(`Comparing versions: ${version1} and ${version2}`);
|
|
86
|
+
if (version1 === version2) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const v1 = parseVersion(version1);
|
|
90
|
+
const v2 = parseVersion(version2);
|
|
91
|
+
if (v1.major < v2.major) {
|
|
92
|
+
return 'major';
|
|
93
|
+
}
|
|
94
|
+
if (v1.major > v2.major) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
if (v1.minor < v2.minor) {
|
|
98
|
+
return 'minor';
|
|
99
|
+
}
|
|
100
|
+
if (v1.minor > v2.minor) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
if (v1.patch < v2.patch) {
|
|
104
|
+
return 'patch';
|
|
105
|
+
}
|
|
106
|
+
if (v1.patch > v2.patch) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
if (v1.prerelease && !v2.prerelease) {
|
|
110
|
+
return 'prerelease';
|
|
111
|
+
}
|
|
112
|
+
if (!v1.prerelease && v2.prerelease) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
if (v1.prerelease < v2.prerelease) {
|
|
116
|
+
return 'prerelease';
|
|
117
|
+
}
|
|
118
|
+
if (v1.prerelease > v2.prerelease) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Function to compare current version with the latest version
|
|
125
|
+
const versionCompare = (currentVersion, latestVersion) => {
|
|
126
|
+
Logger.info(`Comparing current version: ${currentVersion} with latest version: ${latestVersion}`);
|
|
127
|
+
if (!latestVersion) {
|
|
128
|
+
return {
|
|
129
|
+
needsUpdate: false,
|
|
130
|
+
updateType: null,
|
|
131
|
+
notice: 'Error: could not get latest version'
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const updateType = diffLoose(currentVersion, latestVersion);
|
|
136
|
+
return {
|
|
137
|
+
needsUpdate: !!updateType,
|
|
138
|
+
updateType
|
|
139
|
+
};
|
|
140
|
+
} catch (e) {
|
|
141
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
142
|
+
Logger.error(`Error comparing versions: ${message}`);
|
|
143
|
+
const needsUpdate = currentVersion !== latestVersion && latestVersion > currentVersion;
|
|
144
|
+
const updateType = needsUpdate ? 'minor' : null;
|
|
145
|
+
return {
|
|
146
|
+
needsUpdate,
|
|
147
|
+
updateType,
|
|
148
|
+
notice: message.replace(/^Invalid Version:/, 'Not a valid version:')
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Function to fetch version details from Google Play Store
|
|
154
|
+
const fetchAndroidVersion = async url => {
|
|
155
|
+
Logger.info(`Fetching Android version from URL: ${url}`);
|
|
156
|
+
let response;
|
|
157
|
+
try {
|
|
158
|
+
response = await fetch(url, {
|
|
159
|
+
headers: {
|
|
160
|
+
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.96 Mobile Safari/537.36',
|
|
161
|
+
'sec-fetch-site': 'same-origin'
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
} catch (error) {
|
|
165
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
166
|
+
Logger.error(`Error fetching data from Google Play Store: ${message}`);
|
|
167
|
+
throw new Error('Error fetching data from Google Play Store.');
|
|
168
|
+
}
|
|
169
|
+
if (!response.ok) {
|
|
170
|
+
if (response.status === 404) {
|
|
171
|
+
Logger.error('App with app ID not found in Google Play.');
|
|
172
|
+
throw new Error(`App with app ID not found in Google Play.`);
|
|
173
|
+
}
|
|
174
|
+
Logger.error(`Error fetching data: ${response.statusText}`);
|
|
175
|
+
throw new Error(response.statusText);
|
|
176
|
+
}
|
|
177
|
+
const text = await response.text();
|
|
178
|
+
|
|
179
|
+
// Primary regex pattern for current Play Store format
|
|
180
|
+
let versionMatch = text.match(/\[\[\[['"]((\d+\.)+\d+)['"]\]\],/);
|
|
181
|
+
|
|
182
|
+
// Alternate patterns as fallbacks
|
|
183
|
+
if (!versionMatch) {
|
|
184
|
+
// Fallback 1: Look for version in "current version" text
|
|
185
|
+
versionMatch = text.match(/Current Version[^\d]+((\d+\.)+\d+)/i);
|
|
186
|
+
}
|
|
187
|
+
if (!versionMatch) {
|
|
188
|
+
// Fallback 2: Look for itemprop="softwareVersion"
|
|
189
|
+
versionMatch = text.match(/itemprop="softwareVersion"[^>]*>((\d+\.)+\d+)</i);
|
|
190
|
+
}
|
|
191
|
+
if (!versionMatch) {
|
|
192
|
+
// Fallback 3: Try a more generic pattern for version numbers
|
|
193
|
+
const allVersions = text.match(/(\d+\.\d+(\.\d+)?)/g);
|
|
194
|
+
// If we found multiple version-like strings, use the first one
|
|
195
|
+
versionMatch = allVersions && allVersions.length > 0 ? [null, allVersions[0]] : null;
|
|
196
|
+
}
|
|
197
|
+
const version = versionMatch ? versionMatch[1] ?? null : null;
|
|
198
|
+
if (!version) {
|
|
199
|
+
Logger.warn('Could not extract version from Play Store. Using fallback method.');
|
|
200
|
+
}
|
|
201
|
+
const notesMatch = text.match(/<div itemprop="description">(.*?)<\/div>/);
|
|
202
|
+
const notes = notesMatch ? notesMatch[1] ?? '' : '';
|
|
203
|
+
return {
|
|
204
|
+
version,
|
|
205
|
+
releasedAt: new Date().toISOString(),
|
|
206
|
+
notes,
|
|
207
|
+
url,
|
|
208
|
+
lastChecked: new Date().toISOString()
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
// Function to fetch version details from Apple App Store
|
|
212
|
+
const fetchIosVersion = async url => {
|
|
213
|
+
Logger.info(`Fetching iOS version from URL: ${url}`);
|
|
214
|
+
let response;
|
|
215
|
+
try {
|
|
216
|
+
response = await fetch(url);
|
|
217
|
+
} catch {
|
|
218
|
+
Logger.error('Error connecting to iTunes.');
|
|
219
|
+
throw new Error('Error connecting to iTunes.');
|
|
220
|
+
}
|
|
221
|
+
const data = await response.json();
|
|
222
|
+
if (!data || !('results' in data)) {
|
|
223
|
+
Logger.error('Unknown error connecting to iTunes.');
|
|
224
|
+
throw new Error('Unknown error connecting to iTunes.');
|
|
225
|
+
}
|
|
226
|
+
if (!data.results.length) {
|
|
227
|
+
Logger.error('App for this bundle ID not found.');
|
|
228
|
+
throw new Error('App for this bundle ID not found.');
|
|
229
|
+
}
|
|
230
|
+
const result = data.results[0];
|
|
231
|
+
return {
|
|
232
|
+
version: result.version || null,
|
|
233
|
+
released: result.currentVersionReleaseDate || result.releaseDate || null,
|
|
234
|
+
notes: result.releaseNotes || '',
|
|
235
|
+
url: result.trackViewUrl || result.artistViewUrl || result.sellerUrl || null,
|
|
236
|
+
country: getCountry(),
|
|
237
|
+
lastChecked: new Date().toISOString()
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// Function to lookup version based on platform
|
|
242
|
+
const lookupVersion = async (platform, identifier) => {
|
|
243
|
+
Logger.info(`Looking up version for platform: ${platform}, identifier: ${identifier}`);
|
|
244
|
+
const country = getCountry();
|
|
245
|
+
const url = platform === 'ios' ? `https://itunes.apple.com/lookup?bundleId=${identifier}&country=${country}&_=${new Date().valueOf()}` : `https://play.google.com/store/apps/details?id=${identifier}&hl=${country}`;
|
|
246
|
+
return platform === 'ios' ? fetchIosVersion(url) : fetchAndroidVersion(url);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Check for new version with retry logic
|
|
251
|
+
*/
|
|
252
|
+
const checkForUpdates = async () => {
|
|
253
|
+
const platform = getSystemName().toLowerCase();
|
|
254
|
+
const identifier = getBundleId();
|
|
255
|
+
const currentVersion = getVersion();
|
|
256
|
+
|
|
257
|
+
// Implement retry mechanism
|
|
258
|
+
let retryCount = 0;
|
|
259
|
+
let lastError = null;
|
|
260
|
+
while (retryCount <= MAX_RETRIES) {
|
|
261
|
+
try {
|
|
262
|
+
Logger.info(`Checking if new version is available (attempt ${retryCount + 1}/${MAX_RETRIES + 1})`);
|
|
263
|
+
Logger.info(`Platform: ${platform}, identifier: ${identifier}, current version: ${currentVersion}`);
|
|
264
|
+
const latestVersionInfo = await lookupVersion(platform, identifier);
|
|
265
|
+
const comparison = versionCompare(currentVersion, latestVersionInfo.version);
|
|
266
|
+
return {
|
|
267
|
+
isNewVersionAvailable: comparison.needsUpdate,
|
|
268
|
+
updateType: comparison.updateType,
|
|
269
|
+
url: latestVersionInfo.url
|
|
270
|
+
};
|
|
271
|
+
} catch (error) {
|
|
272
|
+
lastError = error;
|
|
273
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
274
|
+
Logger.error(`Error checking version (attempt ${retryCount + 1}/${MAX_RETRIES + 1}): ${message}`);
|
|
275
|
+
|
|
276
|
+
// If we've reached max retries, throw the last error
|
|
277
|
+
if (retryCount === MAX_RETRIES) {
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Otherwise, wait and retry
|
|
282
|
+
retryCount++;
|
|
283
|
+
Logger.info(`Retrying version check in ${RETRY_DELAY}ms...`);
|
|
284
|
+
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// This should never be reached due to the throw above, but just in case
|
|
289
|
+
throw lastError instanceof Error ? lastError : new Error('Failed to check for updates after multiple attempts');
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get fallback store URL based on platform
|
|
294
|
+
*/
|
|
295
|
+
const getFallbackStoreUrl = () => {
|
|
296
|
+
const platform = getSystemName().toLowerCase();
|
|
297
|
+
const bundleId = getBundleId();
|
|
298
|
+
const country = getCountry();
|
|
299
|
+
return platform === 'ios' ? `https://apps.apple.com/${country}/app/${bundleId}` : `https://play.google.com/store/apps/details?id=${bundleId}&hl=${country}`;
|
|
300
|
+
};
|
|
301
|
+
// Version check functionality
|
|
302
|
+
export const VersionCheck = {
|
|
303
|
+
isNewVersionAvailable: async () => {
|
|
304
|
+
return checkForUpdates();
|
|
305
|
+
},
|
|
306
|
+
openUpdateUrl: async url => {
|
|
307
|
+
const {
|
|
308
|
+
Linking
|
|
309
|
+
} = require('react-native');
|
|
310
|
+
try {
|
|
311
|
+
if (url) {
|
|
312
|
+
Logger.info(`Opening app store URL: ${url}`);
|
|
313
|
+
await Linking.openURL(url);
|
|
314
|
+
} else {
|
|
315
|
+
const fallbackUrl = getFallbackStoreUrl();
|
|
316
|
+
Logger.info(`Opening fallback store URL: ${fallbackUrl}`);
|
|
317
|
+
await Linking.openURL(fallbackUrl);
|
|
318
|
+
}
|
|
319
|
+
return true;
|
|
320
|
+
} catch (error) {
|
|
321
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
322
|
+
Logger.error(`Failed to open store URL: ${message}`);
|
|
323
|
+
|
|
324
|
+
// Try with fallback URL if the original failed
|
|
325
|
+
try {
|
|
326
|
+
if (url) {
|
|
327
|
+
// Only try fallback if we were using a specific URL before
|
|
328
|
+
const fallbackUrl = getFallbackStoreUrl();
|
|
329
|
+
Logger.info(`Trying fallback store URL: ${fallbackUrl}`);
|
|
330
|
+
await Linking.openURL(fallbackUrl);
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
} catch (fallbackError) {
|
|
334
|
+
const fbMessage = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
|
335
|
+
Logger.error(`Failed to open fallback URL: ${fbMessage}`);
|
|
336
|
+
}
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
getFallbackStoreUrl
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Custom hook to check for required app updates.
|
|
345
|
+
*
|
|
346
|
+
* @param isAppReady - Whether the app is ready to perform the check
|
|
347
|
+
* @param environment - The app environment (defaults to the one in config)
|
|
348
|
+
*/
|
|
349
|
+
export const useVersionCheck = (isAppReady, environment = getConfigValue('environment', 'Development')) => {
|
|
350
|
+
const [updateInfo, setUpdateInfo] = useState({
|
|
351
|
+
isUpdateRequired: false,
|
|
352
|
+
updateUrl: null
|
|
353
|
+
});
|
|
354
|
+
useEffect(() => {
|
|
355
|
+
let isMounted = true;
|
|
356
|
+
const checkVersion = async () => {
|
|
357
|
+
try {
|
|
358
|
+
// All retry logic is handled in the VersionCheck module
|
|
359
|
+
const {
|
|
360
|
+
isNewVersionAvailable,
|
|
361
|
+
url
|
|
362
|
+
} = await VersionCheck.isNewVersionAvailable();
|
|
363
|
+
if (isNewVersionAvailable && isMounted) {
|
|
364
|
+
Logger.info('Update required. URL:', url || 'Not available yet');
|
|
365
|
+
setUpdateInfo({
|
|
366
|
+
isUpdateRequired: true,
|
|
367
|
+
updateUrl: url
|
|
368
|
+
});
|
|
369
|
+
} else if (isMounted) {
|
|
370
|
+
Logger.info('App is up to date');
|
|
371
|
+
}
|
|
372
|
+
} catch (error) {
|
|
373
|
+
Logger.error('Error checking app version:', error);
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// Only run version check in Production environment when app is ready
|
|
378
|
+
if (isAppReady && environment === 'Production') {
|
|
379
|
+
void checkVersion();
|
|
380
|
+
}
|
|
381
|
+
return () => {
|
|
382
|
+
isMounted = false;
|
|
383
|
+
};
|
|
384
|
+
}, [isAppReady, environment]);
|
|
385
|
+
return updateInfo;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// Export for backward compatibility
|
|
389
|
+
export default VersionCheck;
|
|
390
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type AxiosInstance } from 'axios';
|
|
2
|
+
/**
|
|
3
|
+
* Public Axios Client
|
|
4
|
+
* - Base URL: Set your public API base URL.
|
|
5
|
+
* - No Authorization header is attached.
|
|
6
|
+
* - Includes timeout configuration and error handling.
|
|
7
|
+
* - Handles specific HTTP status codes.
|
|
8
|
+
*/
|
|
9
|
+
declare const publicClient: AxiosInstance;
|
|
10
|
+
export default publicClient;
|
|
11
|
+
//# sourceMappingURL=publicClient.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type AxiosInstance } from 'axios';
|
|
2
|
+
import type { AuthActions } from '../initUserAuth';
|
|
3
|
+
/**
|
|
4
|
+
* Thrown by the request interceptor when the device is offline, before the
|
|
5
|
+
* request ever reaches the adapter (and therefore the network). Distinct from
|
|
6
|
+
* an AxiosError so callers can show an offline message instead of a generic
|
|
7
|
+
* request failure.
|
|
8
|
+
*/
|
|
9
|
+
export declare class RequestOfflineError extends Error {
|
|
10
|
+
constructor(message?: string);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Sub-codes a 401 response body carries when the session itself is dead
|
|
14
|
+
* (terminated by an admin, or expired server-side) rather than merely
|
|
15
|
+
* out-of-date. These never warrant a refresh attempt — only logout.
|
|
16
|
+
*/
|
|
17
|
+
export declare const SESSION_TERMINATION_CODES: {
|
|
18
|
+
readonly TERMINATED: "SESSION_TERMINATED";
|
|
19
|
+
readonly EXPIRED: "SESSION_EXPIRED";
|
|
20
|
+
};
|
|
21
|
+
export type SessionTerminationCode = (typeof SESSION_TERMINATION_CODES)[keyof typeof SESSION_TERMINATION_CODES];
|
|
22
|
+
/**
|
|
23
|
+
* The header is the AUTHORITATIVE signal, verified against the backend:
|
|
24
|
+
* `WeborityJwtMiddleware.ReturnUnauthorized` answers an invalidated session with
|
|
25
|
+
* `401` + `X-Security-Stamp-Invalid: true` and a body of
|
|
26
|
+
* `{ error: 'Unauthorized', message: '...' }` — no code field at all, and
|
|
27
|
+
* `error` is a string rather than an object. An earlier version of this checked
|
|
28
|
+
* only body fields and would therefore never have fired against a real server.
|
|
29
|
+
*
|
|
30
|
+
* The body codes below are kept as a secondary path for any head that does send
|
|
31
|
+
* an explicit sub-code; they match on exact strings, so looking is free.
|
|
32
|
+
*/
|
|
33
|
+
export declare const SESSION_INVALID_HEADER = "x-security-stamp-invalid";
|
|
34
|
+
/**
|
|
35
|
+
* Minimal Redux-store shape required by the user client to dispatch
|
|
36
|
+
* refresh-token / logout actions.
|
|
37
|
+
*/
|
|
38
|
+
export interface RefreshableStore {
|
|
39
|
+
dispatch: (action: unknown) => Promise<{
|
|
40
|
+
error?: {
|
|
41
|
+
message?: string;
|
|
42
|
+
};
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
declare const userClient: AxiosInstance;
|
|
46
|
+
export declare const injectStore: <S>(_store: S) => void;
|
|
47
|
+
export declare const injectAuthActions: (actions: AuthActions) => void;
|
|
48
|
+
export default userClient;
|
|
49
|
+
//# sourceMappingURL=userClient.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type * as KeychainModule from 'react-native-keychain';
|
|
2
|
+
/**
|
|
3
|
+
* Token data persisted in the keychain.
|
|
4
|
+
*/
|
|
5
|
+
export interface StoredToken {
|
|
6
|
+
token: string;
|
|
7
|
+
expiry: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Minimal keychain shape we depend on.
|
|
11
|
+
* Lets us avoid a hard import on `react-native-keychain`, so an app that never
|
|
12
|
+
* stores a token does not have to install it.
|
|
13
|
+
*/
|
|
14
|
+
interface KeychainLike {
|
|
15
|
+
setInternetCredentials: (server: string, username: string, password: string, options?: {
|
|
16
|
+
service?: string;
|
|
17
|
+
}) => Promise<unknown>;
|
|
18
|
+
getInternetCredentials: (server: string, options?: {
|
|
19
|
+
service?: string;
|
|
20
|
+
}) => Promise<KeychainModule.UserCredentials | false>;
|
|
21
|
+
resetInternetCredentials: (options: {
|
|
22
|
+
service: string;
|
|
23
|
+
}) => Promise<unknown>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Inject a custom keychain backend at runtime.
|
|
27
|
+
* Pass `null` to reset to the default react-native-keychain backend.
|
|
28
|
+
*/
|
|
29
|
+
export declare const setKeychainImplementation: (impl: KeychainLike | null) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Store token with expiry.
|
|
32
|
+
*/
|
|
33
|
+
export declare const storeToken: (key: string, token: string, expiry: string) => Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Retrieve token and check if expired.
|
|
36
|
+
*/
|
|
37
|
+
export declare const getToken: (key: string) => Promise<StoredToken | null>;
|
|
38
|
+
/**
|
|
39
|
+
* Remove token by key.
|
|
40
|
+
*/
|
|
41
|
+
export declare const removeToken: (key: string) => Promise<void>;
|
|
42
|
+
export type { KeychainLike };
|
|
43
|
+
//# sourceMappingURL=authStore.d.ts.map
|