@capgo/capacitor-file-sharer 8.1.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/dist/plugin.js ADDED
@@ -0,0 +1,110 @@
1
+ var capacitorFileSharer = (function (exports, core) {
2
+ 'use strict';
3
+
4
+ const FileSharer = core.registerPlugin('FileSharer', {
5
+ web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.FileSharerWeb()),
6
+ });
7
+
8
+ const DEFAULT_CONTENT_TYPE = 'application/octet-stream';
9
+ const ERR_PARAM_NO_FILENAME = 'ERR_PARAM_NO_FILENAME';
10
+ const ERR_PARAM_NO_DATA = 'ERR_PARAM_NO_DATA';
11
+ const ERR_PARAM_DATA_INVALID = 'ERR_PARAM_DATA_INVALID';
12
+ const ERR_LOCAL_FILE_NOT_FOUND = 'ERR_LOCAL_FILE_NOT_FOUND';
13
+ const BASE64_CHUNK_LENGTH = 256 * 1024;
14
+ class FileSharerWeb extends core.WebPlugin {
15
+ async share(options) {
16
+ await this.download(options);
17
+ }
18
+ async save(options) {
19
+ await this.download(options);
20
+ return {};
21
+ }
22
+ async getPluginVersion() {
23
+ return {
24
+ version: '8.0.0',
25
+ };
26
+ }
27
+ async download(options) {
28
+ this.validateOptions(options);
29
+ const blob = await this.createBlob(options);
30
+ const objectUrl = URL.createObjectURL(blob);
31
+ const anchor = document.createElement('a');
32
+ anchor.href = objectUrl;
33
+ anchor.download = options.filename;
34
+ anchor.rel = 'noopener';
35
+ anchor.style.display = 'none';
36
+ document.body.appendChild(anchor);
37
+ anchor.click();
38
+ anchor.remove();
39
+ window.setTimeout(() => URL.revokeObjectURL(objectUrl), 0);
40
+ }
41
+ validateOptions(options) {
42
+ var _a, _b, _c;
43
+ if (!((_a = options.filename) === null || _a === void 0 ? void 0 : _a.trim())) {
44
+ throw new Error(ERR_PARAM_NO_FILENAME);
45
+ }
46
+ if (!((_b = options.base64Data) === null || _b === void 0 ? void 0 : _b.trim()) && !((_c = options.path) === null || _c === void 0 ? void 0 : _c.trim())) {
47
+ throw new Error(ERR_PARAM_NO_DATA);
48
+ }
49
+ }
50
+ async createBlob(options) {
51
+ var _a;
52
+ const contentType = options.contentType || DEFAULT_CONTENT_TYPE;
53
+ if ((_a = options.base64Data) === null || _a === void 0 ? void 0 : _a.trim()) {
54
+ return new Blob(decodeBase64ToBlobParts(options.base64Data), {
55
+ type: contentType,
56
+ });
57
+ }
58
+ try {
59
+ const response = await fetch(options.path);
60
+ if (!response.ok) {
61
+ throw new Error(ERR_LOCAL_FILE_NOT_FOUND);
62
+ }
63
+ const blob = await response.blob();
64
+ return blob.type === contentType ? blob : new Blob([blob], { type: contentType });
65
+ }
66
+ catch (_b) {
67
+ throw new Error(ERR_LOCAL_FILE_NOT_FOUND);
68
+ }
69
+ }
70
+ }
71
+ function decodeBase64ToBlobParts(base64Data) {
72
+ const payload = getBase64Payload(base64Data);
73
+ const chunkLength = Math.max(4, Math.floor(BASE64_CHUNK_LENGTH / 4) * 4);
74
+ const parts = [];
75
+ try {
76
+ for (let offset = 0; offset < payload.length; offset += chunkLength) {
77
+ const byteCharacters = atob(payload.slice(offset, offset + chunkLength));
78
+ const buffer = new ArrayBuffer(byteCharacters.length);
79
+ const bytes = new Uint8Array(buffer);
80
+ for (let index = 0; index < byteCharacters.length; index++) {
81
+ bytes[index] = byteCharacters.charCodeAt(index);
82
+ }
83
+ parts.push(buffer);
84
+ }
85
+ }
86
+ catch (_a) {
87
+ throw new Error(ERR_PARAM_DATA_INVALID);
88
+ }
89
+ return parts;
90
+ }
91
+ function getBase64Payload(base64Data) {
92
+ const trimmed = base64Data.trim();
93
+ const commaIndex = trimmed.indexOf(',');
94
+ if (commaIndex > -1 && trimmed.slice(0, commaIndex).toLowerCase().includes('base64')) {
95
+ return trimmed.slice(commaIndex + 1).replace(/\s/g, '');
96
+ }
97
+ return trimmed.replace(/\s/g, '');
98
+ }
99
+
100
+ var web = /*#__PURE__*/Object.freeze({
101
+ __proto__: null,
102
+ FileSharerWeb: FileSharerWeb
103
+ });
104
+
105
+ exports.FileSharer = FileSharer;
106
+
107
+ return exports;
108
+
109
+ })({}, capacitorExports);
110
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst FileSharer = registerPlugin('FileSharer', {\n web: () => import('./web').then((m) => new m.FileSharerWeb()),\n});\nexport * from './definitions';\nexport { FileSharer };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nconst DEFAULT_CONTENT_TYPE = 'application/octet-stream';\nconst ERR_PARAM_NO_FILENAME = 'ERR_PARAM_NO_FILENAME';\nconst ERR_PARAM_NO_DATA = 'ERR_PARAM_NO_DATA';\nconst ERR_PARAM_DATA_INVALID = 'ERR_PARAM_DATA_INVALID';\nconst ERR_LOCAL_FILE_NOT_FOUND = 'ERR_LOCAL_FILE_NOT_FOUND';\nconst BASE64_CHUNK_LENGTH = 256 * 1024;\nexport class FileSharerWeb extends WebPlugin {\n async share(options) {\n await this.download(options);\n }\n async save(options) {\n await this.download(options);\n return {};\n }\n async getPluginVersion() {\n return {\n version: '8.0.0',\n };\n }\n async download(options) {\n this.validateOptions(options);\n const blob = await this.createBlob(options);\n const objectUrl = URL.createObjectURL(blob);\n const anchor = document.createElement('a');\n anchor.href = objectUrl;\n anchor.download = options.filename;\n anchor.rel = 'noopener';\n anchor.style.display = 'none';\n document.body.appendChild(anchor);\n anchor.click();\n anchor.remove();\n window.setTimeout(() => URL.revokeObjectURL(objectUrl), 0);\n }\n validateOptions(options) {\n var _a, _b, _c;\n if (!((_a = options.filename) === null || _a === void 0 ? void 0 : _a.trim())) {\n throw new Error(ERR_PARAM_NO_FILENAME);\n }\n if (!((_b = options.base64Data) === null || _b === void 0 ? void 0 : _b.trim()) && !((_c = options.path) === null || _c === void 0 ? void 0 : _c.trim())) {\n throw new Error(ERR_PARAM_NO_DATA);\n }\n }\n async createBlob(options) {\n var _a;\n const contentType = options.contentType || DEFAULT_CONTENT_TYPE;\n if ((_a = options.base64Data) === null || _a === void 0 ? void 0 : _a.trim()) {\n return new Blob(decodeBase64ToBlobParts(options.base64Data), {\n type: contentType,\n });\n }\n try {\n const response = await fetch(options.path);\n if (!response.ok) {\n throw new Error(ERR_LOCAL_FILE_NOT_FOUND);\n }\n const blob = await response.blob();\n return blob.type === contentType ? blob : new Blob([blob], { type: contentType });\n }\n catch (_b) {\n throw new Error(ERR_LOCAL_FILE_NOT_FOUND);\n }\n }\n}\nfunction decodeBase64ToBlobParts(base64Data) {\n const payload = getBase64Payload(base64Data);\n const chunkLength = Math.max(4, Math.floor(BASE64_CHUNK_LENGTH / 4) * 4);\n const parts = [];\n try {\n for (let offset = 0; offset < payload.length; offset += chunkLength) {\n const byteCharacters = atob(payload.slice(offset, offset + chunkLength));\n const buffer = new ArrayBuffer(byteCharacters.length);\n const bytes = new Uint8Array(buffer);\n for (let index = 0; index < byteCharacters.length; index++) {\n bytes[index] = byteCharacters.charCodeAt(index);\n }\n parts.push(buffer);\n }\n }\n catch (_a) {\n throw new Error(ERR_PARAM_DATA_INVALID);\n }\n return parts;\n}\nfunction getBase64Payload(base64Data) {\n const trimmed = base64Data.trim();\n const commaIndex = trimmed.indexOf(',');\n if (commaIndex > -1 && trimmed.slice(0, commaIndex).toLowerCase().includes('base64')) {\n return trimmed.slice(commaIndex + 1).replace(/\\s/g, '');\n }\n return trimmed.replace(/\\s/g, '');\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import Foundation
2
+
3
+ @objc public class FileSharer: NSObject {
4
+ @objc public func getPluginVersion() -> String {
5
+ return "8.0.0"
6
+ }
7
+
8
+ public func safeFilename(_ filename: String) -> String? {
9
+ let trimmed = filename.trimmingCharacters(in: .whitespacesAndNewlines)
10
+ let lastPathComponent = URL(fileURLWithPath: trimmed).lastPathComponent
11
+ return lastPathComponent.isEmpty ? nil : lastPathComponent
12
+ }
13
+
14
+ public func temporaryFileUrl(filename: String) -> URL {
15
+ return FileManager.default.temporaryDirectory.appendingPathComponent(filename)
16
+ }
17
+
18
+ public func decodedBase64Data(from base64Data: String) -> Data? {
19
+ let trimmed = base64Data.trimmingCharacters(in: .whitespacesAndNewlines)
20
+ let payload: String
21
+
22
+ if let commaIndex = trimmed.firstIndex(of: ","),
23
+ trimmed[..<commaIndex].lowercased().contains("base64") {
24
+ payload = String(trimmed[trimmed.index(after: commaIndex)...])
25
+ } else {
26
+ payload = trimmed
27
+ }
28
+
29
+ return Data(base64Encoded: payload, options: .ignoreUnknownCharacters)
30
+ }
31
+
32
+ public func fileUrl(from path: String) -> URL? {
33
+ let trimmed = path.trimmingCharacters(in: .whitespacesAndNewlines)
34
+
35
+ if let capacitorRange = trimmed.range(of: "_capacitor_file_") {
36
+ let rawPath = String(trimmed[capacitorRange.upperBound...])
37
+ let decodedPath = rawPath.removingPercentEncoding ?? rawPath
38
+ return URL(fileURLWithPath: decodedPath)
39
+ }
40
+
41
+ if let url = URL(string: trimmed), url.isFileURL {
42
+ return url
43
+ }
44
+
45
+ if trimmed.hasPrefix("/") {
46
+ return URL(fileURLWithPath: trimmed)
47
+ }
48
+
49
+ return nil
50
+ }
51
+ }
@@ -0,0 +1,140 @@
1
+ import Foundation
2
+ import Capacitor
3
+ import UIKit
4
+
5
+ @objc(FileSharerPlugin)
6
+ public class FileSharerPlugin: CAPPlugin, CAPBridgedPlugin {
7
+ public let identifier = "FileSharerPlugin"
8
+ public let jsName = "FileSharer"
9
+ public let pluginMethods: [CAPPluginMethod] = [
10
+ CAPPluginMethod(name: "share", returnType: CAPPluginReturnPromise),
11
+ CAPPluginMethod(name: "save", returnType: CAPPluginReturnPromise),
12
+ CAPPluginMethod(name: "getPluginVersion", returnType: CAPPluginReturnPromise)
13
+ ]
14
+
15
+ private let implementation = FileSharer()
16
+
17
+ @objc func share(_ call: CAPPluginCall) {
18
+ presentShareSheet(call, resolveWithUri: false)
19
+ }
20
+
21
+ @objc func save(_ call: CAPPluginCall) {
22
+ presentShareSheet(call, resolveWithUri: true)
23
+ }
24
+
25
+ @objc func getPluginVersion(_ call: CAPPluginCall) {
26
+ call.resolve([
27
+ "version": implementation.getPluginVersion()
28
+ ])
29
+ }
30
+
31
+ private func presentShareSheet(_ call: CAPPluginCall, resolveWithUri: Bool) {
32
+ let fileUrl: URL
33
+
34
+ do {
35
+ fileUrl = try prepareFile(call)
36
+ } catch let error as FileSharerError {
37
+ call.reject(error.rawValue)
38
+ return
39
+ } catch {
40
+ call.reject(FileSharerError.fileCachingFailed.rawValue, error.localizedDescription)
41
+ return
42
+ }
43
+
44
+ DispatchQueue.main.async {
45
+ guard let viewController = self.bridge?.viewController else {
46
+ call.reject(FileSharerError.noViewController.rawValue)
47
+ return
48
+ }
49
+
50
+ var activityItems: [Any] = [fileUrl]
51
+ if let text = call.getString("text"), !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
52
+ activityItems.append(text)
53
+ }
54
+
55
+ let activityViewController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
56
+ if let subject = call.getString("subject") ?? call.getString("title") {
57
+ activityViewController.setValue(subject, forKey: "subject")
58
+ }
59
+
60
+ if let sourceView = viewController.view {
61
+ activityViewController.popoverPresentationController?.sourceView = sourceView
62
+ activityViewController.popoverPresentationController?.sourceRect = CGRect(
63
+ x: sourceView.bounds.midX,
64
+ y: sourceView.bounds.maxY,
65
+ width: 0,
66
+ height: 0
67
+ )
68
+ }
69
+
70
+ activityViewController.completionWithItemsHandler = { _, completed, _, error in
71
+ if let error = error {
72
+ call.reject(FileSharerError.shareFailed.rawValue, error.localizedDescription)
73
+ return
74
+ }
75
+
76
+ if completed {
77
+ if resolveWithUri {
78
+ call.resolve(["uri": fileUrl.absoluteString])
79
+ } else {
80
+ call.resolve()
81
+ }
82
+ } else {
83
+ call.reject(FileSharerError.userCancelled.rawValue)
84
+ }
85
+ }
86
+
87
+ viewController.present(activityViewController, animated: true)
88
+ }
89
+ }
90
+
91
+ private func prepareFile(_ call: CAPPluginCall) throws -> URL {
92
+ guard let requestedFilename = call.getString("filename"),
93
+ let filename = implementation.safeFilename(requestedFilename) else {
94
+ throw FileSharerError.noFilename
95
+ }
96
+
97
+ let temporaryUrl = implementation.temporaryFileUrl(filename: filename)
98
+ try? FileManager.default.removeItem(at: temporaryUrl)
99
+
100
+ if let base64Data = call.getString("base64Data"), !base64Data.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
101
+ guard let data = implementation.decodedBase64Data(from: base64Data) else {
102
+ throw FileSharerError.invalidData
103
+ }
104
+
105
+ try data.write(to: temporaryUrl, options: .atomic)
106
+ return temporaryUrl
107
+ }
108
+
109
+ if let path = call.getString("path"), !path.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
110
+ guard let sourceUrl = implementation.fileUrl(from: path) else {
111
+ throw FileSharerError.fileNotFound
112
+ }
113
+
114
+ let standardizedSourceUrl = sourceUrl.standardizedFileURL
115
+ if standardizedSourceUrl == temporaryUrl.standardizedFileURL {
116
+ return standardizedSourceUrl
117
+ }
118
+
119
+ guard FileManager.default.fileExists(atPath: standardizedSourceUrl.path) else {
120
+ throw FileSharerError.fileNotFound
121
+ }
122
+
123
+ try FileManager.default.copyItem(at: standardizedSourceUrl, to: temporaryUrl)
124
+ return temporaryUrl
125
+ }
126
+
127
+ throw FileSharerError.noData
128
+ }
129
+ }
130
+
131
+ private enum FileSharerError: String, Error {
132
+ case noFilename = "ERR_PARAM_NO_FILENAME"
133
+ case noData = "ERR_PARAM_NO_DATA"
134
+ case invalidData = "ERR_PARAM_DATA_INVALID"
135
+ case fileCachingFailed = "ERR_FILE_CACHING_FAILED"
136
+ case fileNotFound = "ERR_LOCAL_FILE_NOT_FOUND"
137
+ case noViewController = "ERR_NO_VIEW_CONTROLLER"
138
+ case shareFailed = "ERR_SHARE_FAILED"
139
+ case userCancelled = "USER_CANCELLED"
140
+ }
@@ -0,0 +1,24 @@
1
+ import XCTest
2
+ @testable import FileSharerPlugin
3
+
4
+ class FileSharerTests: XCTestCase {
5
+ func testSafeFilenameUsesLastPathComponent() {
6
+ let implementation = FileSharer()
7
+
8
+ XCTAssertEqual("report.pdf", implementation.safeFilename("../exports/report.pdf"))
9
+ }
10
+
11
+ func testDecodesDataUrlBase64() {
12
+ let implementation = FileSharer()
13
+ let data = implementation.decodedBase64Data(from: "data:text/plain;base64,SGVsbG8=")
14
+
15
+ XCTAssertEqual(String(data: data ?? Data(), encoding: .utf8), "Hello")
16
+ }
17
+
18
+ func testGetPluginVersion() {
19
+ let implementation = FileSharer()
20
+ let result = implementation.getPluginVersion()
21
+
22
+ XCTAssertEqual("8.0.0", result)
23
+ }
24
+ }
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "@capgo/capacitor-file-sharer",
3
+ "version": "8.1.0",
4
+ "description": "Capacitor plugin for sharing and saving files on Android, iOS, and Web.",
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
+ "CapgoCapacitorFileSharer.podspec"
17
+ ],
18
+ "author": "Cap-go <contact@capgo.app>",
19
+ "license": "MPL-2.0",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/Cap-go/capacitor-file-sharer.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/Cap-go/capacitor-file-sharer/issues"
26
+ },
27
+ "homepage": "https://capgo.app/docs/plugins/file-sharer/",
28
+ "keywords": [
29
+ "capacitor",
30
+ "capacitor-plugin",
31
+ "file-sharing",
32
+ "share",
33
+ "files"
34
+ ],
35
+ "scripts": {
36
+ "verify": "bun run verify:ios && bun run verify:android && bun run verify:web",
37
+ "verify:ios": "xcodebuild -scheme CapgoCapacitorFileSharer -destination generic/platform=iOS",
38
+ "verify:android": "cd android && ./gradlew clean build test && cd ..",
39
+ "verify:web": "bun run build",
40
+ "lint": "bun run eslint && bun run prettier -- --check && bun run swiftlint -- lint",
41
+ "fmt": "bun run eslint -- --fix && bun run prettier -- --write && bun run swiftlint -- --fix --format",
42
+ "eslint": "eslint . --ext .ts",
43
+ "prettier": "prettier-pretty-check \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
44
+ "swiftlint": "node-swiftlint",
45
+ "docgen": "docgen --api FileSharerPlugin --output-readme README.md --output-json dist/docs.json",
46
+ "build": "bun run clean && bun run docgen && tsc && rollup -c rollup.config.mjs",
47
+ "clean": "rimraf ./dist",
48
+ "watch": "tsc --watch",
49
+ "prepublishOnly": "bun run build",
50
+ "check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs"
51
+ },
52
+ "devDependencies": {
53
+ "@capacitor/android": "^8.0.0",
54
+ "@capacitor/cli": "^8.0.0",
55
+ "@capacitor/core": "^8.0.0",
56
+ "@capacitor/docgen": "^0.3.1",
57
+ "@capacitor/ios": "^8.0.0",
58
+ "@ionic/eslint-config": "^0.4.0",
59
+ "@ionic/prettier-config": "^4.0.0",
60
+ "@ionic/swiftlint-config": "^2.0.0",
61
+ "@types/node": "^24.10.1",
62
+ "eslint": "^8.57.1",
63
+ "eslint-plugin-import": "^2.31.0",
64
+ "husky": "^9.1.7",
65
+ "prettier": "^3.6.2",
66
+ "prettier-pretty-check": "^0.2.0",
67
+ "prettier-plugin-java": "^2.7.7",
68
+ "rimraf": "^6.1.0",
69
+ "rollup": "^4.53.2",
70
+ "swiftlint": "^2.0.0",
71
+ "typescript": "^5.9.3"
72
+ },
73
+ "peerDependencies": {
74
+ "@capacitor/core": ">=8.0.0"
75
+ },
76
+ "prettier": "@ionic/prettier-config",
77
+ "swiftlint": "@ionic/swiftlint-config",
78
+ "eslintConfig": {
79
+ "extends": "@ionic/eslint-config/recommended"
80
+ },
81
+ "capacitor": {
82
+ "ios": {
83
+ "src": "ios"
84
+ },
85
+ "android": {
86
+ "src": "android"
87
+ }
88
+ }
89
+ }