@hanwha-ss1/plugin 0.7.0 → 0.7.3

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.
@@ -12,9 +12,27 @@ import java.io.File;
12
12
  import java.io.FileOutputStream;
13
13
  import java.io.IOException;
14
14
  import java.io.OutputStream;
15
+ import java.io.RandomAccessFile;
15
16
  import java.net.URLConnection;
17
+ import java.util.concurrent.ConcurrentHashMap;
16
18
 
17
19
  public class FileDownload {
20
+
21
+ // 청크 다운로드를 위한 임시 파일 관리
22
+ private static final ConcurrentHashMap<String, ChunkDownloadInfo> chunkDownloads = new ConcurrentHashMap<>();
23
+
24
+ private static class ChunkDownloadInfo {
25
+ File tempFile;
26
+ int totalChunks;
27
+ int receivedChunks;
28
+ RandomAccessFile randomAccessFile;
29
+
30
+ ChunkDownloadInfo(File tempFile, int totalChunks) {
31
+ this.tempFile = tempFile;
32
+ this.totalChunks = totalChunks;
33
+ this.receivedChunks = 0;
34
+ }
35
+ }
18
36
 
19
37
  public String echo(String value) {
20
38
  Log.i("Echo", value);
@@ -36,6 +54,79 @@ public class FileDownload {
36
54
  }
37
55
  }
38
56
 
57
+ public void downloadChunk(Activity activity, String fileName, String chunkData, int chunkIndex, int totalChunks, boolean isLastChunk, OnResult result) {
58
+ try {
59
+ // Base64 디코딩
60
+ byte[] decodedChunk = android.util.Base64.decode(chunkData, android.util.Base64.DEFAULT);
61
+
62
+ // 첫 번째 청크인 경우 임시 파일 생성
63
+ if (chunkIndex == 0) {
64
+ File tempDir = new File(activity.getCacheDir(), "chunk_downloads");
65
+ if (!tempDir.exists()) {
66
+ tempDir.mkdirs();
67
+ }
68
+ File tempFile = new File(tempDir, fileName + ".tmp");
69
+
70
+ ChunkDownloadInfo downloadInfo = new ChunkDownloadInfo(tempFile, totalChunks);
71
+ try {
72
+ downloadInfo.randomAccessFile = new RandomAccessFile(tempFile, "rw");
73
+ chunkDownloads.put(fileName, downloadInfo);
74
+ } catch (IOException e) {
75
+ if (result != null) {
76
+ result.onFail("임시 파일 생성 실패: " + e.getMessage());
77
+ }
78
+ return;
79
+ }
80
+ }
81
+
82
+ ChunkDownloadInfo downloadInfo = chunkDownloads.get(fileName);
83
+ if (downloadInfo == null) {
84
+ if (result != null) {
85
+ result.onFail("청크 다운로드 정보를 찾을 수 없습니다.");
86
+ }
87
+ return;
88
+ }
89
+
90
+ // 청크 데이터를 파일에 쓰기
91
+ synchronized (downloadInfo) {
92
+ try {
93
+ downloadInfo.randomAccessFile.write(decodedChunk);
94
+ downloadInfo.receivedChunks++;
95
+
96
+ // 모든 청크를 받았거나 마지막 청크인 경우
97
+ if (isLastChunk || downloadInfo.receivedChunks >= downloadInfo.totalChunks) {
98
+ downloadInfo.randomAccessFile.close();
99
+
100
+ // 최종 파일로 이동
101
+ String finalPath = moveToFinalLocation(activity, downloadInfo.tempFile, fileName);
102
+
103
+ // 정리
104
+ chunkDownloads.remove(fileName);
105
+ downloadInfo.tempFile.delete();
106
+
107
+ if (result != null) {
108
+ result.onSuccess(finalPath);
109
+ }
110
+ } else {
111
+ // 중간 청크 완료
112
+ if (result != null) {
113
+ result.onSuccess("청크 " + chunkIndex + " 완료");
114
+ }
115
+ }
116
+ } catch (IOException e) {
117
+ if (result != null) {
118
+ result.onFail("청크 쓰기 실패: " + e.getMessage());
119
+ }
120
+ }
121
+ }
122
+
123
+ } catch (Exception e) {
124
+ if (result != null) {
125
+ result.onFail("청크 다운로드 실패: " + e.getMessage());
126
+ }
127
+ }
128
+ }
129
+
39
130
 
40
131
  private void fileDownload(Activity activity, String param, OnResult result){
41
132
 
@@ -127,4 +218,55 @@ public class FileDownload {
127
218
  }
128
219
  }
129
220
  }
221
+
222
+ private String moveToFinalLocation(Activity activity, File tempFile, String fileName) throws IOException {
223
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
224
+ ContentValues values = new ContentValues();
225
+ String mimeType = URLConnection.guessContentTypeFromName(fileName);
226
+
227
+ Uri uri;
228
+ if (null != mimeType &&
229
+ (mimeType.contains("image") || mimeType.contains("video"))) {
230
+ uri = MediaStore.Downloads.EXTERNAL_CONTENT_URI;
231
+ } else {
232
+ uri = MediaStore.Downloads.EXTERNAL_CONTENT_URI;
233
+ }
234
+
235
+ values.put(MediaStore.Downloads.DISPLAY_NAME, fileName);
236
+ values.put(MediaStore.Downloads.MIME_TYPE, mimeType != null ? mimeType : "application/octet-stream");
237
+ values.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
238
+
239
+ Uri insertedUri = activity.getContentResolver().insert(uri, values);
240
+ if (insertedUri != null) {
241
+ try (OutputStream out = activity.getContentResolver().openOutputStream(insertedUri);
242
+ java.io.FileInputStream in = new java.io.FileInputStream(tempFile)) {
243
+
244
+ byte[] buffer = new byte[8192];
245
+ int bytesRead;
246
+ while ((bytesRead = in.read(buffer)) != -1) {
247
+ out.write(buffer, 0, bytesRead);
248
+ }
249
+ out.flush();
250
+ }
251
+ return insertedUri.toString();
252
+ } else {
253
+ throw new IOException("파일 생성 실패");
254
+ }
255
+ } else {
256
+ File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
257
+ File finalFile = new File(downloadDir, fileName);
258
+
259
+ try (java.io.FileInputStream in = new java.io.FileInputStream(tempFile);
260
+ FileOutputStream out = new FileOutputStream(finalFile)) {
261
+
262
+ byte[] buffer = new byte[8192];
263
+ int bytesRead;
264
+ while ((bytesRead = in.read(buffer)) != -1) {
265
+ out.write(buffer, 0, bytesRead);
266
+ }
267
+ out.flush();
268
+ }
269
+ return finalFile.getAbsolutePath();
270
+ }
271
+ }
130
272
  }
@@ -161,6 +161,41 @@ public class LinkerPlugin extends Plugin{
161
161
  });
162
162
  }
163
163
 
