@ryuzaki13/react-foundation-lib 2.34.0 → 2.35.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.
@@ -4,6 +4,10 @@
4
4
  * Data URL с префиксом `data:*;base64,` поддерживается, сам URL здесь не создаётся.
5
5
  */
6
6
  export declare const binaryToBlob: (binaryData: string, mimeType?: string, isBase64?: boolean) => Blob;
7
+ /**
8
+ * Ищет MIME-тип по метаданным или сигнатуре без подстановки значения по умолчанию.
9
+ */
10
+ export declare const findMimeType: (fileName?: string, binaryData?: string) => string | null;
7
11
  /**
8
12
  * Определяет MIME-тип по имени файла, data URL или сигнатуре base64-данных.
9
13
  *
@@ -1 +1 @@
1
- {"version":3,"file":"binaryToBlob.d.ts","sourceRoot":"","sources":["../../src/binary/binaryToBlob.ts"],"names":[],"mappings":"AAoMA;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,YAAY,MAAM,EAAE,WAAU,MAA+B,EAAE,kBAAe,KAAG,IAmB7G,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,WAAW,MAAM,EAAE,aAAa,MAAM,KAAG,MAwBvE,CAAC"}
1
+ {"version":3,"file":"binaryToBlob.d.ts","sourceRoot":"","sources":["../../src/binary/binaryToBlob.ts"],"names":[],"mappings":"AA8MA;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,YAAY,MAAM,EAAE,WAAU,MAA+B,EAAE,kBAAe,KAAG,IAmB7G,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,WAAW,MAAM,EAAE,aAAa,MAAM,KAAG,MAAM,GAAG,IAwB9E,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,WAAW,MAAM,EAAE,aAAa,MAAM,KAAG,MAA6D,CAAC"}
@@ -1,4 +1,4 @@
1
- export * from "./binaryToBlob";
1
+ export { binaryToBlob, detectMimeType } from "./binaryToBlob";
2
2
  export { getBase64DecodedSize } from "./getBase64DecodedSize";
3
- export * from "./useBinaryFile";
3
+ export { useBinaryFile, type UseBinaryFileResult } from "./useBinaryFile";
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/binary/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/binary/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -1,32 +1,32 @@
1
1
  import { l as e } from "../chunks/utils-DW0Yso1Q.js";
2
2
  import { a as t, c as n, d as r, g as i, h as a, i as o, l as s, m as c, n as l, o as u, p as d, r as f, s as p, t as m, u as h } from "../chunks/mimeTypeConstants-BzONV-b1.js";
3
3
  import { t as g } from "../chunks/hasSignatureBytes-D2wO_Myl.js";
4
- import { useEffect as _, useEffectEvent as v, useState as y } from "react";
4
+ import { useEffect as _, useMemo as v } from "react";
5
5
  //#region src/binary/binaryToBlob.ts
6
- var b = /^data:([^,]*);base64,([\s\S]*)$/i, x = 128, S = 16, C = [
6
+ var y = /^data:([^,]*);base64,([\s\S]*)$/i, b = 128, x = 16, S = [
7
7
  37,
8
8
  80,
9
9
  68,
10
10
  70
11
- ], w = [
11
+ ], C = [
12
12
  255,
13
13
  216,
14
14
  255
15
- ], T = [
15
+ ], w = [
16
16
  137,
17
17
  80,
18
18
  78,
19
19
  71
20
- ], E = [
20
+ ], T = [
21
21
  71,
22
22
  73,
23
23
  70
24
- ], D = [
24
+ ], E = [
25
25
  82,
26
26
  73,
27
27
  70,
28
28
  70
29
- ], O = [80, 75], k = {
29
+ ], D = [80, 75], O = {
30
30
  bmp: "image/bmp",
31
31
  csv: "text/csv",
32
32
  doc: p,
@@ -48,12 +48,12 @@ var b = /^data:([^,]*);base64,([\s\S]*)$/i, x = 128, S = 16, C = [
48
48
  xlsx: c,
49
49
  xml: a,
50
50
  zip: i
51
- }, A = (e) => {
51
+ }, k = (e) => {
52
52
  let t = e.replace(/\s/g, "").replace(/-/g, "+").replace(/_/g, "/"), n = t.length % 4;
53
53
  if (n === 1 || !/^[A-Za-z0-9+/]*={0,2}$/.test(t)) throw Error("Некорректный формат base64");
54
54
  return n === 0 ? t : `${t}${"=".repeat(4 - n)}`;
55
- }, j = (e) => {
56
- let t = e.match(b);
55
+ }, A = (e) => {
56
+ let t = e.match(y);
57
57
  if (!t) return {
58
58
  data: e,
59
59
  mimeType: null
@@ -63,68 +63,72 @@ var b = /^data:([^,]*);base64,([\s\S]*)$/i, x = 128, S = 16, C = [
63
63
  data: r,
64
64
  mimeType: n.split(";")[0].trim() || null
65
65
  };
66
- }, M = (e) => {
67
- let t = j(e);
66
+ }, j = (e) => {
67
+ let t = A(e);
68
68
  return {
69
- data: A(t.data),
69
+ data: k(t.data),
70
70
  mimeType: t.mimeType
71
71
  };
72
- }, N = (e) => {
73
- let t = e.match(b);
72
+ }, M = (e) => {
73
+ let t = e.match(y);
74
74
  return t && t[1].split(";")[0].trim() || null;
75
- }, P = (e) => {
76
- let { data: t } = M(e), n = atob(t), r = new Uint8Array(n.length);
75
+ }, N = (e) => {
76
+ let { data: t } = j(e), n = atob(t), r = new Uint8Array(n.length);
77
77
  for (let e = 0; e < n.length; e++) r[e] = n.charCodeAt(e);
78
78
  return r;
79
- }, F = (e) => {
79
+ }, P = (e) => {
80
80
  let t = new Uint8Array(e.length);
81
81
  for (let n = 0; n < e.length; n++) t[n] = e.charCodeAt(n);
82
82
  return t;
83
- }, I = (e) => {
83
+ }, F = (e) => {
84
84
  let t = new ArrayBuffer(e.byteLength);
85
85
  return new Uint8Array(t).set(e), t;
86
- }, L = (e) => {
86
+ }, I = (e) => {
87
87
  try {
88
- let { data: t } = j(e), n = t.slice(0, x);
89
- return atob(A(n));
88
+ let { data: t } = A(e), n = "";
89
+ for (let e = 0; e < t.length && n.length < b; e++) {
90
+ let r = t[e];
91
+ /\s/.test(r) || (n += r);
92
+ }
93
+ return atob(k(n));
90
94
  } catch {
91
95
  return null;
92
96
  }
93
- }, R = (e) => {
94
- let t = L(e);
97
+ }, L = (e) => {
98
+ let t = I(e);
95
99
  if (!t) return null;
96
100
  let n = new Uint8Array(t.length);
97
101
  for (let e = 0; e < t.length; e++) n[e] = t.charCodeAt(e);
98
- if (g(n, C)) return s;
99
- if (g(n, w)) return f;
100
- if (g(n, T)) return o;
101
- if (g(n, E)) return l;
102
- if (g(n, D)) return u;
103
- if (g(n, O)) return i;
104
- let r = t.replace(/^\uFEFF/, "").trimStart().slice(0, S);
102
+ if (g(n, S)) return s;
103
+ if (g(n, C)) return f;
104
+ if (g(n, w)) return o;
105
+ if (g(n, T)) return l;
106
+ if (g(n, E)) return u;
107
+ if (g(n, D)) return i;
108
+ let r = t.replace(/^\uFEFF/, "").trimStart().slice(0, x);
105
109
  return r.startsWith("<?xml") || r.startsWith("<") ? a : null;
106
- }, z = (e, t = n, r = !0) => {
110
+ }, R = (e, t = n, r = !0) => {
107
111
  if (!e) throw Error("Не переданы бинарные данные");
108
112
  try {
109
- let n = r ? P(e) : F(e);
113
+ let n = r ? N(e) : P(e);
110
114
  if (n.length === 0) throw Error("После преобразования не осталось данных");
111
- return new Blob([I(n)], { type: t.trim() || "application/octet-stream" });
115
+ return new Blob([F(n)], { type: t.trim() || "application/octet-stream" });
112
116
  } catch (e) {
113
117
  throw e instanceof Error ? e : Error("Некорректный формат бинарных данных");
114
118
  }
115
- }, B = (e, t) => {
119
+ }, z = (e, t) => {
116
120
  if (e) {
117
121
  let t = e.toLowerCase().split(".").pop();
118
- if (t && k[t]) return k[t];
122
+ if (t && O[t]) return O[t];
119
123
  }
120
124
  if (t) {
121
- let e = N(t);
125
+ let e = M(t);
122
126
  if (e) return e;
123
- let n = R(t);
127
+ let n = L(t);
124
128
  if (n) return n;
125
129
  }
126
- return s;
127
- }, V = /[^A-Za-z0-9+/=]/;
130
+ return null;
131
+ }, B = (e, t) => z(e, t) ?? "application/pdf", V = /[^A-Za-z0-9+/=]/;
128
132
  function H(e) {
129
133
  if (e.length % 4 != 0 || V.test(e)) return null;
130
134
  let t = e.endsWith("==") ? 2 : +!!e.endsWith("="), n = e.indexOf("=");
@@ -133,24 +137,39 @@ function H(e) {
133
137
  //#endregion
134
138
  //#region src/binary/useBinaryFile.ts
135
139
  function U(t) {
136
- let [n, r] = y(null), [i, a] = y(null), o = v((e) => r(e)), s = v((e) => a(e));
137
- return _(() => {
138
- if (t) try {
139
- let e = B(void 0, t), n = z(t, e, !0);
140
- o({
141
- blob: n,
142
- mime: e
143
- }), s(null);
144
- } catch (t) {
145
- e(t), s(String(t)), o(null);
140
+ let { data: n, failure: r } = v(() => {
141
+ if (!t) return {
142
+ data: null,
143
+ failure: null
144
+ };
145
+ try {
146
+ let e = z(void 0, t), n = e ?? "application/octet-stream";
147
+ return {
148
+ data: {
149
+ blob: R(t, n, !0),
150
+ mime: n,
151
+ isMimeDetected: e !== null
152
+ },
153
+ failure: null
154
+ };
155
+ } catch (e) {
156
+ return {
157
+ data: null,
158
+ failure: {
159
+ cause: e,
160
+ message: String(e)
161
+ }
162
+ };
146
163
  }
147
- else o(null), s(null);
148
- }, [t]), {
164
+ }, [t]);
165
+ return _(() => {
166
+ r && e(r.cause);
167
+ }, [r]), {
149
168
  data: n,
150
- error: i
169
+ error: r?.message ?? null
151
170
  };
152
171
  }
153
172
  //#endregion
154
- export { z as binaryToBlob, B as detectMimeType, H as getBase64DecodedSize, U as useBinaryFile };
173
+ export { R as binaryToBlob, B as detectMimeType, H as getBase64DecodedSize, U as useBinaryFile };
155
174
 
156
175
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/binary/binaryToBlob.ts","../../src/binary/getBase64DecodedSize.ts","../../src/binary/useBinaryFile.ts"],"sourcesContent":["import {\n\tMIME_TYPE_DOCX,\n\tMIME_TYPE_IMAGE_GIF,\n\tMIME_TYPE_IMAGE_JPEG,\n\tMIME_TYPE_IMAGE_PNG,\n\tMIME_TYPE_IMAGE_TIFF,\n\tMIME_TYPE_IMAGE_WEBP,\n\tMIME_TYPE_MSWORD,\n\tMIME_TYPE_OCTET_STREAM,\n\tMIME_TYPE_PDF,\n\tMIME_TYPE_PPT,\n\tMIME_TYPE_PPTX,\n\tMIME_TYPE_XLS,\n\tMIME_TYPE_XLSX,\n\tMIME_TYPE_XML,\n\tMIME_TYPE_ZIP\n} from \"../file/mimeTypeConstants\";\nimport { hasSignatureBytes } from \"../file/signature/hasSignatureBytes\";\n\nimport { BASE64_ENCODED_BLOCK_CHARACTERS, BASE64_PADDING } from \"./base64Constants\";\n\nconst BASE64_DATA_URL_PATTERN = /^data:([^,]*);base64,([\\s\\S]*)$/i;\nconst BASE64_HEADER_SCAN_CHARACTERS = 128;\nconst XML_HEADER_SCAN_CHARACTERS = 16;\n// Здесь распознаётся только прежний короткий prefix, без усиления проверки формата.\nconst PDF_HEADER_PREFIX = [0x25, 0x50, 0x44, 0x46];\nconst JPEG_HEADER_PREFIX = [0xff, 0xd8, 0xff];\nconst PNG_HEADER_PREFIX = [0x89, 0x50, 0x4e, 0x47];\nconst GIF_HEADER_PREFIX = [0x47, 0x49, 0x46];\nconst WEBP_HEADER_PREFIX = [0x52, 0x49, 0x46, 0x46];\nconst ZIP_HEADER_PREFIX = [0x50, 0x4b];\n\nconst MIME_TYPE_BY_EXTENSION: Record<string, string> = {\n\tbmp: \"image/bmp\",\n\tcsv: \"text/csv\",\n\tdoc: MIME_TYPE_MSWORD,\n\tdocx: MIME_TYPE_DOCX,\n\tgif: MIME_TYPE_IMAGE_GIF,\n\tjpeg: MIME_TYPE_IMAGE_JPEG,\n\tjpg: MIME_TYPE_IMAGE_JPEG,\n\tjson: \"application/json\",\n\tpdf: MIME_TYPE_PDF,\n\tpng: MIME_TYPE_IMAGE_PNG,\n\tppt: MIME_TYPE_PPT,\n\tpptx: MIME_TYPE_PPTX,\n\tsvg: \"image/svg+xml\",\n\ttif: MIME_TYPE_IMAGE_TIFF,\n\ttiff: MIME_TYPE_IMAGE_TIFF,\n\ttxt: \"text/plain\",\n\twebp: MIME_TYPE_IMAGE_WEBP,\n\txls: MIME_TYPE_XLS,\n\txlsx: MIME_TYPE_XLSX,\n\txml: MIME_TYPE_XML,\n\tzip: MIME_TYPE_ZIP\n};\n\nconst normalizeBase64 = (value: string): string => {\n\tconst normalized = value.replace(/\\s/g, \"\").replace(/-/g, \"+\").replace(/_/g, \"/\");\n\tconst paddingRest = normalized.length % BASE64_ENCODED_BLOCK_CHARACTERS;\n\n\tif (paddingRest === 1 || !/^[A-Za-z0-9+/]*={0,2}$/.test(normalized)) {\n\t\tthrow new Error(\"Некорректный формат base64\");\n\t}\n\n\treturn paddingRest === 0 ? normalized : `${normalized}${BASE64_PADDING.repeat(BASE64_ENCODED_BLOCK_CHARACTERS - paddingRest)}`;\n};\n\nconst getBase64RawPayload = (binaryData: string): { data: string; mimeType: string | null } => {\n\tconst dataUrlMatch = binaryData.match(BASE64_DATA_URL_PATTERN);\n\n\tif (!dataUrlMatch) {\n\t\treturn { data: binaryData, mimeType: null };\n\t}\n\n\tconst [, rawMimeType, rawData] = dataUrlMatch;\n\tconst mimeType = rawMimeType.split(\";\")[0].trim();\n\n\treturn {\n\t\tdata: rawData,\n\t\tmimeType: mimeType || null\n\t};\n};\n\nconst getBase64Payload = (binaryData: string): { data: string; mimeType: string | null } => {\n\tconst payload = getBase64RawPayload(binaryData);\n\n\treturn {\n\t\tdata: normalizeBase64(payload.data),\n\t\tmimeType: payload.mimeType\n\t};\n};\n\nconst getDataUrlMimeType = (binaryData: string): string | null => {\n\tconst dataUrlMatch = binaryData.match(BASE64_DATA_URL_PATTERN);\n\n\tif (!dataUrlMatch) {\n\t\treturn null;\n\t}\n\n\tconst mimeType = dataUrlMatch[1].split(\";\")[0].trim();\n\n\treturn mimeType || null;\n};\n\nconst decodeBase64ToBytes = (binaryData: string): Uint8Array => {\n\tconst { data } = getBase64Payload(binaryData);\n\tconst binaryString = atob(data);\n\tconst byteArray = new Uint8Array(binaryString.length);\n\n\tfor (let index = 0; index < binaryString.length; index++) {\n\t\tbyteArray[index] = binaryString.charCodeAt(index);\n\t}\n\n\treturn byteArray;\n};\n\nconst binaryStringToBytes = (binaryData: string): Uint8Array => {\n\tconst byteArray = new Uint8Array(binaryData.length);\n\n\tfor (let index = 0; index < binaryData.length; index++) {\n\t\tbyteArray[index] = binaryData.charCodeAt(index);\n\t}\n\n\treturn byteArray;\n};\n\nconst copyBytesToArrayBuffer = (byteArray: Uint8Array): ArrayBuffer => {\n\tconst buffer = new ArrayBuffer(byteArray.byteLength);\n\tconst view = new Uint8Array(buffer);\n\n\tview.set(byteArray);\n\n\treturn buffer;\n};\n\nconst decodeHeaderFromBase64 = (binaryData: string): string | null => {\n\ttry {\n\t\tconst { data } = getBase64RawPayload(binaryData);\n\t\tconst headerData = data.slice(0, BASE64_HEADER_SCAN_CHARACTERS);\n\t\tconst binaryString = atob(normalizeBase64(headerData));\n\n\t\treturn binaryString;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\nconst detectMimeTypeBySignature = (binaryData: string): string | null => {\n\tconst header = decodeHeaderFromBase64(binaryData);\n\n\tif (!header) {\n\t\treturn null;\n\t}\n\n\tconst bytes = new Uint8Array(header.length);\n\n\tfor (let index = 0; index < header.length; index++) {\n\t\tbytes[index] = header.charCodeAt(index);\n\t}\n\n\tif (hasSignatureBytes(bytes, PDF_HEADER_PREFIX)) {\n\t\treturn MIME_TYPE_PDF;\n\t}\n\n\tif (hasSignatureBytes(bytes, JPEG_HEADER_PREFIX)) {\n\t\treturn MIME_TYPE_IMAGE_JPEG;\n\t}\n\n\tif (hasSignatureBytes(bytes, PNG_HEADER_PREFIX)) {\n\t\treturn MIME_TYPE_IMAGE_PNG;\n\t}\n\n\tif (hasSignatureBytes(bytes, GIF_HEADER_PREFIX)) {\n\t\treturn MIME_TYPE_IMAGE_GIF;\n\t}\n\n\tif (hasSignatureBytes(bytes, WEBP_HEADER_PREFIX)) {\n\t\treturn MIME_TYPE_IMAGE_WEBP;\n\t}\n\n\tif (hasSignatureBytes(bytes, ZIP_HEADER_PREFIX)) {\n\t\treturn MIME_TYPE_ZIP;\n\t}\n\n\tconst textHeader = header\n\t\t.replace(/^\\uFEFF/, \"\")\n\t\t.trimStart()\n\t\t.slice(0, XML_HEADER_SCAN_CHARACTERS);\n\n\tif (textHeader.startsWith(\"<?xml\") || textHeader.startsWith(\"<\")) {\n\t\treturn MIME_TYPE_XML;\n\t}\n\n\treturn null;\n};\n\n/**\n * Преобразует base64 или бинарную строку в Blob.\n *\n * Data URL с префиксом `data:*;base64,` поддерживается, сам URL здесь не создаётся.\n */\nexport const binaryToBlob = (binaryData: string, mimeType: string = MIME_TYPE_OCTET_STREAM, isBase64 = true): Blob => {\n\tif (!binaryData) {\n\t\tthrow new Error(\"Не переданы бинарные данные\");\n\t}\n\n\ttry {\n\t\tconst byteArray = isBase64 ? decodeBase64ToBytes(binaryData) : binaryStringToBytes(binaryData);\n\n\t\tif (byteArray.length === 0) {\n\t\t\tthrow new Error(\"После преобразования не осталось данных\");\n\t\t}\n\n\t\treturn new Blob([copyBytesToArrayBuffer(byteArray)], { type: mimeType.trim() || MIME_TYPE_OCTET_STREAM });\n\t} catch (error) {\n\t\tif (error instanceof Error) {\n\t\t\tthrow error;\n\t\t}\n\t\tthrow new Error(\"Некорректный формат бинарных данных\");\n\t}\n};\n\n/**\n * Определяет MIME-тип по имени файла, data URL или сигнатуре base64-данных.\n *\n * Если определить тип не удалось, используется PDF: большинство документов в этом контуре приходят именно так.\n */\nexport const detectMimeType = (fileName?: string, binaryData?: string): string => {\n\tif (fileName) {\n\t\tconst extension = fileName.toLowerCase().split(\".\").pop();\n\n\t\tif (extension && MIME_TYPE_BY_EXTENSION[extension]) {\n\t\t\treturn MIME_TYPE_BY_EXTENSION[extension];\n\t\t}\n\t}\n\n\tif (binaryData) {\n\t\tconst dataUrlMimeType = getDataUrlMimeType(binaryData);\n\n\t\tif (dataUrlMimeType) {\n\t\t\treturn dataUrlMimeType;\n\t\t}\n\n\t\tconst signatureMimeType = detectMimeTypeBySignature(binaryData);\n\n\t\tif (signatureMimeType) {\n\t\t\treturn signatureMimeType;\n\t\t}\n\t}\n\n\treturn MIME_TYPE_PDF;\n};\n","import { BASE64_DECODED_BLOCK_BYTES, BASE64_DOUBLE_PADDING, BASE64_ENCODED_BLOCK_CHARACTERS, BASE64_PADDING } from \"./base64Constants\";\n\nconst INVALID_BASE64_CHARACTER_PATTERN = /[^A-Za-z0-9+/=]/;\n\n/**\n * Проверяет стандартную Base64-форму и считает байты без декодирования или создания Buffer/Blob.\n * Невалидный payload возвращает null, пустая строка — 0; допустимый размер определяет вызывающий контракт.\n * Data URL, whitespace, URL-safe алфавит и отсутствующий padding здесь не нормализуются.\n */\nexport function getBase64DecodedSize(value: string): number | null {\n\tif (value.length % BASE64_ENCODED_BLOCK_CHARACTERS !== 0 || INVALID_BASE64_CHARACTER_PATTERN.test(value)) return null;\n\tconst paddingLength = value.endsWith(BASE64_DOUBLE_PADDING) ? 2 : value.endsWith(BASE64_PADDING) ? 1 : 0;\n\tconst firstPaddingIndex = value.indexOf(BASE64_PADDING);\n\tif (firstPaddingIndex !== -1 && firstPaddingIndex !== value.length - paddingLength) return null;\n\treturn Math.floor((value.length * BASE64_DECODED_BLOCK_BYTES) / BASE64_ENCODED_BLOCK_CHARACTERS) - paddingLength;\n}\n","import { useEffect, useEffectEvent, useState } from \"react\";\n\nimport { logError } from \"../utils\";\n\nimport { binaryToBlob, detectMimeType } from \"./binaryToBlob\";\n\nexport function useBinaryFile(bin: string | undefined) {\n\tconst [entry, setEntry] = useState<{ blob: Blob; mime: string } | null>(null);\n\tconst [error, setError] = useState<string | null>(null);\n\n\tconst updateEntry = useEffectEvent((data: { blob: Blob; mime: string } | null) => setEntry(data));\n\tconst updateError = useEffectEvent((error: string | null) => setError(error));\n\n\tuseEffect(() => {\n\t\tif (bin) {\n\t\t\ttry {\n\t\t\t\tconst mime = detectMimeType(undefined, bin);\n\t\t\t\tconst blob = binaryToBlob(bin, mime, true);\n\n\t\t\t\tupdateEntry({ blob, mime });\n\t\t\t\tupdateError(null);\n\t\t\t} catch (e) {\n\t\t\t\tlogError(e);\n\n\t\t\t\tupdateError(String(e));\n\t\t\t\tupdateEntry(null);\n\t\t\t}\n\t\t} else {\n\t\t\tupdateEntry(null);\n\t\t\tupdateError(null);\n\t\t}\n\t}, [bin]);\n\n\treturn { data: entry, error };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/binary/binaryToBlob.ts","../../src/binary/getBase64DecodedSize.ts","../../src/binary/useBinaryFile.ts"],"sourcesContent":["import {\n\tMIME_TYPE_DOCX,\n\tMIME_TYPE_IMAGE_GIF,\n\tMIME_TYPE_IMAGE_JPEG,\n\tMIME_TYPE_IMAGE_PNG,\n\tMIME_TYPE_IMAGE_TIFF,\n\tMIME_TYPE_IMAGE_WEBP,\n\tMIME_TYPE_MSWORD,\n\tMIME_TYPE_OCTET_STREAM,\n\tMIME_TYPE_PDF,\n\tMIME_TYPE_PPT,\n\tMIME_TYPE_PPTX,\n\tMIME_TYPE_XLS,\n\tMIME_TYPE_XLSX,\n\tMIME_TYPE_XML,\n\tMIME_TYPE_ZIP\n} from \"../file/mimeTypeConstants\";\nimport { hasSignatureBytes } from \"../file/signature/hasSignatureBytes\";\n\nimport { BASE64_ENCODED_BLOCK_CHARACTERS, BASE64_PADDING } from \"./base64Constants\";\n\nconst BASE64_DATA_URL_PATTERN = /^data:([^,]*);base64,([\\s\\S]*)$/i;\nconst BASE64_HEADER_SCAN_CHARACTERS = 128;\nconst XML_HEADER_SCAN_CHARACTERS = 16;\n// Здесь распознаётся только прежний короткий prefix, без усиления проверки формата.\nconst PDF_HEADER_PREFIX = [0x25, 0x50, 0x44, 0x46];\nconst JPEG_HEADER_PREFIX = [0xff, 0xd8, 0xff];\nconst PNG_HEADER_PREFIX = [0x89, 0x50, 0x4e, 0x47];\nconst GIF_HEADER_PREFIX = [0x47, 0x49, 0x46];\nconst WEBP_HEADER_PREFIX = [0x52, 0x49, 0x46, 0x46];\nconst ZIP_HEADER_PREFIX = [0x50, 0x4b];\n\nconst MIME_TYPE_BY_EXTENSION: Record<string, string> = {\n\tbmp: \"image/bmp\",\n\tcsv: \"text/csv\",\n\tdoc: MIME_TYPE_MSWORD,\n\tdocx: MIME_TYPE_DOCX,\n\tgif: MIME_TYPE_IMAGE_GIF,\n\tjpeg: MIME_TYPE_IMAGE_JPEG,\n\tjpg: MIME_TYPE_IMAGE_JPEG,\n\tjson: \"application/json\",\n\tpdf: MIME_TYPE_PDF,\n\tpng: MIME_TYPE_IMAGE_PNG,\n\tppt: MIME_TYPE_PPT,\n\tpptx: MIME_TYPE_PPTX,\n\tsvg: \"image/svg+xml\",\n\ttif: MIME_TYPE_IMAGE_TIFF,\n\ttiff: MIME_TYPE_IMAGE_TIFF,\n\ttxt: \"text/plain\",\n\twebp: MIME_TYPE_IMAGE_WEBP,\n\txls: MIME_TYPE_XLS,\n\txlsx: MIME_TYPE_XLSX,\n\txml: MIME_TYPE_XML,\n\tzip: MIME_TYPE_ZIP\n};\n\nconst normalizeBase64 = (value: string): string => {\n\tconst normalized = value.replace(/\\s/g, \"\").replace(/-/g, \"+\").replace(/_/g, \"/\");\n\tconst paddingRest = normalized.length % BASE64_ENCODED_BLOCK_CHARACTERS;\n\n\tif (paddingRest === 1 || !/^[A-Za-z0-9+/]*={0,2}$/.test(normalized)) {\n\t\tthrow new Error(\"Некорректный формат base64\");\n\t}\n\n\treturn paddingRest === 0 ? normalized : `${normalized}${BASE64_PADDING.repeat(BASE64_ENCODED_BLOCK_CHARACTERS - paddingRest)}`;\n};\n\nconst getBase64RawPayload = (binaryData: string): { data: string; mimeType: string | null } => {\n\tconst dataUrlMatch = binaryData.match(BASE64_DATA_URL_PATTERN);\n\n\tif (!dataUrlMatch) {\n\t\treturn { data: binaryData, mimeType: null };\n\t}\n\n\tconst [, rawMimeType, rawData] = dataUrlMatch;\n\tconst mimeType = rawMimeType.split(\";\")[0].trim();\n\n\treturn {\n\t\tdata: rawData,\n\t\tmimeType: mimeType || null\n\t};\n};\n\nconst getBase64Payload = (binaryData: string): { data: string; mimeType: string | null } => {\n\tconst payload = getBase64RawPayload(binaryData);\n\n\treturn {\n\t\tdata: normalizeBase64(payload.data),\n\t\tmimeType: payload.mimeType\n\t};\n};\n\nconst getDataUrlMimeType = (binaryData: string): string | null => {\n\tconst dataUrlMatch = binaryData.match(BASE64_DATA_URL_PATTERN);\n\n\tif (!dataUrlMatch) {\n\t\treturn null;\n\t}\n\n\tconst mimeType = dataUrlMatch[1].split(\";\")[0].trim();\n\n\treturn mimeType || null;\n};\n\nconst decodeBase64ToBytes = (binaryData: string): Uint8Array => {\n\tconst { data } = getBase64Payload(binaryData);\n\tconst binaryString = atob(data);\n\tconst byteArray = new Uint8Array(binaryString.length);\n\n\tfor (let index = 0; index < binaryString.length; index++) {\n\t\tbyteArray[index] = binaryString.charCodeAt(index);\n\t}\n\n\treturn byteArray;\n};\n\nconst binaryStringToBytes = (binaryData: string): Uint8Array => {\n\tconst byteArray = new Uint8Array(binaryData.length);\n\n\tfor (let index = 0; index < binaryData.length; index++) {\n\t\tbyteArray[index] = binaryData.charCodeAt(index);\n\t}\n\n\treturn byteArray;\n};\n\nconst copyBytesToArrayBuffer = (byteArray: Uint8Array): ArrayBuffer => {\n\tconst buffer = new ArrayBuffer(byteArray.byteLength);\n\tconst view = new Uint8Array(buffer);\n\n\tview.set(byteArray);\n\n\treturn buffer;\n};\n\nconst decodeHeaderFromBase64 = (binaryData: string): string | null => {\n\ttry {\n\t\tconst { data } = getBase64RawPayload(binaryData);\n\t\tlet headerData = \"\";\n\n\t\t// Пробелы и переносы допустимы в Base64 и не должны обрывать блок заголовка.\n\t\tfor (let index = 0; index < data.length && headerData.length < BASE64_HEADER_SCAN_CHARACTERS; index++) {\n\t\t\tconst character = data[index];\n\n\t\t\tif (!/\\s/.test(character)) {\n\t\t\t\theaderData += character;\n\t\t\t}\n\t\t}\n\n\t\tconst binaryString = atob(normalizeBase64(headerData));\n\n\t\treturn binaryString;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\nconst detectMimeTypeBySignature = (binaryData: string): string | null => {\n\tconst header = decodeHeaderFromBase64(binaryData);\n\n\tif (!header) {\n\t\treturn null;\n\t}\n\n\tconst bytes = new Uint8Array(header.length);\n\n\tfor (let index = 0; index < header.length; index++) {\n\t\tbytes[index] = header.charCodeAt(index);\n\t}\n\n\tif (hasSignatureBytes(bytes, PDF_HEADER_PREFIX)) {\n\t\treturn MIME_TYPE_PDF;\n\t}\n\n\tif (hasSignatureBytes(bytes, JPEG_HEADER_PREFIX)) {\n\t\treturn MIME_TYPE_IMAGE_JPEG;\n\t}\n\n\tif (hasSignatureBytes(bytes, PNG_HEADER_PREFIX)) {\n\t\treturn MIME_TYPE_IMAGE_PNG;\n\t}\n\n\tif (hasSignatureBytes(bytes, GIF_HEADER_PREFIX)) {\n\t\treturn MIME_TYPE_IMAGE_GIF;\n\t}\n\n\tif (hasSignatureBytes(bytes, WEBP_HEADER_PREFIX)) {\n\t\treturn MIME_TYPE_IMAGE_WEBP;\n\t}\n\n\tif (hasSignatureBytes(bytes, ZIP_HEADER_PREFIX)) {\n\t\treturn MIME_TYPE_ZIP;\n\t}\n\n\tconst textHeader = header\n\t\t.replace(/^\\uFEFF/, \"\")\n\t\t.trimStart()\n\t\t.slice(0, XML_HEADER_SCAN_CHARACTERS);\n\n\tif (textHeader.startsWith(\"<?xml\") || textHeader.startsWith(\"<\")) {\n\t\treturn MIME_TYPE_XML;\n\t}\n\n\treturn null;\n};\n\n/**\n * Преобразует base64 или бинарную строку в Blob.\n *\n * Data URL с префиксом `data:*;base64,` поддерживается, сам URL здесь не создаётся.\n */\nexport const binaryToBlob = (binaryData: string, mimeType: string = MIME_TYPE_OCTET_STREAM, isBase64 = true): Blob => {\n\tif (!binaryData) {\n\t\tthrow new Error(\"Не переданы бинарные данные\");\n\t}\n\n\ttry {\n\t\tconst byteArray = isBase64 ? decodeBase64ToBytes(binaryData) : binaryStringToBytes(binaryData);\n\n\t\tif (byteArray.length === 0) {\n\t\t\tthrow new Error(\"После преобразования не осталось данных\");\n\t\t}\n\n\t\treturn new Blob([copyBytesToArrayBuffer(byteArray)], { type: mimeType.trim() || MIME_TYPE_OCTET_STREAM });\n\t} catch (error) {\n\t\tif (error instanceof Error) {\n\t\t\tthrow error;\n\t\t}\n\t\tthrow new Error(\"Некорректный формат бинарных данных\");\n\t}\n};\n\n/**\n * Ищет MIME-тип по метаданным или сигнатуре без подстановки значения по умолчанию.\n */\nexport const findMimeType = (fileName?: string, binaryData?: string): string | null => {\n\tif (fileName) {\n\t\tconst extension = fileName.toLowerCase().split(\".\").pop();\n\n\t\tif (extension && MIME_TYPE_BY_EXTENSION[extension]) {\n\t\t\treturn MIME_TYPE_BY_EXTENSION[extension];\n\t\t}\n\t}\n\n\tif (binaryData) {\n\t\tconst dataUrlMimeType = getDataUrlMimeType(binaryData);\n\n\t\tif (dataUrlMimeType) {\n\t\t\treturn dataUrlMimeType;\n\t\t}\n\n\t\tconst signatureMimeType = detectMimeTypeBySignature(binaryData);\n\n\t\tif (signatureMimeType) {\n\t\t\treturn signatureMimeType;\n\t\t}\n\t}\n\n\treturn null;\n};\n\n/**\n * Определяет MIME-тип по имени файла, data URL или сигнатуре base64-данных.\n *\n * Если определить тип не удалось, используется PDF: большинство документов в этом контуре приходят именно так.\n */\nexport const detectMimeType = (fileName?: string, binaryData?: string): string => findMimeType(fileName, binaryData) ?? MIME_TYPE_PDF;\n","import { BASE64_DECODED_BLOCK_BYTES, BASE64_DOUBLE_PADDING, BASE64_ENCODED_BLOCK_CHARACTERS, BASE64_PADDING } from \"./base64Constants\";\n\nconst INVALID_BASE64_CHARACTER_PATTERN = /[^A-Za-z0-9+/=]/;\n\n/**\n * Проверяет стандартную Base64-форму и считает байты без декодирования или создания Buffer/Blob.\n * Невалидный payload возвращает null, пустая строка — 0; допустимый размер определяет вызывающий контракт.\n * Data URL, whitespace, URL-safe алфавит и отсутствующий padding здесь не нормализуются.\n */\nexport function getBase64DecodedSize(value: string): number | null {\n\tif (value.length % BASE64_ENCODED_BLOCK_CHARACTERS !== 0 || INVALID_BASE64_CHARACTER_PATTERN.test(value)) return null;\n\tconst paddingLength = value.endsWith(BASE64_DOUBLE_PADDING) ? 2 : value.endsWith(BASE64_PADDING) ? 1 : 0;\n\tconst firstPaddingIndex = value.indexOf(BASE64_PADDING);\n\tif (firstPaddingIndex !== -1 && firstPaddingIndex !== value.length - paddingLength) return null;\n\treturn Math.floor((value.length * BASE64_DECODED_BLOCK_BYTES) / BASE64_ENCODED_BLOCK_CHARACTERS) - paddingLength;\n}\n","import { useEffect, useMemo } from \"react\";\n\nimport { MIME_TYPE_OCTET_STREAM } from \"../file/mimeTypeConstants\";\nimport { logError } from \"../utils\";\n\nimport { binaryToBlob, findMimeType } from \"./binaryToBlob\";\n\ntype BinaryFileData = {\n\tblob: Blob;\n\tmime: string;\n\t/** MIME взят из data URL или определён по сигнатуре. Значение false обозначает подстановку нейтрального MIME. */\n\tisMimeDetected: boolean;\n};\n\nexport type UseBinaryFileResult = {\n\tdata: BinaryFileData | null;\n\terror: string | null;\n};\n\n/**\n * Декодирует текущий bin при рендере. Мемоизация позволяет просмотрщику\n * переиспользовать Blob при повторном рендере с тем же входом.\n */\nexport function useBinaryFile(bin: string | undefined): UseBinaryFileResult {\n\tconst { data, failure } = useMemo(() => {\n\t\tif (!bin) {\n\t\t\treturn { data: null, failure: null };\n\t\t}\n\n\t\ttry {\n\t\t\tconst detectedMime = findMimeType(undefined, bin);\n\t\t\tconst mime = detectedMime ?? MIME_TYPE_OCTET_STREAM;\n\t\t\tconst blob = binaryToBlob(bin, mime, true);\n\n\t\t\treturn { data: { blob, mime, isMimeDetected: detectedMime !== null }, failure: null };\n\t\t} catch (cause) {\n\t\t\treturn { data: null, failure: { cause, message: String(cause) } };\n\t\t}\n\t}, [bin]);\n\n\tuseEffect(() => {\n\t\tif (failure) {\n\t\t\tlogError(failure.cause);\n\t\t}\n\t}, [failure]);\n\n\treturn { data, error: failure?.message ?? null };\n}\n"],"mappings":"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"}
@@ -1,8 +1,17 @@
1
- export declare function useBinaryFile(bin: string | undefined): {
2
- data: {
3
- blob: Blob;
4
- mime: string;
5
- } | null;
1
+ type BinaryFileData = {
2
+ blob: Blob;
3
+ mime: string;
4
+ /** MIME взят из data URL или определён по сигнатуре. Значение false обозначает подстановку нейтрального MIME. */
5
+ isMimeDetected: boolean;
6
+ };
7
+ export type UseBinaryFileResult = {
8
+ data: BinaryFileData | null;
6
9
  error: string | null;
7
10
  };
11
+ /**
12
+ * Декодирует текущий bin при рендере. Мемоизация позволяет просмотрщику
13
+ * переиспользовать Blob при повторном рендере с тем же входом.
14
+ */
15
+ export declare function useBinaryFile(bin: string | undefined): UseBinaryFileResult;
16
+ export {};
8
17
  //# sourceMappingURL=useBinaryFile.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useBinaryFile.d.ts","sourceRoot":"","sources":["../../src/binary/useBinaryFile.ts"],"names":[],"mappings":"AAMA,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS;;cACT,IAAI;cAAQ,MAAM;;;EA2B7D"}
1
+ {"version":3,"file":"useBinaryFile.d.ts","sourceRoot":"","sources":["../../src/binary/useBinaryFile.ts"],"names":[],"mappings":"AAOA,KAAK,cAAc,GAAG;IACrB,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,iHAAiH;IACjH,cAAc,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;IAC5B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,mBAAmB,CAwB1E"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryuzaki13/react-foundation-lib",
3
- "version": "2.34.0",
3
+ "version": "2.35.0",
4
4
  "description": "Reusable React and TypeScript foundation utilities, hooks, and primitives.",
5
5
  "license": "MIT",
6
6
  "author": "Ryuzaki13",
@@ -4,7 +4,7 @@ import { Meta } from "@storybook/addon-docs/blocks";
4
4
 
5
5
  # Работа с бинарными данными
6
6
 
7
- Модуль `binary` преобразует строковое бинарное содержимое в браузерный `Blob`, определяет предполагаемый MIME-тип документа и предоставляет React hook для типового сценария «base64 → `{ blob, mime }`». Отдельная pure function `getBase64DecodedSize` проверяет стандартный Base64 payload и считает размер без декодирования в browser или server runtime.
7
+ Модуль `binary` преобразует строковое бинарное содержимое в браузерный `Blob`, определяет предполагаемый MIME-тип документа и предоставляет React hook, результат которого показывает, удалось ли определить MIME. Отдельная pure function `getBase64DecodedSize` проверяет стандартный Base64 payload и считает размер без декодирования в browser или server runtime.
8
8
 
9
9
  Основной сценарий:
10
10
 
@@ -278,7 +278,7 @@ const raw = String.fromCharCode(65, 66, 67);
278
278
  | Превратить base64 data URL в `Blob` | `binaryToBlob` |
279
279
  | Превратить raw binary string в `Blob` | `binaryToBlob(..., ..., false)` |
280
280
  | Предположить MIME по имени и данным | `detectMimeType` |
281
- | В React получить `{ blob, mime }` из base64 prop/state | `useBinaryFile` |
281
+ | В React получить `Blob` и результат определения MIME | `useBinaryFile` |
282
282
  | Скачать готовый `Blob` | `downloadFileFromBlob` из `/dom` |
283
283
  | Прочитать пользовательский `File` | `readFile` или `readImageFile` из `/file` |
284
284
  | Использовать известные filename и MIME | Вызвать `detectMimeType`/`binaryToBlob` вручную |
@@ -762,70 +762,41 @@ Fallback PDF является осознанным предметно-незав
762
762
  ### Сигнатура
763
763
 
764
764
  ```ts
765
- function useBinaryFile(bin: string | undefined): {
765
+ type UseBinaryFileResult = {
766
766
  data: {
767
767
  blob: Blob;
768
768
  mime: string;
769
+ isMimeDetected: boolean;
769
770
  } | null;
770
771
  error: string | null;
771
772
  };
772
- ```
773
-
774
- Hook принимает только base64/base64 data URL. Он всегда вызывает:
775
773
 
776
- ```ts
777
- const mime = detectMimeType(undefined, bin);
778
- const blob = binaryToBlob(bin, mime, true);
774
+ function useBinaryFile(bin: string | undefined): UseBinaryFileResult;
779
775
  ```
780
776
 
781
- Filename передать нельзя, raw binary mode использовать нельзя.
777
+ Hook принимает base64 или base64 data URL. Имя файла и MIME можно передать отдельно через `detectMimeType` и `binaryToBlob`.
782
778
 
783
779
  ### Возвращаемое состояние
784
780
 
785
- | Поле | Значение | Смысл |
786
- | ------- | ---------------- | ------------------------------------ |
787
- | `data` | `{ blob, mime }` | Декодирование завершилось успешно |
788
- | `data` | `null` | Нет результата либо произошла ошибка |
789
- | `error` | `string` | Строковое представление ошибки |
790
- | `error` | `null` | Ошибки нет |
791
-
792
- ### Lifecycle
793
-
794
- Преобразование выполняется после commit в React effect:
795
-
796
- 1. первый render возвращает `{ data: null, error: null }`;
797
- 2. после commit effect обрабатывает `bin`;
798
- 3. успех записывает `{ blob, mime }` и очищает ошибку;
799
- 4. ошибка очищает `data` и записывает `String(error)`;
800
- 5. `undefined` или пустая строка очищают оба поля.
801
-
802
- Пример последовательности:
803
-
804
- | `bin` | Состояние после effect |
805
- | --------------------- | ---------------------------------------------------------- |
806
- | `undefined` | `{ data: null, error: null }` |
807
- | `""` | `{ data: null, error: null }` |
808
- | Корректный base64 PDF | `{ data: { blob, mime: "application/pdf" }, error: null }` |
809
- | Некорректный base64 | `{ data: null, error: "Error: ..." }` |
810
- | Строка из пробелов | Ошибка после удаления пробелов |
811
-
812
- У hook нет отдельного `isLoading`/`isProcessing`. На первом render `data: null` и `error: null` может означать как отсутствие входа, так и ещё не выполненный effect.
813
-
814
- ### Смена `bin`
781
+ | Поле | Значение | Смысл |
782
+ | ------- | ------------------------------- | ------------------------------------ |
783
+ | `data` | `{ blob, mime, isMimeDetected }` | Декодирование завершилось успешно |
784
+ | `data` | `null` | Нет результата либо произошла ошибка |
785
+ | `error` | `string` | Строковое представление ошибки |
786
+ | `error` | `null` | Ошибки нет |
815
787
 
816
- При смене одного непустого `bin` на другой предыдущие `data`/`error` могут оставаться видимыми до выполнения следующего effect. Hook не очищает состояние синхронно во время render.
788
+ ### Результат при рендере
817
789
 
818
- Если нельзя кратковременно показать предыдущий документ:
790
+ Хук синхронно декодирует текущий `bin` при рендере. При смене `bin` результат сразу относится к новому входу. Мемоизация позволяет повторно использовать `Blob` при рендере с тем же `bin`.
819
791
 
820
- - разместите hook в компоненте, который remount-ится по стабильному ID документа;
821
- - или выполните преобразование на явной границе загрузки;
822
- - или добавьте внешний state machine с identity результата.
823
-
824
- Не используйте саму большую base64-строку как React `key`. Используйте безопасный стабильный ID документа:
825
-
826
- ```tsx
827
- <BinaryDocument key={document.id} bin={document.content} />
828
- ```
792
+ | `bin` | Результат |
793
+ | ---------------------------------- | ----------------------------------------------------------------------- |
794
+ | `undefined` | `{ data: null, error: null }` |
795
+ | `""` | `{ data: null, error: null }` |
796
+ | Корректный base64 PDF | `data.mime === "application/pdf"`, `data.isMimeDetected === true` |
797
+ | Корректный base64 неизвестного типа | `data.mime === "application/octet-stream"`, `data.isMimeDetected === false` |
798
+ | Некорректный base64 | `{ data: null, error: "Error: ..." }` |
799
+ | Строка из пробелов | Ошибка после удаления пробелов |
829
800
 
830
801
  ### Определение MIME внутри hook
831
802
 
@@ -833,27 +804,22 @@ Hook не знает filename. Приоритет будет таким:
833
804
 
834
805
  1. MIME из data URL;
835
806
  2. сигнатура;
836
- 3. fallback `application/pdf`.
807
+ 3. fallback `application/octet-stream`.
837
808
 
838
- Обычный base64 текста без data URL и известной сигнатуры станет `Blob` с `application/pdf`:
809
+ Проверяйте `data.isMimeDetected` перед выбором просмотрщика. Значение `true` означает, что MIME взят из data URL или определён по сигнатуре. Значение `false` означает, что hook подставил нейтральный MIME и сохранил декодированные байты.
839
810
 
840
811
  ```tsx
841
812
  const { data } = useBinaryFile("SGVsbG8=");
842
813
 
843
- // После effect:
844
- // data?.mime === "application/pdf"
814
+ // data?.mime === "application/octet-stream"
815
+ // data?.isMimeDetected === false
845
816
  ```
846
817
 
847
818
  Если известны имя или MIME, вызывайте `detectMimeType` и `binaryToBlob` вручную.
848
819
 
849
820
  ### Ошибки и logging
850
821
 
851
- При ошибке hook:
852
-
853
- 1. вызывает внутренний `logError(error)`;
854
- 2. `logError` пишет ошибку в `console.error`;
855
- 3. возвращает `error: String(error)`;
856
- 4. очищает `data`.
822
+ При ошибке хук возвращает `{ data: null, error: String(error) }` во время рендера. После commit эффект передаёт исходную ошибку в `logError`, который пишет её в `console.error`.
857
823
 
858
824
  Для обычного `Error` строка включает имя:
859
825
 
@@ -879,12 +845,12 @@ export function BinaryDocument(props: BinaryDocumentProps) {
879
845
  return <p role="alert">Не удалось подготовить документ</p>;
880
846
  }
881
847
 
882
- if (!props.bin) {
848
+ if (!data) {
883
849
  return <p>Документ не передан</p>;
884
850
  }
885
851
 
886
- if (!data) {
887
- return <p>Подготовка документа…</p>;
852
+ if (!data.isMimeDetected) {
853
+ return <p>Формат файла не распознан</p>;
888
854
  }
889
855
 
890
856
  return <DocumentViewer blob={data.blob} mime={data.mime} />;
@@ -936,7 +902,7 @@ Effect синхронизирует React с внешним browser API и си
936
902
  Не используйте `useBinaryFile`, если:
937
903
 
938
904
  - код выполняется вне React;
939
- - MIME уже известен и не равен PDF fallback;
905
+ - MIME передаётся отдельно от base64;
940
906
  - нужно учитывать filename;
941
907
  - данные являются raw binary string;
942
908
  - результат нужен немедленно в event handler;
@@ -1148,19 +1114,13 @@ const mime = detectMimeType(fileName, payload);
1148
1114
  const blob = binaryToBlob(payload, mime);
1149
1115
  ```
1150
1116
 
1151
- ### Hook определил текст как PDF
1152
-
1153
- Причина: `useBinaryFile` не получает filename, сигнатура текста неизвестна, поэтому применяется PDF fallback.
1154
-
1155
- Решения:
1117
+ ### Hook вернул `isMimeDetected: false`
1156
1118
 
1157
- - передать data URL с MIME;
1158
- - вызвать функции вручную с известным MIME;
1159
- - не использовать hook для этого контракта.
1119
+ Байты успешно декодированы. Их можно скачать как `application/octet-stream`. Если тип известен из метаданных, передайте его в data URL или вызовите `binaryToBlob` с этим MIME.
1160
1120
 
1161
1121
  ### MIME не совпал с содержимым
1162
1122
 
1163
- Проверьте приоритет:
1123
+ Проверьте порядок определения MIME в `detectMimeType`:
1164
1124
 
1165
1125
  1. filename;
1166
1126
  2. data URL;
@@ -1185,7 +1145,7 @@ data:<mime>,<percent-encoded-payload>
1185
1145
 
1186
1146
  ### Ошибка появляется в `console.error`
1187
1147
 
1188
- Это текущее поведение `useBinaryFile`: hook логирует пойманную ошибку перед записью `error` state. Для ручного `binaryToBlob` logging не выполняется.
1148
+ `useBinaryFile` логирует пойманную ошибку после commit. При прямом вызове `binaryToBlob` за журналирование отвечает вызывающий код.
1189
1149
 
1190
1150
  ### Object URL перестал работать
1191
1151
 
@@ -1260,7 +1220,7 @@ Base64 обычно примерно на треть больше исходно
1260
1220
 
1261
1221
  ### `detectMimeType` декодирует только начало
1262
1222
 
1263
- Signature detection декодирует ограниченное начало base64, поэтому дешевле полного декодирования. При этом распознавание data URL выполняется регулярным выражением над входной строкой и может просканировать весь payload. Последующий `binaryToBlob` в любом случае обрабатывает полный payload.
1223
+ Для поиска сигнатуры декодируются первые 128 непробельных символов Base64. Распознавание data URL выполняется регулярным выражением над входной строкой и может просканировать весь payload. Последующий `binaryToBlob` обрабатывает полный payload.
1264
1224
 
1265
1225
  ### Рекомендации
1266
1226
 
@@ -1344,8 +1304,8 @@ import { binaryToBlob, detectMimeType, useBinaryFile } from "@ryuzaki13/react-fo
1344
1304
  | `binaryToBlob` MIME | `application/octet-stream` |
1345
1305
  | `binaryToBlob` input mode | base64 |
1346
1306
  | `detectMimeType` fallback | `application/pdf` |
1347
- | `useBinaryFile` initial `data` | `null` |
1348
- | `useBinaryFile` initial `error` | `null` |
1307
+ | `useBinaryFile` fallback | `application/octet-stream` |
1308
+ | `useBinaryFile` без входа | `{ data: null, error: null }` |
1349
1309
  | `useBinaryFile` raw binary support | Нет |
1350
1310
  | Автоматическое создание object URL | Нет |
1351
1311