forked from UdashFramework/udash-core
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduced a simple way to work with files inside frontend
The usecase of this code is something like this: let image that user is making a protonmail or any another application where content inside frontend (=browser storage) some secret user's data that shouldn't be exposed to backend. Secret key for example or anything like that. This code allows to developer to convert any `Array[Byte]` from frontend to URL as simple call `FileService.asURL`, create an anchor to download it as `FileService.asAnchor` and asynchronously convert any uploaded `File` to `Array[Byte]` as `FileService.asBytesArray`, or to `InputStream` via `FileService.asInputStream`. Unfortunately scalatags doesn't support `download` attribute and I need to make it by hand. I've opened a PR: com-lihaoyi/scalatags#212 to introduce it, but it might be a while until it is included to release. `FileService.asInputStream` is using `FileReaderSync` that is also missed inside scala-js-dom. I've opened a PR: scala-js/scala-js-dom#424 but it might be a while. Also, this is draft API but it is supported by majority of modern browsers: https://developer.mozilla.org/en-US/docs/Web/API/FileReaderSync#Browser_Compatibility
- Loading branch information
Showing
3 changed files
with
143 additions
and
2 deletions.
There are no files selected for viewing
115 changes: 115 additions & 0 deletions
115
core/.js/src/main/scala/io/udash/utils/FileService.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package io.udash.utils | ||
|
||
import java.io.{IOException, InputStream} | ||
|
||
import org.scalajs.dom._ | ||
import org.scalajs.dom.html.Anchor | ||
import org.scalajs.dom.raw.Blob | ||
import scalatags.JsDom | ||
|
||
import scala.scalajs.js | ||
import scala.concurrent.{Future, Promise} | ||
import scala.scalajs.js.annotation.JSGlobal | ||
import scala.scalajs.js.typedarray.ArrayBuffer | ||
import scala.util.Try | ||
|
||
@js.native | ||
@JSGlobal | ||
sealed class FileReaderSync() extends js.Object { | ||
def readAsArrayBuffer(blob: Blob): ArrayBuffer = js.native | ||
} | ||
|
||
sealed class FileBufferedInputStream(val file: File) extends InputStream { | ||
val fileReaderSync = new FileReaderSync() | ||
|
||
var filePos: Int = 0 | ||
var pos: Int = 0 | ||
|
||
var buffer: Array[Byte] = Array.empty | ||
|
||
override def read(): Int = { | ||
if (pos >= buffer.length) { | ||
import js.typedarray._ | ||
|
||
if (filePos >= file.size) { | ||
return -1 | ||
} | ||
|
||
val len = math.min(filePos + 1024, file.size.toInt) | ||
val slice = file.slice(filePos, len) | ||
buffer = new Int8Array(fileReaderSync.readAsArrayBuffer(slice)).toArray | ||
filePos += buffer.length | ||
pos = 0 | ||
} | ||
val r = buffer(pos).toInt & 0xff | ||
pos += 1 | ||
r | ||
} | ||
} | ||
|
||
object FileService { | ||
|
||
final val OctetStreamType = "application/octet-stream" | ||
|
||
/** | ||
* Converts specified bytes array to string that contains URL | ||
* that representing the array given in the parameter with optionally specified mime-type. | ||
* | ||
* Keep in mind that returned URL should be revoked via `org.scalajs.dom.revokeObjectURL(url)`. | ||
*/ | ||
def asURL(bytes: Array[Byte], mimeType: String = OctetStreamType): String = { | ||
import js.typedarray._ | ||
|
||
val jsBytes = js.Array[js.Any](bytes.toTypedArray) | ||
val blob = new Blob(jsBytes, BlobPropertyBag(mimeType)) | ||
URL.createObjectURL(blob) | ||
} | ||
|
||
/** | ||
* Create an anchor element that on click downloads byte array as a file with specified name. | ||
* | ||
* Keep in mind that anchor's href URL should be revoked via `org.scalajs.dom.revokeObjectURL(url)`. | ||
*/ | ||
def asAnchor(filename: String, bytes: Array[Byte], mimeType: String = OctetStreamType): JsDom.TypedTag[Anchor] = { | ||
import JsDom.all._ | ||
|
||
val download = attr("download") | ||
a(href := asURL(bytes, mimeType), download := filename) | ||
} | ||
|
||
/** | ||
* Asynchronously convert specified file to bytes array. | ||
*/ | ||
def asBytesArray(file: File): Future[Array[Byte]] = { | ||
import js.typedarray._ | ||
|
||
val fileReader = new FileReader() | ||
val promise = Promise[Array[Byte]]() | ||
|
||
fileReader.onerror = (e: Event) => | ||
promise.failure(new IOException(e.toString)) | ||
|
||
fileReader.onabort = (e: Event) => | ||
promise.failure(new IOException(e.toString)) | ||
|
||
fileReader.onload = (_: UIEvent) => | ||
promise.complete(Try( | ||
new Int8Array(fileReader.result.asInstanceOf[ArrayBuffer]).toArray | ||
)) | ||
|
||
fileReader.readAsArrayBuffer(file) | ||
|
||
promise.future | ||
} | ||
|
||
/** | ||
* Convert specified file to InputStream with blocking I/O | ||
* | ||
* Because it is using synchronous I/O that could potentially this API can be used only inside worker. | ||
* | ||
* This method is using FileReaderSync that is part of Working Draft File API. | ||
* Anyway it is supported for majority of modern browsers | ||
*/ | ||
def asInputStream(file: File): InputStream = | ||
new FileBufferedInputStream(file) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters