@webority/mobile-core 0.0.23
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/LICENSE +21 -0
- package/README.md +39 -0
- package/android/build.gradle +65 -0
- package/android/src/newarch/java/com/webority/mobilecore/smsretriever/AppSignatureHelper.kt +57 -0
- package/android/src/newarch/java/com/webority/mobilecore/smsretriever/SmsRetrieverModule.kt +20 -0
- package/android/src/newarch/java/com/webority/mobilecore/smsretriever/SmsRetrieverModuleImpl.kt +146 -0
- package/android/src/newarch/java/com/webority/mobilecore/smsretriever/SmsRetrieverPackage.kt +33 -0
- package/lib/commonjs/api/baseUrl.js +34 -0
- package/lib/commonjs/api/curl.js +109 -0
- package/lib/commonjs/api/publicClient.js +73 -0
- package/lib/commonjs/api/retry.js +75 -0
- package/lib/commonjs/api/tokenRefresh.js +205 -0
- package/lib/commonjs/api/userClient.js +255 -0
- package/lib/commonjs/auth/authStore.js +232 -0
- package/lib/commonjs/auth/index.js +44 -0
- package/lib/commonjs/auth/jwt.js +112 -0
- package/lib/commonjs/config/index.js +66 -0
- package/lib/commonjs/deepLink/index.js +286 -0
- package/lib/commonjs/download/adapters/expoFs.js +69 -0
- package/lib/commonjs/download/adapters/shared.js +13 -0
- package/lib/commonjs/download/index.js +250 -0
- package/lib/commonjs/formatters/currency.js +69 -0
- package/lib/commonjs/formatters/date.js +168 -0
- package/lib/commonjs/formatters/dateMath.js +310 -0
- package/lib/commonjs/formatters/index.js +246 -0
- package/lib/commonjs/formatters/initials.js +32 -0
- package/lib/commonjs/formatters/number.js +88 -0
- package/lib/commonjs/formatters/phone.js +369 -0
- package/lib/commonjs/formatters/windowsToIanaTable.js +164 -0
- package/lib/commonjs/geolocation/adapters/expoLocation.js +126 -0
- package/lib/commonjs/geolocation/index.js +355 -0
- package/lib/commonjs/index.js +630 -0
- package/lib/commonjs/initSDK.js +66 -0
- package/lib/commonjs/initUserAuth.js +37 -0
- package/lib/commonjs/logger/index.js +291 -0
- package/lib/commonjs/network/index.js +31 -0
- package/lib/commonjs/network/networkStatus.js +133 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/permissions/calendar.js +14 -0
- package/lib/commonjs/permissions/camera.js +14 -0
- package/lib/commonjs/permissions/contacts.js +10 -0
- package/lib/commonjs/permissions/index.js +133 -0
- package/lib/commonjs/permissions/location.js +14 -0
- package/lib/commonjs/permissions/mediaLibrary.js +10 -0
- package/lib/commonjs/permissions/notifications.js +10 -0
- package/lib/commonjs/permissions/photos.js +14 -0
- package/lib/commonjs/phone/countries.js +265 -0
- package/lib/commonjs/share/index.js +70 -0
- package/lib/commonjs/smsRetriever/index.js +91 -0
- package/lib/commonjs/specs/NativeSmsRetriever.js +15 -0
- package/lib/commonjs/sqlite/adapters/expo.js +53 -0
- package/lib/commonjs/sqlite/index.js +604 -0
- package/lib/commonjs/storage/index.js +214 -0
- package/lib/commonjs/types/globals.d.js +6 -0
- package/lib/commonjs/utils/imageCompression.js +389 -0
- package/lib/commonjs/utils/index.js +19 -0
- package/lib/commonjs/validators/index.js +227 -0
- package/lib/commonjs/versionCheck/index.js +419 -0
- package/lib/module/api/baseUrl.js +30 -0
- package/lib/module/api/curl.js +104 -0
- package/lib/module/api/publicClient.js +69 -0
- package/lib/module/api/retry.js +69 -0
- package/lib/module/api/tokenRefresh.js +192 -0
- package/lib/module/api/userClient.js +233 -0
- package/lib/module/auth/authStore.js +224 -0
- package/lib/module/auth/index.js +5 -0
- package/lib/module/auth/jwt.js +106 -0
- package/lib/module/config/index.js +58 -0
- package/lib/module/deepLink/index.js +280 -0
- package/lib/module/download/adapters/expoFs.js +64 -0
- package/lib/module/download/adapters/shared.js +8 -0
- package/lib/module/download/index.js +242 -0
- package/lib/module/formatters/currency.js +64 -0
- package/lib/module/formatters/date.js +160 -0
- package/lib/module/formatters/dateMath.js +294 -0
- package/lib/module/formatters/index.js +9 -0
- package/lib/module/formatters/initials.js +27 -0
- package/lib/module/formatters/number.js +81 -0
- package/lib/module/formatters/phone.js +302 -0
- package/lib/module/formatters/windowsToIanaTable.js +160 -0
- package/lib/module/geolocation/adapters/expoLocation.js +121 -0
- package/lib/module/geolocation/index.js +347 -0
- package/lib/module/index.js +54 -0
- package/lib/module/initSDK.js +61 -0
- package/lib/module/initUserAuth.js +33 -0
- package/lib/module/logger/index.js +285 -0
- package/lib/module/network/index.js +4 -0
- package/lib/module/network/networkStatus.js +124 -0
- package/lib/module/package.json +1 -0
- package/lib/module/permissions/calendar.js +15 -0
- package/lib/module/permissions/camera.js +15 -0
- package/lib/module/permissions/contacts.js +11 -0
- package/lib/module/permissions/index.js +128 -0
- package/lib/module/permissions/location.js +15 -0
- package/lib/module/permissions/mediaLibrary.js +11 -0
- package/lib/module/permissions/notifications.js +11 -0
- package/lib/module/permissions/photos.js +15 -0
- package/lib/module/phone/countries.js +261 -0
- package/lib/module/share/index.js +64 -0
- package/lib/module/smsRetriever/index.js +86 -0
- package/lib/module/specs/NativeSmsRetriever.js +13 -0
- package/lib/module/sqlite/adapters/expo.js +48 -0
- package/lib/module/sqlite/index.js +594 -0
- package/lib/module/storage/index.js +208 -0
- package/lib/module/types/globals.d.js +10 -0
- package/lib/module/utils/imageCompression.js +383 -0
- package/lib/module/utils/index.js +4 -0
- package/lib/module/validators/index.js +205 -0
- package/lib/module/versionCheck/index.js +413 -0
- 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/publicClient.d.ts +11 -0
- package/lib/typescript/commonjs/api/retry.d.ts +27 -0
- package/lib/typescript/commonjs/api/tokenRefresh.d.ts +43 -0
- package/lib/typescript/commonjs/api/userClient.d.ts +37 -0
- package/lib/typescript/commonjs/auth/authStore.d.ts +79 -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 +55 -0
- package/lib/typescript/commonjs/deepLink/index.d.ts +54 -0
- package/lib/typescript/commonjs/download/adapters/expoFs.d.ts +38 -0
- package/lib/typescript/commonjs/download/adapters/shared.d.ts +3 -0
- package/lib/typescript/commonjs/download/index.d.ts +86 -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/dateMath.d.ts +88 -0
- package/lib/typescript/commonjs/formatters/index.d.ts +10 -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 +133 -0
- package/lib/typescript/commonjs/formatters/windowsToIanaTable.d.ts +18 -0
- package/lib/typescript/commonjs/geolocation/adapters/expoLocation.d.ts +42 -0
- package/lib/typescript/commonjs/geolocation/index.d.ts +90 -0
- package/lib/typescript/commonjs/index.d.ts +54 -0
- package/lib/typescript/commonjs/initSDK.d.ts +72 -0
- package/lib/typescript/commonjs/initUserAuth.d.ts +41 -0
- package/lib/typescript/commonjs/logger/index.d.ts +81 -0
- package/lib/typescript/commonjs/network/index.d.ts +3 -0
- package/lib/typescript/commonjs/network/networkStatus.d.ts +43 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/permissions/calendar.d.ts +2 -0
- package/lib/typescript/commonjs/permissions/camera.d.ts +2 -0
- package/lib/typescript/commonjs/permissions/contacts.d.ts +2 -0
- package/lib/typescript/commonjs/permissions/index.d.ts +48 -0
- package/lib/typescript/commonjs/permissions/location.d.ts +2 -0
- package/lib/typescript/commonjs/permissions/mediaLibrary.d.ts +2 -0
- package/lib/typescript/commonjs/permissions/notifications.d.ts +2 -0
- package/lib/typescript/commonjs/permissions/photos.d.ts +2 -0
- package/lib/typescript/commonjs/phone/countries.d.ts +13 -0
- package/lib/typescript/commonjs/share/index.d.ts +29 -0
- package/lib/typescript/commonjs/smsRetriever/index.d.ts +30 -0
- package/lib/typescript/commonjs/specs/NativeSmsRetriever.d.ts +15 -0
- package/lib/typescript/commonjs/sqlite/adapters/expo.d.ts +30 -0
- package/lib/typescript/commonjs/sqlite/index.d.ts +113 -0
- package/lib/typescript/commonjs/storage/index.d.ts +64 -0
- package/lib/typescript/commonjs/utils/imageCompression.d.ts +152 -0
- package/lib/typescript/commonjs/utils/index.d.ts +3 -0
- package/lib/typescript/commonjs/validators/index.d.ts +51 -0
- package/lib/typescript/commonjs/versionCheck/index.d.ts +80 -0
- 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/publicClient.d.ts +11 -0
- package/lib/typescript/module/api/retry.d.ts +27 -0
- package/lib/typescript/module/api/tokenRefresh.d.ts +43 -0
- package/lib/typescript/module/api/userClient.d.ts +37 -0
- package/lib/typescript/module/auth/authStore.d.ts +79 -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 +55 -0
- package/lib/typescript/module/deepLink/index.d.ts +54 -0
- package/lib/typescript/module/download/adapters/expoFs.d.ts +38 -0
- package/lib/typescript/module/download/adapters/shared.d.ts +3 -0
- package/lib/typescript/module/download/index.d.ts +86 -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/dateMath.d.ts +88 -0
- package/lib/typescript/module/formatters/index.d.ts +10 -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 +133 -0
- package/lib/typescript/module/formatters/windowsToIanaTable.d.ts +18 -0
- package/lib/typescript/module/geolocation/adapters/expoLocation.d.ts +42 -0
- package/lib/typescript/module/geolocation/index.d.ts +90 -0
- package/lib/typescript/module/index.d.ts +54 -0
- package/lib/typescript/module/initSDK.d.ts +72 -0
- package/lib/typescript/module/initUserAuth.d.ts +41 -0
- package/lib/typescript/module/logger/index.d.ts +81 -0
- package/lib/typescript/module/network/index.d.ts +3 -0
- package/lib/typescript/module/network/networkStatus.d.ts +43 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/permissions/calendar.d.ts +2 -0
- package/lib/typescript/module/permissions/camera.d.ts +2 -0
- package/lib/typescript/module/permissions/contacts.d.ts +2 -0
- package/lib/typescript/module/permissions/index.d.ts +48 -0
- package/lib/typescript/module/permissions/location.d.ts +2 -0
- package/lib/typescript/module/permissions/mediaLibrary.d.ts +2 -0
- package/lib/typescript/module/permissions/notifications.d.ts +2 -0
- package/lib/typescript/module/permissions/photos.d.ts +2 -0
- package/lib/typescript/module/phone/countries.d.ts +13 -0
- package/lib/typescript/module/share/index.d.ts +29 -0
- package/lib/typescript/module/smsRetriever/index.d.ts +30 -0
- package/lib/typescript/module/specs/NativeSmsRetriever.d.ts +15 -0
- package/lib/typescript/module/sqlite/adapters/expo.d.ts +30 -0
- package/lib/typescript/module/sqlite/index.d.ts +113 -0
- package/lib/typescript/module/storage/index.d.ts +64 -0
- package/lib/typescript/module/utils/imageCompression.d.ts +152 -0
- package/lib/typescript/module/utils/index.d.ts +3 -0
- package/lib/typescript/module/validators/index.d.ts +51 -0
- package/lib/typescript/module/versionCheck/index.d.ts +80 -0
- package/package.json +298 -0
- package/src/specs/NativeSmsRetriever.ts +16 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Webority Technologies
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @webority/mobile-core
|
|
2
|
+
|
|
3
|
+
The platform layer for Webority React Native apps: HTTP clients, auth and token storage, config,
|
|
4
|
+
logging, formatters, validators, network status, permissions, storage, image compression and
|
|
5
|
+
version checks. **No UI** — components live in
|
|
6
|
+
[`@webority/mobile-ui`](https://www.npmjs.com/package/@webority/mobile-ui).
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @webority/mobile-core
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Runtime dependencies: `axios` and `libphonenumber-js`. The required peers are `expo`,
|
|
13
|
+
`expo-application`, `expo-network`, `expo-secure-store` (the HTTP client reads the bearer token from
|
|
14
|
+
it) and `expo-sqlite`. Every other native peer is optional and reached only through its own import
|
|
15
|
+
path below, so the package installs and runs without any of them. `react-native-keychain` is no
|
|
16
|
+
longer used; token storage moved to `expo-secure-store`.
|
|
17
|
+
|
|
18
|
+
Full documentation lives in the repository's `docs/` directory.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## Import paths
|
|
22
|
+
|
|
23
|
+
Anything needing a native package lives behind its own path, so an app only bundles what it
|
|
24
|
+
imports:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { Permissions } from '@webority/mobile-core/permissions';
|
|
28
|
+
import { Geolocation } from '@webority/mobile-core/geolocation';
|
|
29
|
+
import { openDatabase } from '@webority/mobile-core/sqlite';
|
|
30
|
+
import { downloadFile } from '@webority/mobile-core/download';
|
|
31
|
+
import { shareFile } from '@webority/mobile-core/share';
|
|
32
|
+
import { compressImage } from '@webority/mobile-core/compressor';
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Metro resolves requires statically, so anything reachable from the main barrel is bundled for
|
|
36
|
+
every consumer. Keeping these off it is what makes their peers genuinely optional.
|
|
37
|
+
|
|
38
|
+
Storage and network status stay on the main barrel but load no package on their own. Wire them at
|
|
39
|
+
startup with `setStorageImplementation` and `setNetworkStatusImplementation` if you want them.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.safeExtGet = { prop, fallback ->
|
|
3
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
4
|
+
}
|
|
5
|
+
repositories {
|
|
6
|
+
google()
|
|
7
|
+
gradlePluginPortal()
|
|
8
|
+
}
|
|
9
|
+
dependencies {
|
|
10
|
+
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${safeExtGet("kotlinVersion", "2.1.20")}")
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
def isNewArchitectureEnabled() {
|
|
15
|
+
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
apply plugin: "com.android.library"
|
|
19
|
+
apply plugin: "kotlin-android"
|
|
20
|
+
|
|
21
|
+
if (isNewArchitectureEnabled()) {
|
|
22
|
+
apply plugin: "com.facebook.react"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
android {
|
|
26
|
+
namespace "com.webority.mobilecore.smsretriever"
|
|
27
|
+
|
|
28
|
+
buildToolsVersion safeExtGet("buildToolsVersion", "36.0.0")
|
|
29
|
+
compileSdkVersion safeExtGet("compileSdkVersion", 36)
|
|
30
|
+
|
|
31
|
+
buildFeatures {
|
|
32
|
+
buildConfig true
|
|
33
|
+
}
|
|
34
|
+
defaultConfig {
|
|
35
|
+
buildConfigField("boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString())
|
|
36
|
+
minSdkVersion safeExtGet("minSdkVersion", 24)
|
|
37
|
+
targetSdkVersion safeExtGet("targetSdkVersion", 36)
|
|
38
|
+
}
|
|
39
|
+
lintOptions {
|
|
40
|
+
abortOnError false
|
|
41
|
+
}
|
|
42
|
+
sourceSets {
|
|
43
|
+
main {
|
|
44
|
+
if (isNewArchitectureEnabled()) {
|
|
45
|
+
java.srcDirs += ["src/newarch"]
|
|
46
|
+
} else {
|
|
47
|
+
java.srcDirs += ["src/oldarch"]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
repositories {
|
|
54
|
+
maven {
|
|
55
|
+
url("$rootDir/../node_modules/react-native/android")
|
|
56
|
+
}
|
|
57
|
+
google()
|
|
58
|
+
mavenCentral()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
dependencies {
|
|
62
|
+
//noinspection GradleDynamicVersion
|
|
63
|
+
implementation "com.facebook.react:react-native:+"
|
|
64
|
+
implementation "com.google.android.gms:play-services-auth-api-phone:18.3.0"
|
|
65
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
package com.webority.mobilecore.smsretriever
|
|
2
|
+
|
|
3
|
+
import android.content.pm.PackageManager
|
|
4
|
+
import android.util.Base64
|
|
5
|
+
import android.util.Log
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
7
|
+
import java.security.MessageDigest
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The 11-character app-signature hash the SMS Retriever API matches against
|
|
11
|
+
* an incoming message — the backend appends this same hash so Play Services
|
|
12
|
+
* knows which app to hand the SMS to, with no SMS permission granted.
|
|
13
|
+
* Algorithm is Google's own (SHA-256 of "<packageName> <signature>", first 9
|
|
14
|
+
* bytes, base64 NO_PADDING/NO_WRAP, first 11 chars) — not our own scheme.
|
|
15
|
+
*/
|
|
16
|
+
class AppSignatureHelper(private val context: ReactApplicationContext) {
|
|
17
|
+
companion object {
|
|
18
|
+
private const val HASH_TYPE = "SHA-256"
|
|
19
|
+
private const val NUM_HASHED_BYTES = 9
|
|
20
|
+
private const val NUM_BASE64_CHAR = 11
|
|
21
|
+
private const val TAG = "AppSignatureHelper"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
fun getAppSignatures(): List<String> {
|
|
25
|
+
val appCodes = mutableListOf<String>()
|
|
26
|
+
try {
|
|
27
|
+
val packageName = context.packageName
|
|
28
|
+
val packageManager = context.packageManager
|
|
29
|
+
@Suppress("DEPRECATION")
|
|
30
|
+
val signatures = packageManager.getPackageInfo(
|
|
31
|
+
packageName,
|
|
32
|
+
PackageManager.GET_SIGNATURES
|
|
33
|
+
).signatures
|
|
34
|
+
|
|
35
|
+
for (signature in signatures ?: emptyArray()) {
|
|
36
|
+
hash(packageName, signature.toCharsString())?.let { appCodes.add(it) }
|
|
37
|
+
}
|
|
38
|
+
} catch (e: Exception) {
|
|
39
|
+
Log.e(TAG, "Error getting app signature", e)
|
|
40
|
+
}
|
|
41
|
+
return appCodes
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private fun hash(packageName: String, signature: String): String? {
|
|
45
|
+
val appInfo = "$packageName $signature"
|
|
46
|
+
return try {
|
|
47
|
+
val digest = MessageDigest.getInstance(HASH_TYPE)
|
|
48
|
+
digest.update(appInfo.toByteArray())
|
|
49
|
+
val truncated = digest.digest().copyOfRange(0, NUM_HASHED_BYTES)
|
|
50
|
+
Base64.encodeToString(truncated, Base64.NO_PADDING or Base64.NO_WRAP)
|
|
51
|
+
.substring(0, NUM_BASE64_CHAR)
|
|
52
|
+
} catch (e: Exception) {
|
|
53
|
+
Log.e(TAG, "Hash generation failed", e)
|
|
54
|
+
null
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package com.webority.mobilecore.smsretriever
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Promise
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
|
|
6
|
+
class SmsRetrieverModule(reactContext: ReactApplicationContext) :
|
|
7
|
+
NativeSmsRetrieverSpec(reactContext) {
|
|
8
|
+
|
|
9
|
+
companion object {
|
|
10
|
+
const val NAME = "WeborityMobileCoreSmsRetriever"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
private val impl = SmsRetrieverModuleImpl(reactContext)
|
|
14
|
+
|
|
15
|
+
override fun startSMSListener() = impl.startSMSListener()
|
|
16
|
+
|
|
17
|
+
override fun stopSMSListener() = impl.stopSMSListener()
|
|
18
|
+
|
|
19
|
+
override fun getAppHash(promise: Promise) = impl.getAppHash(promise)
|
|
20
|
+
}
|
package/android/src/newarch/java/com/webority/mobilecore/smsretriever/SmsRetrieverModuleImpl.kt
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
package com.webority.mobilecore.smsretriever
|
|
2
|
+
|
|
3
|
+
import android.content.BroadcastReceiver
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.content.IntentFilter
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import android.util.Log
|
|
9
|
+
import androidx.core.content.ContextCompat
|
|
10
|
+
import com.facebook.react.bridge.Arguments
|
|
11
|
+
import com.facebook.react.bridge.Promise
|
|
12
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
13
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
14
|
+
import com.google.android.gms.auth.api.phone.SmsRetriever
|
|
15
|
+
import com.google.android.gms.auth.api.phone.SmsRetrieverClient
|
|
16
|
+
import com.google.android.gms.common.api.CommonStatusCodes
|
|
17
|
+
import com.google.android.gms.common.api.Status
|
|
18
|
+
|
|
19
|
+
private enum class SmsErrorType { TIMEOUT, SERVICE_UNAVAILABLE, UNKNOWN_ERROR }
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Wraps the Play Services SMS Retriever API. Emits the raw SMS text on
|
|
23
|
+
* success — OTP extraction is a JS-side concern (see smsRetriever/index.ts),
|
|
24
|
+
* since the expected format varies per backend and this module's only job
|
|
25
|
+
* is retrieving the message with no SMS permission required.
|
|
26
|
+
*/
|
|
27
|
+
class SmsRetrieverModuleImpl(private val reactContext: ReactApplicationContext) {
|
|
28
|
+
companion object {
|
|
29
|
+
const val NAME = "WeborityMobileCoreSmsRetriever"
|
|
30
|
+
private const val TAG = "SmsRetrieverModuleImpl"
|
|
31
|
+
private const val OTP_EVENT = "WeborityMobileCoreSmsRetrieved"
|
|
32
|
+
private const val ERROR_EVENT = "WeborityMobileCoreSmsRetrieverError"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private var isRegistered = false
|
|
36
|
+
private var isListening = false
|
|
37
|
+
|
|
38
|
+
private val receiver = object : BroadcastReceiver() {
|
|
39
|
+
override fun onReceive(context: Context, intent: Intent) {
|
|
40
|
+
if (SmsRetriever.SMS_RETRIEVED_ACTION != intent.action) {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
val status = intent.extras?.getParcelable<Status>(SmsRetriever.EXTRA_STATUS)
|
|
44
|
+
if (status == null) {
|
|
45
|
+
emitError(SmsErrorType.UNKNOWN_ERROR, "Invalid SMS status")
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
when (status.statusCode) {
|
|
49
|
+
CommonStatusCodes.SUCCESS -> {
|
|
50
|
+
val message = intent.extras?.getString(SmsRetriever.EXTRA_SMS_MESSAGE)
|
|
51
|
+
if (message.isNullOrBlank()) {
|
|
52
|
+
emitError(SmsErrorType.UNKNOWN_ERROR, "Empty SMS message")
|
|
53
|
+
} else {
|
|
54
|
+
emitMessage(message)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
CommonStatusCodes.TIMEOUT ->
|
|
58
|
+
emitError(SmsErrorType.TIMEOUT, "SMS retrieval timeout")
|
|
59
|
+
else ->
|
|
60
|
+
emitError(SmsErrorType.SERVICE_UNAVAILABLE, "SMS Retriever status ${status.statusCode}")
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private fun emitMessage(message: String) {
|
|
66
|
+
isListening = false
|
|
67
|
+
val payload = Arguments.createMap().apply { putString("message", message) }
|
|
68
|
+
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
69
|
+
.emit(OTP_EVENT, payload)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private fun emitError(type: SmsErrorType, message: String) {
|
|
73
|
+
Log.w(TAG, "$type: $message")
|
|
74
|
+
isListening = false
|
|
75
|
+
val payload = Arguments.createMap().apply {
|
|
76
|
+
putString("type", type.name)
|
|
77
|
+
putString("message", message)
|
|
78
|
+
}
|
|
79
|
+
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
80
|
+
.emit(ERROR_EVENT, payload)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fun startSMSListener() {
|
|
84
|
+
if (isListening) {
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
val client: SmsRetrieverClient = SmsRetriever.getClient(reactContext)
|
|
89
|
+
client.startSmsRetriever()
|
|
90
|
+
.addOnSuccessListener {
|
|
91
|
+
registerReceiver()
|
|
92
|
+
isListening = true
|
|
93
|
+
}
|
|
94
|
+
.addOnFailureListener { exception ->
|
|
95
|
+
emitError(SmsErrorType.SERVICE_UNAVAILABLE, exception.message ?: "startSmsRetriever failed")
|
|
96
|
+
}
|
|
97
|
+
} catch (e: Exception) {
|
|
98
|
+
emitError(SmsErrorType.UNKNOWN_ERROR, e.message ?: "startSMSListener threw")
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private fun registerReceiver() {
|
|
103
|
+
if (isRegistered) {
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
val filter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION)
|
|
108
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
109
|
+
ContextCompat.registerReceiver(reactContext, receiver, filter, ContextCompat.RECEIVER_EXPORTED)
|
|
110
|
+
} else {
|
|
111
|
+
ContextCompat.registerReceiver(reactContext, receiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED)
|
|
112
|
+
}
|
|
113
|
+
isRegistered = true
|
|
114
|
+
} catch (e: Exception) {
|
|
115
|
+
emitError(SmsErrorType.UNKNOWN_ERROR, e.message ?: "Failed to register SMS receiver")
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fun stopSMSListener() {
|
|
120
|
+
if (!isRegistered) {
|
|
121
|
+
isListening = false
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
reactContext.unregisterReceiver(receiver)
|
|
126
|
+
} catch (e: IllegalArgumentException) {
|
|
127
|
+
// Already unregistered — not an error, the OS may have torn it down first.
|
|
128
|
+
} finally {
|
|
129
|
+
isRegistered = false
|
|
130
|
+
isListening = false
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fun getAppHash(promise: Promise) {
|
|
135
|
+
try {
|
|
136
|
+
val hash = AppSignatureHelper(reactContext).getAppSignatures().firstOrNull()
|
|
137
|
+
if (hash == null) {
|
|
138
|
+
promise.reject("NO_SIGNATURES", "No app signatures found")
|
|
139
|
+
} else {
|
|
140
|
+
promise.resolve(hash)
|
|
141
|
+
}
|
|
142
|
+
} catch (e: Exception) {
|
|
143
|
+
promise.reject("HASH_ERROR", "Failed to get app hash: ${e.message}")
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package com.webority.mobilecore.smsretriever
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.TurboReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
|
|
9
|
+
class SmsRetrieverPackage : TurboReactPackage() {
|
|
10
|
+
|
|
11
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
12
|
+
return if (name == SmsRetrieverModule.NAME) {
|
|
13
|
+
SmsRetrieverModule(reactContext)
|
|
14
|
+
} else {
|
|
15
|
+
null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
20
|
+
return ReactModuleInfoProvider {
|
|
21
|
+
mapOf(
|
|
22
|
+
SmsRetrieverModule.NAME to
|
|
23
|
+
ReactModuleInfo(
|
|
24
|
+
SmsRetrieverModule.NAME,
|
|
25
|
+
SmsRetrieverModule.NAME,
|
|
26
|
+
false, // canOverrideExistingModule
|
|
27
|
+
false, // needsEagerInit
|
|
28
|
+
false, // isCxxModule
|
|
29
|
+
true // isTurboModule
|
|
30
|
+
))
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.applyConfiguredBaseUrl = void 0;
|
|
7
|
+
var _index = require("../config/index.js");
|
|
8
|
+
/**
|
|
9
|
+
* Resolves `baseURL` PER REQUEST rather than at module evaluation.
|
|
10
|
+
*
|
|
11
|
+
* `axios.create({ baseURL: getConfigValue('baseUrl') })` reads the value once,
|
|
12
|
+
* when the module is first imported — which happens the moment anything touches
|
|
13
|
+
* the package barrel, long before an app's startup code can call `setConfig`.
|
|
14
|
+
* The result was a client permanently pinned to `undefined` and a `setConfig`
|
|
15
|
+
* that appeared to do nothing, with no error anywhere: requests just went to a
|
|
16
|
+
* relative URL.
|
|
17
|
+
*
|
|
18
|
+
* A consumer cannot fix that by ordering its own imports, because the import
|
|
19
|
+
* that loses the race is usually one it never wrote. So the clients stop
|
|
20
|
+
* capturing the value and ask for it per request instead.
|
|
21
|
+
*
|
|
22
|
+
* An explicit per-request `baseURL` still wins, which is what keeps a one-off
|
|
23
|
+
* call to a different host possible.
|
|
24
|
+
*/
|
|
25
|
+
const applyConfiguredBaseUrl = client => {
|
|
26
|
+
client.interceptors.request.use(config => {
|
|
27
|
+
if (!config.baseURL) {
|
|
28
|
+
config.baseURL = (0, _index.getConfigValue)('baseUrl') ?? undefined;
|
|
29
|
+
}
|
|
30
|
+
return config;
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
exports.applyConfiguredBaseUrl = applyConfiguredBaseUrl;
|
|
34
|
+
//# sourceMappingURL=baseUrl.js.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.logCurl = exports.buildCurl = void 0;
|
|
7
|
+
var _index = require("../logger/index.js");
|
|
8
|
+
/**
|
|
9
|
+
* Header names whose VALUE is a credential. Matched case-insensitively against
|
|
10
|
+
* the whole name, since `Authorization` and `authorization` are the same header
|
|
11
|
+
* and axios does not normalise the case of headers a caller sets by hand.
|
|
12
|
+
*/
|
|
13
|
+
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']);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Body fields whose value is a credential. Matched on the key alone, so this
|
|
17
|
+
* catches `password`, `newPassword` and `password_confirmation` alike.
|
|
18
|
+
*/
|
|
19
|
+
const SECRET_BODY_PATTERN = /pass(word|code)|secret|token|otp|pin|cvv|authoriz/i;
|
|
20
|
+
|
|
21
|
+
/** The axios per-method header buckets, which are config plumbing, not headers. */
|
|
22
|
+
const AXIOS_METHOD_KEYS = new Set(['common', 'delete', 'get', 'head', 'post', 'put', 'patch']);
|
|
23
|
+
const REDACTED = '<redacted>';
|
|
24
|
+
const redactBody = (value, seen, depth = 0) => {
|
|
25
|
+
if (value === null || typeof value !== 'object') {
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
// Both guards are needed and they catch different shapes. `seen` breaks a
|
|
29
|
+
// true cycle, which would otherwise recurse forever AND make the eventual
|
|
30
|
+
// JSON.stringify throw. The depth bound catches a body that is merely
|
|
31
|
+
// enormous. A curl line is a debugging aid, and it must never be able to
|
|
32
|
+
// hang or fail the request it is describing.
|
|
33
|
+
if (seen.has(value)) {
|
|
34
|
+
return '<circular>';
|
|
35
|
+
}
|
|
36
|
+
if (depth > 6) {
|
|
37
|
+
return '<truncated>';
|
|
38
|
+
}
|
|
39
|
+
seen.add(value);
|
|
40
|
+
if (Array.isArray(value)) {
|
|
41
|
+
return value.map(entry => redactBody(entry, seen, depth + 1));
|
|
42
|
+
}
|
|
43
|
+
const out = {};
|
|
44
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
45
|
+
out[key] = SECRET_BODY_PATTERN.test(key) ? REDACTED : redactBody(entry, seen, depth + 1);
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
};
|
|
49
|
+
const buildUrl = config => {
|
|
50
|
+
const url = config.url ?? '';
|
|
51
|
+
const base = config.baseURL ? config.baseURL.replace(/\/$/, '') : '';
|
|
52
|
+
let complete = url.startsWith('http') ? url : `${base}/${url.replace(/^\//, '')}`;
|
|
53
|
+
if (config.params) {
|
|
54
|
+
const query = new URLSearchParams(config.params).toString();
|
|
55
|
+
if (query) {
|
|
56
|
+
complete += (complete.includes('?') ? '&' : '?') + query;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return complete;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Builds a copy-pasteable curl command for a request, with every credential
|
|
64
|
+
* replaced by a placeholder.
|
|
65
|
+
*
|
|
66
|
+
* The redaction is the point, not a nicety. An unredacted version of this in a
|
|
67
|
+
* consumer app put `Authorization: Bearer <token>` and the login request's
|
|
68
|
+
* plaintext password into the device console of a *release* staging build.
|
|
69
|
+
* A curl line is worth having; a curl line that has to be trusted not to be
|
|
70
|
+
* enabled in the wrong build is not.
|
|
71
|
+
*/
|
|
72
|
+
const buildCurl = config => {
|
|
73
|
+
const method = (config.method ?? 'get').toUpperCase();
|
|
74
|
+
let command = `curl -X ${method} "${buildUrl(config)}"`;
|
|
75
|
+
const headers = config.headers;
|
|
76
|
+
if (headers) {
|
|
77
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
78
|
+
if (AXIOS_METHOD_KEYS.has(key)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (typeof value !== 'string' && typeof value !== 'number') {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const shown = SECRET_HEADERS.has(key.toLowerCase()) ? REDACTED : value;
|
|
85
|
+
command += ` -H "${key}: ${shown}"`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (config.data !== undefined && config.data !== null) {
|
|
89
|
+
const redacted = typeof config.data === 'object' ? redactBody(config.data, new WeakSet()) : config.data;
|
|
90
|
+
const body = typeof redacted === 'object' ? JSON.stringify(redacted) : String(redacted);
|
|
91
|
+
command += ` -d '${body.replace(/'/g, "'\\''")}'`;
|
|
92
|
+
}
|
|
93
|
+
return command;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Logs the curl equivalent of a request. Never throws: a failure to describe a
|
|
98
|
+
* request must not be able to fail the request itself.
|
|
99
|
+
*/
|
|
100
|
+
exports.buildCurl = buildCurl;
|
|
101
|
+
const logCurl = config => {
|
|
102
|
+
try {
|
|
103
|
+
_index.Logger.info('CURL Command:', buildCurl(config));
|
|
104
|
+
} catch (error) {
|
|
105
|
+
_index.Logger.warn('[api] Failed to build a curl command for the request', error);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
exports.logCurl = logCurl;
|
|
109
|
+
//# sourceMappingURL=curl.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _axios = _interopRequireDefault(require("axios"));
|
|
8
|
+
var _index = require("../config/index.js");
|
|
9
|
+
var _index2 = require("../logger/index.js");
|
|
10
|
+
var _baseUrl = require("./baseUrl.js");
|
|
11
|
+
var _curl = require("./curl.js");
|
|
12
|
+
var _retry = require("./retry.js");
|
|
13
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
|
+
// Define timeout for requests
|
|
15
|
+
const REQUEST_TIMEOUT = 30000; // 30 seconds
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Public Axios Client
|
|
19
|
+
* - Base URL: Set your public API base URL.
|
|
20
|
+
* - No Authorization header is attached.
|
|
21
|
+
* - Includes timeout configuration and error handling.
|
|
22
|
+
* - Handles specific HTTP status codes.
|
|
23
|
+
*/
|
|
24
|
+
const publicClient = _axios.default.create({
|
|
25
|
+
headers: {
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
Accept: 'application/json',
|
|
28
|
+
'Accept-Encoding': 'gzip, deflate, br'
|
|
29
|
+
},
|
|
30
|
+
timeout: REQUEST_TIMEOUT
|
|
31
|
+
});
|
|
32
|
+
(0, _baseUrl.applyConfiguredBaseUrl)(publicClient);
|
|
33
|
+
|
|
34
|
+
// Registered before the logging interceptors below so they only ever observe
|
|
35
|
+
// the final outcome of a request, not its intermediate retries. The policy
|
|
36
|
+
// itself, and why a bare network error is never retried, lives in ./retry.
|
|
37
|
+
(0, _retry.attachRetry)(publicClient);
|
|
38
|
+
|
|
39
|
+
// Add request interceptor for logging
|
|
40
|
+
publicClient.interceptors.request.use(config => {
|
|
41
|
+
_index2.Logger.info(`Request: ${config.method?.toUpperCase()} ${config.url}`);
|
|
42
|
+
if ((0, _index.getConfigValue)('logCurl')) {
|
|
43
|
+
(0, _curl.logCurl)(config);
|
|
44
|
+
}
|
|
45
|
+
return config;
|
|
46
|
+
}, error => {
|
|
47
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
48
|
+
_index2.Logger.error('Request error:', message);
|
|
49
|
+
return Promise.reject(error);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Add response interceptor for error handling
|
|
53
|
+
publicClient.interceptors.response.use(response => {
|
|
54
|
+
_index2.Logger.info(`Response: ${response.status} from ${response.config.url}`);
|
|
55
|
+
return response;
|
|
56
|
+
}, error => {
|
|
57
|
+
if (_axios.default.isAxiosError(error)) {
|
|
58
|
+
const originalRequest = error.config;
|
|
59
|
+
if (!error.response) {
|
|
60
|
+
_index2.Logger.error(`Network error or no response from server: ${error.message}, URL: ${originalRequest?.url}`);
|
|
61
|
+
} else {
|
|
62
|
+
const body = error.response.data;
|
|
63
|
+
const bodyText = body instanceof ArrayBuffer ? `binary response (${body.byteLength} bytes)` : typeof Blob !== 'undefined' && body instanceof Blob ? `binary response (${body.size} bytes)` : body ?? error.message;
|
|
64
|
+
_index2.Logger.error(`Response error: ${bodyText}, URL: ${originalRequest?.url}, Status: ${error.response.status}`);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
68
|
+
_index2.Logger.error(`Unknown response error: ${message}`);
|
|
69
|
+
}
|
|
70
|
+
return Promise.reject(error);
|
|
71
|
+
});
|
|
72
|
+
var _default = exports.default = publicClient;
|
|
73
|
+
//# sourceMappingURL=publicClient.js.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.isRetryableStatus = exports.attachRetry = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Status-based ONLY, deliberately: a bare network error (no `error.response`)
|
|
9
|
+
* is never retryable. Both clients carry non-idempotent requests, login and OTP
|
|
10
|
+
* on the public one and every write on the user one, and a broken connection
|
|
11
|
+
* says nothing about whether the request reached the server. Retrying can
|
|
12
|
+
* therefore create a second session, or a duplicate record, that the user never
|
|
13
|
+
* sees. A generic network-error retry rule is not a substitute: it decides on
|
|
14
|
+
* the transport alone and never looks at the HTTP method, so it retries POSTs
|
|
15
|
+
* as readily as GETs.
|
|
16
|
+
*/
|
|
17
|
+
const isRetryableStatus = status => typeof status === 'number' && (status === 429 || status >= 500 && status <= 599);
|
|
18
|
+
exports.isRetryableStatus = isRetryableStatus;
|
|
19
|
+
const DEFAULT_POLICY = {
|
|
20
|
+
retries: 3,
|
|
21
|
+
retryDelayMs: attempt => attempt <= 1 ? 0 : 2 ** attempt * 1000,
|
|
22
|
+
shouldRetry: error => isRetryableStatus(error.response?.status),
|
|
23
|
+
onRetry: () => undefined
|
|
24
|
+
};
|
|
25
|
+
const waitOrAbort = (delay, signal) => new Promise(resolve => {
|
|
26
|
+
const onAbort = () => {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
resolve(false);
|
|
29
|
+
};
|
|
30
|
+
const timer = setTimeout(() => {
|
|
31
|
+
signal?.removeEventListener?.('abort', onAbort);
|
|
32
|
+
resolve(true);
|
|
33
|
+
}, delay);
|
|
34
|
+
signal?.addEventListener?.('abort', onAbort, {
|
|
35
|
+
once: true
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
const attachRetry = (client, policy = {}) => {
|
|
39
|
+
client.interceptors.response.use(undefined, async error => {
|
|
40
|
+
const axiosError = error;
|
|
41
|
+
const config = axiosError?.config;
|
|
42
|
+
if (!config || config.retry === false) {
|
|
43
|
+
return Promise.reject(error);
|
|
44
|
+
}
|
|
45
|
+
const perRequest = typeof config.retry === 'object' ? config.retry : {};
|
|
46
|
+
const effective = {
|
|
47
|
+
...DEFAULT_POLICY,
|
|
48
|
+
...policy,
|
|
49
|
+
...perRequest
|
|
50
|
+
};
|
|
51
|
+
const attempt = (config.__retryAttempt ?? 0) + 1;
|
|
52
|
+
if (attempt > effective.retries || !effective.shouldRetry(axiosError)) {
|
|
53
|
+
return Promise.reject(error);
|
|
54
|
+
}
|
|
55
|
+
if (config.signal?.aborted) {
|
|
56
|
+
return Promise.reject(error);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// The attempt count rides on the config, which axios copies onto the config
|
|
60
|
+
// of the retried request, so nested attempts continue the same count and
|
|
61
|
+
// separate requests never share one.
|
|
62
|
+
config.__retryAttempt = attempt;
|
|
63
|
+
// The body was serialised by the first pass; transforming it again would
|
|
64
|
+
// JSON-encode the already-encoded string.
|
|
65
|
+
config.transformRequest = [data => data];
|
|
66
|
+
effective.onRetry(attempt, axiosError);
|
|
67
|
+
const proceed = await waitOrAbort(effective.retryDelayMs(attempt), config.signal);
|
|
68
|
+
if (!proceed) {
|
|
69
|
+
return Promise.reject(error);
|
|
70
|
+
}
|
|
71
|
+
return client.request(config);
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
exports.attachRetry = attachRetry;
|
|
75
|
+
//# sourceMappingURL=retry.js.map
|