Skip to content

Commit

Permalink
Merge pull request #38 from zjkmxy/safari
Browse files Browse the repository at this point in the history
Support iPad Safari (IndexedDB ponyfill)
  • Loading branch information
zjkmxy authored Nov 30, 2023
2 parents 4cf0c46 + ce48605 commit a40421b
Show file tree
Hide file tree
Showing 15 changed files with 108 additions and 52 deletions.
6 changes: 6 additions & 0 deletions fix-sw.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
importFile=$(grep -Po '(?<=await import\()(.+)\"' dist/sw.js)
echo "Captured ${importFile}"
ESCAPED_REPLACE=$(printf '%s\n' "$importFile" | sed -e 's/[\/&]/\\&/g')
sed -i -e '1s/^/import { FileSystemWritableFileStream } from '${ESCAPED_REPLACE}';\n/' \
-e 's/.*await import.*/\/\//' \
-e 's/return new t(await this\[ee\]\.createWritable(e));/return new FileSystemWritableFileStream(await this\[ee\]\.createWritable(e));/' dist/sw.js
40 changes: 21 additions & 19 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/ndn.svg" />
<link rel="apple-touch-icon" href="/ndn_app.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#ce93d8" />
<link href="/font/roboto-v30.css" rel="stylesheet" />
<link href="/font/roboto-mono-v23.css" rel="stylesheet" />
<title>NDN Workspace</title>
<meta
name="description"
content="NDN Workspace for collaborative paper writing and documents"
/>
<script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script>
</head>

<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/ndn.svg" />
<link rel="apple-touch-icon" href="/ndn_app.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#ce93d8" />
<link href="/font/roboto-v30.css" rel="stylesheet" />
<link href="/font/roboto-mono-v23.css" rel="stylesheet" />
<title>NDN Workspace</title>
<meta name="description" content="NDN Workspace for collaborative paper writing and documents">
</head>

<body>
<div id="root" style="padding: 0; margin: 0;"></div>
<script type="module" src="/src/index.tsx"></script>
</body>

</html>
<body>
<div id="root" style="padding: 0; margin: 0"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build": "tsc && vite build && ./fix-sw.bash",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"peer-server": "peerjs --port 8000 --key peerjs --path /aincraft --allow_discovery true",
Expand Down Expand Up @@ -50,6 +50,7 @@
"diff": "^5.1.0",
"event-iterator": "^2.0.0",
"eventemitter3": "^5.0.1",
"file-system-access": "^1.0.4",
"jszip": "^3.10.1",
"peerjs": "^1.5.1",
"qr-scanner": "^1.4.2",
Expand Down
18 changes: 18 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions public/registerSW.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker.register("/sw.js", { scope: "/", type: "module" });
});
}
4 changes: 2 additions & 2 deletions src/backend/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { FsStorage, InMemoryStorage, type Storage } from "./storage"
import { SyncAgent } from './sync-agent'
import { Certificate, ECDSA, createSigner } from "@ndn/keychain"
import { v4 as uuidv4 } from "uuid"
import { base64ToBytes, encodeKey as encodePath, Signal as BackendSignal } from "../utils"
import { base64ToBytes, encodeKey as encodePath, Signal as BackendSignal, openRoot } from "../utils"
import { Decoder } from "@ndn/tlv"

