웹 앱은 도달범위가 넓습니다. 여러 플랫폼에서 실행됩니다. 링크를 통해 쉽게 공유할 수 있습니다. 하지만 전통적으로는 운영체제와 통합되지 않았습니다. 얼마 전까지만 해도 설치할 수조차 없었습니다. 다행히 이제는 이 통합을 활용하여 PWA에 유용한 기능을 추가할 수 있습니다. 몇 가지 옵션을 살펴보겠습니다.
파일 시스템 작업
파일을 사용하는 일반적인 사용자 워크플로는 다음과 같습니다.
- 기기에서 파일 또는 폴더를 선택하여 직접 엽니다.
- 해당 파일 또는 폴더를 변경하고 변경사항을 직접 저장합니다.
- 새 파일 및 폴더를 만듭니다.
File System Access API 이전에는 웹 앱에서 이 작업을 할 수 없었습니다. 파일을 열려면 파일을 업로드해야 했고, 변경사항을 저장하려면 사용자가 파일을 다운로드해야 했으며, 웹은 사용자의 파일 시스템에 새 파일과 폴더를 만들 수 있는 액세스 권한이 전혀 없었습니다.
파일 열기
파일을 열려면 window.showOpenFilePicker()
메서드를 사용합니다. 이 메서드에는 버튼 클릭과 같은 사용자 동작이 필요합니다. 파일을 여는 나머지 설정은 다음과 같습니다.
- 파일 시스템 액세스의 파일 선택기 API에서 파일 핸들을 캡처합니다. 그러면 파일에 관한 기본 정보가 표시됩니다.
- 핸들의
getFile()
메서드를 사용하면 파일에 관한 추가 읽기 전용 속성 (예: 이름, 마지막 수정 날짜)이 포함된File
라는 특수한 종류의Blob
를 가져올 수 있습니다. Blob이므로text()
와 같은 Blob 메서드를 호출하여 콘텐츠를 가져올 수 있습니다.
// Have the user select a file.
const [ handle ] = await window.showOpenFilePicker();
// Get the File object from the handle.
const file = await handle.getFile();
// Get the file content.
// Also available, slice(), stream(), arrayBuffer()
const content = await file.text();
변경사항 저장 중
파일에 변경사항을 저장하려면 사용자 동작도 필요합니다. 그런 다음 다음 단계를 따르세요.
- 파일 핸들을 사용하여
FileSystemWritableFileStream
를 만듭니다. - 스트림을 변경합니다. 이렇게 하면 파일이 업데이트되지는 않습니다. 대신 임시 파일이 생성됩니다.
- 마지막으로 변경을 완료하면 스트림을 닫습니다. 그러면 변경사항이 임시에서 영구로 전환됩니다.
코드에서 이를 확인해 보겠습니다.
// Make a writable stream from the handle.
const writable = await handle.createWritable();
// Write the contents of the file to the stream.
await writable.write(contents);
// Close the file and write the contents to disk.
await writable.close();
파일 처리
File System Access API를 사용하면 앱 내에서 파일을 열 수 있지만 그 반대의 경우는 어떨까요? 사용자가 파일을 열 때 사용할 기본 앱으로 좋아하는 앱을 설정하려 합니다. 파일 처리 API는 설치된 PWA가 다음을 실행할 수 있는 실험용 API입니다. 사용자 기기에서 파일 핸들러로 등록하여 웹 앱 매니페스트에서 PWA가 지원하는 MIME 유형 및 파일 확장자를 지정합니다. 지원되는 확장 프로그램에 맞춤 파일 아이콘을 지정할 수 있습니다.
등록되면 설치된 PWA가 사용자의 파일 시스템에 옵션으로 표시되므로 사용자가 파일을 직접 열 수 있습니다. 다음은 PWA가 텍스트 파일을 읽기 위한 매니페스트 설정의 예입니다.
...
"file_handlers": [
{
"action": "/open-file",
"accept": {
"text/*": [".txt"]
}
}
]
...
URL 처리
URL을 처리하면 PWA가 운영체제에서 범위의 일부인 링크를 캡처하고 기본 브라우저 탭 대신 PWA 창 내에 렌더링할 수 있습니다. 예를 들어 PWA로 연결되는 메시지를 받거나 PWA에서 딥 링크 (특정 콘텐츠를 가리키는 URL)를 클릭하면 콘텐츠가 독립형 창에서 열립니다.
이 동작은 사용자가 Chrome으로 PWA를 설치할 때와 같이 WebAPK가 사용될 때 Android에서 자동으로 제공됩니다. iOS 및 iPadOS에 설치된 PWA의 URL은 Safari에서 캡처할 수 없습니다.
데스크톱 브라우저의 경우 웹브라우저 커뮤니티에서 새 사양을 만들었습니다. 이 사양은 현재 실험용이며 새 매니페스트 파일 멤버 url_handlers
를 추가합니다. 이 속성은 PWA에서 캡처하려는 출처 배열을 예상합니다. PWA의 출처는 자동으로 부여되며 다른 모든 출처는 web-app-origin-association
라는 파일을 통해 작동하는 처리를 수락해야 합니다.
예를 들어 PWA의 매니페스트가 web.dev에 호스팅되고 app.web.dev 출처를 추가하려면 다음과 같이 표시됩니다.
"url_handlers": [
{"origin": "https://app.web.dev"},
]
이 경우 브라우저는 app.web.dev/.well-known/web-app-origin-association
에 파일이 있는지 확인하여 PWA 범위 URL의 URL 처리를 수락합니다. 개발자가 이 파일을 만들어야 합니다. 다음 예에서 파일은 다음과 같습니다.
{
"web_apps": [
{
"manifest": "/mypwa/app.webmanifest",
"details": {
"paths": [ "/*" ]
}
}
]
}
URL 프로토콜 처리
URL 처리는 표준 https
프로토콜 URL에서 작동하지만 pwa://
와 같은 맞춤 URI 스키마를 사용할 수도 있습니다. 여러 운영체제에서 설치된 앱은 앱이 스키마를 등록하여 이 기능을 얻습니다.
PWA의 경우 이 기능은 데스크톱 기기에서만 사용할 수 있는 URL 프로토콜 핸들러 API를 사용하여 사용 설정됩니다. 앱 스토어에 PWA를 배포하여 휴대기기용 맞춤 프로토콜만 허용할 수 있습니다.
등록하려면 registerProtocolHandler() 메서드를 사용하거나 매니페스트에서 protocol_handlers
구성원을 원하는 스키마와 PWA 컨텍스트에 로드하려는 URL과 함께 사용할 수 있습니다(예:
...
{
"protocol_handlers": [
{
"protocol": "web+pwa",
"url": "/from-protocol?value=%s"
},
]
}
...
URL from-protocol
를 올바른 핸들러로 라우팅하고 PWA에서 쿼리 문자열 value
을 가져올 수 있습니다. %s
는 작업을 트리거한 이스케이프된 URL의 자리표시자이므로 <a href="web+pwa://testing">
와 같은 위치에 링크가 있으면 PWA에서 /from-protocol?value=testing
가 열립니다.
다른 앱 호출
URI 스키마를 사용하여 모든 플랫폼에서 사용자 기기에 설치된 다른 앱 (PWA 여부와 관계없음)에 연결할 수 있습니다. 링크를 만들거나 navigator.href
를 사용하고 원하는 URI 스킴을 가리키며 인수를 URL 이스케이프 처리된 형식으로 전달하기만 하면 됩니다.
전화 통화의 경우 tel:
, 이메일 전송의 경우 mailto:
, 문자 메시지의 경우 sms:
와 같이 잘 알려진 표준 스키마를 사용하거나 잘 알려진 메시지, 지도, 내비게이션, 온라인 회의, 소셜 네트워크, 앱 스토어 등에서 다른 앱의 URL 스키마를 알아볼 수 있습니다.
웹 공유
Web Share API를 사용하면 PWA가 공유 채널을 통해 기기에 설치된 다른 앱에 콘텐츠를 전송할 수 있습니다.
이 API는 Android, iOS, iPadOS, Windows, ChromeOS를 비롯하여 share
메커니즘이 있는 운영체제에서만 사용할 수 있습니다.
다음을 포함하는 객체를 공유할 수 있습니다.
- 텍스트 (
title
및text
속성) - URL (
url
속성) - 파일 (
files
속성)
현재 기기에서 공유할 수 있는지 확인하려면 텍스트와 같은 간단한 데이터의 경우 navigator.share()
메서드의 존재 여부를 확인하고 파일을 공유하려면 navigator.canShare()
메서드의 존재 여부를 확인합니다.
navigator.share(objectToShare)
를 호출하여 공유 작업을 요청합니다. 이 호출은 undefined
로 확인되거나 예외와 함께 거부되는 Promise를 반환합니다.
웹 공유 타겟
Web Share Target API를 사용하면 PWA가 PWA인지 여부와 관계없이 기기의 다른 앱에서 공유 작업의 대상이 될 수 있습니다. PWA가 다른 앱에서 공유한 데이터를 수신합니다.
이 기능은 현재 WebAPK 및 ChromeOS를 사용하는 Android에서 사용할 수 있으며 사용자가 PWA를 설치한 후에만 작동합니다. 브라우저는 앱이 설치될 때 운영체제 내에 공유 타겟을 등록합니다.
웹 공유 타겟 초안 사양에 정의된 share_target
구성원으로 매니페스트에서 웹 공유 타겟을 설정합니다. share_target
는 다음과 같은 속성이 있는 객체로 설정됩니다.
action
- 공유 데이터를 수신할 것으로 예상되는 PWA 창에 로드될 URL입니다.
method
- HTTP 동사 메서드(예:
GET
,POST
,PUT
)가 작업에 사용됩니다. enctype
- (선택사항) 매개변수의 인코딩 유형입니다. 기본값은
application/x-www-form-urlencoded
이지만POST
과 같은 메서드의 경우multipart/form-data
로 설정할 수도 있습니다. params
- 공유 데이터 (
method: 'GET'
의title
,text
,url
,files
키)를 브라우저가 선택한 인코딩을 사용하여 URL (method: 'GET'
) 또는 요청 본문에 전달하는 인수에 매핑하는 객체입니다.
예를 들어 매니페스트에 다음을 추가하여 공유 데이터 (제목 및 URL만 해당)를 수신할 PWA를 정의할 수 있습니다.
...
"share_target": {
"action": "/receive-share/",
"method": "GET",
"params": {
"title": "shared_title",
"url": "shared_url"
}
}
...
이전 샘플에서 시스템의 앱이 제목이 포함된 URL을 공유하고 사용자가 대화상자에서 PWA를 선택하면 브라우저는 출처의 /receive-share/?shared_title=AAA&shared_url=BBB
로 새 탐색을 만듭니다. 여기서 AAA는 공유된 제목이고 BBB는 공유된 URL입니다. JavaScript를 사용하여 URL
생성자로 파싱하여 window.location
문자열에서 해당 데이터를 읽을 수 있습니다.
브라우저는 매니페스트의 PWA 이름과 아이콘을 사용하여 운영체제의 공유 항목을 제공합니다. 이 목적으로 다른 세트를 선택할 수는 없습니다.
자세한 예시와 파일을 수신하는 방법은 Web Share Target API로 공유 데이터 수신하기를 참고하세요.
연락처 선택도구
Contact Picker API를 사용하면 사용자가 하나 이상의 연락처를 선택할 수 있도록 기기에 모든 사용자 연락처가 포함된 네이티브 대화상자를 렌더링하도록 요청할 수 있습니다. 그러면 PWA에서 이러한 연락처로부터 원하는 데이터를 수신할 수 있습니다.
Contact Picker API는 주로 휴대기기에서 사용할 수 있으며 모든 작업은 호환되는 플랫폼의 navigator.contacts
인터페이스를 통해 이루어집니다.
navigator.contacts.getProperties()
를 사용하여 쿼리할 수 있는 속성을 요청하고 원하는 속성 목록과 함께 하나 또는 여러 개의 연락처 선택을 요청할 수 있습니다.
샘플 속성의 예로는 name
, email
, address
, tel
가 있습니다. 사용자에게 하나 이상의 연락처를 선택하도록 요청할 때 navigator.contacts.select(properties)
를 호출하여 리턴할 속성 배열을 전달할 수 있습니다.
다음 샘플은 선택 도구에서 수신한 연락처를 나열합니다.
async function getContacts() {
const properties = ['name', 'email', 'tel'];
const options = { multiple: true };
try {
const contacts = await navigator.contacts.select(properties, options);
console.log(contacts);
} catch (ex) {
// Handle any errors here.
}
}