Skip to content
This repository has been archived by the owner on Mar 11, 2024. It is now read-only.

Commit

Permalink
feat: accept files in web share target (nolanlawson#1992)
Browse files Browse the repository at this point in the history
  • Loading branch information
nolanlawson committed Mar 14, 2021
1 parent 5e61a85 commit 5e7440a
Show file tree
Hide file tree
Showing 17 changed files with 122 additions and 162 deletions.
2 changes: 1 addition & 1 deletion bin/build-vercel-json.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const HTML_HEADERS = {
"frame-ancestors 'none'",
"object-src 'none'",
"manifest-src 'self'",
"form-action 'none'",
"form-action 'self'", // we need form-action for the Web Share Target API
"base-uri 'self'"
].join(';'),
'referrer-policy': 'no-referrer',
Expand Down
38 changes: 32 additions & 6 deletions src/routes/_actions/showShareDialogIfNecessary.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
import { store } from '../_store/store'
import { importShowComposeDialog } from '../_components/dialog/asyncDialogs/importShowComposeDialog.js'
import { importShowComposeDialog } from '../_components/dialog/asyncDialogs/importShowComposeDialog'
import { database } from '../_database/database'
import { doMediaUpload } from './media'

export async function showShareDialogIfNecessary () {
const { isUserLoggedIn, openShareDialog } = store.get()
store.set({ openShareDialog: false })
if (isUserLoggedIn && openShareDialog) {
const showComposeDialog = await importShowComposeDialog()
showComposeDialog()
const { isUserLoggedIn } = store.get()
if (!isUserLoggedIn) {
return
}
const data = await database.getWebShareData()
if (!data) {
return
}

// delete from IDB and import the dialog in parallel
const [showComposeDialog] = await Promise.all([
importShowComposeDialog(),
database.deleteWebShareData()
])

console.log('share data', data)
const { title, text, url, file } = data

// url is currently ignored on Android, but one can dream
// https://web.dev/web-share-target/#verifying-shared-content
const composeText = [title, text, url].filter(Boolean).join('\n\n')

store.clearComposeData('dialog')
store.setComposeData('dialog', { text: composeText })
store.save()

showComposeDialog()
if (file) { // start the upload once the dialog is in view so it shows the loading spinner and everything
/* no await */ doMediaUpload('dialog', file)
}
}
1 change: 1 addition & 0 deletions src/routes/_database/databaseApis.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from './timelines/deletion'
export { insertTimelineItems, insertStatus } from './timelines/insertion'
export * from './meta'
export * from './relationships'
export * from './webShare'
18 changes: 18 additions & 0 deletions src/routes/_database/webShare.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { get, set, close, del } from '../_thirdparty/idb-keyval/idb-keyval'
import { WEB_SHARE_TARGET_DATA_IDB_KEY } from '../_static/share'

export function deleteWebShareData () {
return del(WEB_SHARE_TARGET_DATA_IDB_KEY)
}

export function setWebShareData (data) {
return set(WEB_SHARE_TARGET_DATA_IDB_KEY, data)
}

export function getWebShareData () {
return get(WEB_SHARE_TARGET_DATA_IDB_KEY)
}

export function closeKeyValIDBConnection () {
return close()
}
1 change: 0 additions & 1 deletion src/routes/_pages/search.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ <h1>{intl.search}</h1>
<a href="/favorites">{intl.favorites}</a>
<a href="/direct">{intl.directMessages}</a>
<a href="/bookmarks">{intl.bookmarks}</a>
<a href="/share">{intl.shareStatus}</a>
</div>
{/if}
<style>
Expand Down
1 change: 1 addition & 0 deletions src/routes/_static/share.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const WEB_SHARE_TARGET_DATA_IDB_KEY = 'web-share-data'
3 changes: 1 addition & 2 deletions src/routes/_store/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ const nonPersistedState = {
sensitivesShown: {},
spoilersShown: {},
statusModifications: {},
verifyCredentials: {},
openShareDialog: false
verifyCredentials: {}
}

const state = Object.assign({}, persistedState, nonPersistedState)
Expand Down
7 changes: 0 additions & 7 deletions src/routes/_utils/decodeURIComponentWithPluses.js

This file was deleted.

4 changes: 2 additions & 2 deletions src/routes/_utils/lifecycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
import { importPageLifecycle } from './asyncModules/importPageLifecycle.js'

function addEventListener (event, func) {
if (process.browser) {
if (process.browser && !process.env.IS_SERVICE_WORKER) {
importPageLifecycle().then(lifecycle => {
lifecycle.addEventListener(event, func)
})
}
}

function removeEventListener (event, func) {
if (process.browser) {
if (process.browser && !process.env.IS_SERVICE_WORKER) {
importPageLifecycle().then(lifecycle => {
lifecycle.removeEventListener(event, func)
})
Expand Down
29 changes: 0 additions & 29 deletions src/routes/share.html

This file was deleted.

13 changes: 13 additions & 0 deletions src/service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
routes as __routes__
} from '../__sapper__/service-worker.js'
import { get, post } from './routes/_utils/ajax'
import { setWebShareData, closeKeyValIDBConnection } from './routes/_database/webShare'

const timestamp = process.env.SAPPER_TIMESTAMP
const ASSETS = `assets_${timestamp}`
Expand Down Expand Up @@ -104,6 +105,18 @@ self.addEventListener('fetch', event => {
const sameOrigin = url.origin === self.origin

if (sameOrigin) {
if (req.method === 'POST' && url.pathname === '/share') {
// handle Web Share Target requests (see manifest.json)
const formData = await req.formData()
const title = formData.get('title')
const text = formData.get('text')
const url = formData.get('url')
const file = formData.get('file')
await setWebShareData({ title, text, url, file })
await closeKeyValIDBConnection() // don't need to keep the IDB connection open
return Response.redirect('/', 303) // 303 recommended by https://web.dev/web-share-target/
}

// always serve webpack-generated resources and
// static from the cache if possible
const response = await caches.match(req)
Expand Down
17 changes: 13 additions & 4 deletions static/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,22 @@
"start_url": "/?pwa=true",
"share_target": {
"action": "/share",
"method": "GET",
"enctype": "application/x-www-form-urlencoded",
"url_template": "/share?title={title}&text={text}&url={url}",
"method": "POST",
"enctype": "multipart/form-data",
"params": {
"title": "title",
"text": "text",
"url": "url"
"url": "url",
"files": [
{
"name": "file",
"accept": [
"audio/*",
"image/*",
"video/*"
]
}
]
}
},
"icons": [
Expand Down
58 changes: 0 additions & 58 deletions tests/spec/027-share-target.js

This file was deleted.

Loading

0 comments on commit 5e7440a

Please sign in to comment.