@lynker-desktop/electron-sdk 0.0.9-alpha.62 → 0.0.9-alpha.65

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.
@@ -1 +1 @@
1
- {"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../src/main/clipboard.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,KAAK,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,GAAG,iBAAiB,GAAG,iBAAiB,EAAE,GAAG,WAAW,CAAC;IACvE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACtE,OAAO,CAAC,EAAE;QACR,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AA09BD;;GAEG;AACH,eAAO,MAAM,mBAAmB,YA0F/B,CAAA"}
1
+ {"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../src/main/clipboard.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,KAAK,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,GAAG,iBAAiB,GAAG,iBAAiB,EAAE,GAAG,WAAW,CAAC;IACvE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACtE,OAAO,CAAC,EAAE;QACR,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AA2qCD;;GAEG;AACH,eAAO,MAAM,mBAAmB,YA0F/B,CAAA"}
@@ -11,6 +11,10 @@ const FILE_URI_PREFIX = 'file://';
11
11
  const PLATFORM = process.platform;
12
12
  const DOWNLOAD_TIMEOUT = 30000; // 30秒下载超时
13
13
  const UNIX_FILE_FORMATS = ['public.file-url', 'text/uri-list', 'x-special/gnome-copied-files'];
14
+ // 图片相关的剪贴板格式
15
+ const IMAGE_FORMATS = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/bmp', 'image/webp', 'image/tiff', 'image/x-png', 'image/x-icon'];
16
+ // Windows 文件格式
17
+ const WINDOWS_FILE_FORMAT = 'FileNameW';
14
18
  // MIME 类型映射表
