Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

如何实现浏览器内容保存为本地文件并持续静默保存最新编辑内容 #70

Open
Topppy opened this issue Jul 13, 2022 · 2 comments
Milestone

Comments

@Topppy
Copy link
Owner

Topppy commented Jul 13, 2022

背景:

最近发现drawio的一些功能很黑魔法。
比如首次创建一个文件,浏览器系统弹窗保存到本地,(到这为止都是常规保存文件的套路,很好实现)。
接下来,你在浏览器中继续绘制编辑你创建的图表,ctrol+S 保存之后,神奇的事情发生了,没有任何系统弹窗,最新的内容已经被静默地写入了首次创建的文件中。

这个行为是有点反直觉的,相当于在无感知的情况下,直接读写系统文件。浏览器一般不会给这么大的权限。

如何实现

如果有,也只能是浏览器提供的能力,于是,找到了这个 API: FileSystemWritableFileStream

https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream

兼容性很不好:

image

思路:

  1. 首次保存创造了一个FileSystemFileHandle对象:newHandle,提供对该读写文件的能力。
  2. 更新文件内容的时候,通过FileSystemFileHandle创造一个对该文件写入流 :writableStream。
  3. 使用writableStream.write 写入最新的文本Blob,(注意此处是覆盖式的)。
  4. 关闭流

代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>file save</title>
    <style>
      #addText {
        width: 400px;
        height: 200px;
      }
    </style>
  </head>
  <body>
    <h1>保存为本地文件并持续实时保存最新编辑的内容</h1>
    <h4>text to save:</h4>
    <div>
      <textarea id="addText" name="addText">hello</textarea>
    </div>
    <button onclick="saveFile()">1. start save</button>
    <button onclick="updateFile()">2. update</button>
    <h4>file contents:</h4>
    <p id="fileContent"></p>
    <script>
      // https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream
      let $textToAdd;
      let $fileContent;
      let newHandle;

      const pickerOpts = {
        types: [
          {
            description: "Text file",
            accept: {
              "text/plain": [".txt"],
            },
          },
        ],
        suggestedName: "testFile",
        excludeAcceptAllOption: true,
        multiple: false,
      };

      async function saveFile() {
        // create a new handle
        console.log("showSaveFilePicker");
        newHandle = await window.showSaveFilePicker(pickerOpts);
        console.log("createWritable");
        // create a FileSystemWritableFileStream to write to
        const writableStream = await newHandle.createWritable();

        updateFile();
      }

      async function getFileContents() {
        const fileData = await newHandle.getFile();
        const res = await fileData.text();
        console.log("fileText: ", res);
        $fileContent.innerText = res;
      }

      async function updateFileContent(text) {
        // create a FileSystemWritableFileStream to write to
        const writableStream = await newHandle.createWritable();

        const data = new Blob([text], { type: "text" });
        // write our file
        await writableStream.write(data);

        // close the file and write the contents to disk.
        await writableStream.close();
      }

      async function updateFile() {
        const text = $textToAdd.value;
        await updateFileContent(text);
        await getFileContents();
      }

      document.addEventListener("DOMContentLoaded", () => {
        $textToAdd = document.getElementById("addText");
        $fileContent = document.getElementById("fileContent");
      });
    </script>
  </body>
</html>
@Topppy
Copy link
Owner Author

Topppy commented Jul 13, 2022

发散一下:可以利用这个特性进行攻击吗?

比如前端通过js改写下载文件的内容。

可以继续研究一下这个API有什么安全限制。

@Topppy
Copy link
Owner Author

Topppy commented Jul 14, 2022

@Topppy Topppy added this to the publish milestone Oct 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant