@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,186 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import StoreKit
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Native iOS implementation for the Rank plugin.
|
|
6
|
+
*
|
|
7
|
+
* This class contains pure platform logic and is isolated from the Capacitor bridge.
|
|
8
|
+
* Architectural constraints:
|
|
9
|
+
* - MUST NOT access CAPPluginCall.
|
|
10
|
+
* - MUST NOT depend on Capacitor bridge APIs directly.
|
|
11
|
+
* - MUST perform UI operations on the Main Thread.
|
|
12
|
+
*/
|
|
13
|
+
@objc public final class RankImpl: NSObject {
|
|
14
|
+
|
|
15
|
+
// MARK: - Properties
|
|
16
|
+
|
|
17
|
+
/// Cached plugin configuration containing logging and behavioral flags.
|
|
18
|
+
private var config: RankConfig?
|
|
19
|
+
|
|
20
|
+
// MARK: - Initialization
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Initializes the implementation instance.
|
|
24
|
+
*/
|
|
25
|
+
override init() {
|
|
26
|
+
super.init()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// MARK: - Configuration
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Applies static plugin configuration.
|
|
33
|
+
*
|
|
34
|
+
* This method MUST be called exactly once from the Plugin bridge layer during `load()`.
|
|
35
|
+
* It synchronizes the native logger state with the provided configuration.
|
|
36
|
+
*
|
|
37
|
+
* - Parameter config: The immutable configuration container.
|
|
38
|
+
*/
|
|
39
|
+
func applyConfig(_ config: RankConfig) {
|
|
40
|
+
precondition(
|
|
41
|
+
self.config == nil,
|
|
42
|
+
"RankImpl.applyConfig(_:) must be called exactly once"
|
|
43
|
+
)
|
|
44
|
+
self.config = config
|
|
45
|
+
RankLogger.verbose = config.verboseLogging
|
|
46
|
+
|
|
47
|
+
RankLogger.debug(
|
|
48
|
+
"Configuration applied. Verbose logging:",
|
|
49
|
+
config.verboseLogging
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// MARK: - Availability
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Checks if the current iOS version supports the SKStoreReviewController API.
|
|
57
|
+
* Since the plugin target is iOS 15+, this generally returns true.
|
|
58
|
+
*/
|
|
59
|
+
@objc public func isAvailable() -> Bool {
|
|
60
|
+
if #available(iOS 10.3, *) {
|
|
61
|
+
return true
|
|
62
|
+
}
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// MARK: - Product Page
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Presents the App Store product page within the application.
|
|
70
|
+
* * - Parameter appId: The numeric Apple App ID.
|
|
71
|
+
* - Parameter completion: Callback to signal when the page has been loaded or failed.
|
|
72
|
+
*/
|
|
73
|
+
@objc public func presentProductPage(appId: String, completion: @escaping (Bool, Error?) -> Void) {
|
|
74
|
+
let storeViewController = SKStoreProductViewController()
|
|
75
|
+
let parameters = [SKStoreProductParameterITunesItemIdentifier: appId]
|
|
76
|
+
|
|
77
|
+
// UI operations must be on the main thread
|
|
78
|
+
DispatchQueue.main.async {
|
|
79
|
+
storeViewController.loadProduct(withParameters: parameters) { success, error in
|
|
80
|
+
if success {
|
|
81
|
+
// Attempt to find the top-most view controller to present the sheet
|
|
82
|
+
if let rootVC = UIApplication.shared.windows.first?.rootViewController {
|
|
83
|
+
rootVC.present(storeViewController, animated: true) {
|
|
84
|
+
completion(true, nil)
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
completion(false, nil)
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
completion(false, error)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// MARK: - Review Methods
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Triggers the native iOS In-App Review prompt.
|
|
100
|
+
*
|
|
101
|
+
* Uses the modern `requestReview(in:)` API for iOS 14+ by identifying the active foreground scene.
|
|
102
|
+
* Fallback logic is provided for older iOS versions (though the plugin target is iOS 15+).
|
|
103
|
+
*
|
|
104
|
+
* NOTE: The OS manages the frequency of this prompt; calling this does not guarantee a UI will appear.
|
|
105
|
+
*/
|
|
106
|
+
@objc public func requestReview() {
|
|
107
|
+
if #available(iOS 14.0, *) {
|
|
108
|
+
let activeScene = UIApplication.shared.connectedScenes
|
|
109
|
+
.compactMap { $0 as? UIWindowScene }
|
|
110
|
+
.first { $0.activationState == .foregroundActive }
|
|
111
|
+
|
|
112
|
+
guard let windowScene = activeScene else {
|
|
113
|
+
// Diagnostic logging only: review prompt cannot be requested without an active scene
|
|
114
|
+
RankLogger.error("Cannot request review: no active UIWindowScene found")
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
RankLogger.debug("Requesting in-app review for active UIWindowScene")
|
|
119
|
+
SKStoreReviewController.requestReview(in: windowScene)
|
|
120
|
+
} else {
|
|
121
|
+
// Legacy fallback (not expected to be used due to iOS 15+ minimum target)
|
|
122
|
+
RankLogger.debug("Requesting in-app review using legacy API")
|
|
123
|
+
SKStoreReviewController.requestReview()
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// MARK: - Store Navigation
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Opens the App Store page for the specified application ID.
|
|
131
|
+
*
|
|
132
|
+
* This method constructs a deep link using the `itms-apps` scheme to direct the user
|
|
133
|
+
* to the review section of the application's store page.
|
|
134
|
+
*
|
|
135
|
+
* - Parameter appId: The numeric Apple App ID (e.g., "123456789").
|
|
136
|
+
*/
|
|
137
|
+
@objc public func openStore(appId: String) {
|
|
138
|
+
guard let url = RankUtils.appStoreURL(appId: appId) else {
|
|
139
|
+
RankLogger.error("Invalid App Store URL for ID: \(appId)")
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// RULE: All UI-related operations MUST be dispatched to the main thread.
|
|
144
|
+
DispatchQueue.main.async {
|
|
145
|
+
UIApplication.shared.open(url, options: [:]) { success in
|
|
146
|
+
if success {
|
|
147
|
+
RankLogger.debug("App Store opened successfully.")
|
|
148
|
+
} else {
|
|
149
|
+
RankLogger.error("Failed to open App Store URL.")
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Opens the App Store product page for the specified application ID.
|
|
157
|
+
*
|
|
158
|
+
* This method constructs a deep link using the `itms-apps` scheme to direct the user
|
|
159
|
+
* to the product page of the application in the App Store.
|
|
160
|
+
*
|
|
161
|
+
* - Parameter appId: The numeric Apple App ID (e.g., "123456789").
|
|
162
|
+
*/
|
|
163
|
+
@objc public func openStoreListing(appId: String) {
|
|
164
|
+
let urlString = "itms-apps://itunes.apple.com/app/id\(appId)"
|
|
165
|
+
if let url = URL(string: urlString) {
|
|
166
|
+
DispatchQueue.main.async {
|
|
167
|
+
UIApplication.shared.open(url)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Opens the App Store search page with the specified search terms.
|
|
174
|
+
*
|
|
175
|
+
* This method constructs a deep link using the `itms-apps` scheme to direct the user
|
|
176
|
+
* to the search results page in the App Store for the given terms.
|
|
177
|
+
*
|
|
178
|
+
* - Parameter terms: The search query string to find relevant apps in the App Store.
|
|
179
|
+
*/
|
|
180
|
+
@objc public func search(terms: String) {
|
|
181
|
+
guard let url = RankUtils.searchURL(terms: terms) else { return }
|
|
182
|
+
DispatchQueue.main.async {
|
|
183
|
+
UIApplication.shared.open(url)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Capacitor bridge for the Rank plugin.
|
|
6
|
+
*
|
|
7
|
+
* This class handles the communication between the JavaScript layer and the native iOS implementation.
|
|
8
|
+
* It is responsible for input validation, configuration merging, and thread safety.
|
|
9
|
+
*/
|
|
10
|
+
@objc(RankPlugin)
|
|
11
|
+
public final class RankPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
12
|
+
|
|
13
|
+
// MARK: - Properties
|
|
14
|
+
|
|
15
|
+
/// An instance of the implementation class that contains the plugin's core functionality.
|
|
16
|
+
private let implementation = RankImpl()
|
|
17
|
+
|
|
18
|
+
/// Internal storage for the plugin configuration read from capacitor.config.ts.
|
|
19
|
+
private var config: RankConfig?
|
|
20
|
+
|
|
21
|
+
/// The unique identifier for the plugin used by the Capacitor bridge.
|
|
22
|
+
public let identifier = "RankPlugin"
|
|
23
|
+
|
|
24
|
+
/// The name used to reference this plugin in JavaScript (e.g., Rank.requestReview()).
|
|
25
|
+
public let jsName = "Rank"
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A list of methods exposed by this plugin to the JavaScript layer.
|
|
29
|
+
* All methods defined here must be implemented with the @objc attribute.
|
|
30
|
+
- `isAvailable`: Checks if the native review prompt can be shown on the current device.
|
|
31
|
+
- `requestReview`: Triggers the native iOS In-App Review prompt.
|
|
32
|
+
- `presentProductPage`: Navigates the user to the App Store product page within the app.
|
|
33
|
+
- `openStore`: Opens the app's page on the App Store for leaving a review or viewing details.
|
|
34
|
+
- `getPluginVersion`: A method that returns the version of the plugin.
|
|
35
|
+
*/
|
|
36
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
37
|
+
CAPPluginMethod(name: "isAvailable", returnType: CAPPluginReturnPromise),
|
|
38
|
+
CAPPluginMethod(name: "requestReview", returnType: CAPPluginReturnPromise),
|
|
39
|
+
CAPPluginMethod(name: "presentProductPage", returnType: CAPPluginReturnPromise),
|
|
40
|
+
CAPPluginMethod(name: "openStore", returnType: CAPPluginReturnPromise),
|
|
41
|
+
CAPPluginMethod(name: "getPluginVersion", returnType: CAPPluginReturnPromise)
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
// MARK: - Lifecycle
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Plugin lifecycle entry point.
|
|
48
|
+
*
|
|
49
|
+
* Called once when the plugin is loaded. This method initializes the configuration
|
|
50
|
+
* and prepares the native implementation.
|
|
51
|
+
*/
|
|
52
|
+
override public func load() {
|
|
53
|
+
// Initialize RankConfig with the correct type
|
|
54
|
+
let cfg = RankConfig(plugin: self)
|
|
55
|
+
self.config = cfg
|
|
56
|
+
implementation.applyConfig(cfg)
|
|
57
|
+
|
|
58
|
+
// Log if verbose logging is enabled
|
|
59
|
+
RankLogger.debug("Rank plugin loaded.")
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// MARK: - Error Mapping
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Maps native RankError categories to standardized JS-facing error codes.
|
|
66
|
+
*
|
|
67
|
+
* - Parameters:
|
|
68
|
+
* - call: The CAPPluginCall to reject.
|
|
69
|
+
* - error: The native RankError encountered during execution.
|
|
70
|
+
*/
|
|
71
|
+
private func reject(
|
|
72
|
+
_ call: CAPPluginCall,
|
|
73
|
+
error: RankError
|
|
74
|
+
) {
|
|
75
|
+
let code: String
|
|
76
|
+
|
|
77
|
+
switch error {
|
|
78
|
+
case .unavailable:
|
|
79
|
+
code = "UNAVAILABLE"
|
|
80
|
+
case .permissionDenied:
|
|
81
|
+
code = "PERMISSION_DENIED"
|
|
82
|
+
case .initFailed:
|
|
83
|
+
code = "INIT_FAILED"
|
|
84
|
+
case .unknownType:
|
|
85
|
+
code = "UNKNOWN_TYPE"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
call.reject(error.message, code)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// MARK: - Availability
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Checks if the native In-App Review prompt can be displayed on the current device.
|
|
95
|
+
*
|
|
96
|
+
* This method verifies if the necessary APIs are available and if the OS version is compatible.
|
|
97
|
+
* It returns a boolean indicating availability, which can be used by the JavaScript layer to
|
|
98
|
+
* conditionally show review prompts or fallback UI.
|
|
99
|
+
*
|
|
100
|
+
* - Parameter call: CAPPluginCall used to return the availability result.
|
|
101
|
+
*/
|
|
102
|
+
@objc func isAvailable(_ call: CAPPluginCall) {
|
|
103
|
+
let available = implementation.isAvailable()
|
|
104
|
+
call.resolve([
|
|
105
|
+
"value": available
|
|
106
|
+
])
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// MARK: - Product Page
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Presents the App Store product page within the application.
|
|
113
|
+
*
|
|
114
|
+
* This method uses SKStoreProductViewController to display the product page without leaving the app.
|
|
115
|
+
* It accepts an optional appId parameter, which can override the global configuration.
|
|
116
|
+
*
|
|
117
|
+
* - Parameter call: CAPPluginCall containing:
|
|
118
|
+
* - appId (String, optional): The Apple App ID. Fallbacks to global config.
|
|
119
|
+
*/
|
|
120
|
+
@objc func presentProductPage(_ call: CAPPluginCall) {
|
|
121
|
+
guard let appId = call.getString("appId") ?? config?.appleAppId else {
|
|
122
|
+
call.reject("Apple App ID is missing.", "INIT_FAILED")
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
implementation.presentProductPage(appId: appId) { success, error in
|
|
127
|
+
if success {
|
|
128
|
+
call.resolve()
|
|
129
|
+
} else {
|
|
130
|
+
call.reject(error?.localizedDescription ?? "Failed to load product page.", "INIT_FAILED")
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// MARK: - Public Plugin Methods
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Triggers the native iOS In-App Review prompt using SKStoreReviewController.
|
|
139
|
+
*
|
|
140
|
+
* This method resolves immediately as iOS does not provide a callback to determine
|
|
141
|
+
* if the prompt was shown or if the user interacted with it.
|
|
142
|
+
*
|
|
143
|
+
* - Parameter call: CAPPluginCall provided by the bridge.
|
|
144
|
+
*/
|
|
145
|
+
@objc func requestReview(_ call: CAPPluginCall) {
|
|
146
|
+
// Log the request if verbose logging is enabled
|
|
147
|
+
RankLogger.debug("Requesting review prompt.")
|
|
148
|
+
|
|
149
|
+
// Native iOS SKStoreReviewController logic
|
|
150
|
+
implementation.requestReview()
|
|
151
|
+
|
|
152
|
+
// Always resolve immediately for iOS consistency
|
|
153
|
+
call.resolve()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// MARK: - Store Navigation
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Navigates the user to the App Store page for a specific application.
|
|
160
|
+
*
|
|
161
|
+
* - Parameter call: CAPPluginCall containing:
|
|
162
|
+
* - appId (String, optional): The Apple App ID. Fallbacks to global config.
|
|
163
|
+
*/
|
|
164
|
+
@objc func openStore(_ call: CAPPluginCall) {
|
|
165
|
+
// Attempt to retrieve the App ID from call parameters or static configuration
|
|
166
|
+
guard let appId = call.getString("appId") ?? config?.appleAppId else {
|
|
167
|
+
call.reject(
|
|
168
|
+
"Apple App ID is missing. Provide it in config or as a parameter.",
|
|
169
|
+
"INIT_FAILED"
|
|
170
|
+
)
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
RankLogger.debug("Opening App Store for ID: \(appId)")
|
|
175
|
+
implementation.openStore(appId: appId)
|
|
176
|
+
call.resolve()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Opens a collection page on the App Store.
|
|
181
|
+
*
|
|
182
|
+
* This method is not supported on iOS as there is no direct way to link to collections. It will reject with an appropriate message.
|
|
183
|
+
*
|
|
184
|
+
* - Parameter call: CAPPluginCall provided by the bridge.
|
|
185
|
+
*/
|
|
186
|
+
@objc func openCollection(_ call: CAPPluginCall) {
|
|
187
|
+
call.unavailable("Collections are not supported on iOS.")
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Opens the App Store page for the specified application ID.
|
|
192
|
+
*
|
|
193
|
+
* This method constructs a deep link using the `itms-apps` scheme to direct the user
|
|
194
|
+
* to the review section of the application's store page.
|
|
195
|
+
*
|
|
196
|
+
* - Parameter appId: The numeric Apple App ID (e.g., "123456789").
|
|
197
|
+
*/
|
|
198
|
+
@objc func openStoreListing(_ call: CAPPluginCall) {
|
|
199
|
+
guard let appId = call.getString("appId") ?? config?.appleAppId else {
|
|
200
|
+
call.reject("Apple App ID is missing.", "INIT_FAILED")
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
implementation.openStoreListing(appId: appId)
|
|
204
|
+
call.resolve()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// MARK: - Search
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Searches the App Store for the given terms.
|
|
211
|
+
*
|
|
212
|
+
* - Parameter call: CAPPluginCall containing:
|
|
213
|
+
* - terms (String): The search query to find apps or developers.
|
|
214
|
+
*/
|
|
215
|
+
@objc func search(_ call: CAPPluginCall) {
|
|
216
|
+
guard let terms = call.getString("terms") else {
|
|
217
|
+
call.reject("Search terms are missing.")
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
implementation.search(terms: terms)
|
|
221
|
+
call.resolve()
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// MARK: - Developer Page
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Opens the developer's page on the App Store.
|
|
228
|
+
*
|
|
229
|
+
* Since iOS does not have a direct link for developer pages, this method performs a search using the developer's name or ID.
|
|
230
|
+
*
|
|
231
|
+
* - Parameter call: CAPPluginCall containing:
|
|
232
|
+
* - devId (String): The developer identifier or name to search for.
|
|
233
|
+
*/
|
|
234
|
+
@objc func openDevPage(_ call: CAPPluginCall) {
|
|
235
|
+
guard let devId = call.getString("devId") else {
|
|
236
|
+
call.reject("devId is missing.")
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
implementation.search(terms: devId) // Fallback on iOS
|
|
240
|
+
call.resolve()
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// MARK: - Version
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Retrieves the current native plugin version.
|
|
247
|
+
*
|
|
248
|
+
* This version is synchronized from the project's package.json during the build process.
|
|
249
|
+
*
|
|
250
|
+
* - Parameter call: CAPPluginCall used to return the version string.
|
|
251
|
+
*/
|
|
252
|
+
@objc func getPluginVersion(_ call: CAPPluginCall) {
|
|
253
|
+
// Standardized enum name across all CapKit plugins
|
|
254
|
+
call.resolve([
|
|
255
|
+
"version": PluginVersion.number
|
|
256
|
+
])
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import Capacitor
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
Centralized native logger for the Rank plugin.
|
|
5
|
+
|
|
6
|
+
Responsibilities:
|
|
7
|
+
- Provide a single logging entry point
|
|
8
|
+
- Support runtime-controlled verbose logging
|
|
9
|
+
- Keep logging behavior consistent across files
|
|
10
|
+
|
|
11
|
+
Forbidden:
|
|
12
|
+
- Controlling application logic
|
|
13
|
+
- Being queried for flow decisions
|
|
14
|
+
*/
|
|
15
|
+
enum RankLogger {
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
Controls whether debug logs are printed.
|
|
19
|
+
|
|
20
|
+
This value MUST be set once during plugin initialization
|
|
21
|
+
based on static configuration.
|
|
22
|
+
*/
|
|
23
|
+
static var verbose: Bool = false
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
Prints a verbose / debug log message.
|
|
27
|
+
|
|
28
|
+
Debug logs are automatically silenced
|
|
29
|
+
when `verbose` is false.
|
|
30
|
+
*/
|
|
31
|
+
static func debug(_ items: Any...) {
|
|
32
|
+
guard verbose else { return }
|
|
33
|
+
log(items)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
Prints an error log message.
|
|
38
|
+
|
|
39
|
+
Error logs are always printed regardless
|
|
40
|
+
of the verbose flag.
|
|
41
|
+
*/
|
|
42
|
+
static func error(_ items: Any...) {
|
|
43
|
+
log(items)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// MARK: - Internal log printer
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
Low-level log printer with a consistent prefix.
|
|
51
|
+
|
|
52
|
+
This function MUST NOT be used outside this file.
|
|
53
|
+
*/
|
|
54
|
+
private func log(
|
|
55
|
+
_ items: [Any],
|
|
56
|
+
separator: String = " ",
|
|
57
|
+
terminator: String = "\n"
|
|
58
|
+
) {
|
|
59
|
+
CAPLog.print("⚡️ Rank -", terminator: separator)
|
|
60
|
+
|
|
61
|
+
for (index, item) in items.enumerated() {
|
|
62
|
+
CAPLog.print(
|
|
63
|
+
item,
|
|
64
|
+
terminator: index == items.count - 1
|
|
65
|
+
? terminator
|
|
66
|
+
: separator
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
Utility helpers for the Rank plugin.
|
|
5
|
+
|
|
6
|
+
This type is intentionally empty and serves as a placeholder
|
|
7
|
+
for future shared helper functions.
|
|
8
|
+
|
|
9
|
+
Keeping a dedicated utilities file helps maintain a clean
|
|
10
|
+
separation between core logic and helper code.
|
|
11
|
+
*/
|
|
12
|
+
struct RankUtils {
|
|
13
|
+
/**
|
|
14
|
+
Builds the App Store URL for the given Apple App ID.
|
|
15
|
+
|
|
16
|
+
- Parameter appId: The numeric Apple App ID.
|
|
17
|
+
- Returns: A valid App Store URL for opening the product page or review flow.
|
|
18
|
+
*/
|
|
19
|
+
static func appStoreURL(appId: String) -> URL? {
|
|
20
|
+
let urlString = "itms-apps://itunes.apple.com/app/id\(appId)?action=write-review"
|
|
21
|
+
return URL(string: urlString)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
Builds the App Store search URL for the given search terms.
|
|
26
|
+
|
|
27
|
+
- Parameter terms: The search query string.
|
|
28
|
+
- Returns: A valid App Store URL for performing a search.
|
|
29
|
+
*/
|
|
30
|
+
static func searchURL(terms: String) -> URL? {
|
|
31
|
+
let encoded = terms.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
|
|
32
|
+
return URL(string: "itms-apps://itunes.apple.com/search?term=\(encoded)")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
Builds the App Store URL for a developer page based on the given developer identifier.
|
|
37
|
+
|
|
38
|
+
- Parameter devId: The developer identifier or name.
|
|
39
|
+
- Returns: A valid App Store URL for the developer's page or search results.
|
|
40
|
+
*/
|
|
41
|
+
static func devPageURL(devId: String) -> URL? {
|
|
42
|
+
// iOS not have a direct link for DevID, so we use search as fallback
|
|
43
|
+
return searchURL(terms: devId)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// This file is automatically generated. Do not modify manually.
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
Container for the plugin's version information.
|
|
6
|
+
This enum provides a centralized, single source of truth for the native
|
|
7
|
+
version string, synchronized directly from the project's 'package.json'.
|
|
8
|
+
It ensures parity across JavaScript, Android, and iOS platforms.
|
|
9
|
+
*/
|
|
10
|
+
public enum PluginVersion {
|
|
11
|
+
/**
|
|
12
|
+
The semantic version string of the plugin.
|
|
13
|
+
Value synchronized from package.json: "8.0.0"
|
|
14
|
+
*/
|
|
15
|
+
public static let number = "8.0.0"
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import Rank
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
Basic functional tests for the Rank plugin native implementation.
|
|
6
|
+
|
|
7
|
+
These tests validate the core behavior of the implementation
|
|
8
|
+
independently from the Capacitor bridge.
|
|
9
|
+
*/
|
|
10
|
+
class RankPluginTests: XCTestCase {}
|
package/package.json
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cap-kit/rank",
|
|
3
|
+
"version": "8.0.0",
|
|
4
|
+
"description": "Unified Capacitor v8 plugin for native In-App Reviews and cross-platform Store navigation.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": false,
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=24.0.0",
|
|
9
|
+
"pnpm": ">=10.0.0"
|
|
10
|
+
},
|
|
11
|
+
"main": "dist/plugin.cjs.js",
|
|
12
|
+
"module": "dist/esm/index.js",
|
|
13
|
+
"types": "dist/esm/index.d.ts",
|
|
14
|
+
"unpkg": "dist/plugin.js",
|
|
15
|
+
"files": [
|
|
16
|
+
"android/src/main/",
|
|
17
|
+
"android/build.gradle",
|
|
18
|
+
"dist/",
|
|
19
|
+
"ios/Sources",
|
|
20
|
+
"ios/Tests",
|
|
21
|
+
"Package.swift",
|
|
22
|
+
"CapKitRank.podspec",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"author": "CapKit Team",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/cap-kit/capacitor-plugins.git",
|
|
30
|
+
"directory": "packages/rank"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/cap-kit/capacitor-plugins/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/cap-kit/capacitor-plugins/tree/main/packages/rank#readme",
|
|
36
|
+
"keywords": [
|
|
37
|
+
"capacitor",
|
|
38
|
+
"capacitor-plugin",
|
|
39
|
+
"cap-kit",
|
|
40
|
+
"mobile",
|
|
41
|
+
"native",
|
|
42
|
+
"ios",
|
|
43
|
+
"android",
|
|
44
|
+
"rank",
|
|
45
|
+
"market",
|
|
46
|
+
"review"
|
|
47
|
+
],
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@capacitor/cli": "^8.0.2",
|
|
53
|
+
"@capacitor/android": "^8.0.2",
|
|
54
|
+
"@capacitor/core": "^8.0.2",
|
|
55
|
+
"@capacitor/docgen": "^0.3.1",
|
|
56
|
+
"@capacitor/ios": "^8.0.2",
|
|
57
|
+
"@eslint/eslintrc": "^3.3.3",
|
|
58
|
+
"@eslint/js": "^9.39.2",
|
|
59
|
+
"eslint": "^9.39.2",
|
|
60
|
+
"eslint-plugin-import": "^2.32.0",
|
|
61
|
+
"globals": "^17.3.0",
|
|
62
|
+
"prettier": "^3.8.1",
|
|
63
|
+
"prettier-plugin-java": "^2.8.1",
|
|
64
|
+
"rimraf": "^6.1.2",
|
|
65
|
+
"rollup": "^4.57.1",
|
|
66
|
+
"swiftlint": "^2.0.0",
|
|
67
|
+
"typescript": "^5.9.3",
|
|
68
|
+
"typescript-eslint": "^8.54.0"
|
|
69
|
+
},
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"@capacitor/core": ">=8.0.2"
|
|
72
|
+
},
|
|
73
|
+
"capacitor": {
|
|
74
|
+
"ios": {
|
|
75
|
+
"src": "ios"
|
|
76
|
+
},
|
|
77
|
+
"android": {
|
|
78
|
+
"src": "android"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"scripts": {
|
|
82
|
+
"verify": "pnpm run verify:ios && pnpm run verify:android && pnpm run verify:web",
|
|
83
|
+
"verify:ios": "node ./scripts/sync-version.js && xcodebuild -scheme CapKitRank -destination generic/platform=iOS",
|
|
84
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
85
|
+
"verify:web": "pnpm run build",
|
|
86
|
+
"lint:android": "cd android && ./gradlew ktlintCheck",
|
|
87
|
+
"fmt:android": "cd android && ./gradlew ktlintFormat",
|
|
88
|
+
"lint": "pnpm run eslint . && pnpm run swiftlint lint --strict || true && pnpm run lint:android || true",
|
|
89
|
+
"format:check": "prettier --check \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
90
|
+
"format": "eslint --fix . && prettier --write \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java && pnpm run swiftlint --fix --format && pnpm run fmt:android",
|
|
91
|
+
"eslint": "eslint",
|
|
92
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
93
|
+
"swiftlint": "node-swiftlint lint ios/Sources",
|
|
94
|
+
"docgen": "docgen --api RankPlugin --output-readme README.md --output-json dist/docs.json",
|
|
95
|
+
"build": "node ./scripts/sync-version.js && pnpm run clean && pnpm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
96
|
+
"clean": "rimraf ./dist",
|
|
97
|
+
"watch": "tsc --watch",
|
|
98
|
+
"test": "pnpm run verify",
|
|
99
|
+
"removePacked": "rimraf -g cap-kit-rank-*.tgz",
|
|
100
|
+
"publish:locally": "pnpm run removePacked && pnpm run build && pnpm pack"
|
|
101
|
+
}
|
|
102
|
+
}
|