react-native-cliqit 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.
Files changed (39) hide show
  1. package/README.md +209 -0
  2. package/android/build.gradle +27 -0
  3. package/android/src/main/AndroidManifest.xml +1 -0
  4. package/android/src/main/java/com/cliqit/CliqItModule.kt +242 -0
  5. package/android/src/main/java/com/cliqit/CliqItPackage.kt +14 -0
  6. package/android/src/main/java/com/cliqit/sdk/CliqItDefaults.kt +17 -0
  7. package/android/src/main/java/com/cliqit/sdk/CliqItDeviceInfo.kt +196 -0
  8. package/android/src/main/java/com/cliqit/sdk/CliqItHttp.kt +98 -0
  9. package/android/src/main/java/com/cliqit/sdk/CliqItModels.kt +130 -0
  10. package/android/src/main/java/com/cliqit/sdk/CliqItSDK.kt +514 -0
  11. package/android/src/main/java/com/cliqit/sdk/CliqItWebFingerprint.kt +230 -0
  12. package/ios/CliqItModule.m +14 -0
  13. package/ios/CliqItModule.swift +238 -0
  14. package/ios/Frameworks/CliqIt.xcframework/Info.plist +44 -0
  15. package/ios/Frameworks/CliqIt.xcframework/ios-arm64/CliqIt.framework/CliqIt +0 -0
  16. package/ios/Frameworks/CliqIt.xcframework/ios-arm64/CliqIt.framework/Headers/CliqIt-Swift.h +382 -0
  17. package/ios/Frameworks/CliqIt.xcframework/ios-arm64/CliqIt.framework/Info.plist +0 -0
  18. package/ios/Frameworks/CliqIt.xcframework/ios-arm64/CliqIt.framework/Modules/CliqIt.swiftmodule/arm64-apple-ios.abi.json +12111 -0
  19. package/ios/Frameworks/CliqIt.xcframework/ios-arm64/CliqIt.framework/Modules/CliqIt.swiftmodule/arm64-apple-ios.private.swiftinterface +419 -0
  20. package/ios/Frameworks/CliqIt.xcframework/ios-arm64/CliqIt.framework/Modules/CliqIt.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  21. package/ios/Frameworks/CliqIt.xcframework/ios-arm64/CliqIt.framework/Modules/CliqIt.swiftmodule/arm64-apple-ios.swiftinterface +419 -0
  22. package/ios/Frameworks/CliqIt.xcframework/ios-arm64/CliqIt.framework/Modules/module.modulemap +3 -0
  23. package/ios/Frameworks/CliqIt.xcframework/ios-arm64_x86_64-simulator/CliqIt.framework/CliqIt +0 -0
  24. package/ios/Frameworks/CliqIt.xcframework/ios-arm64_x86_64-simulator/CliqIt.framework/Headers/CliqIt-Swift.h +760 -0
  25. package/ios/Frameworks/CliqIt.xcframework/ios-arm64_x86_64-simulator/CliqIt.framework/Info.plist +0 -0
  26. package/ios/Frameworks/CliqIt.xcframework/ios-arm64_x86_64-simulator/CliqIt.framework/Modules/CliqIt.swiftmodule/arm64-apple-ios-simulator.abi.json +12111 -0
  27. package/ios/Frameworks/CliqIt.xcframework/ios-arm64_x86_64-simulator/CliqIt.framework/Modules/CliqIt.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +419 -0
  28. package/ios/Frameworks/CliqIt.xcframework/ios-arm64_x86_64-simulator/CliqIt.framework/Modules/CliqIt.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  29. package/ios/Frameworks/CliqIt.xcframework/ios-arm64_x86_64-simulator/CliqIt.framework/Modules/CliqIt.swiftmodule/arm64-apple-ios-simulator.swiftinterface +419 -0
  30. package/ios/Frameworks/CliqIt.xcframework/ios-arm64_x86_64-simulator/CliqIt.framework/Modules/CliqIt.swiftmodule/x86_64-apple-ios-simulator.abi.json +12111 -0
  31. package/ios/Frameworks/CliqIt.xcframework/ios-arm64_x86_64-simulator/CliqIt.framework/Modules/CliqIt.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +419 -0
  32. package/ios/Frameworks/CliqIt.xcframework/ios-arm64_x86_64-simulator/CliqIt.framework/Modules/CliqIt.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  33. package/ios/Frameworks/CliqIt.xcframework/ios-arm64_x86_64-simulator/CliqIt.framework/Modules/CliqIt.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +419 -0
  34. package/ios/Frameworks/CliqIt.xcframework/ios-arm64_x86_64-simulator/CliqIt.framework/Modules/module.modulemap +3 -0
  35. package/package.json +48 -0
  36. package/react-native-cliqit.podspec +27 -0
  37. package/react-native.config.js +11 -0
  38. package/src/index.d.ts +92 -0
  39. package/src/index.js +152 -0
