@capgo/capacitor-updater 4.0.0-alpha.8 → 4.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.
@@ -9,9 +9,10 @@ import Version
9
9
  @objc(CapacitorUpdaterPlugin)
10
10
  public class CapacitorUpdaterPlugin: CAPPlugin {
11
11
  private var implementation = CapacitorUpdater()
12
- static let updateUrlDefault = "https://xvwzpoazmxkqosrdewyv.functions.supabase.co/updates"
13
- static let statsUrlDefault = "https://xvwzpoazmxkqosrdewyv.functions.supabase.co/stats"
14
- static let DELAY_UPDATE = "delayUpdate"
12
+ static let updateUrlDefault = "https://api.capgo.app/updates"
13
+ static let statsUrlDefault = "https://api.capgo.app/stats"
14
+ let DELAY_UPDATE = "delayUpdate"
15
+ let DELAY_UPDATE_VAL = "delayUpdateVal"
15
16
  private var updateUrl = ""
16
17
  private var statsUrl = ""
17
18
  private var currentVersionNative: Version = "0.0.0"
@@ -23,17 +24,18 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
23
24
  private var autoDeletePrevious = false
24
25
 
25
26
  override public func load() {
27
+ print("\(self.implementation.TAG) init for device \(self.implementation.deviceID)")
26
28
  do {
27
- currentVersionNative = try Version(Bundle.main.buildVersionNumber ?? "0.0.0")
29
+ currentVersionNative = try Version(Bundle.main.versionName ?? "0.0.0")
28
30
  } catch {
29
31
  print("\(self.implementation.TAG) Cannot get version native \(currentVersionNative)")
30
32
  }
31
- autoDeleteFailed = getConfigValue("autoDeleteFailed") as? Bool ?? false
32
- autoDeletePrevious = getConfigValue("autoDeletePrevious") as? Bool ?? false
33
- updateUrl = getConfigValue("updateUrl") as? String ?? CapacitorUpdaterPlugin.updateUrlDefault
34
- autoUpdate = getConfigValue("autoUpdate") as? Bool ?? false
35
- appReadyTimeout = getConfigValue("appReadyTimeout") as? Int ?? 10000
36
- resetWhenUpdate = getConfigValue("resetWhenUpdate") as? Bool ?? true
33
+ autoDeleteFailed = getConfig().getBoolean("autoDeleteFailed", true)
34
+ autoDeletePrevious = getConfig().getBoolean("autoDeletePrevious", true)
35
+ updateUrl = getConfig().getString("updateUrl") ?? CapacitorUpdaterPlugin.updateUrlDefault
36
+ autoUpdate = getConfig().getBoolean("autoUpdate", false)
37
+ appReadyTimeout = getConfig().getInt("appReadyTimeout", 10000)
38
+ resetWhenUpdate = getConfig().getBoolean("resetWhenUpdate", true)
37
39
 
38
40
  implementation.appId = Bundle.main.bundleIdentifier ?? ""
39
41
  implementation.notifyDownload = notifyDownload
@@ -41,14 +43,14 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
41
43
  if (config?["appId"] != nil) {
42
44
  implementation.appId = config?["appId"] as! String
43
45
  }
44
- implementation.statsUrl = getConfigValue("statsUrl") as? String ?? CapacitorUpdaterPlugin.statsUrlDefault
45
-
46
+ implementation.statsUrl = getConfig().getString("statsUrl") ?? CapacitorUpdaterPlugin.updateUrlDefault
46
47
  if (resetWhenUpdate) {
47
48
  self.cleanupObsoleteVersions()
48
49
  }
49
50
  let nc = NotificationCenter.default
50
51
  nc.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
51
52
  nc.addObserver(self, selector: #selector(appMovedToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
53
+ self._checkCancelDelay(killed: true)
52
54
  self.appMovedToForeground()
53
55
  }
54
56
 
@@ -61,8 +63,6 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
61
63
  }
62
64
  if (LatestVersionNative != "0.0.0" && currentVersionNative.major > LatestVersionNative.major) {
63
65
  _ = self._reset(toLastSuccessful: false)
64
- UserDefaults.standard.set("", forKey: "LatestVersionAutoUpdate")
65
- UserDefaults.standard.set("", forKey: "LatestVersionNameAutoUpdate")
66
66
  let res = implementation.list()
67
67
  res.forEach { version in
68
68
  print("\(self.implementation.TAG) Deleting obsolete bundle: \(version)")
@@ -81,8 +81,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
81
81
  }
82
82
  }
83
83
 
84
- @objc func getId(_ call: CAPPluginCall) {
85
- call.resolve(["id": implementation.deviceID])
84
+ @objc func getDeviceId(_ call: CAPPluginCall) {
85
+ call.resolve(["deviceId": implementation.deviceID])
86
86
  }
87
87
 
88
88
  @objc func getPluginVersion(_ call: CAPPluginCall) {
@@ -106,6 +106,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
106
106
  let res = try implementation.download(url: url!, version: version)
107
107
  call.resolve(res.toJSON())
108
108
  } catch {
109
+ print("\(self.implementation.TAG) download failed \(error.localizedDescription)")
110
+ self.notifyListeners("downloadFailed", data: ["version": version])
109
111
  call.reject("download failed", error.localizedDescription)
110
112
  }
111
113
  }
@@ -127,8 +129,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
127
129
  if (self._reload()) {
128
130
  call.resolve()
129
131
  } else {
130
- call.reject("Reload failed")
131
132
  print("\(self.implementation.TAG) Reload failed")
133
+ call.reject("Reload failed")
132
134
  }
133
135
  }
