Add web polyfills

This commit is contained in:
Paul Frazee 2023-01-26 12:36:27 -06:00
parent d6ec627c8c
commit 751dfb20fd
18 changed files with 240 additions and 105 deletions

View file

@ -2,7 +2,7 @@ import {autorun} from 'mobx'
import {Platform} from 'react-native'
import {sessionClient as AtpApi, SessionServiceClient} from '@atproto/api'
import {RootStoreModel} from './models/root-store'
import * as libapi from './lib/api'
import * as apiPolyfill from './lib/api-polyfill'
import * as storage from './lib/storage'
export const LOCAL_DEV_SERVICE =
@ -17,7 +17,7 @@ export async function setupState(serviceUri = DEFAULT_SERVICE) {
let rootStore: RootStoreModel
let data: any
libapi.doPolyfill()
apiPolyfill.doPolyfill()
const api = AtpApi.service(serviceUri) as SessionServiceClient
rootStore = new RootStoreModel(api)

View file

@ -0,0 +1,76 @@
import {sessionClient as AtpApi} from '@atproto/api'
export function doPolyfill() {
AtpApi.xrpc.fetch = fetchHandler
}
interface FetchHandlerResponse {
status: number
headers: Record<string, string>
body: ArrayBuffer | undefined
}
async function fetchHandler(
reqUri: string,
reqMethod: string,
reqHeaders: Record<string, string>,
reqBody: any,
): Promise<FetchHandlerResponse> {
const reqMimeType = reqHeaders['Content-Type'] || reqHeaders['content-type']
if (reqMimeType && reqMimeType.startsWith('application/json')) {
reqBody = JSON.stringify(reqBody)
} else if (
typeof reqBody === 'string' &&
(reqBody.startsWith('/') || reqBody.startsWith('file:'))
) {
if (reqBody.endsWith('.jpeg') || reqBody.endsWith('.jpg')) {
// HACK
// React native has a bug that inflates the size of jpegs on upload
// we get around that by renaming the file ext to .bin
// see https://github.com/facebook/react-native/issues/27099
// -prf
const newPath = reqBody.replace(/\.jpe?g$/, '.bin')
await RNFS.moveFile(reqBody, newPath)
reqBody = newPath
}
// NOTE
// React native treats bodies with {uri: string} as file uploads to pull from cache
// -prf
reqBody = {uri: reqBody}
}
const controller = new AbortController()
const to = setTimeout(() => controller.abort(), TIMEOUT)
const res = await fetch(reqUri, {
method: reqMethod,
headers: reqHeaders,
body: reqBody,
signal: controller.signal,
})
const resStatus = res.status
const resHeaders: Record<string, string> = {}
res.headers.forEach((value: string, key: string) => {
resHeaders[key] = value
})
const resMimeType = resHeaders['Content-Type'] || resHeaders['content-type']
let resBody
if (resMimeType) {
if (resMimeType.startsWith('application/json')) {
resBody = await res.json()
} else if (resMimeType.startsWith('text/')) {
resBody = await res.text()
} else {
throw new Error('TODO: non-textual response body')
}
}
clearTimeout(to)
return {
status: resStatus,
headers: resHeaders,
body: resBody,
}
}

View file

@ -0,0 +1,4 @@
export function doPolyfill() {
// TODO needed? native fetch may work fine -prf
// AtpApi.xrpc.fetch = fetchHandler
}

View file

@ -19,10 +19,6 @@ import {Image} from '../../lib/images'
const TIMEOUT = 10e3 // 10s
export function doPolyfill() {
AtpApi.xrpc.fetch = fetchHandler
}
export interface ExternalEmbedDraft {
uri: string
isLoading: boolean
@ -199,74 +195,3 @@ export async function unfollow(store: RootStoreModel, followUri: string) {
rkey: followUrip.rkey,
})
}
interface FetchHandlerResponse {
status: number
headers: Record<string, string>
body: ArrayBuffer | undefined
}
async function fetchHandler(
reqUri: string,
reqMethod: string,
reqHeaders: Record<string, string>,
reqBody: any,
): Promise<FetchHandlerResponse> {
const reqMimeType = reqHeaders['Content-Type'] || reqHeaders['content-type']
if (reqMimeType && reqMimeType.startsWith('application/json')) {
reqBody = JSON.stringify(reqBody)
} else if (
typeof reqBody === 'string' &&
(reqBody.startsWith('/') || reqBody.startsWith('file:'))
) {
if (reqBody.endsWith('.jpeg') || reqBody.endsWith('.jpg')) {
// HACK
// React native has a bug that inflates the size of jpegs on upload
// we get around that by renaming the file ext to .bin
// see https://github.com/facebook/react-native/issues/27099
// -prf
const newPath = reqBody.replace(/\.jpe?g$/, '.bin')
await RNFS.moveFile(reqBody, newPath)
reqBody = newPath
}
// NOTE
// React native treats bodies with {uri: string} as file uploads to pull from cache
// -prf
reqBody = {uri: reqBody}
}
const controller = new AbortController()
const to = setTimeout(() => controller.abort(), TIMEOUT)
const res = await fetch(reqUri, {
method: reqMethod,
headers: reqHeaders,
body: reqBody,
signal: controller.signal,
})
const resStatus = res.status
const resHeaders: Record<string, string> = {}
res.headers.forEach((value: string, key: string) => {
resHeaders[key] = value
})
const resMimeType = resHeaders['Content-Type'] || resHeaders['content-type']
let resBody
if (resMimeType) {
if (resMimeType.startsWith('application/json')) {
resBody = await res.json()
} else if (resMimeType.startsWith('text/')) {
resBody = await res.text()
} else {
throw new Error('TODO: non-textual response body')
}
}
clearTimeout(to)
return {
status: resStatus,
headers: resHeaders,
body: resBody,
}
}

