@capgo/capacitor-contacts 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapgoCapacitorContacts.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +28 -0
- package/README.md +884 -0
- package/android/build.gradle +57 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/app/capgo/contacts/CapacitorContactsPlugin.java +770 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +2437 -0
- package/dist/esm/definitions.d.ts +944 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +28 -0
- package/dist/esm/web.js +70 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +84 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +87 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/CapacitorContactsPlugin/CapacitorContactsPlugin.swift +515 -0
- package/ios/Tests/CapacitorContactsPluginTests/CapacitorContactsPluginTests.swift +9 -0
- package/package.json +89 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import Capacitor
|
|
2
|
+
import Contacts
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
@objc(CapacitorContactsPlugin)
|
|
6
|
+
public class CapacitorContactsPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
7
|
+
public let identifier = "CapacitorContactsPlugin"
|
|
8
|
+
public let jsName = "CapacitorContacts"
|
|
9
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
10
|
+
CAPPluginMethod(name: "countContacts", returnType: CAPPluginReturnPromise),
|
|
11
|
+
CAPPluginMethod(name: "createContact", returnType: CAPPluginReturnPromise),
|
|
12
|
+
CAPPluginMethod(name: "createGroup", returnType: CAPPluginReturnPromise),
|
|
13
|
+
CAPPluginMethod(name: "deleteContactById", returnType: CAPPluginReturnPromise),
|
|
14
|
+
CAPPluginMethod(name: "deleteGroupById", returnType: CAPPluginReturnPromise),
|
|
15
|
+
CAPPluginMethod(name: "displayContactById", returnType: CAPPluginReturnPromise),
|
|
16
|
+
CAPPluginMethod(name: "displayCreateContact", returnType: CAPPluginReturnPromise),
|
|
17
|
+
CAPPluginMethod(name: "displayUpdateContactById", returnType: CAPPluginReturnPromise),
|
|
18
|
+
CAPPluginMethod(name: "getAccounts", returnType: CAPPluginReturnPromise),
|
|
19
|
+
CAPPluginMethod(name: "getContactById", returnType: CAPPluginReturnPromise),
|
|
20
|
+
CAPPluginMethod(name: "getContacts", returnType: CAPPluginReturnPromise),
|
|
21
|
+
CAPPluginMethod(name: "getGroupById", returnType: CAPPluginReturnPromise),
|
|
22
|
+
CAPPluginMethod(name: "getGroups", returnType: CAPPluginReturnPromise),
|
|
23
|
+
CAPPluginMethod(name: "isAvailable", returnType: CAPPluginReturnPromise),
|
|
24
|
+
CAPPluginMethod(name: "isSupported", returnType: CAPPluginReturnPromise),
|
|
25
|
+
CAPPluginMethod(name: "openSettings", returnType: CAPPluginReturnPromise),
|
|
26
|
+
CAPPluginMethod(name: "pickContact", returnType: CAPPluginReturnPromise),
|
|
27
|
+
CAPPluginMethod(name: "pickContacts", returnType: CAPPluginReturnPromise),
|
|
28
|
+
CAPPluginMethod(name: "updateContactById", returnType: CAPPluginReturnPromise),
|
|
29
|
+
CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise),
|
|
30
|
+
CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise)
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
private let contactStore = CNContactStore()
|
|
34
|
+
|
|
35
|
+
// MARK: - Implemented API surface
|
|
36
|
+
|
|
37
|
+
@objc func countContacts(_ call: CAPPluginCall) {
|
|
38
|
+
ensureAuthorized(call) {
|
|
39
|
+
do {
|
|
40
|
+
var count = 0
|
|
41
|
+
let request = CNContactFetchRequest(keysToFetch: [CNContactIdentifierKey as CNKeyDescriptor])
|
|
42
|
+
try self.contactStore.enumerateContacts(with: request) { _, _ in
|
|
43
|
+
count += 1
|
|
44
|
+
}
|
|
45
|
+
call.resolve(["count": count])
|
|
46
|
+
} catch {
|
|
47
|
+
call.reject("Failed to count contacts.", nil, error)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@objc func getContacts(_ call: CAPPluginCall) {
|
|
53
|
+
ensureAuthorized(call) {
|
|
54
|
+
let options = (call.options["options"] as? JSObject) ?? [:]
|
|
55
|
+
let fields = (options["fields"] as? [String]).map { Set($0) }
|
|
56
|
+
let limit = options["limit"] as? Int
|
|
57
|
+
let offset = options["offset"] as? Int ?? 0
|
|
58
|
+
|
|
59
|
+
let keysToFetch = self.keysToFetch(for: fields)
|
|
60
|
+
let request = CNContactFetchRequest(keysToFetch: keysToFetch)
|
|
61
|
+
request.sortOrder = .userDefault
|
|
62
|
+
|
|
63
|
+
var contacts: [CNContact] = []
|
|
64
|
+
var index = 0
|
|
65
|
+
|
|
66
|
+
do {
|
|
67
|
+
try self.contactStore.enumerateContacts(with: request) { contact, stop in
|
|
68
|
+
if index < offset {
|
|
69
|
+
index += 1
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
if let limit, contacts.count >= limit {
|
|
73
|
+
stop.pointee = true
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
contacts.append(contact)
|
|
77
|
+
index += 1
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let includeGroupIds = fields == nil || fields!.contains("groupIds")
|
|
81
|
+
var membership: [String: [String]] = [:]
|
|
82
|
+
if includeGroupIds {
|
|
83
|
+
membership = self.groupMembershipMap(for: contacts.map(\.identifier))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let serialized = contacts.map { self.serialize(contact: $0, fields: fields, membership: membership) }
|
|
87
|
+
call.resolve(["contacts": serialized])
|
|
88
|
+
} catch {
|
|
89
|
+
call.reject("Failed to fetch contacts.", nil, error)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@objc func getContactById(_ call: CAPPluginCall) {
|
|
95
|
+
guard let options = call.options["options"] as? JSObject, let identifier = options["id"] as? String else {
|
|
96
|
+
call.reject("Missing contact identifier.")
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
ensureAuthorized(call) {
|
|
101
|
+
let fields = (options["fields"] as? [String]).map { Set($0) }
|
|
102
|
+
let keysToFetch = self.keysToFetch(for: fields)
|
|
103
|
+
|
|
104
|
+
do {
|
|
105
|
+
let contact = try self.contactStore.unifiedContact(withIdentifier: identifier, keysToFetch: keysToFetch)
|
|
106
|
+
let membership = self.groupMembershipMap(for: [identifier])
|
|
107
|
+
let serialized = self.serialize(contact: contact, fields: fields, membership: membership)
|
|
108
|
+
call.resolve(["contact": serialized])
|
|
109
|
+
} catch {
|
|
110
|
+
call.reject("Failed to fetch contact.", nil, error)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@objc func getAccounts(_ call: CAPPluginCall) {
|
|
116
|
+
do {
|
|
117
|
+
let containers = try contactStore.containers(matching: nil)
|
|
118
|
+
let accounts: [JSObject] = containers.map {
|
|
119
|
+
var account: JSObject = [:]
|
|
120
|
+
account["name"] = $0.name
|
|
121
|
+
account["type"] = mapContainerType($0.type)
|
|
122
|
+
return account
|
|
123
|
+
}
|
|
124
|
+
call.resolve(["accounts": accounts])
|
|
125
|
+
} catch {
|
|
126
|
+
call.reject("Failed to fetch accounts.", nil, error)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@objc func isSupported(_ call: CAPPluginCall) {
|
|
131
|
+
call.resolve(["isSupported": true])
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@objc func isAvailable(_ call: CAPPluginCall) {
|
|
135
|
+
call.resolve(["isAvailable": true])
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@objc func openSettings(_ call: CAPPluginCall) {
|
|
139
|
+
DispatchQueue.main.async {
|
|
140
|
+
guard let url = URL(string: UIApplication.openSettingsURLString) else {
|
|
141
|
+
call.reject("Unable to open settings.")
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
UIApplication.shared.open(url, options: [:]) { success in
|
|
145
|
+
if success {
|
|
146
|
+
call.resolve()
|
|
147
|
+
} else {
|
|
148
|
+
call.reject("Unable to open settings.")
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@objc override public func checkPermissions(_ call: CAPPluginCall) {
|
|
155
|
+
let status = authorizationStatus()
|
|
156
|
+
call.resolve(status)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@objc override public func requestPermissions(_ call: CAPPluginCall) {
|
|
160
|
+
let requested = (call.options["options"] as? JSObject)?["permissions"] as? [String] ?? ["readContacts", "writeContacts"]
|
|
161
|
+
let needsRequest = requested.contains { _ in mapAuthorizationStatus(CNContactStore.authorizationStatus(for: .contacts)) != "granted" }
|
|
162
|
+
|
|
163
|
+
if !needsRequest {
|
|
164
|
+
call.resolve(authorizationStatus())
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
contactStore.requestAccess(for: .contacts, completionHandler: { (granted: Bool, error: Error?) in
|
|
169
|
+
DispatchQueue.main.async {
|
|
170
|
+
if let error {
|
|
171
|
+
call.reject("Permission request failed.", nil, error)
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
_ = granted
|
|
175
|
+
call.resolve(self.authorizationStatus())
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// MARK: - Not yet implemented operations
|
|
181
|
+
|
|
182
|
+
private func notImplemented(_ call: CAPPluginCall) {
|
|
183
|
+
call.reject("Method not implemented yet.")
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@objc func createContact(_ call: CAPPluginCall) { notImplemented(call) }
|
|
187
|
+
@objc func createGroup(_ call: CAPPluginCall) { notImplemented(call) }
|
|
188
|
+
@objc func deleteContactById(_ call: CAPPluginCall) { notImplemented(call) }
|
|
189
|
+
@objc func deleteGroupById(_ call: CAPPluginCall) { notImplemented(call) }
|
|
190
|
+
@objc func displayContactById(_ call: CAPPluginCall) { notImplemented(call) }
|
|
191
|
+
@objc func displayCreateContact(_ call: CAPPluginCall) { notImplemented(call) }
|
|
192
|
+
@objc func displayUpdateContactById(_ call: CAPPluginCall) { notImplemented(call) }
|
|
193
|
+
@objc func getGroupById(_ call: CAPPluginCall) { notImplemented(call) }
|
|
194
|
+
@objc func getGroups(_ call: CAPPluginCall) { notImplemented(call) }
|
|
195
|
+
@objc func pickContact(_ call: CAPPluginCall) { notImplemented(call) }
|
|
196
|
+
@objc func pickContacts(_ call: CAPPluginCall) { notImplemented(call) }
|
|
197
|
+
@objc func updateContactById(_ call: CAPPluginCall) { notImplemented(call) }
|
|
198
|
+
|
|
199
|
+
// MARK: - Helpers
|
|
200
|
+
|
|
201
|
+
private func ensureAuthorized(_ call: CAPPluginCall, completion: @escaping () -> Void) {
|
|
202
|
+
switch CNContactStore.authorizationStatus(for: .contacts) {
|
|
203
|
+
case .authorized, .limited:
|
|
204
|
+
completion()
|
|
205
|
+
case .notDetermined:
|
|
206
|
+
contactStore.requestAccess(for: .contacts, completionHandler: { granted, error in
|
|
207
|
+
DispatchQueue.main.async {
|
|
208
|
+
if let error {
|
|
209
|
+
call.reject("Permission request failed.", nil, error)
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
if granted {
|
|
213
|
+
completion()
|
|
214
|
+
} else {
|
|
215
|
+
call.reject("Contacts permission not granted.")
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
case .denied, .restricted:
|
|
220
|
+
call.reject("Contacts permission not granted.")
|
|
221
|
+
@unknown default:
|
|
222
|
+
call.reject("Contacts permission state unknown.")
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private func authorizationStatus() -> JSObject {
|
|
227
|
+
let status = mapAuthorizationStatus(CNContactStore.authorizationStatus(for: .contacts))
|
|
228
|
+
return [
|
|
229
|
+
"readContacts": status,
|
|
230
|
+
"writeContacts": status
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private func mapAuthorizationStatus(_ status: CNAuthorizationStatus) -> String {
|
|
235
|
+
switch status {
|
|
236
|
+
case .authorized, .limited:
|
|
237
|
+
return "granted"
|
|
238
|
+
case .denied:
|
|
239
|
+
return "denied"
|
|
240
|
+
case .restricted:
|
|
241
|
+
return "denied"
|
|
242
|
+
case .notDetermined:
|
|
243
|
+
return "prompt"
|
|
244
|
+
@unknown default:
|
|
245
|
+
return "prompt"
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private func keysToFetch(for fields: Set<String>?) -> [CNKeyDescriptor] {
|
|
250
|
+
var keys: [CNKeyDescriptor] = [
|
|
251
|
+
CNContactIdentifierKey as CNKeyDescriptor,
|
|
252
|
+
CNContactGivenNameKey as CNKeyDescriptor,
|
|
253
|
+
CNContactFamilyNameKey as CNKeyDescriptor,
|
|
254
|
+
CNContactMiddleNameKey as CNKeyDescriptor,
|
|
255
|
+
CNContactNamePrefixKey as CNKeyDescriptor,
|
|
256
|
+
CNContactNameSuffixKey as CNKeyDescriptor,
|
|
257
|
+
CNContactOrganizationNameKey as CNKeyDescriptor,
|
|
258
|
+
CNContactJobTitleKey as CNKeyDescriptor,
|
|
259
|
+
CNContactEmailAddressesKey as CNKeyDescriptor,
|
|
260
|
+
CNContactPhoneNumbersKey as CNKeyDescriptor,
|
|
261
|
+
CNContactPostalAddressesKey as CNKeyDescriptor,
|
|
262
|
+
CNContactUrlAddressesKey as CNKeyDescriptor,
|
|
263
|
+
CNContactBirthdayKey as CNKeyDescriptor,
|
|
264
|
+
CNContactNoteKey as CNKeyDescriptor,
|
|
265
|
+
CNContactImageDataAvailableKey as CNKeyDescriptor,
|
|
266
|
+
CNContactImageDataKey as CNKeyDescriptor
|
|
267
|
+
]
|
|
268
|
+
|
|
269
|
+
if let fields, fields.contains("groupIds") {
|
|
270
|
+
// No additional keys required, but this preserves the behaviour if custom keys are needed later.
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if let fields, fields.contains("fullName") {
|
|
274
|
+
keys.append(CNContactFormatter.descriptorForRequiredKeys(for: .fullName))
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return keys
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private func groupMembershipMap(for identifiers: [String]) -> [String: [String]] {
|
|
281
|
+
guard !identifiers.isEmpty else { return [:] }
|
|
282
|
+
|
|
283
|
+
var result: [String: [String]] = [:]
|
|
284
|
+
let identifierSet = Set(identifiers)
|
|
285
|
+
|
|
286
|
+
do {
|
|
287
|
+
let groups = try contactStore.groups(matching: nil)
|
|
288
|
+
for group in groups {
|
|
289
|
+
let predicate = CNContact.predicateForContactsInGroup(withIdentifier: group.identifier)
|
|
290
|
+
let members = try contactStore.unifiedContacts(matching: predicate, keysToFetch: [CNContactIdentifierKey as CNKeyDescriptor])
|
|
291
|
+
for member in members where identifierSet.contains(member.identifier) {
|
|
292
|
+
result[member.identifier, default: []].append(group.identifier)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
} catch {
|
|
296
|
+
CAPLog.print("CapacitorContactsPlugin", "Failed to compute group membership: \(error.localizedDescription)")
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return result
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private func serialize(contact: CNContact, fields: Set<String>?, membership: [String: [String]]) -> JSObject {
|
|
303
|
+
let includeAll = fields == nil
|
|
304
|
+
func shouldInclude(_ field: String) -> Bool {
|
|
305
|
+
includeAll || fields!.contains(field)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
var result: JSObject = [:]
|
|
309
|
+
|
|
310
|
+
if shouldInclude("id") {
|
|
311
|
+
result["id"] = contact.identifier
|
|
312
|
+
}
|
|
313
|
+
if shouldInclude("givenName") {
|
|
314
|
+
result["givenName"] = contact.givenName
|
|
315
|
+
}
|
|
316
|
+
if shouldInclude("familyName") {
|
|
317
|
+
result["familyName"] = contact.familyName
|
|
318
|
+
}
|
|
319
|
+
if shouldInclude("middleName") {
|
|
320
|
+
result["middleName"] = contact.middleName
|
|
321
|
+
}
|
|
322
|
+
if shouldInclude("namePrefix") {
|
|
323
|
+
result["namePrefix"] = contact.namePrefix
|
|
324
|
+
}
|
|
325
|
+
if shouldInclude("nameSuffix") {
|
|
326
|
+
result["nameSuffix"] = contact.nameSuffix
|
|
327
|
+
}
|
|
328
|
+
if shouldInclude("organizationName") {
|
|
329
|
+
result["organizationName"] = contact.organizationName
|
|
330
|
+
}
|
|
331
|
+
if shouldInclude("jobTitle") {
|
|
332
|
+
result["jobTitle"] = contact.jobTitle
|
|
333
|
+
}
|
|
334
|
+
if shouldInclude("note") {
|
|
335
|
+
result["note"] = contact.note
|
|
336
|
+
}
|
|
337
|
+
if shouldInclude("fullName") {
|
|
338
|
+
result["fullName"] = CNContactFormatter.string(from: contact, style: .fullName) ?? ""
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if shouldInclude("emailAddresses") {
|
|
342
|
+
let emails: [JSObject] = contact.emailAddresses.map { labeledValue in
|
|
343
|
+
var entry: JSObject = [:]
|
|
344
|
+
entry["value"] = labeledValue.value as String
|
|
345
|
+
let (type, label) = mapEmailLabel(labeledValue.label)
|
|
346
|
+
entry["type"] = type
|
|
347
|
+
if let label { entry["label"] = label }
|
|
348
|
+
entry["isPrimary"] = false
|
|
349
|
+
return entry
|
|
350
|
+
}
|
|
351
|
+
result["emailAddresses"] = emails
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if shouldInclude("phoneNumbers") {
|
|
355
|
+
let phones: [JSObject] = contact.phoneNumbers.map { labeledValue in
|
|
356
|
+
var entry: JSObject = [:]
|
|
357
|
+
entry["value"] = labeledValue.value.stringValue
|
|
358
|
+
let (type, label) = mapPhoneLabel(labeledValue.label)
|
|
359
|
+
entry["type"] = type
|
|
360
|
+
if let label { entry["label"] = label }
|
|
361
|
+
entry["isPrimary"] = false
|
|
362
|
+
return entry
|
|
363
|
+
}
|
|
364
|
+
result["phoneNumbers"] = phones
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if shouldInclude("postalAddresses") {
|
|
368
|
+
let addresses: [JSObject] = contact.postalAddresses.map { labeledValue in
|
|
369
|
+
let postal = labeledValue.value
|
|
370
|
+
var entry: JSObject = [:]
|
|
371
|
+
entry["city"] = postal.city
|
|
372
|
+
entry["country"] = postal.country
|
|
373
|
+
entry["formatted"] = CNPostalAddressFormatter.string(from: postal, style: .mailingAddress)
|
|
374
|
+
entry["isoCountryCode"] = postal.isoCountryCode
|
|
375
|
+
entry["isPrimary"] = false
|
|
376
|
+
entry["neighborhood"] = postal.subLocality
|
|
377
|
+
entry["postalCode"] = postal.postalCode
|
|
378
|
+
entry["state"] = postal.state
|
|
379
|
+
entry["street"] = postal.street
|
|
380
|
+
let (type, label) = mapPostalLabel(labeledValue.label)
|
|
381
|
+
entry["type"] = type
|
|
382
|
+
if let label { entry["label"] = label }
|
|
383
|
+
return entry
|
|
384
|
+
}
|
|
385
|
+
result["postalAddresses"] = addresses
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if shouldInclude("urlAddresses") {
|
|
389
|
+
let urls: [JSObject] = contact.urlAddresses.map { labeledValue in
|
|
390
|
+
var entry: JSObject = [:]
|
|
391
|
+
entry["value"] = labeledValue.value as String
|
|
392
|
+
let (type, label) = mapURLLabel(labeledValue.label)
|
|
393
|
+
entry["type"] = type
|
|
394
|
+
if let label { entry["label"] = label }
|
|
395
|
+
return entry
|
|
396
|
+
}
|
|
397
|
+
result["urlAddresses"] = urls
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if shouldInclude("birthday"), let birthday = contact.birthday {
|
|
401
|
+
var birthdayJS: JSObject = [:]
|
|
402
|
+
if let day = birthday.day { birthdayJS["day"] = day }
|
|
403
|
+
if let month = birthday.month { birthdayJS["month"] = month }
|
|
404
|
+
if let year = birthday.year { birthdayJS["year"] = year }
|
|
405
|
+
result["birthday"] = birthdayJS
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if shouldInclude("photo"), contact.imageDataAvailable, let imageData = contact.imageData {
|
|
409
|
+
result["photo"] = imageData.base64EncodedString()
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if shouldInclude("groupIds") {
|
|
413
|
+
result["groupIds"] = membership[contact.identifier] ?? []
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if shouldInclude("account") {
|
|
417
|
+
result["account"] = NSNull()
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return result
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private func mapEmailLabel(_ label: String?) -> (String, String?) {
|
|
424
|
+
switch label {
|
|
425
|
+
case CNLabelHome:
|
|
426
|
+
return ("HOME", nil)
|
|
427
|
+
case CNLabelWork:
|
|
428
|
+
return ("WORK", nil)
|
|
429
|
+
case CNLabelEmailiCloud:
|
|
430
|
+
return ("ICLOUD", nil)
|
|
431
|
+
case CNLabelOther:
|
|
432
|
+
return ("OTHER", nil)
|
|
433
|
+
case .none:
|
|
434
|
+
return ("OTHER", nil)
|
|
435
|
+
default:
|
|
436
|
+
return ("CUSTOM", CNLabeledValue<NSString>.localizedString(forLabel: label ?? ""))
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
private func mapPhoneLabel(_ label: String?) -> (String, String?) {
|
|
441
|
+
switch label {
|
|
442
|
+
case CNLabelPhoneNumberMobile:
|
|
443
|
+
return ("MOBILE", nil)
|
|
444
|
+
case CNLabelPhoneNumberiPhone:
|
|
445
|
+
return ("IPHONE", nil)
|
|
446
|
+
case CNLabelPhoneNumberMain:
|
|
447
|
+
return ("MAIN", nil)
|
|
448
|
+
case CNLabelPhoneNumberHomeFax:
|
|
449
|
+
return ("HOME_FAX", nil)
|
|
450
|
+
case CNLabelPhoneNumberWorkFax:
|
|
451
|
+
return ("WORK_FAX", nil)
|
|
452
|
+
case CNLabelPhoneNumberOtherFax:
|
|
453
|
+
return ("OTHER_FAX", nil)
|
|
454
|
+
case CNLabelPhoneNumberPager:
|
|
455
|
+
return ("PAGER", nil)
|
|
456
|
+
case CNLabelHome:
|
|
457
|
+
return ("HOME", nil)
|
|
458
|
+
case CNLabelWork:
|
|
459
|
+
return ("WORK", nil)
|
|
460
|
+
case CNLabelOther:
|
|
461
|
+
return ("OTHER", nil)
|
|
462
|
+
case .none:
|
|
463
|
+
return ("OTHER", nil)
|
|
464
|
+
default:
|
|
465
|
+
return ("CUSTOM", CNLabeledValue<NSString>.localizedString(forLabel: label ?? ""))
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private func mapPostalLabel(_ label: String?) -> (String, String?) {
|
|
470
|
+
switch label {
|
|
471
|
+
case CNLabelHome:
|
|
472
|
+
return ("HOME", nil)
|
|
473
|
+
case CNLabelWork:
|
|
474
|
+
return ("WORK", nil)
|
|
475
|
+
case CNLabelOther:
|
|
476
|
+
return ("OTHER", nil)
|
|
477
|
+
case .none:
|
|
478
|
+
return ("OTHER", nil)
|
|
479
|
+
default:
|
|
480
|
+
return ("CUSTOM", CNLabeledValue<CNPostalAddress>.localizedString(forLabel: label ?? ""))
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
private func mapURLLabel(_ label: String?) -> (String, String?) {
|
|
485
|
+
switch label {
|
|
486
|
+
case CNLabelURLAddressHomePage:
|
|
487
|
+
return ("HOMEPAGE", nil)
|
|
488
|
+
case CNLabelHome:
|
|
489
|
+
return ("HOME", nil)
|
|
490
|
+
case CNLabelWork:
|
|
491
|
+
return ("WORK", nil)
|
|
492
|
+
case CNLabelOther:
|
|
493
|
+
return ("OTHER", nil)
|
|
494
|
+
case .none:
|
|
495
|
+
return ("OTHER", nil)
|
|
496
|
+
default:
|
|
497
|
+
return ("CUSTOM", CNLabeledValue<NSString>.localizedString(forLabel: label ?? ""))
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
private func mapContainerType(_ type: CNContainerType) -> String {
|
|
502
|
+
switch type {
|
|
503
|
+
case .local:
|
|
504
|
+
return "local"
|
|
505
|
+
case .exchange:
|
|
506
|
+
return "exchange"
|
|
507
|
+
case .cardDAV:
|
|
508
|
+
return "carddav"
|
|
509
|
+
case .unassigned:
|
|
510
|
+
return "unassigned"
|
|
511
|
+
@unknown default:
|
|
512
|
+
return "unknown"
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@capgo/capacitor-contacts",
|
|
3
|
+
"version": "7.0.0",
|
|
4
|
+
"description": "Work with device contacts using Capacitor APIs",
|
|
5
|
+
"main": "dist/plugin.cjs.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"unpkg": "dist/plugin.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"android/src/main/",
|
|
11
|
+
"android/build.gradle",
|
|
12
|
+
"dist/",
|
|
13
|
+
"ios/Sources",
|
|
14
|
+
"ios/Tests",
|
|
15
|
+
"Package.swift",
|
|
16
|
+
"CapgoCapacitorContacts.podspec"
|
|
17
|
+
],
|
|
18
|
+
"author": "Cap-go <contact@capgo.app>",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/Cap-go/capacitor-contacts.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/Cap-go/capacitor-contacts/issues"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"capacitor",
|
|
29
|
+
"plugin",
|
|
30
|
+
"native",
|
|
31
|
+
"contacts",
|
|
32
|
+
"address book",
|
|
33
|
+
"phonebook",
|
|
34
|
+
"capgo",
|
|
35
|
+
"capacitor"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
39
|
+
"verify:ios": "xcodebuild -scheme CapgoCapacitorContacts -destination generic/platform=iOS",
|
|
40
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
41
|
+
"verify:web": "npm run build",
|
|
42
|
+
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
43
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
|
|
44
|
+
"eslint": "eslint . --ext .ts",
|
|
45
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
46
|
+
"swiftlint": "node-swiftlint",
|
|
47
|
+
"docgen": "docgen --api CapacitorContactsPlugin --output-readme README.md --output-json dist/docs.json",
|
|
48
|
+
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
49
|
+
"clean": "rimraf ./dist",
|
|
50
|
+
"watch": "tsc --watch",
|
|
51
|
+
"prepublishOnly": "npm run build"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@capacitor/android": "^7.0.0",
|
|
55
|
+
"@capacitor/cli": "^7.0.0",
|
|
56
|
+
"@capacitor/core": "^7.0.0",
|
|
57
|
+
"@capacitor/docgen": "^0.3.0",
|
|
58
|
+
"@capacitor/ios": "^7.0.0",
|
|
59
|
+
"@ionic/eslint-config": "^0.4.0",
|
|
60
|
+
"@ionic/prettier-config": "^4.0.0",
|
|
61
|
+
"@ionic/swiftlint-config": "^2.0.0",
|
|
62
|
+
"@types/node": "^22.13.1",
|
|
63
|
+
"eslint": "^8.57.0",
|
|
64
|
+
"eslint-plugin-import": "^2.31.0",
|
|
65
|
+
"husky": "^9.1.7",
|
|
66
|
+
"prettier": "^3.4.2",
|
|
67
|
+
"prettier-plugin-java": "^2.6.7",
|
|
68
|
+
"rimraf": "^6.0.1",
|
|
69
|
+
"rollup": "^4.34.6",
|
|
70
|
+
"swiftlint": "^2.0.0",
|
|
71
|
+
"typescript": "^5.7.3"
|
|
72
|
+
},
|
|
73
|
+
"peerDependencies": {
|
|
74
|
+
"@capacitor/core": ">=7.0.0"
|
|
75
|
+
},
|
|
76
|
+
"eslintConfig": {
|
|
77
|
+
"extends": "@ionic/eslint-config/recommended"
|
|
78
|
+
},
|
|
79
|
+
"prettier": "@ionic/prettier-config",
|
|
80
|
+
"swiftlint": "@ionic/swiftlint-config",
|
|
81
|
+
"capacitor": {
|
|
82
|
+
"ios": {
|
|
83
|
+
"src": "ios"
|
|
84
|
+
},
|
|
85
|
+
"android": {
|
|
86
|
+
"src": "android"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|