15
19
  const MIME_TYPES = {
16
20
  // 文本文件
@@ -212,12 +216,133 @@ function normalizeFilePath(filePath) {
212
216
  return path.isAbsolute(normalized) ? normalized : path.resolve(normalized);
213
217
  }
214
218
  /**
215
- * 判断字符串是否为 URL
219
+ * 判断字符串是否为 URL(更严格的判断)
216
220
  */
217
221
  function isUrl(str) {
222
+ if (!str || typeof str !== 'string') {
223
+ return false;
224
+ }
225
+ const trimmed = str.trim();
226
+ if (trimmed.length === 0) {
227
+ return false;
228
+ }
229
+ try {
230
+ const url = new URL(trimmed);
231
+ // 必须是 http 或 https 协议
232
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
233
+ return false;
234
+ }
235
+ // 必须有 hostname
236
+ if (!url.hostname || url.hostname.length === 0) {
237
+ return false;
238
+ }
239
+ // 避免将本地文件路径误判为 URL(如 C:\path\to\file)
240
+ if (url.hostname.includes('\\') || url.hostname.includes(':')) {
241
+ return false;
242
+ }
243
+ return true;
244
+ }
245
+ catch {
246
+ return false;
247
+ }
248
+ }
249
+ /**
250
+ * 检查剪贴板是否有图片格式
251
+ */
252
+ function hasImageFormat() {
218
253
  try {
219
- const url = new URL(str);
220
- return url.protocol === 'http:' || url.protocol === 'https:';
254
+ const formats = clipboard.availableFormats();
255
+ // 检查是否有图片格式
256
+ for (const format of formats) {
257
+ if (!format)
258
+ continue;
259
+ const lowerFormat = format.toLowerCase();
260
+ // 检查是否匹配图片格式
261
+ if (IMAGE_FORMATS.some(imgFormat => lowerFormat.includes(imgFormat.toLowerCase()))) {
262
+ return true;
263
+ }
264
+ // 检查常见的图片格式标识
265
+ if (lowerFormat.startsWith('image/')) {
266
+ return true;
267
+ }
268
+ }
269
+ // Electron 的 readImage 可能在没有明确格式时也能读取
270
+ // 尝试读取一次来验证(但不实际读取数据,只检查是否为空)
271
+ try {
272
+ const image = clipboard.readImage();
273
+ if (!image.isEmpty()) {
274
+ return true;
275
+ }
276
+ }
277
+ catch {
278
+ // 读取失败,说明不是图片
279
+ }
280
+ return false;
281
+ }
282
+ catch {
283
+ return false;
284
+ }
285
+ }
286
+ /**
287
+ * 检查剪贴板是否有文件格式
288
+ */
289
+ function hasFileFormat() {
290
+ try {
291
+ const formats = clipboard.availableFormats();
292
+ if (PLATFORM === 'win32') {
293
+ // Windows: 检查 FileNameW 格式
294
+ return formats.includes(WINDOWS_FILE_FORMAT);
295
+ }
296
+ else {
297
+ // macOS/Linux: 检查文件 URI 格式
298
+ for (const format of UNIX_FILE_FORMATS) {
299
+ if (formats.includes(format)) {
300
+ return true;
301
+ }
302
+ }
303
+ // 也检查文本中是否包含 file:// URI
304
+ try {
305
+ const text = clipboard.readText();
306
+ if (text && text.includes(FILE_URI_PREFIX)) {
307
+ // 进一步验证:检查是否真的是文件路径
308
+ const lines = text.split('\n').filter(Boolean);
309
+ for (const line of lines) {
310
+ if (line.trim().startsWith(FILE_URI_PREFIX)) {
311
+ const filePath = parseFileUri(line.trim());
312
+ const normalizedPath = normalizeFilePath(filePath);
313
+ if (fs.existsSync(normalizedPath)) {
314
+ const stat = fs.statSync(normalizedPath);
315
+ if (stat.isFile()) {
316
+ return true;
317
+ }
318
+ }
319
+ }
320
+ }
321
+ }
322
+ }
323
+ catch {
324
+ // 忽略错误
325
+ }
326
+ }
327
+ return false;
328
+ }
329
+ catch {
330
+ return false;
331
+ }
332
+ }
333
+ /**
334
+ * 检查剪贴板内容是否为 URL(基于格式判断,不读取内容)
335
+ */
336
+ function hasUrlFormat() {
337
+ try {
338
+ const formats = clipboard.availableFormats();
339
+ // 检查是否有 URL 相关的格式
340
+ if (formats.includes('text/uri-list') || formats.includes('text/x-moz-url')) {
341
+ return true;
342
+ }
343
+ // 注意:这里不读取文本内容来判断,因为读取会消耗剪贴板
344
+ // URL 检测将在 handleGetUrl 中进行
345
+ return false;
221
346
  }
222
347
  catch {
223
348
  return false;
@@ -599,6 +724,17 @@ function createImageFileDataFromClipboard(options) {
599
724
  * 读取图片类型
600
725
  */
601
726
  function handleGetImage(options) {
727
+ // 先检查是否有图片格式,避免误判
728
+ if (!hasImageFormat()) {
729
+ // 即使没有明确的图片格式,也尝试读取(Electron 可能能读取)
730
+ // 但如果读取失败,返回空结果
731
+ const imageData = createImageFileDataFromClipboard(options);
732
+ if (imageData) {
733
+ return { type: 'image', image: imageData };
734
+ }
735
+ return { type: 'image' };
736
+ }
737
+ // 有图片格式,尝试读取
602
738
  const imageData = createImageFileDataFromClipboard(options);
603
739
  if (imageData) {
604
740
  return { type: 'image', image: imageData };
@@ -610,22 +746,49 @@ function handleGetImage(options) {
610
746
  */
611
747
  function handleGetUrl() {
612
748
  try {
613
- // 先尝试读取文本,检查是否为 URL
614
- const text = clipboard.readText();
615
- if (text && isUrl(text)) {
616
- return { type: 'url', url: text };
749
+ // 先检查是否有 URL 格式
750
+ if (!hasUrlFormat()) {
751
+ return { type: 'url' };
617
752
  }
618
- // 检查是否有链接格式
753
+ // 检查是否有 URL 相关的格式
619
754
  const formats = clipboard.availableFormats();
620
- if (formats.includes('text/uri-list')) {
621
- const uriList = clipboard.readText();
622
- if (uriList) {
623
- const uris = uriList.split('\n').filter(Boolean);
624
- const url = uris.find(uri => isUrl(uri));
625
- if (url) {
626
- return { type: 'url', url };
755
+ // 优先检查 text/uri-list 或 text/x-moz-url 格式
756
+ if (formats.includes('text/uri-list') || formats.includes('text/x-moz-url')) {
757
+ try {
758
+ const uriList = clipboard.readText();
759
+ if (uriList) {
760
+ const uris = uriList.split('\n').filter(Boolean);
761
+ // 查找第一个有效的 HTTP/HTTPS URL
762
+ for (const uri of uris) {
763
+ const trimmed = uri.trim();
764
+ if (isUrl(trimmed)) {
765
+ return { type: 'url', url: trimmed };
766
+ }
767
+ }
627
768
  }
628
769
  }
770
+ catch {
771
+ // 忽略错误,继续尝试其他方法
772
+ }
773
+ }
774
+ // 尝试从普通文本中读取 URL
775
+ try {
776
+ const text = clipboard.readText();
777
+ if (text) {
778
+ const trimmed = text.trim();
779
+ // 检查整段文本是否为 URL
780
+ if (isUrl(trimmed)) {
781
+ return { type: 'url', url: trimmed };
782
+ }
783
+ // 尝试从文本中提取 URL(简单模式:查找 http:// 或 https://)
784
+ const urlMatch = trimmed.match(/https?:\/\/[^\s]+/);
785
+ if (urlMatch && urlMatch[0] && isUrl(urlMatch[0])) {
786
+ return { type: 'url', url: urlMatch[0] };
787
+ }
788
+ }
789
+ }
790
+ catch {
791
+ // 忽略错误
629
792
  }
630
793
  return { type: 'url' };
631
794
  }
@@ -655,6 +818,10 @@ function handleGetBuffer() {
655
818
  * 读取文件类型(单个)
656
819
  */
657
820
  function handleGetFile(options) {
821
+ // 先检查是否有文件格式,避免误判
822
+ if (!hasFileFormat()) {
823
+ return { type: 'file' };
824
+ }
658
825
  const filePaths = readClipboardFiles();
659
826
  if (filePaths.length === 0 || !filePaths[0]) {
660
827
  return { type: 'file' };
@@ -666,44 +833,78 @@ function handleGetFile(options) {
666
833
  return { type: 'file', file: fileData };
667
834
  }
668
835
  /**
669
- * 自动检测类型(优先文件,然后是图片,最后是文本)
836
+ * 自动检测类型(基于格式检测,优先文件,然后是图片、URL,最后是文本)
670
837
  */
671
838
  function handleGetAuto(options) {
672
- // 1. 优先检测文件
673
- const filePaths = readClipboardFiles();
674
- if (filePaths.length > 0) {
675
- if (filePaths.length === 1) {
676
- const firstPath = filePaths[0];
677
- if (firstPath) {
678
- const fileData = readFileData(firstPath, options);
679
- if (fileData) {
680
- return { type: 'file', file: fileData };
839
+ // 先获取剪贴板格式,避免重复调用
840
+ let formats = [];
841
+ try {
842
+ formats = clipboard.availableFormats();
843
+ }
844
+ catch {
845
+ // 如果获取格式失败,降级到文本
846
+ const text = handleGetText();
847
+ return { type: 'text', text: text || '' };
848
+ }
849
+ // 1. 优先检测文件(基于格式检测)
850
+ if (hasFileFormat()) {
851
+ const filePaths = readClipboardFiles();
852
+ if (filePaths.length > 0) {
853
+ if (filePaths.length === 1) {
854
+ const firstPath = filePaths[0];
855
+ if (firstPath) {
856
+ const fileData = readFileData(firstPath, options);
857
+ if (fileData) {
858
+ return { type: 'file', file: fileData };
859
+ }
681
860
  }
682
861
  }
683
- }
684
- else {
685
- // 多个文件
686
- const files = filePaths
687
- .map(filePath => readFileData(filePath, options))
688
- .filter((file) => file !== null);
689
- if (files.length > 0) {
690
- return { type: 'files', files };
862
+ else {
863
+ // 多个文件
864
+ const files = filePaths
865
+ .map(filePath => readFileData(filePath, options))
866
+ .filter((file) => file !== null);
867
+ if (files.length > 0) {
868
+ return { type: 'files', files };
869
+ }
691
870
  }
692
871
  }
693
872
  }
694
- // 2. 检测图片(直接使用可重用逻辑,避免重复读取)
695
- const imageData = createImageFileDataFromClipboard(options);
696
- if (imageData) {
697
- return { type: 'image', image: imageData };
873
+ // 2. 检测图片(基于格式检测)
874
+ if (hasImageFormat()) {
875
+ const imageData = createImageFileDataFromClipboard(options);
876
+ if (imageData) {
877
+ return { type: 'image', image: imageData };
878
+ }
698
879
  }
699
- // 3. 检测 HTML
700
- const htmlData = safeClipboardOperation(() => clipboard.readHTML(), '', '读取 HTML 失败:');
701
- if (htmlData && htmlData.trim().length > 0) {
702
- return { type: 'html', html: htmlData };
880
+ // 3. 检测 URL(基于格式检测,或文本内容判断)
881
+ // 优先检查是否有 URL 格式
882
+ if (hasUrlFormat()) {
883
+ const urlResult = handleGetUrl();
884
+ if (urlResult.url) {
885
+ return urlResult;
886
+ }
887
+ }
888
+ // 4. 检测 HTML(检查格式)
889
+ if (formats.includes('text/html') || formats.some(f => f && f.toLowerCase().includes('html'))) {
890
+ const htmlData = safeClipboardOperation(() => clipboard.readHTML(), '', '读取 HTML 失败:');
891
+ if (htmlData && htmlData.trim().length > 0) {
892
+ return { type: 'html', html: htmlData };
893
+ }
894
+ }
895
+ // 5. 最后读取文本,并检查是否为 URL
896
+ const text = safeClipboardOperation(() => clipboard.readText(), '', '读取文本失败:');
897
+ if (text && text.trim().length > 0) {
898
+ const trimmed = text.trim();
899
+ // 如果文本是纯 URL(单行且是有效的 URL),且之前没有检测到 URL 格式
900
+ // 则判断为 URL 类型
901
+ if (!trimmed.includes('\n') && isUrl(trimmed)) {
902
+ return { type: 'url', url: trimmed };
903
+ }
904
+ // 否则返回文本类型
905
+ return { type: 'text', text: trimmed };
703
906
  }
704
- // 4. 最后读取文本
705
- const text = handleGetText();
706
- return { type: 'text', text: text || '' };
907
+ return { type: 'text', text: '' };
707
908
  }
708
909
  /**
709
910
  * 判断字符串是否为文件路径或 URL