View file

@ -0,0 +1,18 @@
import BackgroundFetch, {
BackgroundFetchStatus,
} from 'react-native-background-fetch'
export function configure(
handler: (taskId: string) => Promise<void>,
timeoutHandler: (taskId: string) => Promise<void>,
): Promise<BackgroundFetchStatus> {
return BackgroundFetch.configure(
{minimumFetchInterval: 15},
handler,
timeoutHandler,
)
}
export function finish(taskId: string) {
return BackgroundFetch.finish(taskId)
}

View file

@ -0,0 +1,13 @@
type BackgroundFetchStatus = 0 | 1 | 2
export async function configure(
_handler: (taskId: string) => Promise<void>,
_timeoutHandler: (taskId: string) => Promise<void>,
): Promise<BackgroundFetchStatus> {
// TODO
return 0
}
export function finish(_taskId: string) {
// TODO
}

View file

@ -1,5 +1,5 @@
import {makeAutoObservable, runInAction} from 'mobx'
import {Image as PickedImage} from 'react-native-image-crop-picker'
import {Image as PickedImage} from '../../view/com/util/images/ImageCropPicker'
import {
AppBskyActorGetProfile as GetProfile,
AppBskyActorProfile as Profile,

View file

@ -6,7 +6,7 @@ import {makeAutoObservable} from 'mobx'
import {sessionClient as AtpApi, SessionServiceClient} from '@atproto/api'
import {createContext, useContext} from 'react'
import {DeviceEventEmitter, EmitterSubscription} from 'react-native'
import BackgroundFetch from 'react-native-background-fetch'
import * as BgScheduler from '../lib/bg-scheduler'
import {isObj, hasProp} from '../lib/type-guards'
import {LogModel} from './log'
import {SessionModel} from './session'
@ -124,8 +124,7 @@ export class RootStoreModel {
// background fetch runs every 15 minutes *at most* and will get slowed down
// based on some heuristics run by iOS, meaning it is not a reliable form of delivery
// -prf
BackgroundFetch.configure(
{minimumFetchInterval: 15},
BgScheduler.configure(
this.onBgFetch.bind(this),
this.onBgFetchTimeout.bind(this),
).then(status => {
@ -138,12 +137,12 @@ export class RootStoreModel {
if (this.session.hasSession) {
await this.me.bgFetchNotifications()
}
BackgroundFetch.finish(taskId)
BgScheduler.finish(taskId)
}
onBgFetchTimeout(taskId: string) {
this.log.debug(`Background fetch timed out for task ${taskId}`)
BackgroundFetch.finish(taskId)
BgScheduler.finish(taskId)
}
}