웹 앱은 도달 범위가 넓습니다. 여러 플랫폼에서 실행됩니다. 링크를 통해 쉽게 공유할 수 있습니다. 그러나 전통적으로 운영 체제와의 통합이 없었습니다. 얼마 전까지만 해도 설치가 불가능했습니다. 다행히 이 점은 변경되었으며 이제 이러한 통합을 활용하여 PWA에 유용한 기능을 추가할 수 있습니다. 이러한 옵션 중 일부를 살펴보겠습니다.
파일 시스템 작업
파일을 사용하는 일반적인 사용자 워크플로는 다음과 같습니다.
- 기기에서 파일이나 폴더를 선택하여 직접 엽니다.
- 해당 파일이나 폴더를 변경하고 변경사항을 다시 직접 저장합니다.
- 새 파일 및 폴더를 만듭니다.
File System Access API 전에는 웹 앱에서 이러한 작업을 할 수 없었습니다. 파일을 열려면 파일 업로드가 필요했고 변경사항을 저장하려면 사용자가 다운로드해야 했으며 웹은 사용자의 파일 시스템에 새 파일과 폴더를 만들 수 있는 액세스 권한이 전혀 없었습니다.
파일 열기
파일을 열려면 window.showOpenFilePicker()
메서드를 사용합니다. 이 메서드에는 버튼 클릭과 같은 사용자 동작이 필요합니다. 파일을 열기 위한 나머지 설정은 다음과 같습니다.
- 파일 시스템 액세스의 파일 선택 도구 API에서 파일 핸들을 캡처합니다. 이렇게 하면 파일에 대한 기본 정보가 표시됩니다.
- 핸들의
getFile()
메서드를 사용하면 파일에 관한 추가 읽기 전용 속성 (예: 이름, 최종 수정일)이 포함된 특별한 종류의Blob
인File
를 가져올 수 있습니다. Blob이므로 Blob 메서드(예:text()
)를 호출하여 콘텐츠를 가져올 수 있습니다.
// 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)를 클릭하면 콘텐츠가 독립형 창에서 열립니다.
이 동작은 WebAPK가 사용될 때(예: 사용자가 Chrome으로 PWA를 설치할 때) Android에서 자동으로 적용됩니다. Safari에서는 iOS 및 iPadOS에 설치된 PWA의 URL을 캡처할 수 없습니다.
데스크톱 브라우저의 경우 웹브라우저 커뮤니티에서 새로운 사양을 만들었습니다. 이 사양은 현재 실험용이며 새로운 매니페스트 파일 멤버인 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() 메서드를 사용하거나 매니페스트에서 다음과 같이 원하는 스키마 및 PWA 컨텍스트에서 로드할 URL과 함께 protocol_handlers
멤버를 사용합니다.
...
{
"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
로 확인되거나 예외와 함께 거부되는 프로미스를 반환합니다.
웹 공유 타겟
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
- 웹 공유의
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.
}
}