164
+ @PluginMethod
165
+ public void fileDownloadChunk(PluginCall call) {
166
+ String fileName = call.getString("fileName");
167
+ String chunkData = call.getString("chunkData");
168
+ Integer chunkIndex = call.getInt("chunkIndex");
169
+ Integer totalChunks = call.getInt("totalChunks");
170
+ Boolean isLastChunk = call.getBoolean("isLastChunk");
171
+
172
+ if(fileDownloadImplementation == null) {
173
+ fileDownloadImplementation = new FileDownload();
174
+ }
175
+
176
+ fileDownloadImplementation.downloadChunk(getActivity(), fileName, chunkData,
177
+ chunkIndex != null ? chunkIndex : 0,
178
+ totalChunks != null ? totalChunks : 1,
179
+ isLastChunk != null ? isLastChunk : false,
180
+ new FileDownload.OnResult() {
181
+ @Override
182
+ public void onSuccess(String path) {
183
+ JSObject ret = new JSObject();
184
+ ret.put("result", true);
185
+ ret.put("message", "청크 다운로드 완료");
186
+ call.resolve(ret);
187
+ }
188
+
189
+ @Override
190
+ public void onFail(String message) {
191
+ JSObject ret = new JSObject();
192
+ ret.put("result", false);
193
+ ret.put("message", message);
194
+ call.resolve(ret);
195
+ }
196
+ });
197
+ }
198
+
164
199
  public void handleDocumentsViewer(String filePath) {
165
200
  Handler handler = new Handler(Looper.getMainLooper()) {
166
201
  @Override
@@ -25,7 +25,6 @@ export interface Plugin {
25
25
  }): Promise<{
26
26
  value: string;
27
27
  }>;
28
- getCertificateProvisioning(): Promise<any>;
29
28
  /**
30
29
  * TouchID, FaceID
31
30
  */
@@ -79,6 +78,24 @@ export interface Plugin {
79
78
  }): Promise<{
80
79
  value: string;
81
80
  }>;
