Page 1
© 2015 Apple Inc. All rights reserved. Redistribution or public display not permitted without written permission from Apple.
#WWDC15
CloudKit JS and Web Services
Chris Edstrom iCloud Server Engineering Onar Vikingstad iCloud Web Engineering
System Frameworks
Session 710
Page 2
#1CloudKit Feature Request
Page 3
Background
Familiarity with CloudKit
Introducing CloudKit WWDC14
Advanced CloudKit WWDC14
Page 4
CloudKit ArchitectureFundamental CloudKit objects
Page 5
CloudKit ArchitectureFundamental CloudKit objects
CloudKit Container
Page 6
CloudKit ArchitectureFundamental CloudKit objects
CloudKit Container
Public Database
Page 7
CloudKit ArchitectureFundamental CloudKit objects
CloudKit Container
Public Database Private Database
Page 8
CloudKit ArchitectureFundamental CloudKit objects
CloudKit Container
Public Database Private Database
DefaultZone
Page 9
CloudKit ArchitectureFundamental CloudKit objects
CloudKit Container
Public Database Private Database
DefaultZoneRecord
Page 10
CloudKit ArchitectureFundamental CloudKit objects
CloudKit Container
Public Database Private Database
DefaultZone
DefaultZoneRecord
Page 11
CloudKit ArchitectureFundamental CloudKit objects
CloudKit Container
Public Database Private Database
DefaultZone
DefaultZone
CustomZoneRecord
Page 12
CloudKit ArchitectureFundamental CloudKit objects
CloudKit Container
Public Database Private Database
DefaultZone
DefaultZone
CustomZoneRecordRecord
Page 13
CloudKit ArchitectureFundamental CloudKit objects
CloudKit Container
Public Database Private Database
DefaultZone
DefaultZone
CustomZoneRecord RecordRecord
Page 14
CloudKit Architecture
CloudKit
Page 15
CloudKit Architecture
CloudKit.framework
CloudKit
Page 16
CloudKit Architecture
CloudKit.framework
Notes
Your App
CloudKit
Page 17
CloudKit Architecture
CloudKit Web Services
CloudKit.framework
Notes
Your App
CloudKit
Page 18
CloudKit Architecture
CloudKit Web Services
CloudKit.framework
Notes
Your App
CloudKit
Notes Web App
Page 19
CloudKit Architecture
CloudKit Web Services
CloudKit.framework
Notes
Your App
CloudKit
Notes Web App
Your App
Page 20
What Is It?
A JSON/HTTPS interface to CloudKitWeb sign-in with Apple IDA JavaScript library
Page 22
Features
Public/private database access
Page 23
Features
Public/private database accessCreate/read/update/delete
Page 24
Features
Public/private database accessCreate/read/update/deleteAssets
Page 25
Features
Public/private database accessCreate/read/update/deleteAssetsQuery
Page 26
Features
Public/private database accessCreate/read/update/deleteAssetsQuerySubscriptions and notifications
Page 27
Features
Public/private database accessCreate/read/update/deleteAssetsQuerySubscriptions and notificationsUser discoverability
Page 28
Features
Public/private database accessCreate/read/update/deleteAssetsQuerySubscriptions and notificationsUser discoverabilitySync
Page 29
Features
Public/private database accessCreate/read/update/deleteAssetsQuerySubscriptions and notificationsUser discoverabilitySyncAuthentication
Page 31
Feature Parity
All operations are available in the JSON APIField namesCompletions via JS promisesCode ends up looking similar
Page 32
Getting Started
Create a containerCreate a schemaContainer can be created from either Portal or XcodeSchema can be created from Dashboard or on-demand by your app
Page 33
Enable Web Access
Generate a web service API tokenSet your login callback mechanismSet domain restrictions
Page 36
Authentication
Redirect URL provided whenauthentication is neededUsers authenticate with an Apple IDUsername and password are not seen by your app
Page 37
JSON API Example
Container name is com.example.tasksPrivate database use case
Tasks: { name: STRING, priority: INT64 }
Page 38
JSON API Example
Container name is com.example.tasksPrivate database use case
Tasks: { name: STRING, priority: INT64 }
Page 39
JSON API ExampleRequest
POST https://api.apple-cloudkit.com/database/1/com.example.tasks/¬ production/private/records/modify
{ "operations" : [{ "operationType" : "create", "record" : { "recordType" : "Tasks", "fields" : { "name": { "value" : "Buy Milk" }}, "recordName" : "task-1" } }] }
Page 40
JSON API ExampleRequest
POST https://api.apple-cloudkit.com/database/1/com.example.tasks/¬ production/private/records/modify
{ "operations" : [{ "operationType" : "create", "record" : { "recordType" : "Tasks", "fields" : { "name": { "value" : "Buy Milk" }}, "recordName" : "task-1" } }] }
Page 41
JSON API ExampleRequest
POST https://api.apple-cloudkit.com/database/1/com.example.tasks/¬ production/private/records/modify
{ "operations" : [{ "operationType" : "create", "record" : { "recordType" : "Tasks", "fields" : { "name": { "value" : "Buy Milk" }}, "recordName" : "task-1" } }] }
Page 42
JSON API ExampleUnauthorized
{ "uuid":"4f02f7aa-fbb5-4cf8ae8e-4dd463793841", "serverErrorCode":"AUTHENTICATION_REQUIRED", "reason":"request needs authorization", "redirectUrl":"https://signin.apple.com/IDMSWebAuth/auth2?…"
}
Page 43
JSON API ExampleUnauthorized
{ "uuid":"4f02f7aa-fbb5-4cf8ae8e-4dd463793841", "serverErrorCode":"AUTHENTICATION_REQUIRED", "reason":"request needs authorization", "redirectUrl":"https://signin.apple.com/IDMSWebAuth/auth2?…"
}
Page 44
JSON API Example
Sign in to Tasks
Page 45
JSON API Example
Sign in to Tasks
[email protected]
●●●●●●●
Page 46
JSON API ExampleRequest
POST https://api.apple-cloudkit.com/database/1/com.example.tasks/¬ production/private/records/modify?ckSession=1_2_AQV... { "operations" : [{ "operationType" : "create", "record" : { "recordType" : "Tasks", "fields" : { "name": { "value" : "Buy Milk" }}, "recordName" : "task-1" } }] }
Page 47
JSON API ExampleResponse
{ "records" : [{ "recordName” : "task-1", "recordChangeTag" : "1e", "fields" : { ... }, "created" : { "timestamp” : 1422312944104, "userRecordName" : "_9a065b60313a5937e75e03ed8e8f383d", "deviceId" : "_2" }, "modified" : { ...
Page 48
JSON API ExampleResponse
{ "records" : [{ "recordName” : "task-1", "recordChangeTag" : "1e", "fields" : { ... }, "created" : { "timestamp” : 1422312944104, "userRecordName" : "_9a065b60313a5937e75e03ed8e8f383d", "deviceId" : "_2" }, "modified" : { ...
Page 49
CloudKit JS
Onar Vikingstad
Page 50
What Is CloudKit JS?
Page 51
What Is CloudKit JS?
A JavaScript library for using CloudKit on the web
Page 52
What Is CloudKit JS?
A JavaScript library for using CloudKit on the webLow-level transport API
Page 53
What Is CloudKit JS?
A JavaScript library for using CloudKit on the webLow-level transport APIEasy transition from CloudKit Framework
Page 54
What Is CloudKit JS?
A JavaScript library for using CloudKit on the webLow-level transport APIEasy transition from CloudKit FrameworkNatural to web developers
Page 55
Browser Support
SafariFirefoxChromeInternet ExplorerMicrosoft Edge
Page 56
DemoLet’s check out CloudKit JS in action
Page 61
ConfigurationEmbedding CloudKit JS on your website
Page 62
ConfigurationEmbedding CloudKit JS on your website
<script src="https://cdn.apple-cloudkit.com/ck/1/cloudkit.js">
Page 63
ConfigurationEmbedding CloudKit JS on your website
<script src="https://cdn.apple-cloudkit.com/ck/1/cloudkit.js"><script> CloudKit.configure({ containers: [{ containerIdentifier: 'com.example.tasks', environment: 'production', apiToken: '<apiToken>' }] }); </script>
Page 65
AuthenticationWith iOS and OS X
Container
Page 66
AuthenticationWith iOS and OS X
[email protected]
Container
Page 67
AuthenticationWith iOS and OS X
[email protected]
Container
9ECDC8B9
Page 68
AuthenticationWith CloudKit web services
Tasks Web App
Page 69
AuthenticationWith CloudKit web services
Tasks Web AppSign in to Tasks
Page 70
AuthenticationWith CloudKit web services
Tasks Web AppSign in to Tasks
Page 71
AuthenticationWith CloudKit web services
Tasks Web AppSign in to Tasks
postMessage with the CloudKit Session
Page 72
AuthenticationSign-in button and checking authentication state
Page 73
AuthenticationSign-in button and checking authentication state
<div id="apple-sign-in-button"></div>
Page 74
AuthenticationSign-in button and checking authentication state
<div id="apple-sign-in-button"></div> var myContainer = CloudKit.getDefaultContainer(); myContainer.setUpAuth().then(function(userInfo) { if(userInfo) { // The user is already authenticated // userInfo.userRecordName is the stable user identifier } });
Page 75
AuthenticationHandling user sign-in and sign-out
Page 76
AuthenticationHandling user sign-in and sign-out
myContainer.whenUserSignsIn().then(function(userInfo) { // The user just signed in});
Page 77
AuthenticationHandling user sign-in and sign-out
myContainer.whenUserSignsIn().then(function(userInfo) { // The user just signed in});myContainer.whenUserSignsOut().then(function() { // The user just signed out});
Page 78
AuthenticationPersisting the auth token
Page 79
AuthenticationPersisting the auth token
CloudKit.configure({ containers: [{ ... auth: { persist: true } }] });
Page 80
AuthenticationPersisting the auth token
CloudKit.configure({ containers: ..., services: { authTokenStore: { putToken: function(containerIdentifier, token) { ... }, getToken: function(containerIdentifier) { ... } } } });
Page 81
Record Operations
Page 82
RecordsThe structure of a CloudKit record
Page 83
RecordsThe structure of a CloudKit record
{ recordName: 'task-1', recordType: 'Tasks', created: { ... }, modified: { ... }, recordChangeTag: '234ljknwer',
fields: { taskName: { type: 'STRING', value: 'Buy milk' } } }
Page 84
RecordsRecord field values
Page 85
RecordsRecord field values
CloudKit Framework CloudKit JS
Page 86
RecordsRecord field values
CloudKit Framework CloudKit JS
NSString JavaScript String
Page 87
RecordsRecord field values
CloudKit Framework CloudKit JS
NSString JavaScript String
NSNumber JavaScript Number
Page 88
RecordsRecord field values
CloudKit Framework CloudKit JS
NSString JavaScript String
NSNumber JavaScript Number
NSData Base64 encoded binary
Page 89
RecordsRecord field values
CloudKit Framework CloudKit JS
NSString JavaScript String
NSNumber JavaScript Number
NSData Base64 encoded binary
NSDate JavaScript Number (UNIX time, ms)
Page 90
RecordsRecord field values
CloudKit Framework CloudKit JS
NSString JavaScript String
NSNumber JavaScript Number
NSData Base64 encoded binary
NSDate JavaScript Number (UNIX time, ms)
CLLocation Location object
Page 91
RecordsRecord field values
CloudKit Framework CloudKit JS
NSString JavaScript String
NSNumber JavaScript Number
NSData Base64 encoded binary
NSDate JavaScript Number (UNIX time, ms)
CLLocation Location object
CKReference Reference object
Page 92
RecordsRecord field values
CloudKit Framework CloudKit JS
NSString JavaScript String
NSNumber JavaScript Number
NSData Base64 encoded binary
NSDate JavaScript Number (UNIX time, ms)
CLLocation Location object
CKReference Reference object
CKAsset Asset object
Page 93
RecordsCreating a record
var record = { recordType: 'Tasks', fields: { taskName: { value: 'Buy milk' } } };
var privateDB = myContainer.privateCloudDatabase;
privateDB.saveRecord(record).then(function(response) { var record = response.records[0]; });
Page 94
RecordsCreating a record
var record = { recordType: 'Tasks', fields: { taskName: { value: 'Buy milk' } } };
var privateDB = myContainer.privateCloudDatabase;
privateDB.saveRecord(record).then(function(response) { var record = response.records[0]; });
Page 95
RecordsCreating a record
var record = { recordType: 'Tasks', fields: { taskName: { value: 'Buy milk' } } };
var privateDB = myContainer.privateCloudDatabase;
privateDB.saveRecord(record).then(function(response) { var record = response.records[0]; });
Page 97
Fetching Records with a Query
Page 98
Fetching Records with a Query
var query = { recordType: 'Tasks' };privateDB.performQuery(query).then(function(response) { console.log(response.records);});
Page 99
Fetching Records with a Query
var query = { recordType: 'Tasks' };privateDB.performQuery(query).then(function(response) { console.log(response.records);});privateDB.performQuery(query, { desiredKeys: ['taskName'], resultsLimit: 10, zoneID: 'allTasks'}).then(function(response) { console.log(response.records);});
Page 100
Fetching Records with a QueryUsing a filter
Page 101
Fetching Records with a QueryUsing a filter
var query = { recordType: 'Tasks', filterBy: [{ fieldName: 'priority', comparator: 'EQUALS', fieldValue: { value: 1 } }] };
privateDB.performQuery(query).then(function(response) { console.log('Tasks with priority 1', response.records); });
Page 102
Fetching Records with a QueryUsing a filter
var query = { recordType: 'Tasks', filterBy: [{ fieldName: 'priority', comparator: 'EQUALS', fieldValue: { value: 1 } }] };
privateDB.performQuery(query).then(function(response) { console.log('Tasks with priority 1', response.records); });
Page 103
Fetching Records with a QueryContinuing queries
Page 104
Fetching Records with a QueryContinuing queries
privateDB.performQuery(query, { resultsLimit: 10 }) .then(function(response) { console.log('First 10 records:', response.records); if(response.moreComing) { return privateDB.performQuery(response); } }) .then(function(response) { if(response) { console.log('Next 10 records:', response.records); } });
Page 106
CloudKit Container
Public Database
DefaultZoneRecord
Assets
Page 107
CloudKit Container
Public Database
DefaultZoneRecord
Assets
Asset Storage
Page 108
AssetsUploading an asset
Page 109
AssetsUploading an asset
<input type="file" onchange="handleFileSelect"> <script> function handleFileSelect(evt) { var myImage = evt.target.files[0]; database.saveRecord({ recordType: 'Users', fields: { profilePhoto: { value: myImage } } }).then(function(response) { console.log(response.records[0]); }); } </script>
Page 110
AssetsUploading an asset
<input type="file" onchange="handleFileSelect"> <script> function handleFileSelect(evt) { var myImage = evt.target.files[0]; database.saveRecord({ recordType: 'Users', fields: { profilePhoto: { value: myImage } } }).then(function(response) { console.log(response.records[0]); }); } </script>
Page 111
AssetsDownloading an asset
Page 112
AssetsDownloading an asset
database.fetchRecord('user1').then(function(response) { console.log(response.records[0]);
/* fields: { profilePhoto: { value: { downloadUrl: 'https://...' } } } */ });
Page 113
AssetsDownloading an asset
database.fetchRecord('user1').then(function(response) { console.log(response.records[0]);
/* fields: { profilePhoto: { value: { downloadUrl: 'https://...' } } } */ });
Page 114
Subscriptions and Push Notifications
Page 115
Apple Push Notification Service
Page 116
Apple Push Notification Service
Third-PartyServer
Page 117
Apple Push Notification Service
Page 118
Apple Push Notification Service
CloudKit Subscriptions
Page 119
Apple Push Notification Service
CloudKit Subscriptions
Page 120
SubscriptionsSubscribing to changes in a zone
Page 121
SubscriptionsSubscribing to changes in a zone
var zoneSubscription = { subscriptionType: 'zone', subscriptionID: 'changedTasks', zoneID: 'allTasks' };
database.saveSubscription(zoneSubscription);
Page 122
SubscriptionsSubscribing to changes in a zone
var zoneSubscription = { subscriptionType: 'zone', subscriptionID: 'changedTasks', zoneID: 'allTasks' };
database.saveSubscription(zoneSubscription);
Page 123
SubscriptionsSubscribing to changes based on a query
var querySubscription = { subscriptionType: 'query', subscriptionID: 'changedTasks', zoneID: 'allTasks', firesOn: ['create', 'update', 'delete'],
Page 124
SubscriptionsSubscribing to changes based on a query
var querySubscription = { subscriptionType: 'query', subscriptionID: 'changedTasks', zoneID: 'allTasks', firesOn: ['create', 'update', 'delete'], query: { recordType: 'Tasks', filterBy: [{ fieldName: 'priority', comparator: 'EQUALS', fieldValue: { value: 1 } }] } };
Page 125
Push NotificationsGet notified when subscriptions are firing
Page 126
Push NotificationsGet notified when subscriptions are firing
var myContainer = CloudKit.getDefaultContainer();myContainer.registerForNotifications();
Page 127
Push NotificationsGet notified when subscriptions are firing
var myContainer = CloudKit.getDefaultContainer();myContainer.registerForNotifications();myContainer.addNotificationListener(function(notification) { console.log(notification);});
Page 128
Best Practices with CloudKit JS
Page 129
Best Practices with CloudKit JS
Dynamically link to the CDN-hosted version
Page 130
Best Practices with CloudKit JS
Dynamically link to the CDN-hosted versionConsider loading CloudKit JS asynchronously on your page
Page 131
Best Practices with CloudKit JS
Dynamically link to the CDN-hosted versionConsider loading CloudKit JS asynchronously on your pageHandle request throttling responses
Page 133
Summary
Full-parity interface
Page 134
Summary
Full-parity interfaceEnabled on a per-container basis
Page 135
Summary
Full-parity interfaceEnabled on a per-container basisCloudKit handles authentication, metadata storage, and asset upload/download
Page 136
Summary
Full-parity interfaceEnabled on a per-container basisCloudKit handles authentication, metadata storage, and asset upload/downloadYou handle hosting your static assets and code
Page 137
Summary
Full-parity interfaceEnabled on a per-container basisCloudKit handles authentication, metadata storage, and asset upload/downloadYou handle hosting your static assets and codeUse CloudKit JS
Page 138
More Information
Documentation and VideosCloudKit Documentationdeveloper.apple.com/cloudkit
Technical SupportApple Developer Forumsdeveloper.apple.com/forums
Developer Technical Supportdeveloper.apple.com/support/technical
General [email protected]
Page 139
Related Sessions
What’s New in CloudKit Mission Tuesday 3:30PM
CloudKit Tips and Tricks Pacific Heights Thursday 4:30PM
Page 140
Related Labs
CloudKit Lab Frameworks Lab A Thursday 9:00AM
CloudKit Lab Frameworks Lab D Friday 9:00AM