@capgo/capacitor-ricoh360 8.0.12

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.
@@ -0,0 +1,580 @@
1
+ import Foundation
2
+ import Capacitor
3
+ import Alamofire
4
+ import AVKit
5
+ import UIKit
6
+ import Network
7
+
8
+ /**
9
+ * Please read the Capacitor iOS Plugin Development Guide
10
+ * here: https://capacitorjs.com/docs/plugins/ios
11
+ */
12
+ @objc(Ricoh360CameraPlugin)
13
+ public class Ricoh360CameraPlugin: CAPPlugin, CAPBridgedPlugin, URLSessionDataDelegate { // swiftlint:disable:this type_body_length
14
+ private let pluginVersion: String = "8.0.12"
15
+ public let identifier = "Ricoh360CameraPlugin"
16
+ public let jsName = "Ricoh360Camera"
17
+ public let pluginMethods: [CAPPluginMethod] = [
18
+ CAPPluginMethod(name: "initialize", returnType: CAPPluginReturnPromise),
19
+ CAPPluginMethod(name: "capturePicture", returnType: CAPPluginReturnPromise),
20
+ CAPPluginMethod(name: "readSettings", returnType: CAPPluginReturnPromise),
21
+ CAPPluginMethod(name: "setSettings", returnType: CAPPluginReturnPromise),
22
+ CAPPluginMethod(name: "livePreview", returnType: CAPPluginReturnPromise),
23
+ CAPPluginMethod(name: "stopLivePreview", returnType: CAPPluginReturnPromise),
24
+ CAPPluginMethod(name: "captureVideo", returnType: CAPPluginReturnPromise),
25
+ CAPPluginMethod(name: "sendCommand", returnType: CAPPluginReturnPromise),
26
+ CAPPluginMethod(name: "getCameraAsset", returnType: CAPPluginReturnPromise),
27
+ CAPPluginMethod(name: "listFiles", returnType: CAPPluginReturnPromise),
28
+ CAPPluginMethod(name: "getPluginVersion", returnType: CAPPluginReturnPromise)
29
+ ]
30
+
31
+ private var previewView: UIImageView?
32
+ private var cameraUrl: String = "http://192.168.1.1"
33
+ private var httpStream: HttpStream?
34
+ private var connection: NWConnection?
35
+ private var browser: NWBrowser?
36
+ private var permissionCompletion: ((Bool) -> Void)?
37
+
38
+ enum RicohThetaError: Error {
39
+ case invalidUrl, badResponse, parseImageProvider, parseCGImage
40
+ }
41
+
42
+ private let startMarker = Data([0xFF, 0xD8])
43
+ private let endMarker = Data([0xFF, 0xD9])
44
+ private var buffer = Data()
45
+ private var session: URLSession
46
+ private var task: URLSessionTask?
47
+ private var imageCompletion: ((UIImage?, Error?) -> Void)?
48
+
49
+ override init() {
50
+ let config = URLSessionConfiguration.default
51
+ config.timeoutIntervalForRequest = 5
52
+ config.waitsForConnectivity = true
53
+ self.session = URLSession(configuration: config, delegate: nil, delegateQueue: OperationQueue.main)
54
+ super.init()
55
+ }
56
+
57
+ struct CameraInfo: Decodable {
58
+ let model: String
59
+ let firmwareVersion: String
60
+ let manufacturer: String
61
+ let serialNumber: String
62
+ let supportUrl: String
63
+ let gps: String
64
+ let gyro: String
65
+ let uptime: String
66
+ let api: String
67
+ let apiLevel: String
68
+ let endpoints: Endpoints
69
+ }
70
+
71
+ struct Endpoints: Decodable {
72
+ let httpPort: String
73
+ let httpUpdatesPort: String
74
+ }
75
+
76
+ struct CommandResponse: Decodable {
77
+ let name: String
78
+ let state: String
79
+ let results: CommandResults?
80
+ let error: CommandError?
81
+ }
82
+
83
+ struct CommandResults: Decodable {
84
+ // Define fields based on the specific command response
85
+ }
86
+
87
+ struct CommandError: Decodable {
88
+ let code: String
89
+ let message: String
90
+ }
91
+
92
+ protocol HttpStreamProtocol: URLSessionDataDelegate {
93
+ init(request: URLRequest, plugin: Ricoh360CameraPlugin)
94
+ func startLivePreview(completion: @escaping (UIImage?, Error?) -> Void)
95
+ func stopLivePreview()
96
+ }
97
+
98
+ final class HttpStream: NSObject, HttpStreamProtocol {
99
+ enum HttpStreamError: Error {
100
+ case badResponse, parseImageProvider, parseCGImage
101
+ }
102
+
103
+ let startMarker = Data([0xFF, 0xD8])
104
+ let endMarker = Data([0xFF, 0xD9])
105
+ let queue = DispatchQueue(label: "com.ricoh.theta.stream.queue")
106
+
107
+ var buffer = Data()
108
+ var request: URLRequest
109
+ var task: URLSessionTask?
110
+ var isLivePreviewing: Bool = false
111
+ var imageCompletion: ((UIImage?, Error?) -> Void)?
112
+ weak var plugin: Ricoh360CameraPlugin?
113
+
114
+ deinit {
115
+ self.stopLivePreview()
116
+ }
117
+
118
+ required init(request: URLRequest, plugin: Ricoh360CameraPlugin) {
119
+ self.request = request
120
+ self.plugin = plugin
121
+ super.init()
122
+ }
123
+
124
+ func startLivePreview(completion: @escaping (UIImage?, Error?) -> Void) {
125
+ if !self.isLivePreviewing {
126
+ self.imageCompletion = completion
127
+ let payload = ["name": "camera.getLivePreview"]
128
+ self.request.httpBody = try? JSONEncoder().encode(payload)
129
+ self.request.setValue("application/json", forHTTPHeaderField: "Content-Type")
130
+ self.request.setValue("application/json", forHTTPHeaderField: "Accept")
131
+
132
+ let config = URLSessionConfiguration.default
133
+ config.timeoutIntervalForRequest = 5
134
+ config.waitsForConnectivity = true
135
+
136
+ plugin?.requestLocalNetworkPermission { [weak self] granted in
137
+ guard let self = self else { return }
138
+
139
+ if granted {
140
+ let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
141
+ self.task = session.dataTask(with: self.request)
142
+ self.task?.resume()
143
+ self.isLivePreviewing = true
144
+ } else {
145
+ self.imageCompletion?(nil, HttpStreamError.badResponse)
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ func stopLivePreview() {
152
+ self.task?.cancel()
153
+ }
154
+
155
+ func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
156
+ guard let response = dataTask.response as? HTTPURLResponse, response.statusCode == 200 else {
157
+ self.imageCompletion?(nil, HttpStreamError.badResponse)
158
+ return
159
+ }
160
+
161
+ if data.range(of: self.startMarker) != nil {
162
+ self.buffer = Data()
163
+ }
164
+
165
+ self.buffer.append(data)
166
+
167
+ if data.range(of: self.endMarker) != nil {
168
+ self.parseFrame(self.buffer) { [weak self] image, error in
169
+ guard let self = self else { return }
170
+ self.imageCompletion?(image, error)
171
+ }
172
+ }
173
+ }
174
+
175
+ func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
176
+ session.invalidateAndCancel()
177
+ self.isLivePreviewing = false
178
+ self.imageCompletion?(nil, error)
179
+ }
180
+
181
+ private func parseFrame(_ data: Data, _ completion: @escaping ((UIImage?, Error?) -> Void)) {
182
+ self.queue.async {
183
+ guard let imgProvider = CGDataProvider(data: data as CFData) else {
184
+ completion(nil, HttpStreamError.parseImageProvider)
185
+ return
186
+ }
187
+
188
+ guard let image = CGImage(
189
+ jpegDataProviderSource: imgProvider,
190
+ decode: nil,
191
+ shouldInterpolate: true,
192
+ intent: .defaultIntent) else {
193
+ completion(nil, HttpStreamError.parseCGImage)
194
+ return
195
+ }
196
+
197
+ completion(UIImage(cgImage: image), nil)
198
+ }
199
+ }
200
+ }
201
+
202
+ @objc func initialize(_ call: CAPPluginCall) {
203
+ if let url = call.getString("url") {
204
+ cameraUrl = url
205
+ }
206
+
207
+ let infoUrl = "\(cameraUrl)/osc/info"
208
+
209
+ requestLocalNetworkPermission { [weak self] granted in
210
+ if granted {
211
+ AF.request(infoUrl).validate().responseData { response in
212
+ switch response.result {
213
+ case .success(let data):
214
+ do {
215
+ if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
216
+ call.resolve(["session": "Initialized", "info": json])
217
+ } else {
218
+ call.reject("Camera is not reachable or info could not be retrieved")
219
+ }
220
+ } catch {
221
+ call.reject("Camera is not reachable or info could not be retrieved")
222
+ }
223
+ case .failure(let error):
224
+ call.reject("Camera is not reachable or info could not be retrieved")
225
+ }
226
+ }
227
+ } else {
228
+ call.reject("Permission not granted")
229
+ }
230
+ }
231
+ }
232
+
233
+ @objc func livePreview(_ call: CAPPluginCall) {
234
+ DispatchQueue.main.async {
235
+ if self.previewView == nil {
236
+ let displayInFront = call.getBool("displayInFront") ?? true
237
+ let cropPreview = call.getBool("cropPreview") ?? false
238
+
239
+ // Create preview view with zero frame initially
240
+ self.previewView = UIImageView(frame: .zero)
241
+ self.previewView?.backgroundColor = .clear
242
+ self.previewView?.contentMode = cropPreview ? .scaleAspectFill : .scaleAspectFit
243
+ self.previewView?.clipsToBounds = cropPreview // Only clip bounds if cropping
244
+
245
+ // Make webview transparent
246
+ self.webView?.isOpaque = false
247
+ self.webView?.backgroundColor = UIColor.clear
248
+ self.webView?.scrollView.backgroundColor = UIColor.clear
249
+
250
+ if let previewView = self.previewView {
251
+ if displayInFront {
252
+ self.bridge?.viewController?.view.addSubview(previewView)
253
+ } else {
254
+ self.webView?.superview?.addSubview(previewView)
255
+ self.webView?.superview?.bringSubviewToFront(self.webView!)
256
+ }
257
+
258
+ // Make preview view fill parent
259
+ previewView.translatesAutoresizingMaskIntoConstraints = false
260
+ NSLayoutConstraint.activate([
261
+ previewView.topAnchor.constraint(equalTo: previewView.superview!.topAnchor),
262
+ previewView.bottomAnchor.constraint(equalTo: previewView.superview!.bottomAnchor),
263
+ previewView.leadingAnchor.constraint(equalTo: previewView.superview!.leadingAnchor),
264
+ previewView.trailingAnchor.constraint(equalTo: previewView.superview!.trailingAnchor)
265
+ ])
266
+ }
267
+ }
268
+
269
+ let url = URL(string: "\(self.cameraUrl)/osc/commands/execute")!
270
+ var request = URLRequest(url: url)
271
+ request.httpMethod = "POST"
272
+
273
+ self.httpStream = HttpStream(request: request, plugin: self)
274
+ self.httpStream?.startLivePreview { [weak self] (image: UIImage?, _: Error?) in
275
+ guard let self = self else { return }
276
+ if let image = image {
277
+ DispatchQueue.main.async {
278
+ self.previewView?.image = image
279
+ }
280
+ }
281
+ }
282
+ }
283
+ call.resolve(["preview": "Started"])
284
+ }
285
+
286
+ @objc func stopLivePreview(_ call: CAPPluginCall) {
287
+ DispatchQueue.main.async {
288
+ self.httpStream?.stopLivePreview()
289
+ self.previewView?.removeFromSuperview()
290
+ self.previewView = nil
291
+
292
+ // Revert webview transparency
293
+ self.webView?.isOpaque = true
294
+ self.webView?.backgroundColor = .white
295
+ self.webView?.scrollView.backgroundColor = .white
296
+
297
+ call.resolve(["preview": "Stopped"])
298
+ }
299
+ }
300
+
301
+ @objc func capturePicture(_ call: CAPPluginCall) {
302
+ let parameters: [String: Any] = ["name": "camera.takePicture"]
303
+ guard let jsonData = try? JSONSerialization.data(withJSONObject: parameters),
304
+ let jsonInputString = String(data: jsonData, encoding: .utf8) else {
305
+ call.reject("Failed to serialize parameters")
306
+ return
307
+ }
308
+
309
+ sendCommandRaw(endpoint: "/osc/commands/execute", payload: jsonInputString, call: call)
310
+ }
311
+
312
+ @objc func readSettings(_ call: CAPPluginCall) {
313
+ guard let data = call.options,
314
+ let jsonData = try? JSONSerialization.data(withJSONObject: data),
315
+ let options = String(data: jsonData, encoding: .utf8) else {
316
+ call.reject("Invalid options")
317
+ return
318
+ }
319
+
320
+ let jsonInputString = """
321
+ {"name": "camera.getOptions", "parameters": {"optionNames": \(options)}}
322
+ """
323
+
324
+ sendCommandRaw(endpoint: "/osc/commands/execute", payload: jsonInputString, call: call)
325
+ }
326
+
327
+ @objc func setSettings(_ call: CAPPluginCall) {
328
+ guard let data = call.options,
329
+ let jsonData = try? JSONSerialization.data(withJSONObject: data),
330
+ let options = String(data: jsonData, encoding: .utf8) else {
331
+ call.reject("Invalid options")
332
+ return
333
+ }
334
+
335
+ let jsonInputString = """
336
+ {"name": "camera.setOptions", "parameters": {"options": \(options)}}
337
+ """
338
+
339
+ sendCommandRaw(endpoint: "/osc/commands/execute", payload: jsonInputString, call: call)
340
+ }
341
+
342
+ @objc func sendCommand(_ call: CAPPluginCall) {
343
+ guard let endpoint = call.getString("endpoint"),
344
+ let data = call.getObject("payload"),
345
+ let jsonData = try? JSONSerialization.data(withJSONObject: data),
346
+ let payload = String(data: jsonData, encoding: .utf8) else {
347
+ call.reject("Invalid endpoint or payload")
348
+ return
349
+ }
350
+
351
+ sendCommandRaw(endpoint: endpoint, payload: payload, call: call)
352
+ }
353
+
354
+ private func sendCommandRaw(endpoint: String, payload: String, call: CAPPluginCall) {
355
+ guard let url = URL(string: cameraUrl + endpoint) else {
356
+ call.reject("Invalid URL")
357
+ return
358
+ }
359
+
360
+ requestLocalNetworkPermission { [weak self] granted in
361
+ print("Permission flow completed with result: \(granted)")
362
+ if granted {
363
+ print("Proceeding with socket connection")
364
+ var request = URLRequest(url: url)
365
+ request.httpMethod = "POST"
366
+ request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
367
+ request.setValue("application/json", forHTTPHeaderField: "Accept")
368
+ request.httpBody = payload.data(using: .utf8)
369
+
370
+ let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
371
+ if let error = error {
372
+ call.reject("Command failed", error.localizedDescription)
373
+ return
374
+ }
375
+
376
+ guard let httpResponse = response as? HTTPURLResponse,
377
+ let data = data else {
378
+ call.reject("Command failed")
379
+ return
380
+ }
381
+
382
+ if httpResponse.statusCode == 200 {
383
+ if let result = String(data: data, encoding: .utf8),
384
+ let jsonData = result.data(using: .utf8),
385
+ let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] {
386
+ call.resolve(json)
387
+ } else {
388
+ call.reject("Failed to parse command response")
389
+ }
390
+ } else {
391
+ if let error = String(data: data, encoding: .utf8) {
392
+ call.reject("Command failed: \(error)")
393
+ } else {
394
+ call.reject("Command failed")
395
+ }
396
+ }
397
+ }
398
+ task.resume()
399
+ } else {
400
+ print("Permission not granted")
401
+ call.reject("Permission not granted")
402
+ }
403
+ }
404
+ }
405
+
406
+ private func requestLocalNetworkPermission(completion: @escaping (Bool) -> Void) {
407
+ self.permissionCompletion = completion
408
+
409
+ // Use Bonjour browsing to trigger local network permission
410
+ let parameters = NWParameters()
411
+ parameters.includePeerToPeer = true
412
+
413
+ // Browse for all services
414
+ let browser = NWBrowser(for: .bonjour(type: "_http._tcp", domain: nil), using: parameters)
415
+ self.browser = browser
416
+
417
+ browser.stateUpdateHandler = { [weak self] state in
418
+ switch state {
419
+ case .ready:
420
+ print("Browser is ready")
421
+ self?.permissionCompletion?(true)
422
+ self?.browser?.cancel()
423
+ case .failed(let error):
424
+ print("Browser failed: \(error)")
425
+ if let error = error as? NWError {
426
+ switch error {
427
+ case .dns(let dnsError):
428
+ print("DNS error: \(dnsError)")
429
+ case .posix(let code):
430
+ print("POSIX error: \(code)")
431
+ default:
432
+ print("Other error: \(error)")
433
+ }
434
+ }
435
+ self?.permissionCompletion?(false)
436
+ self?.browser?.cancel()
437
+ case .cancelled:
438
+ print("Browser was cancelled")
439
+ case .waiting(let error):
440
+ print("Browser is waiting: \(error)")
441
+ default:
442
+ break
443
+ }
444
+ }
445
+
446
+ browser.browseResultsChangedHandler = { results, _ in
447
+ print("Found \(results.count) services")
448
+ for result in results {
449
+ print("Service: \(result.endpoint)")
450
+ }
451
+ }
452
+
453
+ print("Starting network service browser")
454
+ browser.start(queue: .main)
455
+
456
+ // Set a timeout
457
+ DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { [weak self] in
458
+ if self?.browser != nil {
459
+ print("Browser timeout - proceeding anyway")
460
+ self?.permissionCompletion?(true)
461
+ self?.browser?.cancel()
462
+ }
463
+ }
464
+ }
465
+
466
+ @objc func getCameraAsset(_ call: CAPPluginCall) {
467
+ guard let assetUrl = call.getString("url") else {
468
+ call.reject("URL is required")
469
+ return
470
+ }
471
+
472
+ let saveToFile = call.getBool("saveToFile", false)
473
+
474
+ guard let url = URL(string: assetUrl) else {
475
+ call.reject("Invalid URL")
476
+ return
477
+ }
478
+
479
+ requestLocalNetworkPermission { [weak self] granted in
480
+ guard let self = self else { return }
481
+
482
+ if granted {
483
+ let task = URLSession.shared.dataTask(with: url) { [weak self] (data, response, error) in
484
+ guard let self = self else { return }
485
+
486
+ if let error = error {
487
+ call.reject("Failed to fetch asset: \(error.localizedDescription)")
488
+ return
489
+ }
490
+
491
+ guard let httpResponse = response as? HTTPURLResponse,
492
+ let data = data else {
493
+ call.reject("Failed to fetch asset")
494
+ return
495
+ }
496
+
497
+ var result = JSObject()
498
+ result["statusCode"] = httpResponse.statusCode
499
+
500
+ if saveToFile {
501
+ let timestamp = Int(Date().timeIntervalSince1970 * 1000)
502
+ let filename = "ricoh_\(timestamp).jpg"
503
+ let tempDir = FileManager.default.temporaryDirectory
504
+ let fileURL = tempDir.appendingPathComponent(filename)
505
+
506
+ do {
507
+ try data.write(to: fileURL)
508
+ result["filePath"] = fileURL.path
509
+ call.resolve(result)
510
+ } catch {
511
+ call.reject("Failed to save file: \(error.localizedDescription)")
512
+ }
513
+ } else {
514
+ // Downsize and convert to base64
515
+ if let downsizedData = self.downSizeImage(data) {
516
+ let base64String = downsizedData.base64EncodedString()
517
+ result["data"] = base64String
518
+ call.resolve(result)
519
+ } else {
520
+ call.reject("Failed to process image")
521
+ }
522
+ }
523
+ }
524
+ task.resume()
525
+ } else {
526
+ call.reject("Permission not granted")
527
+ }
528
+ }
529
+ }
530
+
531
+ private func downSizeImage(_ imageData: Data) -> Data? {
532
+ let maxWidth: CGFloat = 2048 // Must be equal to or less than 4096
533
+
534
+ guard let image = UIImage(data: imageData) else { return nil }
535
+ let originalWidth = image.size.width
536
+
537
+ if originalWidth > maxWidth {
538
+ let scaleFactor = maxWidth / originalWidth
539
+ let newSize = CGSize(width: originalWidth * scaleFactor, height: image.size.height * scaleFactor)
540
+
541
+ UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
542
+ image.draw(in: CGRect(origin: .zero, size: newSize))
543
+ guard let resizedImage = UIGraphicsGetImageFromCurrentImageContext() else {
544
+ UIGraphicsEndImageContext()
545
+ return nil
546
+ }
547
+ UIGraphicsEndImageContext()
548
+
549
+ return resizedImage.jpegData(compressionQuality: 1.0)
550
+ }
551
+
552
+ return imageData
553
+ }
554
+
555
+ @objc func listFiles(_ call: CAPPluginCall) {
556
+ var parameters = JSObject()
557
+ parameters["fileType"] = call.getString("fileType", "all")
558
+ parameters["startPosition"] = call.getInt("startPosition", 0)
559
+ parameters["entryCount"] = call.getInt("entryCount", 100)
560
+ parameters["maxThumbSize"] = call.getInt("maxThumbSize", 0)
561
+ parameters["_detail"] = call.getBool("_detail", true)
562
+
563
+ var payload = JSObject()
564
+ payload["name"] = "camera.listFiles"
565
+ payload["parameters"] = parameters
566
+
567
+ guard let jsonData = try? JSONSerialization.data(withJSONObject: payload),
568
+ let jsonInputString = String(data: jsonData, encoding: .utf8) else {
569
+ call.reject("Failed to serialize parameters")
570
+ return
571
+ }
572
+
573
+ sendCommandRaw(endpoint: "/osc/commands/execute", payload: jsonInputString, call: call)
574
+ }
575
+
576
+ @objc func getPluginVersion(_ call: CAPPluginCall) {
577
+ call.resolve(["version": self.pluginVersion])
578
+ }
579
+
580
+ }
@@ -0,0 +1,15 @@
1
+ import XCTest
2
+ @testable import Ricoh360CameraPlugin
3
+
4
+ class Ricoh360CameraTests: XCTestCase {
5
+ func testEcho() {
6
+ // This is an example of a functional test case for a plugin.
7
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
8
+
9
+ let implementation = Ricoh360Camera()
10
+ let value = "Hello, World!"
11
+ let result = implementation.echo(value)
12
+
13
+ XCTAssertEqual(value, result)
14
+ }
15
+ }
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@capgo/capacitor-ricoh360",
3
+ "version": "8.0.12",
4
+ "description": "Provides an SDK for the Ricoh360 cameras for Capacitor",
5
+ "main": "dist/plugin.cjs.js",
6
+ "module": "dist/esm/index.js",
7
+ "types": "dist/esm/index.d.ts",
8
+ "unpkg": "dist/plugin.js",
9
+ "files": [
10
+ "android/src/main/",
11
+ "android/build.gradle",
12
+ "dist/",
13
+ "ios/Sources",
14
+ "ios/Tests",
15
+ "Package.swift",
16
+ "CapgoCapacitorRicoh360.podspec"
17
+ ],
18
+ "author": "",
19
+ "license": "MPL-2.0",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/Cap-go/capacitor-ricoh360-camera-plugin.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/Cap-go/capacitor-ricoh360-camera-plugin/issues"
26
+ },
27
+ "homepage": "https://capgo.app/docs/plugins/ricoh360-camera/",
28
+ "keywords": [
29
+ "capacitor",
30
+ "plugin",
31
+ "native",
32
+ "ricoh360",
33
+ "camera",
34
+ "360",
35
+ "camera360",
36
+ "360camera",
37
+ "360camera360",
38
+ "360camera360camera",
39
+ "ricoh",
40
+ "capgo"
41
+ ],
42
+ "scripts": {
43
+ "verify": "bun run verify:ios && bun run verify:android && bun run verify:web",
44
+ "verify:ios": "xcodebuild -scheme CapgoCapacitorRicoh360 -destination generic/platform=iOS",
45
+ "verify:android": "cd android && ./gradlew clean build test && cd ..",
46
+ "verify:web": "bun run build",
47
+ "lint": "bun run eslint && bun run prettier -- --check && bun run swiftlint -- lint",
48
+ "fmt": "bun run eslint -- --fix && bun run prettier -- --write && bun run swiftlint -- --fix --format",
49
+ "eslint": "eslint . --ext ts",
50
+ "prettier": "prettier-pretty-check \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
51
+ "swiftlint": "node-swiftlint",
52
+ "docgen": "docgen --api Ricoh360CameraPlugin --output-readme README.md --output-json dist/docs.json",
53
+ "build": "bun run clean && bun run docgen && tsc && rollup -c rollup.config.mjs",
54
+ "clean": "rimraf ./dist",
55
+ "watch": "tsc --watch",
56
+ "prepublishOnly": "bun run build",
57
+ "check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs"
58
+ },
59
+ "devDependencies": {
60
+ "@capacitor/android": "^8.0.0",
61
+ "@capacitor/core": "^8.0.0",
62
+ "@capacitor/docgen": "^0.3.1",
63
+ "@capacitor/ios": "^8.0.0",
64
+ "@ionic/eslint-config": "^0.4.0",
65
+ "@ionic/prettier-config": "^4.0.0",
66
+ "@ionic/swiftlint-config": "^2.0.0",
67
+ "@types/node": "^24.10.1",
68
+ "eslint": "^8.57.1",
69
+ "eslint-plugin-import": "^2.31.0",
70
+ "prettier": "^3.6.2",
71
+ "prettier-plugin-java": "^2.7.7",
72
+ "rimraf": "^6.1.0",
73
+ "rollup": "^4.53.2",
74
+ "swiftlint": "^2.0.0",
75
+ "typescript": "^5.9.3",
76
+ "prettier-pretty-check": "^0.2.0"
77
+ },
78
+ "peerDependencies": {
79
+ "@capacitor/core": ">=8.0.0"
80
+ },
81
+ "prettier": "@ionic/prettier-config",
82
+ "swiftlint": "@ionic/swiftlint-config",
83
+ "eslintConfig": {
84
+ "extends": "@ionic/eslint-config/recommended"
85
+ },
86
+ "capacitor": {
87
+ "ios": {
88
+ "src": "ios"
89
+ },
90
+ "android": {
91
+ "src": "android"
92
+ }
93
+ }
94
+ }