[개발일지] 02. 쉐어 익스텐션에서 탭탭 익스텐션 데이터를 추출하는 방법

2026. 1. 6. 23:07·탭탭 - TapTap/개발일지
728x90

 탭탭은 사파리 익스텐션과 쉐어 익스텐션에서 생성되는 데이터를 하나의 단일 도메인 모델로 보고, 단일 DB에서 관리하는 구조로 재설계했습니다. 이번 글에서는 이러한 구조에서 사파리 익스텐션 데이터를 쉐어 익스텐션으로 안전하게 전달하는 방법에 대해 살펴보겠습니다.

 

기존 쉐어 익스텐션의 데이터 추출 방식의 한계

 기존 쉐어 익스텐션은 NSExtensionContext를 통해 전달되는 item에 접근하여 URL, 이미지 등 시스템에서 제공하는 데이터를 추출하는 방식이 일반적입니다. 이 방식은 기본적인 컨텐츠를 수집하는 데는 충분하지만, 사파리 익스텐션에서 생성된 하이라이팅이나 코멘트와 같은 커스텀 데이터는 함께 수집할 수 없다는 한계가 있습니다. 따라서 탭탭에서는 이러한 데이터를 포함해 하나의 흐름으로 통합하여 수집할 방법을 고민하게 되었습니다.

 

정답은 가이드 문서에 있었다

  동시에 데이터를 추출할 수 있는 방법에 대해 고민을 정말 많이 했습니다. 스택 오버플로우를 찾아보고, AI도 활용해보았지만, 워낙 예외적인 케이스라 관련 자료가 거의 없었습니다. 포기하려다 마지막으로 가이드 문서를 뒤져보았고, 오래된 가이드 문서에서야 해법을 찾을 수 있었습니다.

 

iOS, macOS. Also supported in a Share extension. Specifies the name of a JavaScript file supplied by a Share extension. If you provide a JavaScript file, Safari runs the functions in the file when your Share extension starts and stops. You might want to include a JavaScript file in your Share extension if you want to get information from a webpage to display in your extension, or update a webpage when your extension completes its task.

iOS, macOS. 공유 확장 기능에서도 지원됩니다. Share 확장자에서 제공하는 JavaScript 파일의 이름을 지정합니다. JavaScript 파일을 제공하면, Safari는 Share 확장자가 시작되고 종료될 때 파일 내 함수들을 실행합니다. 웹페이지에서 정보를 얻어 확장 프로그램에 표시하거나, 확장 기능이 완료되면 웹페이지를 업데이트하려면 공유 확장 프로그램에 자바스크립트 파일을 포함하는 것이 좋습니다.

애플 가이드 문서 - App Extension Keys

 NSExtensionJavaScriptPreprocessingFile 문서를 자세히 살펴보면, "웹페이지에서 정보를 얻어 확장 프로그램에 표시한다"라는 문구가 있었습니다. '표시'라는 표현이 다소 애매했지만, 가능성이 있다는 점에 주목하여 NSExtensionJavaScriptPreprocessingFile를 깊게 조사했습니다. 결국, 여러 정보를 바탕으로 사파리 익스텐션에서 생성된 데이터와 쉐어 익스텐션 데이터를 한 번에 추출하는 것을 구현할 수 있었습니다.

 

직접 추출해보자

...

function(arguments) {
    const draftSpans = document.querySelectorAll('.highlighted-text[data-draft-id]');
    
    const drafts = Array.from(draftSpans).map(span => {
      return {
        id: span.dataset.draftId,
        sentence: span.textContent,
        type: span.dataset.highlightType,
        comments: JSON.parse(span.dataset.comments || '[]'),
        url: window.location.href,
        isDraft: true
      };
    });
    
    const result = {
      "title": document.title,
      "url": document.URL,
      "drafts": drafts,
      "imageURL": this.extractThumbnailImage(),
      "mediaCompany": this.extractMediaCompany()
    };
    
    arguments.completionFunction(result);
  },
  
  ...

 일단, 추출을 위한 자바스크립트 코드가 필요합니다. 탭탭에서는 사파리 스토리지에 하이라이팅 초안을 저장하고 있습니다. 간단하게 초안에서 데이터를 추출하는 코드를 작성하였습니다.

 


 작성한 자바스크립트 코드를 사용하기 위해서는 쉐어 익스텐션의 Info.plist 수정이 필요합니다. NSExtensionAttributes 속성에 NSExtensionJavaScriptPreprocessingFile 키를 추가하고, 타입을 String으로 설정한 뒤, 추출에 사용할 자바스크립트 파일 이름을 값으로 넣어주면 됩니다. 이렇게 설정하면 쉐어 익스텐션이 실행될 때 자동으로 자바스크립트 파일이 로드되어, 사파리 익스텐션에서 생성된 데이터를 한 번에 추출할 수 있습니다.