134
136
 
@@ -138,9 +140,9 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
138
140
  call.reject("Next called without id")
139
141
  return
140
142
  }
141
-
142
143
  print("\(self.implementation.TAG) Setting next active id \(id)")
143
- if (!self.implementation.setNextVersion(next: id)) {
144
+ if (!self.implementation.setNextBundle(next: id)) {
145
+ print("\(self.implementation.TAG) Set next version failed. id \(id) does not exist.")
144
146
  call.reject("Set next version failed. id \(id) does not exist.")
145
147
  } else {
146
148
  call.resolve(self.implementation.getBundleInfo(id: id).toJSON())
@@ -173,6 +175,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
173
175
  if (res) {
174
176
  call.resolve()
175
177
  } else {
178
+ print("\(self.implementation.TAG) Delete failed, id \(id) doesn't exist")
176
179
  call.reject("Delete failed, id \(id) doesn't exist")
177
180
  }
178
181
  }
@@ -190,14 +193,14 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
190
193
 
191
194
  @objc func getLatest(_ call: CAPPluginCall) {
192
195
  let res = self.implementation.getLatest(url: URL(string: self.updateUrl)!)
193
- call.resolve((res?.toDict())!)
196
+ call.resolve(res.toDict())
194
197
  }
195
198
 
196
199
  @objc func _reset(toLastSuccessful: Bool) -> Bool {
197
200
  guard let bridge = self.bridge else { return false }
198
201
 
199
202
  if let vc = bridge.viewController as? CAPBridgeViewController {
200
- let fallback: BundleInfo = self.implementation.getFallbackVersion()
203
+ let fallback: BundleInfo = self.implementation.getFallbackBundle()
201
204
  if (toLastSuccessful && !fallback.isBuiltin()) {
202
205
  print("\(self.implementation.TAG) Resetting to: \(fallback.toString())")
203
206
  return self.implementation.set(bundle: fallback) && self._reload()
@@ -219,6 +222,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
219
222
  if (self._reset(toLastSuccessful: toLastSuccessful)) {
220
223
  return call.resolve()
221
224
  }
225
+ print("\(self.implementation.TAG) Reset failed")
222
226
  call.reject("\(self.implementation.TAG) Reset failed")
223
227
  }
224
228
 
@@ -226,27 +230,79 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
226
230
  let bundle: BundleInfo = self.implementation.getCurrentBundle()
227
231
  call.resolve([
228
232
  "bundle": bundle.toJSON(),
229
- "native": self.currentVersionNative
233
+ "native": self.currentVersionNative.description
230
234
  ])
231
235
  }
232
236
 
233
237
  @objc func notifyAppReady(_ call: CAPPluginCall) {
234
- print("\(self.implementation.TAG) Current bundle loaded successfully. ['notifyAppReady()' was called]")
235
238
  let version = self.implementation.getCurrentBundle()
236
- self.implementation.commit(bundle: version)
239
+ self.implementation.setSuccess(bundle: version, autoDeletePrevious: self.autoDeletePrevious)
240
+ print("\(self.implementation.TAG) Current bundle loaded successfully. ['notifyAppReady()' was called] \(version.toString())")
237
241
  call.resolve()
238
242
  }
239
243
 
240
244
  @objc func setDelay(_ call: CAPPluginCall) {
241
- guard let delay = call.getBool("delay") else {
242
- print("\(self.implementation.TAG) setDelay called without delay")
243
- call.reject("setDelay called without delay")
245
+ guard let kind = call.getString("kind") else {
246
+ print("\(self.implementation.TAG) setDelay called without kind")
247
+ call.reject("setDelay called without kind")
244
248
  return
245
249
  }
246
- UserDefaults.standard.set(delay, forKey: "delayUpdate")
250
+ let val = call.getString("value") ?? ""
251
+ UserDefaults.standard.set(kind, forKey: DELAY_UPDATE)
252
+ UserDefaults.standard.set(val, forKey: DELAY_UPDATE_VAL)
253
+ UserDefaults.standard.synchronize()
254
+ print("\(self.implementation.TAG) Delay update saved.", kind, val)
247
255
  call.resolve()
248
256
  }
249
-
257
+
258
+ private func _cancelDelay(source: String) -> Void {
259
+ print("\(self.implementation.TAG) delay Canceled from \(source)")
260
+ UserDefaults.standard.removeObject(forKey: DELAY_UPDATE)
261
+ UserDefaults.standard.removeObject(forKey: DELAY_UPDATE_VAL)
262
+ UserDefaults.standard.synchronize()
263
+ }
264
+
265
+ @objc func cancelDelay(_ call: CAPPluginCall) {
266
+ self._cancelDelay(source: "JS")
267
+ call.resolve()
268
+ }
269
+
270
+ private func _checkCancelDelay(killed: Bool) -> Void {
271
+ let delayUpdate = UserDefaults.standard.string(forKey: DELAY_UPDATE)
272
+ if (delayUpdate != nil) {
273
+ if (delayUpdate == "background" && !killed) {
274
+ self._cancelDelay(source: "background check")
275
+ } else if (delayUpdate == "kill" && killed) {
276
+ self._cancelDelay(source: "kill check")
277
+ }
278
+ guard let delayVal = UserDefaults.standard.string(forKey: DELAY_UPDATE_VAL) else {
279
+ self._cancelDelay(source: "delayVal absent")
280
+ return
281
+ }
282
+ if (delayUpdate == "date") {
283
+ let dateFormatter = ISO8601DateFormatter()
284
+ guard let ExpireDate = dateFormatter.date(from: delayVal) else {
285
+ self._cancelDelay(source: "date parsing issue")
286
+ return
287
+ }
288
+ if (ExpireDate < Date()) {
289
+ self._cancelDelay(source: "date expired")
290
+ }
291
+ } else if (delayUpdate == "nativeVersion") {
292
+ do {
293
+ let versionLimit = try Version(delayVal)
294
+ if (self.currentVersionNative >= versionLimit) {
295
+ self._cancelDelay(source: "nativeVersion above limit")
296
+ }
297
+ } catch {
298
+ self._cancelDelay(source: "nativeVersion parsing issue")
299
+ }
300
+ }
301
+ }
302
+
303
+ self.checkAppReady()
304
+ }
305
+
250
306
  private func _isAutoUpdateEnabled() -> Bool {
251
307
  return self.autoUpdate && self.updateUrl != ""
252
308
  }
@@ -266,7 +322,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
266
322
  DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(self.appReadyTimeout), execute: self.appReadyCheck!)
267
323
  }
268
324
 
269
- func DeferredNotifyAppReadyCheck() {
325
+ func checkRevert() {
270
326
  // Automatically roll back to fallback version if notifyAppReady has not been called yet
271
327
  let current: BundleInfo = self.implementation.getCurrentBundle()
272
328
  if(current.isBuiltin()) {
@@ -274,16 +330,33 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
274
330
  return
275
331
  }
276
332
 
333
+ print("\(self.implementation.TAG) Current bundle is: \(current.toString())")
334
+
277
335
  if(BundleStatus.SUCCESS.localizedString != current.getStatus()) {
278
336
  print("\(self.implementation.TAG) notifyAppReady was not called, roll back current bundle: \(current.toString())")
279
- self.implementation.rollback(bundle: current)
280
- let res = self._reset(toLastSuccessful: true)
281
- if (!res) {
282
- return
337
+ print("\(self.implementation.TAG) Did you forget to call 'notifyAppReady()' in your Capacitor App code?")
338
+ self.notifyListeners("updateFailed", data: [
339
+ "bundle": current.toJSON()
340
+ ])
341
+ self.implementation.sendStats(action: "update_fail", versionName: current.getVersionName())
342
+ self.implementation.setError(bundle: current)
343
+ _ = self._reset(toLastSuccessful: true)
344
+ if (self.autoDeleteFailed && !current.isBuiltin()) {
345
+ print("\(self.implementation.TAG) Deleting failing bundle: \(current.toString())")
346
+ let res = self.implementation.delete(id: current.getId(), removeInfo: false)
347
+ if (!res) {
348
+ print("\(self.implementation.TAG) Delete version deleted: \(current.toString())")
349
+ } else {
350
+ print("\(self.implementation.TAG) Failed to delete failed bundle: \(current.toString())")
351
+ }
283
352
  }
284
353
  } else {
285
354
  print("\(self.implementation.TAG) notifyAppReady was called. This is fine: \(current.toString())")
286
355
  }
356
+ }
357
+
358
+ func DeferredNotifyAppReadyCheck() {
359
+ self.checkRevert()
287
360
  self.appReadyCheck = nil
288
361
  }
289
362
 
@@ -293,45 +366,51 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
293
366
  print("\(self.implementation.TAG) Check for update via \(self.updateUrl)")
294
367
  let url = URL(string: self.updateUrl)!
295
368
  let res = self.implementation.getLatest(url: url)
296
- if (res == nil) {
297
- print("\(self.implementation.TAG) No result found in \(self.updateUrl)")
298
- return
299
- }
300
- if ((res?.message) != nil) {
301
- print("\(self.implementation.TAG) message \(res!.message ?? "")")
302
- if (res?.major == true) {
303
- self.notifyListeners("majorAvailable", data: ["version": res?.version ?? "0.0.0"])
369
+ let current = self.implementation.getCurrentBundle()
370
+
371
+ if ((res.message) != nil) {
372
+ print("\(self.implementation.TAG) message \(res.message ?? "")")
373
+ if (res.major == true) {
374
+ self.notifyListeners("majorAvailable", data: ["version": res.version])
304
375
  }
376
+ self.notifyListeners("noNeedUpdate", data: ["bundle": current.toJSON()])
305
377
  return
306
378
  }
307
- guard let downloadUrl = URL(string: res?.url ?? "") else {
379
+ guard let downloadUrl = URL(string: res.url) else {
308
380
  print("\(self.implementation.TAG) Error no url or wrong format")
381
+ self.notifyListeners("noNeedUpdate", data: ["bundle": current.toJSON()])
309
382
  return
310
383
  }
311
- let current = self.implementation.getCurrentBundle()
312
- let latestVersionName = res?.version
313
- if (latestVersionName != nil && latestVersionName != "" && current.getVersionName() != latestVersionName) {
314
- let latest = self.implementation.getBundleInfoByVersionName(version: latestVersionName!)
384
+ let latestVersionName = res.version
385
+ if (latestVersionName != "" && current.getVersionName() != latestVersionName) {
386
+ let latest = self.implementation.getBundleInfoByVersionName(version: latestVersionName)
315
387
  if (latest != nil) {
316
388
  if(latest!.isErrorStatus()) {
317
389
  print("\(self.implementation.TAG) Latest version already exists, and is in error state. Aborting update.")
390
+ self.notifyListeners("noNeedUpdate", data: ["bundle": current.toJSON()])
318
391
  return
319
392
  }
320
393
  if(latest!.isDownloaded()){
321
394
  print("\(self.implementation.TAG) Latest version already exists and download is NOT required. Update will occur next time app moves to background.")
322
- let _ = self.implementation.setNextVersion(next: latest!.getId())
395
+ self.notifyListeners("updateAvailable", data: ["bundle": current.toJSON()])
396
+ let _ = self.implementation.setNextBundle(next: latest!.getId())
323
397
  return
324
398
  }
325
399
  }
326
400
 
327
401
  do {
328
- print("\(self.implementation.TAG) New bundle: \(latestVersionName!) found. Current is: \(current.getVersionName()). Update will occur next time app moves to background.")
329
- let next = try self.implementation.download(url: downloadUrl, version: latestVersionName!)
330
-
331
- let _ = self.implementation.setNextVersion(next: next.getId())
402
+ print("\(self.implementation.TAG) New bundle: \(latestVersionName) found. Current is: \(current.getVersionName()). Update will occur next time app moves to background.")
403
+ let next = try self.implementation.download(url: downloadUrl, version: latestVersionName)
404
+ self.notifyListeners("updateAvailable", data: ["bundle": next.toJSON()])
405
+ let _ = self.implementation.setNextBundle(next: next.getId())
332
406
  } catch {
333
407
  print("\(self.implementation.TAG) Error downloading file", error.localizedDescription)
408
+ self.notifyListeners("downloadFailed", data: ["version": latestVersionName])
409
+ self.notifyListeners("noNeedUpdate", data: ["bundle": current.toJSON()])
334
410
  }
411
+ } else {
412
+ print("\(self.implementation.TAG) No need to update, \(current.getId()) is the latest bundle.")
413
+ self.notifyListeners("noNeedUpdate", data: ["bundle": current.toJSON()])
335
414
  }
336
415
  }
337
416
  }
@@ -340,85 +419,24 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
340
419
  }
341
420
 
342
421
  @objc func appMovedToBackground() {
343
- print("\(self.implementation.TAG) Check for waiting update")
344
- let delayUpdate = UserDefaults.standard.bool(forKey: "delayUpdate")
345
- UserDefaults.standard.set(false, forKey: "delayUpdate")
346
- if (delayUpdate) {
422
+ print("\(self.implementation.TAG) Check for pending update")
423
+ let delayUpdate = UserDefaults.standard.string(forKey: DELAY_UPDATE)
424
+ self._checkCancelDelay(killed: false)
425
+ if (delayUpdate != nil) {
347
426
  print("\(self.implementation.TAG) Update delayed to next backgrounding")
348
427
  return
349
428
  }
350
429
 
351
- let fallback: BundleInfo = self.implementation.getFallbackVersion()
352
430
  let current: BundleInfo = self.implementation.getCurrentBundle()
353
- let next: BundleInfo? = self.implementation.getNextVersion()
354
-
355
- let success: Bool = current.getStatus() == BundleStatus.SUCCESS.localizedString
431
+ let next: BundleInfo? = self.implementation.getNextBundle()
356
432
 
357
- print("\(self.implementation.TAG) Fallback bundle is: \(fallback.toString())")
358
- print("\(self.implementation.TAG) Current bundle is: \(current.toString())")
359
-
360
- if (next != nil && !next!.isErrorStatus() && (next!.getVersionName() != current.getVersionName())) {
433
+ if (next != nil && !next!.isErrorStatus() && next!.getVersionName() != current.getVersionName()) {
361
434
  print("\(self.implementation.TAG) Next bundle is: \(next!.toString())")
362
435
  if (self.implementation.set(bundle: next!) && self._reload()) {
363
- print("\(self.implementation.TAG) Updated to bundle: \(next!)")
364
- let _ = self.implementation.setNextVersion(next: Optional<String>.none)
365
- } else {
366
- print("\(self.implementation.TAG) Updated to bundle: \(next!) Failed!")
367
- }
368
- } else if (!success) {
369
- // There is a no next version, and the current version has failed
370
-
371
- if(!current.isBuiltin()) {
372
- // Don't try to roll back the builtin version. Nothing we can do.
373
-
374
- self.implementation.rollback(bundle: current)
375
-
376
- print("\(self.implementation.TAG) Update failed: 'notifyAppReady()' was never called.")
377
- print("\(self.implementation.TAG) Version: \(current.toString()), is in error state.")
378
- print("\(self.implementation.TAG) Will fallback to: \(fallback.toString()) on application restart.")
379
- print("\(self.implementation.TAG) Did you forget to call 'notifyAppReady()' in your Capacitor App code?")
380
-
381
- self.notifyListeners("updateFailed", data: [
382
- "bundle": current.toJSON()
383
- ])
384
- self.implementation.sendStats(action: "revert", bundle: current)
385
- if (!fallback.isBuiltin() && !(fallback == current)) {
386
- let res = self.implementation.set(bundle: fallback)
387
- if (res && self._reload()) {
388
- print("\(self.implementation.TAG) Revert to bundle: \(fallback.toString())")
389
- } else {
390
- print("\(self.implementation.TAG) Revert to bundle: \(fallback.toString()) Failed!")
391
- }
392
- } else {
393
- if (self._reset(toLastSuccessful: false)) {
394
- print("\(self.implementation.TAG) Reverted to 'builtin' bundle.")
395
- }
396
- }
397
-
398
- if (self.autoDeleteFailed) {
399
- print("\(self.implementation.TAG) Deleting failing bundle: \(current.toString())")
400
- let res = self.implementation.delete(id: current.getId())
401
- if (!res) {
402
- print("\(self.implementation.TAG) Delete version deleted: \(current.toString())")
403
- } else {
404
- print("\(self.implementation.TAG) Failed to delete failed bundle: \(current.toString())")
405
- }
406
- }
436
+ print("\(self.implementation.TAG) Updated to bundle: \(next!.toString())")
437
+ let _ = self.implementation.setNextBundle(next: Optional<String>.none)
407
438
  } else {
408
- // Nothing we can/should do by default if the 'builtin' bundle fails to call 'notifyAppReady()'.
409
- }
410
- } else if (!fallback.isBuiltin()) {
411
- // There is a no next version, and the current version has succeeded
412
- self.implementation.commit(bundle: current)
413
-
414
- if(self.autoDeletePrevious) {
415
- print("\(self.implementation.TAG) Version successfully loaded: \(current.toString())")
416
- let res = self.implementation.delete(id: fallback.getId())
417
- if (res) {
418
- print("\(self.implementation.TAG) Deleted previous bundle: \(fallback.toString())")
419
- } else {
420
- print("\(self.implementation.TAG) Failed to delete previous bundle: \(fallback.toString())")
421
- }
439
+ print("\(self.implementation.TAG) Update to bundle: \(next!.toString()) Failed!")
422
440
  }
423
441
  }
424
442
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-updater",
3
- "version": "4.0.0-alpha.8",
4
- "license": "AGPL-3.0-only",
3
+ "version": "4.0.1",
4
+ "license": "LGPL-3.0-only",
5
5
  "description": "OTA update for capacitor apps",
6
6
  "main": "dist/plugin.cjs.js",
7
7
  "module": "dist/esm/index.js",
@@ -68,7 +68,7 @@
68
68
  "typescript": "^4.6.2"
69
69
  },
70
70
  "peerDependencies": {
71
- "@capacitor/core": "^3.0.0"
71
+ "@capacitor/core": "^3.0.0 || ^4.0.0 || ^4.0.0"
72
72
  },
73
73
  "prettier": "@ionic/prettier-config",
74
74
  "swiftlint": "@ionic/swiftlint-config",