-
Notifications
You must be signed in to change notification settings - Fork 309
/
fileContentLoader.ts
135 lines (122 loc) · 5.23 KB
/
fileContentLoader.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/**
* Retrieves the specified file from the app's data store.
* @param {String} path - the path to the file to read
* @returns {Promise} that resolves to the raw data in the file
* or rejects with an error
*/
export
/** @ignore */
type PutFileContent = string | Buffer | ArrayBufferView | ArrayBufferLike | Blob
/** @ignore */
export class FileContentLoader {
readonly content: Buffer | Blob
readonly wasString: boolean
readonly contentType: string
readonly contentByteLength: number
private loadedData?: Promise<Buffer>
static readonly supportedTypesMsg = 'Supported types are: `string` (to be UTF8 encoded), '
+ '`Buffer`, `Blob`, `File`, `ArrayBuffer`, `UInt8Array` or any other typed array buffer. '
constructor(content: PutFileContent, contentType: string) {
this.wasString = typeof content === 'string'
this.content = FileContentLoader.normalizeContentDataType(content, contentType)
this.contentType = contentType || this.detectContentType()
this.contentByteLength = this.detectContentLength()
}
private static normalizeContentDataType(content: PutFileContent,
contentType: string): Buffer | Blob {
try {
if (typeof content === 'string') {
// If a charset is specified it must be either utf8 or ascii, otherwise the encoded content
// length cannot be reliably detected. If no charset specified it will be treated as utf8.
const charset = (contentType || '').toLowerCase().replace('-', '')
if (charset.includes('charset') && !charset.includes('charset=utf8') && !charset.includes('charset=ascii')) {
throw new Error(`Unable to determine byte length with charset: ${contentType}`)
}
if (typeof TextEncoder !== 'undefined') {
const encodedString = new TextEncoder().encode(content)
return Buffer.from(encodedString.buffer)
}
return Buffer.from(content)
} else if (Buffer.isBuffer(content)) {
return content
} else if (ArrayBuffer.isView(content)) {
return Buffer.from(content.buffer, content.byteOffset, content.byteLength)
} else if (typeof Blob !== 'undefined' && content instanceof Blob) {
return content
} else if (typeof ArrayBuffer !== 'undefined' && content instanceof ArrayBuffer) {
return Buffer.from(content)
} else if (Array.isArray(content)) {
// Provided with a regular number `Array` -- this is either an (old) method
// of representing an octet array, or a dev error. Perform basic check for octet array.
if (content.length > 0
&& (!Number.isInteger(content[0]) || content[0] < 0 || content[0] > 255)) {
throw new Error(`Unexpected array values provided as file data: value "${content[0]}" at index 0 is not an octet number. ${this.supportedTypesMsg}`)
}
return Buffer.from(content)
} else {
const typeName = Object.prototype.toString.call(content)
throw new Error(`Unexpected type provided as file data: ${typeName}. ${this.supportedTypesMsg}`)
}
} catch (error) {
console.error(error)
throw new Error(`Error processing data: ${error}`)
}
}
private detectContentType(): string {
if (this.wasString) {
return 'text/plain; charset=utf-8'
} else if (typeof Blob !== 'undefined' && this.content instanceof Blob && this.content.type) {
return this.content.type
} else {
return 'application/octet-stream'
}
}
private detectContentLength(): number {
if (ArrayBuffer.isView(this.content) || Buffer.isBuffer(this.content)) {
return this.content.byteLength
} else if (typeof Blob !== 'undefined' && this.content instanceof Blob) {
return this.content.size
}
const typeName = Object.prototype.toString.call(this.content)
const error = new Error(`Unexpected type "${typeName}" while detecting content length`)
console.error(error)
throw error
}
private async loadContent(): Promise<Buffer> {
try {
if (Buffer.isBuffer(this.content)) {
return this.content
} else if (ArrayBuffer.isView(this.content)) {
return Buffer.from(this.content.buffer, this.content.byteOffset, this.content.byteLength)
} else if (typeof Blob !== 'undefined' && this.content instanceof Blob) {
const reader = new FileReader()
const readPromise = new Promise<Buffer>((resolve, reject) => {
reader.onerror = (err) => {
reject(err)
}
reader.onload = () => {
const arrayBuffer = reader.result as ArrayBuffer
resolve(Buffer.from(arrayBuffer))
}
reader.readAsArrayBuffer(this.content as Blob)
})
const result = await readPromise
return result
} else {
const typeName = Object.prototype.toString.call(this.content)
throw new Error(`Unexpected type ${typeName}`)
}
} catch (error) {
console.error(error)
const loadContentError = new Error(`Error loading content: ${error}`)
console.error(loadContentError)
throw loadContentError
}
}
load(): Promise<Buffer | string> {
if (this.loadedData === undefined) {
this.loadedData = this.loadContent()
}
return this.loadedData
}
}