func extractAllData() {
    guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem,
          let attachments = extensionItem.attachments else {
      self.closeExtension(clearDrafts: false)
      return
    }
    
    let propertyListIdentifier = UTType.propertyList.identifier
    let urlIdentifier = UTType.url.identifier
    
    var propertyListFound = false
    
    for itemProvider in attachments {
      if itemProvider.hasItemConformingToTypeIdentifier(propertyListIdentifier) {
        propertyListFound = true
        itemProvider.loadItem(forTypeIdentifier: propertyListIdentifier, options: nil) { [weak self] (item, error) in
          DispatchQueue.main.async {
            guard let self = self,
                  let dictionary = item as? [String: Any],
                  let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as? [String: Any] else {
              return
            }
            
            if let title = results["title"] as? String, let url = results["url"] as? String {
              self.pageTitle = title
              self.pageURL = url
            }
            
            if let imageURL = results["imageURL"] as? String {
              self.pageImageURL = imageURL
            }
            
            if let mediaCompany = results["mediaCompany"] as? String {
              self.pageMediaCompany = mediaCompany
            }
            
            if let drafts = results["drafts"] as? [[String: Any]] {
              self.draftHighlights = drafts
            }
            self.checkIfURLExistsAndConfigure()
          }
        }
        break
      }
    }
    
    if !propertyListFound {
      for itemProvider in attachments {
        if itemProvider.hasItemConformingToTypeIdentifier(urlIdentifier) {
          itemProvider.loadItem(forTypeIdentifier: urlIdentifier, options: nil) { [weak self] (item, error) in
            guard let self = self, let url = item as? URL else {
              self?.closeExtension(clearDrafts: false)
              return
            }
            DispatchQueue.main.async {
              if self.pageURL.isEmpty {
                self.pageURL = url.absoluteString
                self.pageTitle = url.host ?? ""
                self.draftHighlights = []
                self.checkIfURLExistsAndConfigure()
              }
            }
          }
          break
        }
      }
    }
  }

 

 마지막으로, 쉐어 익스텐션 컨트롤러에서는 extensionContext에 접근해 데이터를 처리하면 됩니다... 만! 몇 가지 확인해야 할 점이 있습니다.

 

let propertyListIdentifier = UTType.propertyList.identifier
itemProvider.hasItemConformingToTypeIdentifier(propertyListIdentifier)

 사파리 익스텐션에서 자바 스크립트로 생성한 데이터를 한 번에 가져오기 위해 Property List 형태 즉, 딕셔너리 형태를 사용합니다. 이렇게 하면 URL, 제목, 이미지, 하이라이팅, 코멘트 등 다양한 필드를 하나의 구조로 안전하게 전달받을 수 있습니다. 

 

이 과정에서 주의할 점은 아래와 같습니다.

 

1. 타입확인

-> 모든 attachment가 Propery List를 제공하는 것은 아니므로, hasItemConformingToTypeIdentifier()로 먼저 타입을 체크해야 안전합니다.

 

2. 메인 스레드에서 UI 업데이트

-> loadItem()은 비동기 호출이므로, 데이터를 가져온 뒤 UI나 DB에 반영할 때는 반드시 메인 스레드에서 처리해야 합니다.

 

3. Fallback 처리

-> Property List가 없을 경우, fallback 처리를 통해 최소한의 데이터를 확보할 수 있도록 구현해야 합니다. 탭탭은 기존처럼 URL 타입으로 fallback하도록 구현하였습니다.

 

 

'탭탭 - TapTap > 개발일지' 카테고리의 다른 글

[개발일지] 03. 신기하고 재밌는 탭탭 온보딩! 어떻게 구현했을까요?  (0) 2026.01.14
[개발일지] 01. TapTap Extension이 NativeApp과 통신하는 방법  (0) 2026.01.06
'탭탭 - TapTap/개발일지' 카테고리의 다른 글
  • [개발일지] 03. 신기하고 재밌는 탭탭 온보딩! 어떻게 구현했을까요?
  • [개발일지] 01. TapTap Extension이 NativeApp과 통신하는 방법
여성일
여성일
  • 여성일
    성일노트
    여성일
  • 전체
    오늘
    어제
    • 분류 전체보기 N
      • 탭탭 - TapTap
        • 리팩토링
        • 트러블슈팅
        • 개발일지
      • 애플 디벨로퍼 아카데미
        • 챌린지 회고
        • 하루의 날씨
      • Swift Student Challenge 202..
      • AI를 잘쓰는 개발자가 될래요
      • 우리 같이 협업하자
      • ToyProject - 사카마카 (살까말까 고민 ..
      • ToyProject - Book2OnNon (모바..
      • ToyProject - 바꿔조 (환율 계산기)
      • iOS N
        • iOS
        • Vapor
        • Design Pattern
        • CoreData
        • Tuist
        • RxSwift
        • ReactorKit
        • TCA N
      • Swift
        • Swift 기본기
        • UIkit
        • SwiftUI
      • 원티드 프리온보딩 챌린지 iOS 과정
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.6
    여성일
    [개발일지] 02. 쉐어 익스텐션에서 탭탭 익스텐션 데이터를 추출하는 방법
    상단으로

    티스토리툴바