Skip to content

Commit 3b3b839

Browse files
authored
feat: add Instagram authentication (#372)
* feat: add Instagram login * fix: swiftlint warnings. * test: add test for using default api url for Instagram login * docs: fix documentation for id parameter * Update CHANGELOG.md * fix: swiftlint warnings. * test: fix test implementations
1 parent 51a44ef commit 3b3b839

File tree

8 files changed

+1604
-0
lines changed

8 files changed

+1604
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
__New features__
99
- Add the ability to send APN and FCM push notifications. Also adds the ability to query _PushStatus ([#371](https://github.com/parse-community/Parse-Swift/pull/371)), thanks to [Corey Baker](https://github.com/cbaker6).
1010
- Add ParseSchema, ParseCLP, and ParseFieldOptions. Should only be used when using the Swift SDK on a secured server ([#370](https://github.com/parse-community/Parse-Swift/pull/370)), thanks to [Corey Baker](https://github.com/cbaker6).
11+
- Add ParseInstagram authentication ([#372](https://github.com/parse-community/Parse-Swift/pull/372)), thanks to [Ulaş Sancak](https://github.com/rocxteady).
1112

1213
### 4.5.0
1314
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.4.0...4.5.0)

ParseSwift.xcodeproj/project.pbxproj

Lines changed: 62 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//
2+
// ParseInstagram+async.swift
3+
// ParseSwift
4+
//
5+
// Created by Ulaş Sancak on 06/19/22.
6+
// Copyright © 2022 Parse Community. All rights reserved.
7+
//
8+
9+
#if compiler(>=5.5.2) && canImport(_Concurrency)
10+
import Foundation
11+
12+
public extension ParseInstagram {
13+
// MARK: Async/Await
14+
15+
/**
16+
Login a `ParseUser` *asynchronously* using Instagram authentication.
17+
- parameter id: The **Instagram profile id** from **Instagram**.
18+
- parameter accessToken: Required **access_token** from **Instagram**.
19+
- parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**.
20+
- parameter options: A set of header options sent to the server. Defaults to an empty set.
21+
- returns: An instance of the logged in `ParseUser`.
22+
- throws: An error of type `ParseError`.
23+
*/
24+
func login(id: String,
25+
accessToken: String,
26+
apiURL: String = Self.graphAPIBaseURL,
27+
options: API.Options = []) async throws -> AuthenticatedUser {
28+
try await withCheckedThrowingContinuation { continuation in
29+
self.login(id: id,
30+
accessToken: accessToken,
31+
apiURL: apiURL,
32+
options: options,
33+
completion: continuation.resume)
34+
}
35+
}
36+
37+
/**
38+
Login a `ParseUser` *asynchronously* using Instagram authentication.
39+
- parameter authData: Dictionary containing key/values.
40+
- returns: An instance of the logged in `ParseUser`.
41+
- throws: An error of type `ParseError`.
42+
*/
43+
func login(authData: [String: String],
44+
options: API.Options = []) async throws -> AuthenticatedUser {
45+
try await withCheckedThrowingContinuation { continuation in
46+
self.login(authData: authData,
47+
options: options,
48+
completion: continuation.resume)
49+
}
50+
}
51+
}
52+
53+
public extension ParseInstagram {
54+
55+
/**
56+
Link the *current* `ParseUser` *asynchronously* using Instagram authentication.
57+
- parameter id: The **Instagram profile id** from **Instagram**.
58+
- parameter accessToken: Required **access_token** from **Instagram**.
59+
- parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**.
60+
- parameter options: A set of header options sent to the server. Defaults to an empty set.
61+
- returns: An instance of the logged in `ParseUser`.
62+
- throws: An error of type `ParseError`.
63+
*/
64+
func link(id: String,
65+
accessToken: String,
66+
apiURL: String = Self.graphAPIBaseURL,
67+
options: API.Options = []) async throws -> AuthenticatedUser {
68+
try await withCheckedThrowingContinuation { continuation in
69+
self.link(id: id,
70+
accessToken: accessToken,
71+
apiURL: apiURL,
72+
options: options,
73+
completion: continuation.resume)
74+
}
75+
}
76+
77+
/**
78+
Link the *current* `ParseUser` *asynchronously* using Instagram authentication.
79+
- parameter authData: Dictionary containing key/values.
80+
- parameter options: A set of header options sent to the server. Defaults to an empty set.
81+
- returns: An instance of the logged in `ParseUser`.
82+
- throws: An error of type `ParseError`.
83+
*/
84+
func link(authData: [String: String],
85+
options: API.Options = []) async throws -> AuthenticatedUser {
86+
try await withCheckedThrowingContinuation { continuation in
87+
self.link(authData: authData,
88+
options: options,
89+
completion: continuation.resume)
90+
}
91+
}
92+
}
93+
#endif
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//
2+
// ParseInstagram+combine.swift
3+
// ParseSwift
4+
//
5+
// Created by Ulaş Sancak on 06/19/22.
6+
// Copyright © 2022 Parse Community. All rights reserved.
7+
//
8+
9+
#if canImport(Combine)
10+
import Foundation
11+
import Combine
12+
13+
public extension ParseInstagram {
14+
// MARK: Combine
15+
/**
16+
Login a `ParseUser` *asynchronously* using Instagram authentication. Publishes when complete.
17+
- parameter id: The **Instagram profile id** from **Instagram**.
18+
- parameter accessToken: Required **access_token** from **Instagram**.
19+
- parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**.
20+
- parameter options: A set of header options sent to the server. Defaults to an empty set.
21+
- returns: A publisher that eventually produces a single value and then finishes or fails.
22+
*/
23+
func loginPublisher(id: String,
24+
accessToken: String,
25+
apiURL: String = Self.graphAPIBaseURL,
26+
options: API.Options = []) -> Future<AuthenticatedUser, ParseError> {
27+
Future { promise in
28+
self.login(id: id,
29+
accessToken: accessToken,
30+
apiURL: apiURL,
31+
options: options,
32+
completion: promise)
33+
}
34+
}
35+
36+
/**
37+
Login a `ParseUser` *asynchronously* using Instagram authentication. Publishes when complete.
38+
- parameter authData: Dictionary containing key/values.
39+
- returns: A publisher that eventually produces a single value and then finishes or fails.
40+
*/
41+
func loginPublisher(authData: [String: String],
42+
options: API.Options = []) -> Future<AuthenticatedUser, ParseError> {
43+
Future { promise in
44+
self.login(authData: authData,
45+
options: options,
46+
completion: promise)
47+
}
48+
}
49+
}
50+
51+
public extension ParseInstagram {
52+
/**
53+
Link the *current* `ParseUser` *asynchronously* using Instagram authentication.
54+
Publishes when complete.
55+
- parameter id: The **Instagram profile id** from **Instagram**.
56+
- parameter accessToken: Required **access_token** from **Instagram**.
57+
- parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**.
58+
- parameter options: A set of header options sent to the server. Defaults to an empty set.
59+
- returns: A publisher that eventually produces a single value and then finishes or fails.
60+
*/
61+
func linkPublisher(id: String,
62+
accessToken: String,
63+
apiURL: String = Self.graphAPIBaseURL,
64+
options: API.Options = []) -> Future<AuthenticatedUser, ParseError> {
65+
Future { promise in
66+
self.link(id: id,
67+
accessToken: accessToken,
68+
apiURL: apiURL,
69+
options: options,
70+
completion: promise)
71+
}
72+
}
73+
74+
/**
75+
Link the *current* `ParseUser` *asynchronously* using Instagram authentication.
76+
Publishes when complete.
77+
- parameter authData: Dictionary containing key/values.
78+
- returns: A publisher that eventually produces a single value and then finishes or fails.
79+
*/
80+
func linkPublisher(authData: [String: String],
81+
options: API.Options = []) -> Future<AuthenticatedUser, ParseError> {
82+
Future { promise in
83+
self.link(authData: authData,
84+
options: options,
85+
completion: promise)
86+
}
87+
}
88+
}
89+
90+
#endif
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
//
2+
// ParseInstagram.swift
3+
// ParseSwift
4+
//
5+
// Created by Ulaş Sancak on 06/19/22.
6+
// Copyright © 2022 Parse Community. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
// swiftlint:disable line_length
12+
13+
/**
14+
Provides utility functions for working with Instagram User Authentication and `ParseUser`'s.
15+
Be sure your Parse Server is configured for [sign in with Instagram](https://docs.parseplatform.org/parse-server/guide/#instagram-authdata).
16+
For information on acquiring Instagram sign-in credentials to use with `ParseInstagram`, refer to [Facebook's Documentation](https://developers.facebook.com/docs/instagram-basic-display-api/overview).
17+
*/
18+
public struct ParseInstagram<AuthenticatedUser: ParseUser>: ParseAuthentication {
19+
20+
public static var graphAPIBaseURL: String {
21+
"https://graph.instagram.com/"
22+
}
23+
24+
/// Authentication keys required for Instagram authentication.
25+
enum AuthenticationKeys: String, Codable {
26+
case id
27+
case accessToken = "access_token"
28+
case apiURL
29+
30+
/// Properly makes an authData dictionary with the required keys.
31+
/// - parameter id: Required id for the user.
32+
/// - parameter accessToken: Required access token for Instagram.
33+
/// - returns: authData dictionary.
34+
func makeDictionary(id: String,
35+
accessToken: String,
36+
apiURL: String = ParseInstagram.graphAPIBaseURL) -> [String: String] {
37+
38+
let returnDictionary = [
39+
AuthenticationKeys.id.rawValue: id,
40+
AuthenticationKeys.accessToken.rawValue: accessToken,
41+
AuthenticationKeys.apiURL.rawValue: apiURL
42+
]
43+
return returnDictionary
44+
}
45+
46+
/// Verifies all mandatory keys are in authData.
47+
/// - parameter authData: Dictionary containing key/values.
48+
/// - returns: **true** if all the mandatory keys are present, **false** otherwise.
49+
func verifyMandatoryKeys(authData: [String: String]) -> Bool {
50+
guard authData[AuthenticationKeys.id.rawValue] != nil,
51+
authData[AuthenticationKeys.accessToken.rawValue] != nil,
52+
authData[AuthenticationKeys.apiURL.rawValue] != nil else {
53+
return false
54+
}
55+
return true
56+
}
57+
}
58+
59+
public static var __type: String { // swiftlint:disable:this identifier_name
60+
"instagram"
61+
}
62+
63+
public init() { }
64+
}
65+
66+
// MARK: Login
67+
public extension ParseInstagram {
68+
69+
/**
70+
Login a `ParseUser` *asynchronously* using Instagram authentication.
71+
- parameter id: The **Instagram profile id** from **Instagram**.
72+
- parameter accessToken: Required **access_token** from **Instagram**.
73+
- parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**.
74+
- parameter options: A set of header options sent to the server. Defaults to an empty set.
75+
- parameter callbackQueue: The queue to return to after completion. Default value of .main.
76+
- parameter completion: The block to execute.
77+
*/
78+
func login(id: String,
79+
accessToken: String,
80+
apiURL: String = Self.graphAPIBaseURL,
81+
options: API.Options = [],
82+
callbackQueue: DispatchQueue = .main,
83+
completion: @escaping (Result<AuthenticatedUser, ParseError>) -> Void) {
84+
85+
let instagramAuthData = AuthenticationKeys.id
86+
.makeDictionary(id: id,
87+
accessToken: accessToken,
88+
apiURL: apiURL)
89+
print(instagramAuthData)
90+
login(authData: instagramAuthData,
91+
options: options,
92+
callbackQueue: callbackQueue,
93+
completion: completion)
94+
}
95+
96+
func login(authData: [String: String],
97+
options: API.Options = [],
98+
callbackQueue: DispatchQueue = .main,
99+
completion: @escaping (Result<AuthenticatedUser, ParseError>) -> Void) {
100+
guard AuthenticationKeys.id.verifyMandatoryKeys(authData: authData) else {
101+
callbackQueue.async {
102+
completion(.failure(.init(code: .unknownError,
103+
message: "Should have authData in consisting of keys \"id\", \"accessToken\", and \"isMobileSDK\".")))
104+
}
105+
return
106+
}
107+
AuthenticatedUser.login(Self.__type,
108+
authData: authData,
109+
options: options,
110+
callbackQueue: callbackQueue,
111+
completion: completion)
112+
}
113+
}
114+
115+
// MARK: Link
116+
public extension ParseInstagram {
117+
118+
/**
119+
Link the *current* `ParseUser` *asynchronously* using Instagram authentication.
120+
- parameter id: The **Instagram profile id** from **Instagram**.
121+
- parameter accessToken: Required **access_token** from **Instagram**.
122+
- parameter apiURL: The `Instagram's most recent graph api url` from **Instagram**.
123+
- parameter options: A set of header options sent to the server. Defaults to an empty set.
124+
- parameter callbackQueue: The queue to return to after completion. Default value of .main.
125+
- parameter completion: The block to execute.
126+
*/
127+
func link(id: String,
128+
accessToken: String,
129+
apiURL: String = Self.graphAPIBaseURL,
130+
options: API.Options = [],
131+
callbackQueue: DispatchQueue = .main,
132+
completion: @escaping (Result<AuthenticatedUser, ParseError>) -> Void) {
133+
let instagramAuthData = AuthenticationKeys.id
134+
.makeDictionary(id: id,
135+
accessToken: accessToken,
136+
apiURL: apiURL)
137+
link(authData: instagramAuthData,
138+
options: options,
139+
callbackQueue: callbackQueue,
140+
completion: completion)
141+
}
142+
143+
func link(authData: [String: String],
144+
options: API.Options = [],
145+
callbackQueue: DispatchQueue = .main,
146+
completion: @escaping (Result<AuthenticatedUser, ParseError>) -> Void) {
147+
guard AuthenticationKeys.id.verifyMandatoryKeys(authData: authData) else {
148+
callbackQueue.async {
149+
completion(.failure(.init(code: .unknownError,
150+
message: "Should have authData in consisting of keys \"id\", \"accessToken\", and \"isMobileSDK\".")))
151+
}
152+
return
153+
}
154+
AuthenticatedUser.link(Self.__type,
155+
authData: authData,
156+
options: options,
157+
callbackQueue: callbackQueue,
158+
completion: completion)
159+
}
160+
}
161+
162+
// MARK: 3rd Party Authentication - ParseInstagram
163+
public extension ParseUser {
164+
165+
/// A Instagram `ParseUser`.
166+
static var instagram: ParseInstagram<Self> {
167+
ParseInstagram<Self>()
168+
}
169+
170+
/// An Instagram `ParseUser`.
171+
var instagram: ParseInstagram<Self> {
172+
Self.instagram
173+
}
174+
}

0 commit comments

Comments
 (0)