@@ -0,0 +1,196 @@
1
+ package com.cliqit.sdk
2
+
3
+ import android.content.Context
4
+ import android.content.Intent
5
+ import android.content.IntentFilter
6
+ import android.content.pm.PackageManager
7
+ import android.content.res.Configuration
8
+ import android.net.ConnectivityManager
9
+ import android.net.NetworkCapabilities
10
+ import android.os.BatteryManager
11
+ import android.os.Build
12
+ import android.util.DisplayMetrics
13
+ import android.view.WindowManager
14
+ import java.security.MessageDigest
15
+ import java.util.Currency
16
+ import java.util.Locale
17
+ import java.util.TimeZone
18
+
19
+ internal object CliqItDeviceInfo {
20
+ fun osVersionMajor(): String =
21
+ Build.VERSION.RELEASE.substringBefore('.').ifEmpty { Build.VERSION.SDK_INT.toString() }
22
+
23
+ fun osVersionMajorMinor(): String {
24
+ val parts = Build.VERSION.RELEASE.split('.')
25
+ return when {
26
+ parts.size >= 2 -> "${parts[0]}.${parts[1]}"
27
+ parts.isNotEmpty() -> parts[0]
28
+ else -> Build.VERSION.SDK_INT.toString()
29
+ }
30
+ }
31
+
32
+ fun deviceName(): String = Build.MODEL ?: "unknown"
33
+
34
+ fun matchLocale(): String =
35
+ Locale.getDefault().toLanguageTag().ifEmpty { Locale.getDefault().toString().replace('_', '-') }
36
+
37
+ fun languagesOrdered(): String {
38
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
39
+ val list = android.os.LocaleList.getDefault()
40
+ (0 until list.size()).joinToString(",") { i -> list[i].toLanguageTag() }
41
+ .ifEmpty { Locale.getDefault().toLanguageTag() }
42
+ } else {
43
+ Locale.getDefault().toLanguageTag()
44
+ }
45
+ }
46
+
47
+ fun boldText(context: Context): Boolean {
48
+ return try {
49
+ android.provider.Settings.Secure.getInt(
50
+ context.contentResolver,
51
+ "accessibility_bold_text",
52
+ 0,
53
+ ) == 1
54
+ } catch (_: Exception) {
55
+ false
56
+ }
57
+ }
58
+
59
+ fun reduceMotion(context: Context): Boolean {
60
+ return try {
61
+ val scale = android.provider.Settings.Global.getFloat(
62
+ context.contentResolver,
63
+ android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE,
64
+ 1f,
65
+ )
66
+ scale == 0f
67
+ } catch (_: Exception) {
68
+ false
69
+ }
70
+ }
71
+
72
+ fun increaseContrast(context: Context): Boolean {
73
+ return try {
74
+ android.provider.Settings.Secure.getInt(
75
+ context.contentResolver,
76
+ "high_text_contrast_enabled",
77
+ 0,
78
+ ) == 1
79
+ } catch (_: Exception) {
80
+ false
81
+ }
82
+ }
83
+
84
+ fun timezone(): String = TimeZone.getDefault().id
85
+
86
+ fun screenBucket(context: Context): String {
87
+ val metrics = DisplayMetrics()
88
+ @Suppress("DEPRECATION")
89
+ (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.getMetrics(metrics)
90
+ val minSide = minOf(metrics.widthPixels, metrics.heightPixels).toFloat() / metrics.density
91
+ val maxSide = maxOf(metrics.widthPixels, metrics.heightPixels).toFloat() / metrics.density
92
+ return when {
93
+ minSide >= 600 -> "tablet"
94
+ maxSide >= 800 -> "large"
95
+ maxSide >= 700 -> "medium"
96
+ else -> "small"
97
+ }
98
+ }
99
+
100
+ fun colorScheme(context: Context): String {
101
+ val night = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
102
+ return if (night == Configuration.UI_MODE_NIGHT_YES) "dark" else "light"
103
+ }
104
+
105
+ fun hourCycle(): String {
106
+ val pattern = android.text.format.DateFormat.getBestDateTimePattern(Locale.getDefault(), "j")
107
+ return if (pattern.contains('a')) "h12" else "h24"
108
+ }
109
+
110
+ fun currencyCode(): String? = try {
111
+ Currency.getInstance(Locale.getDefault()).currencyCode
112
+ } catch (_: Exception) {
113
+ null
114
+ }
115
+
116
+ fun regionCode(): String? = Locale.getDefault().country.takeIf { it.isNotEmpty() }
117
+
118
+ /// Matches iOS / web: `"high"` (≥2×) or `"standard"`.
119
+ fun devicePixelRatioBucket(context: Context): String {
120
+ val density = context.resources.displayMetrics.density
121
+ return if (density >= 2.0f) "high" else "standard"
122
+ }
123
+
124
+ fun battery(context: Context): Pair<Double?, Boolean?> {
125
+ val intent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
126
+ ?: return null to null
127
+ val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
128
+ val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
129
+ val status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
130
+ val pct = if (level >= 0 && scale > 0) level.toDouble() / scale.toDouble() else null
131
+ val charging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
132
+ status == BatteryManager.BATTERY_STATUS_FULL
133
+ return pct to charging
134
+ }
135
+
136
+ fun connectionType(context: Context): String? {
137
+ val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager ?: return null
138
+ val network = cm.activeNetwork ?: return null
139
+ val caps = cm.getNetworkCapabilities(network) ?: return null
140
+ return when {
141
+ caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> "wifi"
142
+ caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> "cellular"
143
+ caps.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> "ethernet"
144
+ else -> "other"
145
+ }
146
+ }
147
+
148
+ fun hardwareConcurrency(): Int = Runtime.getRuntime().availableProcessors()
149
+
150
+ fun fontScale(context: Context): String {
151
+ val scale = context.resources.configuration.fontScale
152
+ return when {
153
+ scale <= 0.85f -> "S"
154
+ scale <= 1.0f -> "M"
155
+ scale <= 1.15f -> "L"
156
+ scale <= 1.3f -> "XL"
157
+ else -> "XXL"
158
+ }
159
+ }
160
+
161
+ fun packageName(context: Context): String = context.packageName
162
+
163
+ /** Signing cert SHA-256, colon-separated uppercase (same style as keytool / assetlinks). */
164
+ fun androidSha256(context: Context): String? {
165
+ return try {
166
+ val pm = context.packageManager
167
+ val packageName = context.packageName
168
+ val signatures: Array<android.content.pm.Signature> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
169
+ val info = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
170
+ pm.getPackageInfo(
171
+ packageName,
172
+ PackageManager.PackageInfoFlags.of(PackageManager.GET_SIGNING_CERTIFICATES.toLong()),
173
+ )
174
+ } else {
175
+ @Suppress("DEPRECATION")
176
+ pm.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES)
177
+ }
178
+ val signingInfo = info.signingInfo ?: return null
179
+ if (signingInfo.hasMultipleSigners()) {
180
+ signingInfo.apkContentsSigners
181
+ } else {
182
+ signingInfo.signingCertificateHistory
183
+ }
184
+ } else {
185
+ @Suppress("DEPRECATION")
186
+ pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES).signatures
187
+ } ?: return null
188
+
189
+ val cert = signatures.firstOrNull()?.toByteArray() ?: return null
190
+ val digest = MessageDigest.getInstance("SHA-256").digest(cert)
191
+ digest.joinToString(":") { b -> "%02X".format(b) }
192
+ } catch (e: Exception) {
193
+ null
194
+ }
195
+ }
196
+ }
@@ -0,0 +1,98 @@
1
+ package com.cliqit.sdk
2
+
3
+ import org.json.JSONObject
4
+ import java.io.BufferedReader
5
+ import java.io.InputStreamReader
6
+ import java.io.OutputStreamWriter
7
+ import java.net.HttpURLConnection
8
+ import java.net.URL
9
+
10
+ internal object CliqItHttp {
11
+ data class Response(val statusCode: Int, val body: String)
12
+
13
+ fun get(url: String, apiKey: String): Result<Response> =
14
+ request(url, "GET", apiKey, null)
15
+
16
+ fun postJson(url: String, apiKey: String, jsonBody: String): Result<Response> =
17
+ request(url, "POST", apiKey, jsonBody)
18
+
19
+ private fun request(
20
+ url: String,
21
+ method: String,
22
+ apiKey: String,
23
+ jsonBody: String?,
24
+ ): Result<Response> {
25
+ var lastError = "Request failed"
26
+ repeat(CliqItDefaults.MAX_RETRY) { attempt ->
27
+ if (attempt > 0) {
28
+ Thread.sleep(CliqItDefaults.RETRY_INITIAL_DELAY_MS * (1L shl (attempt - 1)))
29
+ }
30
+ try {
31
+ val conn = (URL(url).openConnection() as HttpURLConnection).apply {
32
+ requestMethod = method
33
+ connectTimeout = 15_000
34
+ readTimeout = 20_000
35
+ setRequestProperty(CliqItDefaults.API_KEY_HEADER, apiKey)
36
+ setRequestProperty("Accept", "application/json")
37
+ if (jsonBody != null) {
38
+ doOutput = true
39
+ setRequestProperty("Content-Type", "application/json")
40
+ }
41
+ }
42
+ if (jsonBody != null) {
43
+ OutputStreamWriter(conn.outputStream, Charsets.UTF_8).use { it.write(jsonBody) }
44
+ }
45
+ val code = conn.responseCode
46
+ val stream = if (code in 200..299) conn.inputStream else conn.errorStream
47
+ val body = stream?.let { BufferedReader(InputStreamReader(it, Charsets.UTF_8)).readText() }.orEmpty()
48
+ conn.disconnect()
49
+
50
+ if (code in 200..299) {
51
+ return Result.success(Response(code, body))
52
+ }
53
+ lastError = "Request failed ($code): ${body.ifEmpty { "empty" }}"
54
+ if (code == 408 || code == 429 || code in 500..599) {
55
+ // retry
56
+ } else {
57
+ return Result.failure(Exception(lastError))
58
+ }
59
+ } catch (e: Exception) {
60
+ lastError = e.message ?: "Request failed"
61
+ }
62
+ }
63
+ return Result.failure(Exception(lastError))
64
+ }
65
+
66
+ fun joinUrl(base: String, vararg parts: String): String {
67
+ val trimmedBase = base.trimEnd('/')
68
+ val path = parts.joinToString("/") { it.trim('/') }
69
+ return "$trimmedBase/$path"
70
+ }
71
+
72
+ /** Append platform identity query params (same fields as verify / match body). */
73
+ fun appendIdentityQuery(url: String, context: android.content.Context): String {
74
+ val sep = if (url.contains('?')) '&' else '?'
75
+ val platform = "platform=android"
76
+ val packageName = "packageName=${java.net.URLEncoder.encode(CliqItDeviceInfo.packageName(context), Charsets.UTF_8.name())}"
77
+ val sha = CliqItDeviceInfo.androidSha256(context)?.let {
78
+ "&androidSha256=${java.net.URLEncoder.encode(it, Charsets.UTF_8.name())}"
79
+ }.orEmpty()
80
+ return "$url$sep$platform&$packageName$sha"
81
+ }
82
+
83
+ fun optString(obj: JSONObject?, key: String): String? {
84
+ if (obj == null || !obj.has(key) || obj.isNull(key)) return null
85
+ val v = obj.optString(key, "")
86
+ return v.takeIf { it.isNotEmpty() && !it.equals("null", ignoreCase = true) }
87
+ }
88
+
89
+ fun optBool(obj: JSONObject?, key: String): Boolean? {
90
+ if (obj == null || !obj.has(key) || obj.isNull(key)) return null
91
+ return obj.optBoolean(key)
92
+ }
93
+
94
+ fun optDouble(obj: JSONObject?, key: String): Double? {
95
+ if (obj == null || !obj.has(key) || obj.isNull(key)) return null
96
+ return obj.optDouble(key).takeUnless { it.isNaN() }
97
+ }
98
+ }
@@ -0,0 +1,130 @@
1
+ package com.cliqit.sdk
2
+
3
+ /** Mirrors iOS `CliqItLinkField`. */
4
+ object LinkField {
5
+ const val destination = "destination"
6
+ const val iosDestination = "iosDestination"
7
+ const val androidDestination = "androidDestination"
8
+ const val ogTitle = "ogTitle"
9
+ const val ogDescription = "ogDescription"
10
+ const val ogImage = "ogImage"
11
+ const val ogUrl = "ogUrl"
12
+ const val slug = "slug"
13
+ const val webFallback = "webFallback"
14
+ const val showInterstitial = "showInterstitial"
15
+ const val isDeepLink = "isDeepLink"
16
+ const val appleTeamId = "appleTeamId"
17
+ const val iosBundleId = "iosBundleId"
18
+ const val androidPackageName = "androidPackageName"
19
+ const val resolvedPath = "resolvedPath"
20
+ }
21
+
22
+ data class CliqItPayload(
23
+ val url: String,
24
+ val path: String,
25
+ val pathComponents: List<String>,
26
+ val query: Map<String, String>,
27
+ val source: String,
28
+ val isDeferred: Boolean,
29
+ )
30
+
31
+ data class DeferredMatchInfo(
32
+ val matched: Boolean,
33
+ val tier: String?,
34
+ val confidence: String?,
35
+ val score: Double?,
36
+ val destinationPath: String?,
37
+ val slug: String?,
38
+ )
39
+
40
+ sealed class DeferredMatchOutcome {
41
+ data class Matched(val info: DeferredMatchInfo) : DeferredMatchOutcome()
42
+ data class NotMatched(val info: DeferredMatchInfo) : DeferredMatchOutcome()
43
+ data class Failed(val error: String) : DeferredMatchOutcome()
44
+ }
45
+
46
+ data class LinkDetails(
47
+ val destination: String?,
48
+ val iosDestination: String?,
49
+ val androidDestination: String?,
50
+ val ogTitle: String?,
51
+ val ogDescription: String?,
52
+ val ogImage: String?,
53
+ val ogUrl: String?,
54
+ val slug: String?,
55
+ val webFallback: String?,
56
+ val showInterstitial: Boolean?,
57
+ val isDeepLink: Boolean?,
58
+ val appleTeamId: String?,
59
+ val iosBundleId: String?,
60
+ val androidPackageName: String?,
61
+ ) {
62
+ /** Android: `androidDestination` ?? `destination`. */
63
+ val resolvedPath: String?
64
+ get() = normalizePath(androidDestination) ?: normalizePath(destination)
65
+
66
+ fun field(key: String): String? = when (key) {
67
+ LinkField.destination -> destination
68
+ LinkField.iosDestination -> iosDestination
69
+ LinkField.androidDestination -> androidDestination
70
+ LinkField.ogTitle -> ogTitle
71
+ LinkField.ogDescription -> ogDescription
72
+ LinkField.ogImage -> ogImage
73
+ LinkField.ogUrl -> ogUrl
74
+ LinkField.slug -> slug
75
+ LinkField.webFallback -> webFallback
76
+ LinkField.showInterstitial -> showInterstitial?.toString()
77
+ LinkField.isDeepLink -> isDeepLink?.toString()
78
+ LinkField.appleTeamId -> appleTeamId
79
+ LinkField.iosBundleId -> iosBundleId
80
+ LinkField.androidPackageName -> androidPackageName
81
+ LinkField.resolvedPath -> resolvedPath
82
+ else -> null
83
+ }
84
+
85
+ companion object {
86
+ fun normalizePath(raw: String?): String? {
87
+ var value = raw?.trim().orEmpty()
88
+ if (value.isEmpty() || value.equals("null", ignoreCase = true)) return null
89
+ if (!value.startsWith("/")) value = "/$value"
90
+ return value
91
+ }
92
+ }
93
+ }
94
+
95
+ sealed class VerifyOutcome {
96
+ data class Passed(val result: VerifyResult) : VerifyOutcome()
97
+ data class Mismatched(val result: VerifyResult) : VerifyOutcome()
98
+ data class Error(val message: String) : VerifyOutcome()
99
+ }
100
+
101
+ data class VerifyCheck(
102
+ val actual: String?,
103
+ val expected: String?,
104
+ val match: Boolean,
105
+ )
106
+
107
+ data class VerifyResult(
108
+ val ok: Boolean,
109
+ val appId: String?,
110
+ val appName: String?,
111
+ val checks: Map<String, VerifyCheck>,
112
+ val rawJSON: String,
113
+ ) {
114
+ val failedChecks: List<Pair<String, VerifyCheck>>
115
+ get() = checks.filter { !it.value.match }.toList().sortedBy { it.first }
116
+
117
+ val mismatchMessage: String
118
+ get() {
119
+ if (ok) return "Verify OK"
120
+ if (failedChecks.isEmpty()) return "Verify failed (ok=false)"
121
+ return failedChecks.joinToString("; ") { (field, check) ->
122
+ "$field: actual=${check.actual ?: "-"} expected=${check.expected ?: "-"}"
123
+ }
124
+ }
125
+ }
126
+
127
+ sealed class LinkLookupResult {
128
+ data class Resolved(val details: LinkDetails) : LinkLookupResult()
129
+ data class Failed(val error: String) : LinkLookupResult()
130
+ }