2022年9月

假设你要把 www.baidu.com 301 到 baidu.com,需要以下2步操作:

1. page rules里

  • URL填写:www.nestjs.gay/*,开头不需要要http
  • Pick a Setting选择forwarding URL
  • destination URL填写 https://baidu.com/$1,填/$1的目的是为了保留转发的路径不变

2. DNS解析那里

  • 将www.baidu.com cname到www.cloudflare.com,并且勾选Proxied,当然也可以用A解析到某个cloudflare的ip上,没这个解析的话,301无法生效,解析到非cloudflare ip也无效,这就是坑

const whiteListSuffixs = [
  '*', // allow all domain suffix
]
async function handleRequest(request) {
  const urlReq = new URL(request?.url)
  let url = urlReq.href?.replace(urlReq.origin+'/', '').trim()
  if (!url) {
    return new Response(`error code 1, usage: ${urlReq.origin}/http(s)://api.example.com`, {status: 400})
  }
  if (0!==url.indexOf('https://') && 0===url.indexOf('https:')) {
    url = url.replace('https:/', 'https://')
  } else if (0!==url.indexOf('http://') && 0===url.indexOf('http:')) {
    url = url.replace('http:/', 'http://')
  }
  let urlTarget
  try {
    urlTarget = new URL(url)
  } catch(e) {
    return new Response(`error code 2, usage: ${urlReq.origin}/http(s)://api.example.com`, {status: 400})
  }
  if (urlTarget?.port) {
    return new Response('port is not allowed', {status: 400})
  }
  const hostname = urlTarget?.hostname
  if (!hostname) {
    return new Response(`error code 3, usage: ${urlReq.origin}/http(s)://api.example.com`, {status: 400})
  }
  let inWhiteList = false
  for (const suffix of whiteListSuffixs) {
    if (hostname.indexOf(suffix) > -1 || '*'===suffix.trim()) {
      inWhiteList = true
      break
    }
  }
  if (!inWhiteList) {
    return new Response( `${urlTarget.hostname} not in white list`, {status: 403} )
  }
  const response = await fetch(url, {
    headers: request.headers,
    body: request.body,
    method: request.method
  })
  let respHeaders = {}
  response.headers.forEach((value, key)=>respHeaders[key] = value)
  if ('*'!==respHeaders['Access-Control-Allow-Origin']?.trim() && '*'!==respHeaders['access-control-allow-origin']?.trim()) {
    respHeaders['Access-Control-Allow-Origin'] = '*'
  }
  return new Response( await response.body , {
    headers: respHeaders,
    status: response.status
  })
}
addEventListener('fetch', event => {
  return event.respondWith(handleRequest(event.request))
})

已知Bug:如果你反代带端口的url,例如http(s)://example.com:3000/path?query=string,它会自动去除端口再请求,变成请求http(s)://example.com/path?query=string

安卓

安卓很简单,用以下代码即可:

<WebView 
  source={{ uri: '包含支付宝的网页,例如有赞手机网页商城' }} 
  originWhitelist={["*"]} // default only http and https
  sharedCookiesEnabled={true}
  onShouldStartLoadWithRequest={navState => {
    if (0===navState.url.indexOf('alipays://') || 0===navState.url.indexOf('alipay://')) {
      Linking.openURL(navState.url)
      return false
    }
    return true // if not alipay scheme, just open
  }}
/>

其中安卓scheme是alipays://,iOS是alipay://

iOS/iPadOS

iOS的话,无法直接在Expo Go上测试支付宝支付,需要更改配置编译.ipa安装包
https://docs.expo.dev/guides/linking/#opening-links-to-other-apps
在./app.json里

"expo": {
  ... ...
  "ios": {
    "supportsTablet": true,
    "infoPlist": {
      "LSApplicationQueriesSchemes": ["alipay", "alipays"]
    },
    "bundleIdentifier": "com.yourdomain.yourproject"
  },
  ... ...
}

编译.ipa安装包并在iOS/iPadOS设备上安装运行的方式:https://medium.com/nerd-for-tech/abbde4086d08
备份文章:https://nftstorage.link/ipfs/bafybeidpjbd7ye5vfxa6vhsrjssfsk5qw6ib2kmnkkbe6lqd3qdrcakpym
上文用的是windows系统,如果你是Mac系统,USB连接iPhone后,可以在Finder左边栏里找到iPhone设备的界面,将.ipa安装包拖到这个界面即可安装到手机

./src/components/NoticeDialog/NoticeDialog.tsx

import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Skeleton, Typography } from "@mui/material";
export type NoticeDialogType = {open: boolean, title: string, content: string}
export function NoticeDialog(props: NoticeDialogType & {close: Function}) {
  return (
    <Dialog
        open={props.open||false}
        aria-labelledby="alert-dialog-title"
        aria-describedby="alert-dialog-description"
        fullWidth
      >
      <DialogTitle id="alert-dialog-title">
        {props.title}
      </DialogTitle>
      <DialogContent>
        <DialogContentText id="alert-dialog-description">
          {props.content}
        </DialogContentText>
      </DialogContent>
      <DialogActions>
        <Button onClick={()=>props.close()}>Close</Button>
      </DialogActions>
    </Dialog>
  )
}

./src/examples/NoticeDialogExample.tsx

import { Button } from "@mui/material"
import { Box } from "@mui/system"
import { useState } from "react"
import { NoticeDialog, NoticeDialogType } from "../../components/NoticeDialog/NoticeDialog" // STEP 1

export default function NoticeDialogExample() {
  const [dialog, setDialog] = useState<NoticeDialogType>() // STEP 2

  // STEP 3
  function openDialog(content: string, title='' ) {
    setDialog({open: true, content, title})
  }

  return (
    <Box className='flex items-center justify-center h-screen'>

      {/* Usage */}
      <Button onClick={()=>openDialog('Dialog content, title can be empty', 'Title')}>Show Dilog</Button>

      {/* STEP 4 */}
      <NoticeDialog title={dialog?.title||''} content={dialog?.content||''} open={dialog?.open||false} close={()=>{
        if (dialog?.open) {
          setDialog({...dialog, open: false})
        }
      }} />
    </Box>
  )
}