sanity-plugin-media
Version:
This version of `sanity-plugin-media` is for Sanity Studio V3.
97 lines (88 loc) • 3 kB
text/typescript
// Sourced from:
// https://github.com/sanity-io/sanity/blob/ccb777e115a8cdf20d81a9a2bc9d8c228568faff/packages/%40sanity/form-builder/src/sanity/inputs/client-adapters/assets.ts
import type {SanityAssetDocument, SanityClient, SanityImageAssetDocument} from '@sanity/client'
import type {HttpError} from '../types'
import {Observable, of, throwError} from 'rxjs'
import {map, mergeMap} from 'rxjs/operators'
import {withMaxConcurrency} from './withMaxConcurrency'
const fetchExisting$ = (client: SanityClient, type: string, hash: string) => {
return client.observable.fetch('*[_type == $documentType && sha1hash == $hash][0]', {
documentType: type,
hash
})
}
const readFile$ = (file: File): Observable<ArrayBuffer> => {
return new Observable(subscriber => {
const reader = new FileReader()
reader.onload = () => {
subscriber.next(reader.result as ArrayBuffer)
subscriber.complete()
}
reader.onerror = err => {
subscriber.error(err)
}
reader.readAsArrayBuffer(file)
return () => {
reader.abort()
}
})
}
const hexFromBuffer = (buffer: ArrayBuffer): string => {
return Array.prototype.map
.call(new Uint8Array(buffer), x => `00${x.toString(16)}`.slice(-2))
.join('')
}
export const hashFile$ = (file: File): Observable<string> => {
if (!window.crypto || !window.crypto.subtle || !window.FileReader) {
return throwError({
message: 'Unable to generate hash: uploads are only allowed in secure contexts',
statusCode: 500
})
}
return readFile$(file).pipe(
mergeMap(arrayBuffer => window.crypto.subtle.digest('SHA-1', arrayBuffer)),
map(hexFromBuffer)
)
}
const uploadSanityAsset$ = (
client: SanityClient,
assetType: 'file' | 'image',
file: File,
hash: string
) => {
return of(null).pipe(
// NOTE: the sanity api will still dedupe unique files, but this saves us from uploading the asset file entirely
mergeMap(() => fetchExisting$(client, `sanity.${assetType}Asset`, hash)),
// Cancel if the asset already exists
mergeMap((existingAsset: SanityAssetDocument | SanityImageAssetDocument | null) => {
if (existingAsset) {
return throwError({
message: 'Asset already exists',
statusCode: 409
} as HttpError)
}
return of(null)
}),
mergeMap(() => {
// Begin upload if no existing asset found
return client.observable.assets
.upload(assetType, file, {
extract: ['blurhash', 'exif', 'location', 'lqip', 'palette'],
preserveFilename: true
})
.pipe(
map(event =>
event.type === 'response'
? {
// rewrite to a 'complete' event
asset: event.body.document,
id: event.body.document._id,
type: 'complete'
}
: event
)
)
})
)
}
export const uploadAsset$ = withMaxConcurrency(uploadSanityAsset$)