export const UseAutoAnnouncement = false
Expand Down Expand Up @@ -208,7 +208,7 @@ export async function bootstrapWorkspace(opts: {
if (opts.inMemory) {
persistStore = new InMemoryStorage()
} else {
const handle = await navigator.storage.getDirectory()
const handle = await openRoot()
const subFolder = await handle.getDirectoryHandle(encodePath(nodeId.toString()), { create: true })
persistStore = new FsStorage(subFolder)
}
Expand Down
3 changes: 2 additions & 1 deletion src/backend/models/connections.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { openRoot } from "../../utils"
import { TypedModel } from "./typed-models"

export type ConfigBase = {
Expand Down Expand Up @@ -50,7 +51,7 @@ export const storageFolder = 'connections'
export const connections = new TypedModel<Config>('connections', getName)

export async function initDefault() {
const rootHandle = await navigator.storage.getDirectory()
const rootHandle = await openRoot()
try {
await rootHandle.getDirectoryHandle(storageFolder)
return
Expand Down
14 changes: 7 additions & 7 deletions src/backend/models/typed-models.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { encodeKey as encodePath } from "../../utils"
import { encodeKey as encodePath, openRoot } from "../../utils"

export class TypedModel<T> {
constructor(
Expand All @@ -7,7 +7,7 @@ export class TypedModel<T> {
) { }

async save(object: T) {
const rootHandle = await navigator.storage.getDirectory()
const rootHandle = await openRoot()
const connections = await rootHandle.getDirectoryHandle(this.storageFolder, { create: true })
const fileHandle = await connections.getFileHandle(encodePath(this.getName(object)), { create: true })
const textFile = await fileHandle.createWritable()
Expand All @@ -16,7 +16,7 @@ export class TypedModel<T> {
}

async remove(connName: string) {
const rootHandle = await navigator.storage.getDirectory()
const rootHandle = await openRoot()
const objects = await rootHandle.getDirectoryHandle(this.storageFolder, { create: true })
try {
await objects.removeEntry(encodePath(connName), { recursive: true })
Expand All @@ -27,7 +27,7 @@ export class TypedModel<T> {
}

async isExisting(connName: string) {
const rootHandle = await navigator.storage.getDirectory()
const rootHandle = await openRoot()
const objects = await rootHandle.getDirectoryHandle(this.storageFolder, { create: true })
try {
await objects.getFileHandle(encodePath(connName), { create: false })
Expand All @@ -38,7 +38,7 @@ export class TypedModel<T> {
}

async load(connName: string) {
const rootHandle = await navigator.storage.getDirectory()
const rootHandle = await openRoot()
const objects = await rootHandle.getDirectoryHandle(this.storageFolder, { create: true })
try {
const file = await objects.getFileHandle(encodePath(connName), { create: false })
Expand All @@ -52,12 +52,12 @@ export class TypedModel<T> {
}

async loadAll() {
const rootHandle = await navigator.storage.getDirectory()
const rootHandle = await openRoot()

const objects = await rootHandle.getDirectoryHandle(this.storageFolder, { create: true })
const ret: Array<T> = []
for await (const [, handle] of objects.entries()) {
if (handle instanceof FileSystemFileHandle) {
if (handle.kind === 'file') {
const jsonFile = await handle.getFile()
const jsonText = await jsonFile.text()
const object = JSON.parse(jsonText) as T
Expand Down
35 changes: 22 additions & 13 deletions src/components/connect/ndn-testbed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,30 @@ export default function NdnTestbed(props: {
setTempFace(nfdWsFace)
}

// Request profile
const caProfile = await ndncert.retrieveCaProfile({
caCertFullName: TestbedAnchorName,
})
// Probe step
const probeRes = await ndncert.requestProbe({
profile: caProfile,
parameters: { email: new TextEncoder().encode(curEmail) },
})
if (probeRes.entries.length <= 0) {
console.error('No available name to register')
return
let caProfile: ndncert.CaProfile | undefined = undefined
let caFullName = TestbedAnchorName
let probeRes
while (caProfile === undefined) {
// Request profile
caProfile = await ndncert.retrieveCaProfile({
caCertFullName: caFullName,
})
// Probe step
probeRes = await ndncert.requestProbe({
profile: caProfile,
parameters: { email: new TextEncoder().encode(curEmail) },
})
if (probeRes.entries.length <= 0) {
console.error('No available name to register')
return
}
if (probeRes.redirects.length > 0) {
caFullName = probeRes.redirects[0].caCertFullName
caProfile = undefined
}
}
// Generate key pair
const myPrefix = probeRes.entries[0].prefix
const myPrefix = probeRes!.entries[0].prefix
const keyName = keychain.CertNaming.makeKeyName(myPrefix)
const algo = keychain.ECDSA
const gen = await keychain.ECDSA.cryptoGenerate({}, true)
Expand Down
4 changes: 2 additions & 2 deletions src/components/oauth-test/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default function OauthTest() {
access_type: 'offline',
}).toString()
const url = 'https://accounts.google.com/o/oauth2/v2/auth?' + queryStr
window.open(url)
window.open(url) // TODO: not working on Safari
}


Expand All @@ -48,7 +48,7 @@ export default function OauthTest() {
state: requestId(),
}).toString()
const url = 'https://github.com/login/oauth/authorize?' + queryStr
window.open(url)
window.open(url) // TODO: not working on Safari
}


Expand Down
8 changes: 4 additions & 4 deletions src/components/share-latex/share-latex/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export default function ShareLatex(props: {
const content = await zip.generateAsync({ type: "uint8array" })
const file = new Blob([content], { type: 'application/zip;base64' })
const fileUrl = URL.createObjectURL(file)
window.open(fileUrl)
window.open(fileUrl) // TODO: not working on Safari
}

const [texEngine, setTexEngine] = createSignal<PdfTeXEngine>()
Expand Down Expand Up @@ -202,7 +202,7 @@ export default function ShareLatex(props: {
// URL.revokeObjectURL(previewUrl()!);
// setPreviewUrl(URL.createObjectURL(blob))
const fileUrl = URL.createObjectURL(blob)
window.open(fileUrl)
window.open(fileUrl) // TODO: not working on Safari
}

const onCompileRemote = async () => {
Expand Down Expand Up @@ -242,7 +242,7 @@ export default function ShareLatex(props: {
const pdfContent = await segObj.fetch(`/ndn/workspace-compiler/result/${reqId}`)
const file = new Blob([pdfContent], { type: 'application/pdf;base64' })
const fileUrl = URL.createObjectURL(file)
window.open(fileUrl)
window.open(fileUrl) // TODO: not working on Safari
}
}

Expand Down Expand Up @@ -295,7 +295,7 @@ export default function ShareLatex(props: {
if (blob !== undefined) {
const file = new Blob([blob], { type: 'application/octet-stream;base64' })
const fileUrl = URL.createObjectURL(file)
window.open(fileUrl)
window.open(fileUrl) // TODO: not working on Safari
}
} catch (e) {
console.error(`Unable to fetch blob file: `, e)
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './reset'
export * from './solid-assist'
export * from './callcc'
export * from './signals'
export * from './opfs-ponyfill'
13 changes: 13 additions & 0 deletions src/utils/opfs-ponyfill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { getOriginPrivateDirectory } from 'file-system-access'
import indexedDbAdapter from 'file-system-access/lib/adapters/indexeddb'

export const openRoot: () => Promise<FileSystemDirectoryHandle> = (() => {
if (FileSystemFileHandle.prototype.createWritable !== undefined) {
// Normal browsers
return getOriginPrivateDirectory
} else {
// Weird Safari
console.log('Safari ponyfill applied')
return () => getOriginPrivateDirectory(indexedDbAdapter)
}
})()
5 changes: 2 additions & 3 deletions src/workers/sw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { clientsClaim } from 'workbox-core'
import * as navigationPreload from 'workbox-navigation-preload'
import { registerRoute, NavigationRoute } from 'workbox-routing'
import { DefaultTexliveEndpoint } from '../constants'
import { encodeKey } from '../utils'
import { encodeKey, openRoot } from '../utils'

declare let self: ServiceWorkerGlobalScope;

Expand Down Expand Up @@ -43,8 +43,7 @@ registerRoute(/\/stored\/.*/, async (options) => {
newUrl = originalUrl;
}


const opfsRoot = await navigator.storage.getDirectory();
const opfsRoot = await openRoot();
const stored = await opfsRoot.getDirectoryHandle('stored', { create: true });
const hashStr = encodeKey(newUrl.toString());
try {
Expand Down
1 change: 1 addition & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default defineConfig({
srcDir: 'src/workers',
filename: 'sw.ts',
registerType: 'autoUpdate',
injectRegister: null,
devOptions: {
enabled: true // SW and devtools adds > 1 sec to loading time. Enable only when nesessary.
},
Expand Down

0 comments on commit a40421b

Please sign in to comment.