@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.
Files changed (36) hide show
  1. package/CapKitRank.podspec +17 -0
  2. package/LICENSE +21 -0
  3. package/Package.swift +28 -0
  4. package/README.md +574 -0
  5. package/android/build.gradle +110 -0
  6. package/android/src/main/AndroidManifest.xml +16 -0
  7. package/android/src/main/java/io/capkit/rank/RankConfig.kt +72 -0
  8. package/android/src/main/java/io/capkit/rank/RankError.kt +40 -0
  9. package/android/src/main/java/io/capkit/rank/RankImpl.kt +240 -0
  10. package/android/src/main/java/io/capkit/rank/RankPlugin.kt +312 -0
  11. package/android/src/main/java/io/capkit/rank/utils/RankLogger.kt +85 -0
  12. package/android/src/main/java/io/capkit/rank/utils/RankUtils.kt +14 -0
  13. package/android/src/main/res/.gitkeep +0 -0
  14. package/dist/docs.json +441 -0
  15. package/dist/esm/definitions.d.ts +330 -0
  16. package/dist/esm/definitions.js +21 -0
  17. package/dist/esm/definitions.js.map +1 -0
  18. package/dist/esm/index.d.ts +16 -0
  19. package/dist/esm/index.js +19 -0
  20. package/dist/esm/index.js.map +1 -0
  21. package/dist/esm/web.d.ts +81 -0
  22. package/dist/esm/web.js +157 -0
  23. package/dist/esm/web.js.map +1 -0
  24. package/dist/plugin.cjs.js +204 -0
  25. package/dist/plugin.cjs.js.map +1 -0
  26. package/dist/plugin.js +207 -0
  27. package/dist/plugin.js.map +1 -0
  28. package/ios/Sources/RankPlugin/RankConfig.swift +89 -0
  29. package/ios/Sources/RankPlugin/RankError.swift +49 -0
  30. package/ios/Sources/RankPlugin/RankImpl.swift +186 -0
  31. package/ios/Sources/RankPlugin/RankPlugin.swift +258 -0
  32. package/ios/Sources/RankPlugin/Utils/RankLogger.swift +69 -0
  33. package/ios/Sources/RankPlugin/Utils/RankUtils.swift +45 -0
  34. package/ios/Sources/RankPlugin/Version.swift +16 -0
  35. package/ios/Tests/RankPluginTests/RankPluginTests.swift +10 -0
  36. 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