81
+ /**
82
+ * 파일 청크 다운로드
83
+ * @param options fileName : 파일명
84
+ * @param options chunkData : 청크 데이터 (base64)
85
+ * @param options chunkIndex : 청크 인덱스
86
+ * @param options totalChunks : 전체 청크 수
87
+ * @param options isLastChunk : 마지막 청크 여부
88
+ */
89
+ fileDownloadChunk(options: {
90
+ fileName: string;
91
+ chunkData: string;
92
+ chunkIndex: number;
93
+ totalChunks: number;
94
+ isLastChunk: boolean;
95
+ }): Promise<{
96
+ result: boolean;
97
+ message: string;
98
+ }>;
82
99
  /**
83
100
  * iOS 캡쳐 방지
84
101
  * @see default false
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export interface Plugin {\n /**\n * 앱 실행\n * @param options package : 패키지명\n */\n executeApp(options: {\n package: string;\n }): Promise<{ value: string }>;\n\n /**\n * 연락처 저장\n * @param options name : 성명\n * @param options phone : 전화번호\n * @param options email : 이메일\n * @param options dept : 소속\n * @param options ext : 내선번호\n */\n addContact(options: {\n name: string;\n phone: string;\n email: string;\n dept: string;\n ext: string;\n }): Promise<{ value: string }>;\n\n // 인증서 만료일 확인\n getCertificateProvisioning(): Promise<any>;\n /**\n * TouchID, FaceID\n */\n auth(): Promise<{ value: string }>;\n\n /**\n * 시스템에 설정된 지역이 서울인지 확인\n */\n checkSeoulTimeZone(): Promise<{ value: string }>;\n\n timezone(): Promise<{ value: string }>;\n\n /**\n *\n * @param options url : \"웹 페이지 주소\"\n * @param options ext : false(내부 웹뷰), true(외부 브라우저)\n */\n open(options: { url: string; ext: boolean; isCloseBtn?: boolean; clear?: boolean }): Promise<{ value: string }>;\n\n /**\n * EdgeSwipe 허용\n * @see default false\n */\n edgeSwipe(options: {on: boolean}): Promise<{ value: string }>;\n\n\n /**\n * 카메라앱 열기\n * @see default false\n */\n openCamera(): Promise<{ value: string }>;\n\n\n /**\n * 파일 다운로드\n * @see default false\n */\n fileDownload(options: {file: string; }): Promise<{ value: string }>;\n\n /**\n * iOS 캡쳐 방지\n * @see default false\n */\n doDisabledCapture(options: {disabled: boolean; }): Promise<{ value: string }>;\n /**\n * iOS 키보드 메뉴 바\n */\n addKeyboardMenu():any;\n removeKeyboardMenu():any;\n}"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export interface Plugin {\n /**\n * 앱 실행\n * @param options package : 패키지명\n */\n executeApp(options: {\n package: string;\n }): Promise<{ value: string }>;\n\n /**\n * 연락처 저장\n * @param options name : 성명\n * @param options phone : 전화번호\n * @param options email : 이메일\n * @param options dept : 소속\n * @param options ext : 내선번호\n */\n addContact(options: {\n name: string;\n phone: string;\n email: string;\n dept: string;\n ext: string;\n }): Promise<{ value: string }>;\n\n /**\n * TouchID, FaceID\n */\n auth(): Promise<{ value: string }>;\n\n /**\n * 시스템에 설정된 지역이 서울인지 확인\n */\n checkSeoulTimeZone(): Promise<{ value: string }>;\n\n timezone(): Promise<{ value: string }>;\n\n /**\n *\n * @param options url : \"웹 페이지 주소\"\n * @param options ext : false(내부 웹뷰), true(외부 브라우저)\n */\n open(options: { url: string; ext: boolean; isCloseBtn?: boolean; clear?: boolean }): Promise<{ value: string }>;\n\n /**\n * EdgeSwipe 허용\n * @see default false\n */\n edgeSwipe(options: {on: boolean}): Promise<{ value: string }>;\n\n\n /**\n * 카메라앱 열기\n * @see default false\n */\n openCamera(): Promise<{ value: string }>;\n\n\n /**\n * 파일 다운로드\n * @see default false\n */\n fileDownload(options: {file: string; }): Promise<{ value: string }>;\n\n /**\n * 파일 청크 다운로드\n * @param options fileName : 파일명\n * @param options chunkData : 청크 데이터 (base64)\n * @param options chunkIndex : 청크 인덱스\n * @param options totalChunks : 전체 청크 수\n * @param options isLastChunk : 마지막 청크 여부\n */\n fileDownloadChunk(options: {\n fileName: string;\n chunkData: string;\n chunkIndex: number;\n totalChunks: number;\n isLastChunk: boolean;\n }): Promise<{ result: boolean; message: string }>;\n\n /**\n * iOS 캡쳐 방지\n * @see default false\n */\n doDisabledCapture(options: {disabled: boolean; }): Promise<{ value: string }>;\n /**\n * iOS 키보드 메뉴 바\n */\n addKeyboardMenu():any;\n removeKeyboardMenu():any;\n}"]}
package/dist/esm/web.d.ts CHANGED
@@ -6,7 +6,6 @@ export declare class PluginWeb extends WebPlugin implements Plugin {
6
6
  addKeyboardMenu(): any;
7
7
  removeKeyboardMenu(): any;
8
8
  auth(): Promise<any>;
9
- getCertificateProvisioning(): Promise<any>;
10
9
  checkSeoulTimeZone(): Promise<any>;
11
10
  timezone(): Promise<any>;
12
11
  open(_options: {
@@ -22,6 +21,16 @@ export declare class PluginWeb extends WebPlugin implements Plugin {
22
21
  fileDownload(_options: {
23
22
  file: string;
24
23
  }): Promise<any>;
24
+ fileDownloadChunk(_options: {
25
+ fileName: string;
26
+ chunkData: string;
27
+ chunkIndex: number;
28
+ totalChunks: number;
29
+ isLastChunk: boolean;
30
+ }): Promise<{
31
+ result: boolean;
32
+ message: string;
33
+ }>;
25
34
  doDisabledCapture(_options: {
26
35
  disabled: boolean;
27
36
  }): Promise<any>;
package/dist/esm/web.js CHANGED
@@ -16,9 +16,6 @@ export class PluginWeb extends WebPlugin {
16
16
  async auth() {
17
17
  return { results: {} };
18
18
  }
19
- async getCertificateProvisioning() {
20
- return { result: false };
21
- }
22
19
  async checkSeoulTimeZone() {
23
20
  return { results: {} };
24
21
  }
@@ -40,6 +37,9 @@ export class PluginWeb extends WebPlugin {
40
37
  async fileDownload(_options) {
41
38
  return { results: {} };
42
39
  }
40
+ async fileDownloadChunk(_options) {
41
+ return { result: true, message: '웹에서는 청크 다운로드가 지원되지 않습니다.' };
42
+ }
43
43
  async doDisabledCapture(_options) {
44
44
  if (Capacitor.getPlatform() !== 'ios') {
45
45
  return Promise.reject('doDisabledCapture is only supported on iOS.');
@@ -1 +1 @@
1
- {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,OAAO,EAAC,SAAS,EAAE,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAGrD,MAAM,OAAO,SAAU,SAAQ,SAAS;IAEtC,KAAK,CAAC,UAAU;QACd,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,UAAU;QACd,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IACD,eAAe;QACb,OAAO;IACT,CAAC;IACD,kBAAkB;QAChB,OAAO;IACT,CAAC;IAED,KAAK,CAAC,IAAI;QACR,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IACF,KAAK,CAAC,0BAA0B;QAC/B,OAAO,EAAC,MAAM,EAAC,KAAK,EAAC,CAAC;IACvB,CAAC;IACA,KAAK,CAAC,kBAAkB;QACtB,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,QAA8E;QACvF,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,QAAyB;QACvC,IAAI,SAAS,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE;YACrC,OAAO,OAAO,CAAC,MAAM,CAAC,qCAAqC,CAAC,CAAC;SAC9D;QACD,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,UAAU;QACd,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,QAAyB;QAC1C,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAC,QAA8B;QACpD,IAAI,SAAS,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE;YACrC,OAAO,OAAO,CAAC,MAAM,CAAC,6CAA6C,CAAC,CAAC;SACtE;QACD,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;CACF","sourcesContent":["/* eslint-disable @typescript-eslint/no-unused-vars */\nimport {Capacitor, WebPlugin} from '@capacitor/core';\nimport type {Plugin} from './definitions';\n\nexport class PluginWeb extends WebPlugin implements Plugin {\n\n async executeApp(): Promise<any> {\n return { results: {} };\n }\n\n async addContact(): Promise<any> {\n return { results: {} };\n }\n addKeyboardMenu():any {\n return;\n }\n removeKeyboardMenu():any {\n return;\n }\n\n async auth(): Promise<any> {\n return { results: {} };\n }\n async getCertificateProvisioning(): Promise<any> {\n return {result:false};\n }\n async checkSeoulTimeZone(): Promise<any> {\n return { results: {} };\n }\n\n async timezone(): Promise<any> {\n return { results: {} };\n }\n\n async open(_options: { url: string, ext: boolean, isCloseBtn?: boolean; clear?: boolean }): Promise<any> {\n return { results: {} };\n }\n\n async edgeSwipe(_options: { on: boolean }): Promise<any> {\n if (Capacitor.getPlatform() !== 'ios') {\n return Promise.reject('edgeSwipe is only supported on iOS.');\n }\n return { results: {} };\n }\n\n async openCamera(): Promise<any> {\n return { results: {} };\n }\n\n async fileDownload(_options: { file: string}): Promise<any> {\n return { results: {} };\n }\n\n async doDisabledCapture(_options: { disabled: boolean}): Promise<any> {\n if (Capacitor.getPlatform() !== 'ios') {\n return Promise.reject('doDisabledCapture is only supported on iOS.');\n }\n return { results: {} };\n }\n}\n"]}
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,OAAO,EAAC,SAAS,EAAE,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAGrD,MAAM,OAAO,SAAU,SAAQ,SAAS;IAEtC,KAAK,CAAC,UAAU;QACd,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,UAAU;QACd,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IACD,eAAe;QACb,OAAO;IACT,CAAC;IACD,kBAAkB;QAChB,OAAO;IACT,CAAC;IAED,KAAK,CAAC,IAAI;QACR,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,kBAAkB;QACtB,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,QAA8E;QACvF,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,QAAyB;QACvC,IAAI,SAAS,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE;YACrC,OAAO,OAAO,CAAC,MAAM,CAAC,qCAAqC,CAAC,CAAC;SAC9D;QACD,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,UAAU;QACd,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,QAAyB;QAC1C,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAC,QAMvB;QACC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,0BAA0B,EAAE,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAC,QAA8B;QACpD,IAAI,SAAS,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE;YACrC,OAAO,OAAO,CAAC,MAAM,CAAC,6CAA6C,CAAC,CAAC;SACtE;QACD,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;CACF","sourcesContent":["/* eslint-disable @typescript-eslint/no-unused-vars */\nimport {Capacitor, WebPlugin} from '@capacitor/core';\nimport type {Plugin} from './definitions';\n\nexport class PluginWeb extends WebPlugin implements Plugin {\n\n async executeApp(): Promise<any> {\n return { results: {} };\n }\n\n async addContact(): Promise<any> {\n return { results: {} };\n }\n addKeyboardMenu():any {\n return;\n }\n removeKeyboardMenu():any {\n return;\n }\n\n async auth(): Promise<any> {\n return { results: {} };\n }\n\n async checkSeoulTimeZone(): Promise<any> {\n return { results: {} };\n }\n\n async timezone(): Promise<any> {\n return { results: {} };\n }\n\n async open(_options: { url: string, ext: boolean, isCloseBtn?: boolean; clear?: boolean }): Promise<any> {\n return { results: {} };\n }\n\n async edgeSwipe(_options: { on: boolean }): Promise<any> {\n if (Capacitor.getPlatform() !== 'ios') {\n return Promise.reject('edgeSwipe is only supported on iOS.');\n }\n return { results: {} };\n }\n\n async openCamera(): Promise<any> {\n return { results: {} };\n }\n\n async fileDownload(_options: { file: string}): Promise<any> {\n return { results: {} };\n }\n\n async fileDownloadChunk(_options: {\n fileName: string;\n chunkData: string;\n chunkIndex: number;\n totalChunks: number;\n isLastChunk: boolean;\n }): Promise<{ result: boolean; message: string }> {\n return { result: true, message: '웹에서는 청크 다운로드가 지원되지 않습니다.' };\n }\n\n async doDisabledCapture(_options: { disabled: boolean}): Promise<any> {\n if (Capacitor.getPlatform() !== 'ios') {\n return Promise.reject('doDisabledCapture is only supported on iOS.');\n }\n return { results: {} };\n }\n}\n"]}
@@ -25,9 +25,6 @@ class PluginWeb extends core.WebPlugin {
25
25
  async auth() {
26
26
  return { results: {} };
27
27
  }
28
- async getCertificateProvisioning() {
29
- return { result: false };
30
- }
31
28
  async checkSeoulTimeZone() {
32
29
  return { results: {} };
33
30
  }
@@ -49,6 +46,9 @@ class PluginWeb extends core.WebPlugin {
49
46
  async fileDownload(_options) {
50
47
  return { results: {} };
51
48
  }
49
+ async fileDownloadChunk(_options) {
50
+ return { result: true, message: '웹에서는 청크 다운로드가 지원되지 않습니다.' };
51
+ }
52
52
  async doDisabledCapture(_options) {
53
53
  if (core.Capacitor.getPlatform() !== 'ios') {
54
54
  return Promise.reject('doDisabledCapture is only supported on iOS.');
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst HanwhaPlugin = registerPlugin('Plugin', {\n web: () => import('./web').then(m => new m.PluginWeb()),\n});\nexport * from './definitions';\nexport { HanwhaPlugin };\n//# sourceMappingURL=index.js.map","/* eslint-disable @typescript-eslint/no-unused-vars */\nimport { Capacitor, WebPlugin } from '@capacitor/core';\nexport class PluginWeb extends WebPlugin {\n async executeApp() {\n return { results: {} };\n }\n async addContact() {\n return { results: {} };\n }\n addKeyboardMenu() {\n return;\n }\n removeKeyboardMenu() {\n return;\n }\n async auth() {\n return { results: {} };\n }\n async getCertificateProvisioning() {\n return { result: false };\n }\n async checkSeoulTimeZone() {\n return { results: {} };\n }\n async timezone() {\n return { results: {} };\n }\n async open(_options) {\n return { results: {} };\n }\n async edgeSwipe(_options) {\n if (Capacitor.getPlatform() !== 'ios') {\n return Promise.reject('edgeSwipe is only supported on iOS.');\n }\n return { results: {} };\n }\n async openCamera() {\n return { results: {} };\n }\n async fileDownload(_options) {\n return { results: {} };\n }\n async doDisabledCapture(_options) {\n if (Capacitor.getPlatform() !== 'ios') {\n return Promise.reject('doDisabledCapture is only supported on iOS.');\n }\n return { results: {} };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin","Capacitor"],"mappings":";;;;;;AACK,MAAC,YAAY,GAAGA,mBAAc,CAAC,QAAQ,EAAE;AAC9C,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;AAC3D,CAAC;;ACHD;AAEO,MAAM,SAAS,SAASC,cAAS,CAAC;AACzC,IAAI,MAAM,UAAU,GAAG;AACvB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL,IAAI,MAAM,UAAU,GAAG;AACvB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL,IAAI,eAAe,GAAG;AACtB,QAAQ,OAAO;AACf,KAAK;AACL,IAAI,kBAAkB,GAAG;AACzB,QAAQ,OAAO;AACf,KAAK;AACL,IAAI,MAAM,IAAI,GAAG;AACjB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL,IAAI,MAAM,0BAA0B,GAAG;AACvC,QAAQ,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AACjC,KAAK;AACL,IAAI,MAAM,kBAAkB,GAAG;AAC/B,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL,IAAI,MAAM,QAAQ,GAAG;AACrB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL,IAAI,MAAM,IAAI,CAAC,QAAQ,EAAE;AACzB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL,IAAI,MAAM,SAAS,CAAC,QAAQ,EAAE;AAC9B,QAAQ,IAAIC,cAAS,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE;AAC/C,YAAY,OAAO,OAAO,CAAC,MAAM,CAAC,qCAAqC,CAAC,CAAC;AACzE,SAAS;AACT,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL,IAAI,MAAM,UAAU,GAAG;AACvB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL,IAAI,MAAM,YAAY,CAAC,QAAQ,EAAE;AACjC,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL,IAAI,MAAM,iBAAiB,CAAC,QAAQ,EAAE;AACtC,QAAQ,IAAIA,cAAS,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE;AAC/C,YAAY,OAAO,OAAO,CAAC,MAAM,CAAC,6CAA6C,CAAC,CAAC;AACjF,SAAS;AACT,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL;;;;;;;;;"}
1
+ {"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst HanwhaPlugin = registerPlugin('Plugin', {\n web: () => import('./web').then(m => new m.PluginWeb()),\n});\nexport * from './definitions';\nexport { HanwhaPlugin };\n//# sourceMappingURL=index.js.map","/* eslint-disable @typescript-eslint/no-unused-vars */\nimport { Capacitor, WebPlugin } from '@capacitor/core';\nexport class PluginWeb extends WebPlugin {\n async executeApp() {\n return { results: {} };\n }\n async addContact() {\n return { results: {} };\n }\n addKeyboardMenu() {\n return;\n }\n removeKeyboardMenu() {\n return;\n }\n async auth() {\n return { results: {} };\n }\n async checkSeoulTimeZone() {\n return { results: {} };\n }\n async timezone() {\n return { results: {} };\n }\n async open(_options) {\n return { results: {} };\n }\n async edgeSwipe(_options) {\n if (Capacitor.getPlatform() !== 'ios') {\n return Promise.reject('edgeSwipe is only supported on iOS.');\n }\n return { results: {} };\n }\n async openCamera() {\n return { results: {} };\n }\n async fileDownload(_options) {\n return { results: {} };\n }\n async fileDownloadChunk(_options) {\n return { result: true, message: '웹에서는 청크 다운로드가 지원되지 않습니다.' };\n }\n async doDisabledCapture(_options) {\n if (Capacitor.getPlatform() !== 'ios') {\n return Promise.reject('doDisabledCapture is only supported on iOS.');\n }\n return { results: {} };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin","Capacitor"],"mappings":";;;;;;AACK,MAAC,YAAY,GAAGA,mBAAc,CAAC,QAAQ,EAAE;AAC9C,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;AAC3D,CAAC;;ACHD;AAEO,MAAM,SAAS,SAASC,cAAS,CAAC;AACzC,IAAI,MAAM,UAAU,GAAG;AACvB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL,IAAI,MAAM,UAAU,GAAG;AACvB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL,IAAI,eAAe,GAAG;AACtB,QAAQ,OAAO;AACf,KAAK;AACL,IAAI,kBAAkB,GAAG;AACzB,QAAQ,OAAO;AACf,KAAK;AACL,IAAI,MAAM,IAAI,GAAG;AACjB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL,IAAI,MAAM,kBAAkB,GAAG;AAC/B,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL,IAAI,MAAM,QAAQ,GAAG;AACrB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL,IAAI,MAAM,IAAI,CAAC,QAAQ,EAAE;AACzB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL,IAAI,MAAM,SAAS,CAAC,QAAQ,EAAE;AAC9B,QAAQ,IAAIC,cAAS,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE;AAC/C,YAAY,OAAO,OAAO,CAAC,MAAM,CAAC,qCAAqC,CAAC,CAAC;AACzE,SAAS;AACT,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL,IAAI,MAAM,UAAU,GAAG;AACvB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL,IAAI,MAAM,YAAY,CAAC,QAAQ,EAAE;AACjC,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL,IAAI,MAAM,iBAAiB,CAAC,QAAQ,EAAE;AACtC,QAAQ,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,0BAA0B,EAAE,CAAC;AACrE,KAAK;AACL,IAAI,MAAM,iBAAiB,CAAC,QAAQ,EAAE;AACtC,QAAQ,IAAIA,cAAS,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE;AAC/C,YAAY,OAAO,OAAO,CAAC,MAAM,CAAC,6CAA6C,CAAC,CAAC;AACjF,SAAS;AACT,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC/B,KAAK;AACL;;;;;;;;;"}
package/dist/plugin.js CHANGED
@@ -22,9 +22,6 @@ var capacitorContact = (function (exports, core) {
22
22
  async auth() {
23
23
  return { results: {} };
24
24
  }
25
- async getCertificateProvisioning() {
26
- return { result: false };
27
- }
28
25
  async checkSeoulTimeZone() {
29
26
  return { results: {} };
30
27
  }
@@ -46,6 +43,9 @@ var capacitorContact = (function (exports, core) {
46
43
  async fileDownload(_options) {
47
44
  return { results: {} };
48
45
  }
46
+ async fileDownloadChunk(_options) {
47
+ return { result: true, message: '웹에서는 청크 다운로드가 지원되지 않습니다.' };
48
+ }
49
49
  async doDisabledCapture(_options) {
50
50
  if (core.Capacitor.getPlatform() !== 'ios') {
51
51
  return Promise.reject('doDisabledCapture is only supported on iOS.');
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst HanwhaPlugin = registerPlugin('Plugin', {\n web: () => import('./web').then(m => new m.PluginWeb()),\n});\nexport * from './definitions';\nexport { HanwhaPlugin };\n//# sourceMappingURL=index.js.map","/* eslint-disable @typescript-eslint/no-unused-vars */\nimport { Capacitor, WebPlugin } from '@capacitor/core';\nexport class PluginWeb extends WebPlugin {\n async executeApp() {\n return { results: {} };\n }\n async addContact() {\n return { results: {} };\n }\n addKeyboardMenu() {\n return;\n }\n removeKeyboardMenu() {\n return;\n }\n async auth() {\n return { results: {} };\n }\n async getCertificateProvisioning() {\n return { result: false };\n }\n async checkSeoulTimeZone() {\n return { results: {} };\n }\n async timezone() {\n return { results: {} };\n }\n async open(_options) {\n return { results: {} };\n }\n async edgeSwipe(_options) {\n if (Capacitor.getPlatform() !== 'ios') {\n return Promise.reject('edgeSwipe is only supported on iOS.');\n }\n return { results: {} };\n }\n async openCamera() {\n return { results: {} };\n }\n async fileDownload(_options) {\n return { results: {} };\n }\n async doDisabledCapture(_options) {\n if (Capacitor.getPlatform() !== 'ios') {\n return Promise.reject('doDisabledCapture is only supported on iOS.');\n }\n return { results: {} };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin","Capacitor"],"mappings":";;;AACK,UAAC,YAAY,GAAGA,mBAAc,CAAC,QAAQ,EAAE;IAC9C,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;IAC3D,CAAC;;ICHD;IAEO,MAAM,SAAS,SAASC,cAAS,CAAC;IACzC,IAAI,MAAM,UAAU,GAAG;IACvB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL,IAAI,MAAM,UAAU,GAAG;IACvB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL,IAAI,eAAe,GAAG;IACtB,QAAQ,OAAO;IACf,KAAK;IACL,IAAI,kBAAkB,GAAG;IACzB,QAAQ,OAAO;IACf,KAAK;IACL,IAAI,MAAM,IAAI,GAAG;IACjB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL,IAAI,MAAM,0BAA0B,GAAG;IACvC,QAAQ,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACjC,KAAK;IACL,IAAI,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL,IAAI,MAAM,QAAQ,GAAG;IACrB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL,IAAI,MAAM,IAAI,CAAC,QAAQ,EAAE;IACzB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL,IAAI,MAAM,SAAS,CAAC,QAAQ,EAAE;IAC9B,QAAQ,IAAIC,cAAS,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE;IAC/C,YAAY,OAAO,OAAO,CAAC,MAAM,CAAC,qCAAqC,CAAC,CAAC;IACzE,SAAS;IACT,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL,IAAI,MAAM,UAAU,GAAG;IACvB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL,IAAI,MAAM,YAAY,CAAC,QAAQ,EAAE;IACjC,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL,IAAI,MAAM,iBAAiB,CAAC,QAAQ,EAAE;IACtC,QAAQ,IAAIA,cAAS,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE;IAC/C,YAAY,OAAO,OAAO,CAAC,MAAM,CAAC,6CAA6C,CAAC,CAAC;IACjF,SAAS;IACT,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst HanwhaPlugin = registerPlugin('Plugin', {\n web: () => import('./web').then(m => new m.PluginWeb()),\n});\nexport * from './definitions';\nexport { HanwhaPlugin };\n//# sourceMappingURL=index.js.map","/* eslint-disable @typescript-eslint/no-unused-vars */\nimport { Capacitor, WebPlugin } from '@capacitor/core';\nexport class PluginWeb extends WebPlugin {\n async executeApp() {\n return { results: {} };\n }\n async addContact() {\n return { results: {} };\n }\n addKeyboardMenu() {\n return;\n }\n removeKeyboardMenu() {\n return;\n }\n async auth() {\n return { results: {} };\n }\n async checkSeoulTimeZone() {\n return { results: {} };\n }\n async timezone() {\n return { results: {} };\n }\n async open(_options) {\n return { results: {} };\n }\n async edgeSwipe(_options) {\n if (Capacitor.getPlatform() !== 'ios') {\n return Promise.reject('edgeSwipe is only supported on iOS.');\n }\n return { results: {} };\n }\n async openCamera() {\n return { results: {} };\n }\n async fileDownload(_options) {\n return { results: {} };\n }\n async fileDownloadChunk(_options) {\n return { result: true, message: '웹에서는 청크 다운로드가 지원되지 않습니다.' };\n }\n async doDisabledCapture(_options) {\n if (Capacitor.getPlatform() !== 'ios') {\n return Promise.reject('doDisabledCapture is only supported on iOS.');\n }\n return { results: {} };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin","Capacitor"],"mappings":";;;AACK,UAAC,YAAY,GAAGA,mBAAc,CAAC,QAAQ,EAAE;IAC9C,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;IAC3D,CAAC;;ICHD;IAEO,MAAM,SAAS,SAASC,cAAS,CAAC;IACzC,IAAI,MAAM,UAAU,GAAG;IACvB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL,IAAI,MAAM,UAAU,GAAG;IACvB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL,IAAI,eAAe,GAAG;IACtB,QAAQ,OAAO;IACf,KAAK;IACL,IAAI,kBAAkB,GAAG;IACzB,QAAQ,OAAO;IACf,KAAK;IACL,IAAI,MAAM,IAAI,GAAG;IACjB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL,IAAI,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL,IAAI,MAAM,QAAQ,GAAG;IACrB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL,IAAI,MAAM,IAAI,CAAC,QAAQ,EAAE;IACzB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL,IAAI,MAAM,SAAS,CAAC,QAAQ,EAAE;IAC9B,QAAQ,IAAIC,cAAS,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE;IAC/C,YAAY,OAAO,OAAO,CAAC,MAAM,CAAC,qCAAqC,CAAC,CAAC;IACzE,SAAS;IACT,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL,IAAI,MAAM,UAAU,GAAG;IACvB,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL,IAAI,MAAM,YAAY,CAAC,QAAQ,EAAE;IACjC,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL,IAAI,MAAM,iBAAiB,CAAC,QAAQ,EAAE;IACtC,QAAQ,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,0BAA0B,EAAE,CAAC;IACrE,KAAK;IACL,IAAI,MAAM,iBAAiB,CAAC,QAAQ,EAAE;IACtC,QAAQ,IAAIA,cAAS,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE;IAC/C,YAAY,OAAO,OAAO,CAAC,MAAM,CAAC,6CAA6C,CAAC,CAAC;IACjF,SAAS;IACT,QAAQ,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/B,KAAK;IACL;;;;;;;;;;;;;;;;;"}
@@ -4,49 +4,151 @@ import Capacitor
4
4
 
5
5
  public class DownloadPlugin: CAPPlugin {
6
6
 
7
- @objc public func doDownload(_ call: CAPPluginCall, _bridge: CAPBridgeProtocol) {
7
+ @objc public func doDownload(_ call: CAPPluginCall, _ bridge: CAPBridgeProtocol) {
8
8
  let body = call.getString("file") ?? ""
9
-
10
9
  let aSplit = body.components(separatedBy: ";")
11
-
10
+
12
11
  if aSplit.count > 2 {
13
12
  let fileName = aSplit[0]
14
13
  let sData1 = "\(aSplit[1])\(aSplit[2])"
15
14
  let aData2 = sData1.components(separatedBy: ",")
16
15
 
17
16
  if aData2.count > 1 {
18
- let sData = aData2[1]
19
-
20
- if let data = Data(base64Encoded: sData, options: .ignoreUnknownCharacters) {
21
- let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
22
- let documentsDirectory = paths[0]
23
- let filePath = documentsDirectory.appendingPathComponent(fileName)
17
+ let sData = aData2[1] // Base64 본문만 추출
18
+
19
+ // 저장 경로
20
+ let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
21
+ let documentsDirectory = paths[0]
22
+ let filePath = documentsDirectory.appendingPathComponent(fileName)
23
+
24
+ DispatchQueue.global(qos: .userInitiated).async {
25
+ let success = self.decodeBase64ToFile(base64: sData, fileURL: filePath)
24
26
 
25
- DispatchQueue.global(qos: .userInitiated).async {
26
- do {
27
- try data.write(to: filePath)
28
- DispatchQueue.main.async {
29
- call.resolve([
30
- "result": true,
31
- ])
32
- }
33
- } catch {
34
- DispatchQueue.main.async {
35
- call.resolve([
36
- "result": false,
37
- ])
38
- }
39
- }
40
- }
41
- } else {
42
27
  DispatchQueue.main.async {
43
28
  call.resolve([
44
- "result": false,
29
+ "result": success
45
30
  ])
46
31
  }
47
32
  }
48
33
  }
49
34
  }
50
35
  }
36
+
37
+ @objc func fileDownloadChunk(_ call: CAPPluginCall) {
38
+ guard let fileName = call.getString("fileName"),
39
+ let chunkData = call.getString("chunkData") else {
40
+ call.resolve([
41
+ "result": false,
42
+ "message": "fileName 또는 chunkData 누락"
43
+ ])
44
+ return
45
+ }
46
+
47
+ let chunkIndex = call.getInt("chunkIndex") ?? 0
48
+ let totalChunks = call.getInt("totalChunks") ?? 1
49
+ let isLastChunk = call.getBool("isLastChunk") ?? false
50
+
51
+ // Base64 → Data 변환
52
+ guard let data = Data(base64Encoded: chunkData) else {
53
+ call.resolve([
54
+ "result": false,
55
+ "message": "청크 데이터(Base64) 디코딩 실패"
56
+ ])
57
+ return
58
+ }
59
+
60
+ // 다운로드 생성 (최초 호출 시만)
61
+ if chunkIndex == 0 {
62
+ _ = DownloadService.createDownload(id: fileName, totalChunks: totalChunks)
63
+ }
64
+
65
+ // 오프셋 = 청크 크기 * 인덱스
66
+ let offset = UInt64(data.count * chunkIndex)
67
+ DownloadService.appendChunk(id: fileName, data: data, offset: offset)
68
+
69
+ if isLastChunk {
70
+ if DownloadService.isComplete(id: fileName),
71
+ let finalURL = DownloadService.finishDownload(id: fileName) {
72
+ call.resolve([
73
+ "result": true,
74
+ "message": "청크 다운로드 완료",
75
+ "path": finalURL.path
76
+ ])
77
+ return
78
+ } else {
79
+ call.resolve([
80
+ "result": false,
81
+ "message": "마지막 청크 처리 중 오류 발생"
82
+ ])
83
+ return
84
+ }
85
+ }
86
+
87
+ // 마지막 청크가 아니면 그냥 성공 반환
88
+ call.resolve([
89
+ "result": true,
90
+ "message": "청크 \(chunkIndex + 1)/\(totalChunks) 저장 완료"
91
+ ])
92
+ }
93
+
94
+ /// 대용량 Base64 문자열을 스트리밍 방식으로 파일로 변환
95
+ private func decodeBase64ToFile(base64: String, fileURL: URL) -> Bool {
96
+ guard let inputData = base64.data(using: .utf8) else {
97
+ return false
98
+ }
99
+
100
+ let inputStream = InputStream(data: inputData)
101
+ inputStream.open()
102
+
103
+ guard let outputStream = OutputStream(url: fileURL, append: false) else {
104
+ return false
105
+ }
106
+ outputStream.open()
107
+
108
+ let bufferSize = 1024 * 64 // 64KB 단위 처리
109
+ let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
110
+
111
+ var base64Buffer = Data()
112
+ var success = true
113
+
114
+ while inputStream.hasBytesAvailable {
115
+ let read = inputStream.read(buffer, maxLength: bufferSize)
116
+ if read > 0 {
117
+ base64Buffer.append(buffer, count: read)
118
+
119
+ // Base64는 4의 배수 단위로 끊어서 처리 가능
120
+ let validLength = (base64Buffer.count / 4) * 4
121
+ if validLength > 0 {
122
+ let chunk = base64Buffer.prefix(validLength)
123
+ base64Buffer.removeFirst(validLength)
124
+
125
+ if let decoded = Data(base64Encoded: chunk, options: .ignoreUnknownCharacters) {
126
+ decoded.withUnsafeBytes {
127
+ _ = outputStream.write($0.bindMemory(to: UInt8.self).baseAddress!, maxLength: decoded.count)
128
+ }
129
+ } else {
130
+ success = false
131
+ break
132
+ }
133
+ }
134
+ } else {
135
+ break
136
+ }
137
+ }
138
+
139
+ // 남은 데이터 처리
140
+ if base64Buffer.count > 0,
141
+ let decoded = Data(base64Encoded: base64Buffer, options: .ignoreUnknownCharacters) {
142
+ decoded.withUnsafeBytes {
143
+ _ = outputStream.write($0.bindMemory(to: UInt8.self).baseAddress!, maxLength: decoded.count)
144
+ }
145
+ }
146
+
147
+ buffer.deallocate()
148
+ inputStream.close()
149
+ outputStream.close()
150
+
151
+ return success
152
+ }
51
153
  }
52
154
 
@@ -0,0 +1,94 @@
1
+ import Foundation
2
+ import UIKit
3
+ import Capacitor
4
+
5
+ public class DownloadService: NSObject {
6
+
7
+ // MARK: - 내부 구조체 (안드로이드의 ChunkDownloadInfo 대응)
8
+ private class ChunkDownloadInfo {
9
+ var tempFile: URL
10
+ var totalChunks: Int
11
+ var receivedChunks: Int
12
+ var fileHandle: FileHandle?
13
+
14
+ init(tempFile: URL, totalChunks: Int) {
15
+ self.tempFile = tempFile
16
+ self.totalChunks = totalChunks
17
+ self.receivedChunks = 0
18
+
19
+ // 임시 파일이 없으면 생성
20
+ if !FileManager.default.fileExists(atPath: tempFile.path) {
21
+ FileManager.default.createFile(atPath: tempFile.path, contents: nil, attributes: nil)
22
+ }
23
+
24
+ // 파일 핸들 열기
25
+ do {
26
+ self.fileHandle = try FileHandle(forWritingTo: tempFile)
27
+ } catch {
28
+ print("❌ 파일 핸들 생성 실패: \(error)")
29
+ }
30
+ }
31
+
32
+ deinit {
33
+ if #available(iOS 13.0, *) {
34
+ try? fileHandle?.close()
35
+ }
36
+ }
37
+ }
38
+
39
+ // MARK: - 다운로드 상태 관리 (안드로이드의 ConcurrentHashMap 대응)
40
+ private static var chunkDownloads: [String: ChunkDownloadInfo] = [:]
41
+ private static let queue = DispatchQueue(label: "DownloadServiceQueue", attributes: .concurrent)
42
+
43
+ // MARK: - 다운로드 생성
44
+ public static func createDownload(id: String, totalChunks: Int) -> URL? {
45
+ let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("\(id).tmp")
46
+ let info = ChunkDownloadInfo(tempFile: tempURL, totalChunks: totalChunks)
47
+
48
+ queue.async(flags: .barrier) {
49
+ chunkDownloads[id] = info
50
+ }
51
+
52
+ return tempURL
53
+ }
54
+
55
+ // MARK: - 청크 추가
56
+ public static func appendChunk(id: String, data: Data, offset: UInt64) {
57
+ queue.async(flags: .barrier) {
58
+ guard let info = chunkDownloads[id],
59
+ let handle = info.fileHandle else { return }
60
+
61
+ do {
62
+ if #available(iOS 13.0, *) {
63
+ try handle.seek(toOffset: offset)
64
+ }
65
+ handle.write(data)
66
+ info.receivedChunks += 1
67
+ } catch {
68
+ print("❌ 청크 쓰기 실패: \(error)")
69
+ }
70
+ }
71
+ }
72
+
73
+ // MARK: - 완료 여부 확인
74
+ public static func isComplete(id: String) -> Bool {
75
+ var complete = false
76
+ queue.sync {
77
+ if let info = chunkDownloads[id] {
78
+ complete = info.receivedChunks >= info.totalChunks
79
+ }
80
+ }
81
+ return complete
82
+ }
83
+
84
+ // MARK: - 다운로드 마무리
85
+ public static func finishDownload(id: String) -> URL? {
86
+ var fileURL: URL?
87
+ queue.sync(flags: .barrier) {
88
+ if let info = chunkDownloads.removeValue(forKey: id) {
89
+ fileURL = info.tempFile
90
+ }
91
+ }
92
+ return fileURL
93
+ }
94
+ }
@@ -12,12 +12,9 @@ CAP_PLUGIN(Plugin, "Plugin",
12
12
  CAP_PLUGIN_METHOD(open, CAPPluginReturnPromise);
13
13
  CAP_PLUGIN_METHOD(edgeSwipe, CAPPluginReturnPromise);
14
14
  CAP_PLUGIN_METHOD(fileDownload, CAPPluginReturnPromise);
15
+ CAP_PLUGIN_METHOD(fileDownloadChunk, CAPPluginReturnPromise);
15
16
  CAP_PLUGIN_METHOD(doDisabledCapture, CAPPluginReturnPromise);
16
17
  CAP_PLUGIN_METHOD(addKeyboardMenu, CAPPluginReturnPromise);
17
18
  CAP_PLUGIN_METHOD(removeKeyboardMenu, CAPPluginReturnPromise);
18
19
  CAP_PLUGIN_METHOD(executeApp, CAPPluginReturnPromise);
19
- CAP_PLUGIN_METHOD(getCertificateProvisioning, CAPPluginReturnPromise);
20
- CAP_PLUGIN_METHOD(saveUserDefault, CAPPluginReturnPromise);
21
- CAP_PLUGIN_METHOD(getUserDefault, CAPPluginReturnPromise);
22
-
23
20
  )
@@ -13,107 +13,6 @@ import Contacts
13
13
  public class Plugin: CAPPlugin {
14
14
 
15
15
 
16
-
17
- @objc func getUserDefault(_ call: CAPPluginCall) {
18
- guard let userInfo = call.getString("data") else {
19
- call.reject("No data string provided")
20
- return
21
- }
22
-
23
- print("UserDefaults에 저장할 데이터: \(userInfo)")
24
-
25
- guard let data = userInfo.data(using: .utf8) else {
26
- call.reject("Failed to convert string to data")
27
- return
28
- }
29
-
30
- do {
31
- guard let jsonObject = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
32
- call.reject("Failed to convert string to jsonObject")
33
- return
34
- }
35
-
36
- guard let keyName = jsonObject["key"] as? String else {
37
- call.reject("Failed to get key from jsonObject")
38
- return
39
- }
40
-
41
- guard let sharedDefaults = UserDefaults(suiteName: "group.com.hanwha.cleverselite") else {
42
- call.reject("Failed to access shared UserDefaults")
43
- return
44
- }
45
-
46
- if let value = sharedDefaults.string(forKey: keyName) {
47
- // 성공적으로 가져온 값 반환
48
- call.resolve([
49
- "data": value
50
- ])
51
- // call.resolve([
52
- // "data": value
53
- // ])
54
- } else {
55
- call.reject("No value found for key: \(keyName)")
56
- }
57
-
58
- } catch {
59
- call.reject("JSON parsing error: \(error.localizedDescription)")
60
- }
61
- }
62
-
63
-
64
- @objc func saveUserDefault(_ call: CAPPluginCall) {
65
- if let userInfo = call.getString("data") {
66
- print("UserDefaults에 저장할 데이터: \(userInfo)")
67
-
68
- // data 매개변수가 없으면 에러
69
- guard let data = userInfo.data(using: .utf8) else {
70
- call.reject("Failed to convert string to data")
71
- return
72
- }
73
-
74
- do {
75
- // json 직렬화 안되면 에러
76
- guard let jsonObject = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
77
- call.reject("Failed to convert string to jsonObject")
78
- return
79
- }
80
- // 키 값이 없으면 에러
81
- guard let keyName = jsonObject["key"] as? String else {
82
- call.reject("Failed to get key from jsonObject")
83
- return
84
- }
85
- // value 값이 없으면 에러
86
- guard let valueAny = jsonObject["value"] else {
87
- call.reject("Failed to get key from value")
88
- return
89
- }
90
-
91
- let valueData = try JSONSerialization.data(withJSONObject: valueAny, options: [])
92
-
93
- guard let valueString = String(data: valueData, encoding: .utf8) else {
94
- call.reject("Failed to valueString Data to String")
95
- return;
96
- }
97
-
98
-
99
- // 그룹 설정에 대한 UserDefaults를 가져오지 못하면 에러
100
- guard let sharedDefaults = UserDefaults(suiteName: "group.com.hanwha.cleverselite") else {
101
- call.reject("Failed to access shared UserDefaults")
102
- return
103
- }
104
-
105
- sharedDefaults.set(valueString, forKey: keyName);
106
-
107
-
108
- } catch {
109
- call.reject("JSON parsing error: \(error.localizedDescription)")
110
- }
111
-
112
-
113
- }
114
- }
115
-
116
-
117
16
  override public func load() {
118
17
  // 문서뷰어 요청 이벤트
119
18
  NotificationCenter.default.addObserver(self, selector: #selector(handleDocumentViewRequest(_:)), name: .documentViewRequest, object: nil)
@@ -131,61 +30,6 @@ public class Plugin: CAPPlugin {
131
30
  let keyboardSize = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue.size
132
31
  // data 매개변수에 keyboardsize를 전달
133
32
  self.notifyListeners(notification.name.rawValue, data: ["size": keyboardSize.height])
134
- }
135
- // ios 인증서 만료일을 가져온다
136
- @objc func getCertificateProvisioning(_ call: CAPPluginCall){
137
-
138
-
139
- guard let path = Bundle.main.path(forResource: "embedded", ofType: "mobileprovision"),
140
- let content = try? Data(contentsOf: URL(fileURLWithPath: path)) else {
141
- print("embedded.mobileprovision not found")
142
- call.resolve([
143
- "result": false,
144
- "message": "embedded.mobileprovision not found"
145
- ])
146
- return;
147
- }
148
-
149
- // 구형기기에서는 ascii, 최신기기는 isoLatin1
150
- guard let contentString = String(data: content, encoding: .isoLatin1) ?? String(data: content, encoding: .ascii),
151
- let startRange = contentString.range(of: "<plist"),
152
- let endRange = contentString.range(of: "</plist>") else {
153
- print("Failed to parse plist from provisioning profile")
154
- call.resolve([
155
- "result": false,
156
- "message": "Failed to parse plist from provisioning profile"
157
- ])
158
- return;
159
- }
160
-
161
- let plistString = String(contentString[startRange.lowerBound...endRange.upperBound])
162
- guard let plistData = plistString.data(using: .utf8) else { call.resolve([
163
- "result": false
164
- ])
165
- return; }
166
-
167
- do {
168
- if let plist = try PropertyListSerialization.propertyList(from: plistData, options: [], format: nil) as? [String: Any],
169
- let expirationDate = plist["ExpirationDate"] as? Date {
170
- // Date를 timestamp로 변환
171
- let timestamp = expirationDate.timeIntervalSince1970
172
-
173
- call.resolve([
174
- "result": true,
175
- "timestamp": timestamp,
176
- "date": expirationDate.description,
177
- ])
178
- return;
179
- // return expirationDate
180
- }
181
- } catch {
182
- print("Plist parsing failed: \(error)")
183
- }
184
-
185
- return;
186
-
187
-
188
-
189
33
  }
190
34
 
191
35
  @objc func handleDocumentViewRequest(_ notification: Notification) {
@@ -202,15 +46,11 @@ public class Plugin: CAPPlugin {
202
46
  안드로이드에서만 사용
203
47
  */
204
48
  @objc func executeApp(_ call: CAPPluginCall) {
205
-
206
-
207
- if let url = URL(string: call.getString("package") ?? "") {
49
+ if let url = URL(string: call.getString("package") ?? "") {
208
50
  DispatchQueue.main.async {
209
51
  UIApplication.shared.open(url, options: [:], completionHandler: nil)
210
52
  }
211
53
  }
212
-
213
-
214
54
  }
215
55
 
216
56
  @objc func auth(_ call: CAPPluginCall) {
@@ -225,9 +65,6 @@ public class Plugin: CAPPlugin {
225
65
  KeyboardMenuPlugin().remove(call)
226
66
  }
227
67
 
228
-
229
-
230
-
231
68
  @objc func addContact(_ call: CAPPluginCall) {
232
69
  ContactPlugin().addContact(call)
233
70
  }
@@ -250,7 +87,11 @@ public class Plugin: CAPPlugin {
250
87
  }
251
88
 
252
89
  @objc func fileDownload(_ call: CAPPluginCall) {
253
- DownloadPlugin().doDownload(call, _bridge: self.bridge!)
90
+ DownloadPlugin().doDownload(call, self.bridge!)
91
+ }
92
+
93
+ @objc func fileDownloadChunk(_ call: CAPPluginCall) {
94
+ DownloadPlugin().fileDownloadChunk(call)
254
95
  }
255
96
 
256
97
  @objc func doDisabledCapture(_ call: CAPPluginCall) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanwha-ss1/plugin",
3
- "version": "0.7.0",
3
+ "version": "0.7.3",
4
4
  "description": "Plugin",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",