본문으로 바로가기

paste 이벤트와 클립보드 검사는 다른거다!

 

paste 이벤트에서 읽어온 클립보드 데이터 (pasteEvent.clipboardData)

 

 - 웹에서 저장한 이미지의 이름은 무조건 name: "image.png"임. 크로 브라우저 기준으로, 이미지를 클립보드에 복사하면 이름은 무조건 image.png, MIME type도 image/png임.

- 반면 로컬에서 복사한 이미지들은 이름 그대로 저장함.

 

특정 이벤트와 상관없이 클립보드에서 읽어온 데이터 (navigator.clipboard)

 

- 마찬가지로 웹에서 복사한 이미지는 image/png를 반환합니다. 단, paste 이벤트와 다른 점은  text/html과 image/png를 같이 반환한다는 것입니다. 

- 자신의 로컬 환경에 있는 이미지 복사 => (text/html 형식으로 파일의 이름을 반환) 혹은 아무 것도 출력하지 않음. 파일 이름을 반환하는 점에서 착안해서 확장자를 정규식으로 발라내서 이미지인지 파악할 수 있겠지만 너무 위험함. 게다가 확장자가 없는 경우도 있음.

 

 

결론적으로

 

paste 이벤트와 관련없이 클립보드 정보를 확인하기 위해서는 유저의 권한을 받아야 하며, 받았다고 하더라도 접근할 수 있는 정보가 비교적 한정적임. 반면 paste 이벤트의 경우 유저가 명확한 의도를 가지고 클립보드의 정보를 붙여넣기 하겠다는 것이므로 클립보드 내부의 구체적인 정보를 확인할 수 있고, 조작할 수도 있음.

 

 

paste 이벤트에서 단순히 text 가져오기

 - window.clipboardData.getData()는 최신 브라우저에서 보안상 이제 안됨. clipboard 읽는데 유저 permission이 필요함.

function handlePasteEvent (event) {
    const clipboardData, pastedData;

    // window.clipboardData는 IE 때 사용하던 거라던데 안 쓰려구요.
    clipboardData = event.clipboardData;
    pastedData = clipboardData.getData('Text'); // 
    
    console.log(pastedData);
}

document.addEventListener('paste', handlePasteEvent);

 

 

clipbard에 있는 내용을 가져오기

dnd api에서만 사용하는 줄 알았던 dataTransfer를 사용하게 되었다. paste event가 넘겨준 녀석을 File 객체로 만들기 위해서...

    const handleClipboardPaste = async event => {
      event.preventDefault()

      const pasteData = event.clipboardData

      for (const dataTransferItem of pasteData.items) {
        if (dataTransferItem.type.match('^image/')) {
          const file = dataTransferItem.getAsFile()
          const maxFileSize = mb2byte(MAX_FILE_MB)

          if (file.size > maxFileSize) {
            return alert(`Only files under ${MAX_FILE_MB}mb can be uploaded.`)
          }

          const dataTransfer = new DataTransfer()
          dataTransfer.items.add(file)
          const fileList = dataTransfer.files

          await addResourceCards({
            type: 'file',
            data: { files: fileList, folder: focusedItem }
          })
        }

        if (dataTransferItem.type.match('^text/plain')) {
          dataTransferItem.getAsString(async str => {
            if (isValidUrl(str)) {
              await addResourceCards({
                type: 'url',
                data: { url: str, folder: focusedItem }
              })
            }
          })
        }
      }
    }

 

특정 내용을 클립보드에 저장하기

  const copyToClipboard = blob => {
    if (blob) {
      const { ClipboardItem } = window
      const clipboardItem = new ClipboardItem({ [blob.type]: blob })
      navigator.clipboard.write([clipboardItem])
    }
  }

  const convertToPngAndCopyToClipboard = imgBlob => {
    const imageUrl = window.URL.createObjectURL(imgBlob)
    const canvas = document.createElement('canvas')
    const ctx = canvas.getContext('2d')

    if (ctx) {
      const imageEl = document.createElement('img')
      imageEl.src = imageUrl

      imageEl.onload = ({ target }) => {
        const { width, height } = target

        canvas.width = width
        canvas.height = height
        ctx.drawImage(target, 0, 0, width, height)
        canvas.toBlob(copyToClipboard, 'image/png', 1)
      }
    }
  }

  const handleCopyURL = () => {
    setIsClicked(true)
    navigator.clipboard.writeText(data.data.url)
    toolTipTimer()
  }

  const handleCopyImage = async () => {
    try {
      const response = await fetch(data.signedUrl)
      const blob = await response.blob()
      if (blob.type === 'image/png') {
        copyToClipboard(blob)
      } else {
        convertToPngAndCopyToClipboard(blob)
      }
    } catch (e) {
      console.error(e)
    }
    setIsClicked(true)
    toolTipTimer()
  }

 

export const isImageMimeType = mimeType => {
  const filterImageMimeTypeList = [
    'image/apng',
    'image/avif',
    'image/gif',
    'image/jpeg',
    'image/png',
    'image/svg+xml',
    'image/webp',
    'image/bmp',
    'image/tiff'
  ]
  const filterMimeType = filterImageMimeTypeList.includes(mimeType)
    ? mimeType
    : null
  return isSupportedCopyToClipboardFromMimeType(filterMimeType)
}

const isSupportedCopyToClipboardFromMimeType = mimeType => {
  const notSupportedImageMimeTypeList = [
    'image/avif',
    'image/bmp',
    'image/tiff'
  ]
  return notSupportedImageMimeTypeList.includes(mimeType) ? null : mimeType
}

 

그 외 자잘한 정보 및 reference

 

clipboard api : https://developer.mozilla.org/ko/docs/Web/API/Clipboard_API

clipboard 객체 :  https://developer.mozilla.org/ko/docs/Web/API/Clipboard

const clipboard = navigator.clipboard // clipboard 객체 반환

 

clipboardItem : https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem

단순히 blob을 곧바로 clipboard에 넣는 것이 아니라, clipboardItem으로 변환 후 넣어야 합니다.

const { ClipboardItem } = window
const clipboardItem = new ClipboardItem({ [blob.type]: blob })
navigator.clipboard.write([clipboardItem])

 

blob과 file.

 

blob : https://developer.mozilla.org/ko/docs/Web/API/Blob

file: https://developer.mozilla.org/ko/docs/Web/API/File

 

 


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체