@cap-kit/rank 8.0.0
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/CapKitRank.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +28 -0
- package/README.md +574 -0
- package/android/build.gradle +110 -0
- package/android/src/main/AndroidManifest.xml +16 -0
- package/android/src/main/java/io/capkit/rank/RankConfig.kt +72 -0
- package/android/src/main/java/io/capkit/rank/RankError.kt +40 -0
- package/android/src/main/java/io/capkit/rank/RankImpl.kt +240 -0
- package/android/src/main/java/io/capkit/rank/RankPlugin.kt +312 -0
- package/android/src/main/java/io/capkit/rank/utils/RankLogger.kt +85 -0
- package/android/src/main/java/io/capkit/rank/utils/RankUtils.kt +14 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +441 -0
- package/dist/esm/definitions.d.ts +330 -0
- package/dist/esm/definitions.js +21 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +16 -0
- package/dist/esm/index.js +19 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +81 -0
- package/dist/esm/web.js +157 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +204 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +207 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/RankPlugin/RankConfig.swift +89 -0
- package/ios/Sources/RankPlugin/RankError.swift +49 -0
- package/ios/Sources/RankPlugin/RankImpl.swift +186 -0
- package/ios/Sources/RankPlugin/RankPlugin.swift +258 -0
- package/ios/Sources/RankPlugin/Utils/RankLogger.swift +69 -0
- package/ios/Sources/RankPlugin/Utils/RankUtils.swift +45 -0
- package/ios/Sources/RankPlugin/Version.swift +16 -0
- package/ios/Tests/RankPluginTests/RankPluginTests.swift +10 -0
- package/package.json +102 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
package io.capkit.rank
|
|
2
|
+
|
|
3
|
+
import com.getcapacitor.JSObject
|
|
4
|
+
import com.getcapacitor.Plugin
|
|
5
|
+
import com.getcapacitor.PluginCall
|
|
6
|
+
import com.getcapacitor.PluginMethod
|
|
7
|
+
import com.getcapacitor.annotation.CapacitorPlugin
|
|
8
|
+
import com.getcapacitor.annotation.Permission
|
|
9
|
+
import io.capkit.rank.utils.RankLogger
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Capacitor bridge for the Rank plugin.
|
|
13
|
+
*
|
|
14
|
+
* This class acts as the boundary between JavaScript and native Android code.
|
|
15
|
+
* It handles input parsing, configuration management, and delegates execution
|
|
16
|
+
* to the platform-specific implementation.
|
|
17
|
+
*/
|
|
18
|
+
@CapacitorPlugin(
|
|
19
|
+
name = "Rank",
|
|
20
|
+
permissions = [
|
|
21
|
+
Permission(
|
|
22
|
+
alias = "network",
|
|
23
|
+
strings = [android.Manifest.permission.INTERNET],
|
|
24
|
+
),
|
|
25
|
+
],
|
|
26
|
+
)
|
|
27
|
+
class RankPlugin : Plugin() {
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Properties
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Immutable plugin configuration read from capacitor.config.ts.
|
|
34
|
+
* * CONTRACT:
|
|
35
|
+
* - Initialized exactly once in `load()`.
|
|
36
|
+
* - Treated as read-only afterwards.
|
|
37
|
+
*/
|
|
38
|
+
private lateinit var config: RankConfig
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Native implementation layer containing core Android logic.
|
|
42
|
+
*
|
|
43
|
+
* CONTRACT:
|
|
44
|
+
* - Owned by the Plugin layer.
|
|
45
|
+
* - MUST NOT access PluginCall or Capacitor bridge APIs directly.
|
|
46
|
+
*/
|
|
47
|
+
private lateinit var implementation: RankImpl
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Companion Object
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
private companion object {
|
|
54
|
+
/**
|
|
55
|
+
* Account type identifier for internal plugin identification.
|
|
56
|
+
*/
|
|
57
|
+
const val ACCOUNT_TYPE = "io.capkit.rank"
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Human-readable account name for the plugin.
|
|
61
|
+
*/
|
|
62
|
+
const val ACCOUNT_NAME = "Rank"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Lifecycle
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Called once when the plugin is loaded by the Capacitor bridge.
|
|
71
|
+
*
|
|
72
|
+
* This method initializes the configuration container and the native
|
|
73
|
+
* implementation layer, ensuring all dependencies are injected.
|
|
74
|
+
*/
|
|
75
|
+
override fun load() {
|
|
76
|
+
super.load()
|
|
77
|
+
|
|
78
|
+
config = RankConfig(this)
|
|
79
|
+
implementation = RankImpl(context)
|
|
80
|
+
implementation.updateConfig(config)
|
|
81
|
+
|
|
82
|
+
// RULE: Perform pre-warm of native resources to improve UX
|
|
83
|
+
implementation.preloadReviewInfo()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Error Mapping
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Maps native RankError sealed class instances to standardized
|
|
92
|
+
* JavaScript-facing error codes.
|
|
93
|
+
*
|
|
94
|
+
* * @param call The PluginCall to reject.
|
|
95
|
+
* * @param error The native error encountered.
|
|
96
|
+
*/
|
|
97
|
+
private fun reject(
|
|
98
|
+
call: PluginCall,
|
|
99
|
+
error: RankError,
|
|
100
|
+
) {
|
|
101
|
+
val code =
|
|
102
|
+
when (error) {
|
|
103
|
+
is RankError.Unavailable -> "UNAVAILABLE"
|
|
104
|
+
is RankError.PermissionDenied -> "PERMISSION_DENIED"
|
|
105
|
+
is RankError.InitFailed -> "INIT_FAILED"
|
|
106
|
+
is RankError.UnknownType -> "UNKNOWN_TYPE"
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
call.reject(error.message, code)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Availability
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Checks if the device supports the Google Play Review API.
|
|
118
|
+
*/
|
|
119
|
+
@PluginMethod
|
|
120
|
+
fun isAvailable(call: PluginCall) {
|
|
121
|
+
implementation.isAvailable { available ->
|
|
122
|
+
val ret = JSObject()
|
|
123
|
+
ret.put("value", available)
|
|
124
|
+
call.resolve(ret)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Performs a diagnostic check to determine whether the Google Play
|
|
130
|
+
* In-App Review dialog can be displayed in the current environment_attach.
|
|
131
|
+
*
|
|
132
|
+
* This method does NOT trigger the review flow and does NOT represent
|
|
133
|
+
* an error condition. It is intended purely for runtime diagnostics.
|
|
134
|
+
*
|
|
135
|
+
* Typical cases where the review dialog cannot be shown include:
|
|
136
|
+
* - The Google Play Store app is not installed or is disabled
|
|
137
|
+
* - The app was not installed from the official Play Store
|
|
138
|
+
* - The device or environment is not Play Store–certified (e.g. emulator)
|
|
139
|
+
*
|
|
140
|
+
* The result is returned as a structured object instead of rejecting
|
|
141
|
+
* the call, allowing consumers to make UI decisions (e.g. disabling
|
|
142
|
+
* the review button) without treating this as a failure.
|
|
143
|
+
*/
|
|
144
|
+
@PluginMethod
|
|
145
|
+
fun checkReviewEnvironment(call: PluginCall) {
|
|
146
|
+
implementation.checkReviewEnvironment { canRequest ->
|
|
147
|
+
val ret = JSObject()
|
|
148
|
+
ret.put("canRequestReview", canRequest)
|
|
149
|
+
|
|
150
|
+
if (!canRequest) {
|
|
151
|
+
ret.put("reason", "PLAY_STORE_NOT_AVAILABLE")
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
call.resolve(ret)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// Product Page (Fallback for Android)
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Android fallback for presentProductPage.
|
|
164
|
+
* Redirects to the Play Store app as no native internal overlay exists.
|
|
165
|
+
*/
|
|
166
|
+
@PluginMethod
|
|
167
|
+
fun presentProductPage(call: PluginCall) {
|
|
168
|
+
openStore(call)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// Public Plugin Methods
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Requests the display of the native Google Play In-App Review flow.
|
|
177
|
+
*
|
|
178
|
+
* This method extracts the 'fireAndForget' policy to determine if the
|
|
179
|
+
* promise should resolve immediately or wait for the review flow results.
|
|
180
|
+
*
|
|
181
|
+
* * @param call PluginCall containing:
|
|
182
|
+
* - fireAndForget (Boolean, optional): Override for the global resolution policy.
|
|
183
|
+
*/
|
|
184
|
+
@PluginMethod
|
|
185
|
+
fun requestReview(call: PluginCall) {
|
|
186
|
+
val fireAndForget = call.getBoolean("fireAndForget") ?: config.fireAndForget
|
|
187
|
+
|
|
188
|
+
// If fireAndForget is enabled, resolve the call immediately
|
|
189
|
+
if (fireAndForget) {
|
|
190
|
+
call.resolve()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
val currentActivity = activity
|
|
194
|
+
if (currentActivity == null) {
|
|
195
|
+
// Edge case: plugin invoked while Activity is not available
|
|
196
|
+
RankLogger.error("Cannot request review: Activity is null")
|
|
197
|
+
if (!fireAndForget) {
|
|
198
|
+
call.reject("Activity not available", "INIT_FAILED")
|
|
199
|
+
}
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// RULE: UI operations MUST be executed on the main thread
|
|
204
|
+
currentActivity.runOnUiThread {
|
|
205
|
+
implementation.requestReview(currentActivity) { error: Exception? ->
|
|
206
|
+
// Ensure we only respond if we haven't already resolved via fireAndForget
|
|
207
|
+
if (!fireAndForget) {
|
|
208
|
+
if (error != null) {
|
|
209
|
+
RankLogger.error("Review flow failed", error)
|
|
210
|
+
call.reject(error.message, "INIT_FAILED")
|
|
211
|
+
} else {
|
|
212
|
+
call.resolve()
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Navigates the user to the Google Play Store page for the application.
|
|
221
|
+
*
|
|
222
|
+
* * @param call PluginCall containing:
|
|
223
|
+
* - packageName (String, optional): The target app package. Defaults to the host app.
|
|
224
|
+
*/
|
|
225
|
+
@PluginMethod
|
|
226
|
+
fun openStore(call: PluginCall) {
|
|
227
|
+
val packageName = call.getString("packageName") ?: config.androidPackageName
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
implementation.openStore(packageName)
|
|
231
|
+
call.resolve()
|
|
232
|
+
} catch (e: Exception) {
|
|
233
|
+
call.reject(
|
|
234
|
+
e.message,
|
|
235
|
+
"INIT_FAILED",
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Opens the App Store listing page.
|
|
242
|
+
* * This method resolves the target identifier by checking the 'appId' parameter,
|
|
243
|
+
* then 'packageName', and finally falling back to the configured static ID.
|
|
244
|
+
* * @param call PluginCall containing optional 'appId' or 'packageName'.
|
|
245
|
+
*/
|
|
246
|
+
@PluginMethod
|
|
247
|
+
fun openStoreListing(call: PluginCall) {
|
|
248
|
+
val appId = call.getString("appId") ?: call.getString("packageName") ?: config.androidPackageName
|
|
249
|
+
activity.runOnUiThread {
|
|
250
|
+
try {
|
|
251
|
+
implementation.openStoreListing(appId ?: context.packageName)
|
|
252
|
+
call.resolve()
|
|
253
|
+
} catch (e: Exception) {
|
|
254
|
+
call.reject(e.message, "INIT_FAILED")
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Opens a specific app collection on the Play Store.
|
|
261
|
+
* * @param call PluginCall containing the required 'name' string.
|
|
262
|
+
*/
|
|
263
|
+
@PluginMethod
|
|
264
|
+
fun openCollection(call: PluginCall) {
|
|
265
|
+
val name = call.getString("name") ?: return call.reject("Collection name is missing")
|
|
266
|
+
activity.runOnUiThread {
|
|
267
|
+
implementation.openCollection(name)
|
|
268
|
+
call.resolve()
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Performs a store search for the given terms.
|
|
274
|
+
* * @param call PluginCall containing the required 'terms' string.
|
|
275
|
+
*/
|
|
276
|
+
@PluginMethod
|
|
277
|
+
fun search(call: PluginCall) {
|
|
278
|
+
val terms = call.getString("terms") ?: return call.reject("Terms are missing")
|
|
279
|
+
implementation.search(terms)
|
|
280
|
+
call.resolve()
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Opens the developer page on the store.
|
|
285
|
+
* * @param call PluginCall containing the required 'devId' string.
|
|
286
|
+
*/
|
|
287
|
+
@PluginMethod
|
|
288
|
+
fun openDevPage(call: PluginCall) {
|
|
289
|
+
val devId = call.getString("devId") ?: return call.reject("devId is missing")
|
|
290
|
+
implementation.openDevPage(devId)
|
|
291
|
+
call.resolve()
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
// Version Information
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Returns the native plugin version synchronized from package.json.
|
|
300
|
+
*
|
|
301
|
+
* This information is used for diagnostics and ensuring parity between
|
|
302
|
+
* the JavaScript and native layers.
|
|
303
|
+
*
|
|
304
|
+
* @param call The bridge call to resolve with version data.
|
|
305
|
+
*/
|
|
306
|
+
@PluginMethod
|
|
307
|
+
fun getPluginVersion(call: PluginCall) {
|
|
308
|
+
val ret = JSObject()
|
|
309
|
+
ret.put("version", BuildConfig.PLUGIN_VERSION)
|
|
310
|
+
call.resolve(ret)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
package io.capkit.rank.utils
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Centralized logging utility for the Rank plugin.
|
|
7
|
+
*
|
|
8
|
+
* This logging provides a single entry point for all native logs
|
|
9
|
+
* and supports runtime-controlled verbose logging.
|
|
10
|
+
*
|
|
11
|
+
* The goal is to avoid scattering `if (verbose)` checks across
|
|
12
|
+
* business logic and keep logging behavior consistent.
|
|
13
|
+
*/
|
|
14
|
+
object RankLogger {
|
|
15
|
+
/**
|
|
16
|
+
* Logcat tag used for all plugin logs.
|
|
17
|
+
* Helps filtering logs during debugging.
|
|
18
|
+
*/
|
|
19
|
+
private const val TAG = "⚡️ Rank"
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Controls whether debug logs are printed.
|
|
23
|
+
*
|
|
24
|
+
* This flag should be set once during plugin initialization
|
|
25
|
+
* based on configuration values.
|
|
26
|
+
*/
|
|
27
|
+
var verbose: Boolean = false
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Prints a debug / verbose log message.
|
|
31
|
+
*
|
|
32
|
+
* This method should be used for development-time diagnostics
|
|
33
|
+
* and is automatically silenced when [verbose] is false.
|
|
34
|
+
*
|
|
35
|
+
* @param messages One or more message fragments to be concatenated.
|
|
36
|
+
*/
|
|
37
|
+
fun debug(vararg messages: String) {
|
|
38
|
+
if (verbose) {
|
|
39
|
+
log(TAG, Log.DEBUG, *messages)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Prints an error log message.
|
|
45
|
+
*
|
|
46
|
+
* Error logs are always printed regardless of [verbose] state.
|
|
47
|
+
*
|
|
48
|
+
* @param message Human-readable error description.
|
|
49
|
+
* @param e Optional exception for stack trace logging.
|
|
50
|
+
*/
|
|
51
|
+
fun error(
|
|
52
|
+
message: String,
|
|
53
|
+
e: Throwable? = null,
|
|
54
|
+
) {
|
|
55
|
+
val sb = StringBuilder(message)
|
|
56
|
+
if (e != null) {
|
|
57
|
+
sb.append(" | Error: ").append(e.message)
|
|
58
|
+
}
|
|
59
|
+
Log.e(TAG, sb.toString(), e)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Internal low-level log dispatcher.
|
|
64
|
+
*
|
|
65
|
+
* Joins message fragments and forwards them to Android's Log API
|
|
66
|
+
* using the specified priority.
|
|
67
|
+
*/
|
|
68
|
+
fun log(
|
|
69
|
+
tag: String,
|
|
70
|
+
level: Int,
|
|
71
|
+
vararg messages: String,
|
|
72
|
+
) {
|
|
73
|
+
val sb = StringBuilder()
|
|
74
|
+
for (msg in messages) {
|
|
75
|
+
sb.append(msg).append(" ")
|
|
76
|
+
}
|
|
77
|
+
when (level) {
|
|
78
|
+
Log.DEBUG -> Log.d(tag, sb.toString())
|
|
79
|
+
Log.INFO -> Log.i(tag, sb.toString())
|
|
80
|
+
Log.WARN -> Log.w(tag, sb.toString())
|
|
81
|
+
Log.ERROR -> Log.e(tag, sb.toString())
|
|
82
|
+
else -> Log.v(tag, sb.toString())
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
package io.capkit.rank.utils
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Utility helpers for the Rank plugin.
|
|
5
|
+
*
|
|
6
|
+
* This object is intentionally empty and serves as a placeholder
|
|
7
|
+
* for future shared utility functions.
|
|
8
|
+
*
|
|
9
|
+
* Keeping a dedicated utils package helps maintain a clean
|
|
10
|
+
* separation between core logic and helper code.
|
|
11
|
+
*/
|
|
12
|
+
object RankUtils {
|
|
13
|
+
// Utilities will be added here as the plugin evolves
|
|
14
|
+
}
|
|
File without changes
|