@@ -24,9 +24,13 @@ final class LoginDetailsViewController: UIViewController {
2424 private static let missingProfile = " Current Profile unavailable "
2525 private static let missingAccessToken = " Access Token Unavailable "
2626
27+ private var buttonContainerView : UIView !
28+
2729 override func viewDidLoad( ) {
2830 super. viewDidLoad ( )
2931
32+ setupButtonContainer ( )
33+
3034 let accessToken = AccessToken . current
3135 let authenticationToken = AuthenticationToken . current
3236 let profile = Profile . current
@@ -50,4 +54,172 @@ final class LoginDetailsViewController: UIViewController {
5054 emailLabel. text = profile? . email ?? " Email unavailable "
5155 }
5256
57+ private func setupButtonContainer( ) {
58+ buttonContainerView = UIView ( )
59+ if #available( iOS 13 . 0 , * ) {
60+ buttonContainerView. backgroundColor = . systemBackground
61+ } else {
62+ buttonContainerView. backgroundColor = . white
63+ }
64+ buttonContainerView. translatesAutoresizingMaskIntoConstraints = false
65+ view. addSubview ( buttonContainerView)
66+
67+ let revokeButton = UIButton ( type: . system)
68+ revokeButton. setTitle ( " Revoke Access Token " , for: . normal)
69+ revokeButton. translatesAutoresizingMaskIntoConstraints = false
70+ revokeButton. addTarget ( self , action: #selector( revokeAccessTokenTapped) , for: . touchUpInside)
71+ buttonContainerView. addSubview ( revokeButton)
72+
73+ let refreshButton = UIButton ( type: . system)
74+ refreshButton. setTitle ( " Refresh Token " , for: . normal)
75+ refreshButton. translatesAutoresizingMaskIntoConstraints = false
76+ refreshButton. addTarget ( self , action: #selector( refreshTokenTapped) , for: . touchUpInside)
77+ buttonContainerView. addSubview ( refreshButton)
78+
79+ NSLayoutConstraint . activate ( [
80+ buttonContainerView. leadingAnchor. constraint ( equalTo: view. leadingAnchor) ,
81+ buttonContainerView. trailingAnchor. constraint ( equalTo: view. trailingAnchor) ,
82+ buttonContainerView. bottomAnchor. constraint ( equalTo: view. safeAreaLayoutGuide. bottomAnchor) ,
83+
84+ revokeButton. topAnchor. constraint ( equalTo: buttonContainerView. topAnchor, constant: 12 ) ,
85+ revokeButton. centerXAnchor. constraint ( equalTo: buttonContainerView. centerXAnchor) ,
86+
87+ refreshButton. topAnchor. constraint ( equalTo: revokeButton. bottomAnchor, constant: 12 ) ,
88+ refreshButton. centerXAnchor. constraint ( equalTo: buttonContainerView. centerXAnchor) ,
89+ refreshButton. bottomAnchor. constraint ( equalTo: buttonContainerView. bottomAnchor, constant: - 12 ) ,
90+ ] )
91+ }
92+
93+ @objc private func revokeAccessTokenTapped( ) {
94+ guard AccessToken . current != nil else {
95+ showAlert ( title: " Error " , message: " No access token available. Please log in first. " )
96+ return
97+ }
98+ executeRevokeRequest ( )
99+ }
100+
101+ @objc private func refreshTokenTapped( ) {
102+ refreshAccessToken ( )
103+ }
104+
105+ private func executeRevokeRequest( ) {
106+ guard let accessToken = AccessToken . current? . tokenString else {
107+ showAlert ( title: " Error " , message: " No access token available " )
108+ return
109+ }
110+
111+ let urlString = " https://graph.facebook.com/v17.0/me/permissions?access_token= \( accessToken) "
112+ guard let url = URL ( string: urlString) else {
113+ showAlert ( title: " Error " , message: " Invalid URL " )
114+ return
115+ }
116+
117+ var request = URLRequest ( url: url)
118+ request. httpMethod = " DELETE "
119+
120+ let task = URLSession . shared. dataTask ( with: request) { [ weak self] data, response, error in
121+ DispatchQueue . main. async {
122+ if let error = error as NSError ? {
123+ self ? . showAlert (
124+ title: " Error ( \( error. code) ) " ,
125+ message: " \( error. localizedDescription) "
126+ )
127+ return
128+ }
129+
130+ self ? . showAlert ( title: " Token Revoked " , message: " All permissions have been revoked. Tap 'Refresh Token' to update the UI. " )
131+ }
132+ }
133+ task. resume ( )
134+ }
135+
136+ private func showAlert( title: String , message: String ) {
137+ let alert = UIAlertController ( title: title, message: message, preferredStyle: . alert)
138+ alert. addAction ( UIAlertAction ( title: " OK " , style: . default) )
139+ present ( alert, animated: true )
140+ }
141+
142+ private func refreshAccessToken( ) {
143+ guard let accessToken = AccessToken . current? . tokenString else {
144+ showAlert ( title: " Error " , message: " No access token available " )
145+ return
146+ }
147+
148+ let urlString = " https://graph.facebook.com/v17.0/me?fields=id,name,email&access_token= \( accessToken) "
149+ guard let url = URL ( string: urlString) else {
150+ showAlert ( title: " Error " , message: " Invalid URL " )
151+ return
152+ }
153+
154+ let task = URLSession . shared. dataTask ( with: url) { [ weak self] data, response, error in
155+ DispatchQueue . main. async {
156+ if let error = error {
157+ self ? . showAlert ( title: " Network Error " , message: error. localizedDescription)
158+ return
159+ }
160+
161+ guard let data = data else {
162+ self ? . showAlert ( title: " Error " , message: " No data received " )
163+ return
164+ }
165+
166+ do {
167+ if let json = try JSONSerialization . jsonObject ( with: data) as? [ String : Any ] {
168+ if let errorInfo = json [ " error " ] as? [ String : Any ] {
169+ // Token is invalid/revoked
170+ let message = errorInfo [ " message " ] as? String ?? " Token is invalid "
171+ AccessToken . current = nil
172+ AuthenticationToken . current = nil
173+ Profile . current = nil
174+ self ? . updateUIAfterRevocation ( )
175+ self ? . showAlert ( title: " Token Invalid " , message: message)
176+ } else {
177+ // Token is valid
178+ self ? . updateUIWithCurrentToken ( )
179+ let name = json [ " name " ] as? String ?? " Unknown "
180+ self ? . showAlert ( title: " Token Valid " , message: " Token is valid for user: \( name) " )
181+ }
182+ }
183+ } catch {
184+ self ? . showAlert ( title: " Parse Error " , message: error. localizedDescription)
185+ }
186+ }
187+ }
188+ task. resume ( )
189+ }
190+
191+ private func updateUIAfterRevocation( ) {
192+ accessTokenLabel. text = " REVOKED "
193+ permissionsLabel. text = " REVOKED "
194+ declinedPermissionsLabel. text = " REVOKED "
195+ authenticationTokenLabel. text = " REVOKED "
196+ nonceLabel. text = " REVOKED "
197+ nameLabel. text = Self . missingProfile
198+ userIdentifierLabel. text = Self . missingProfile
199+ emailLabel. text = " Email unavailable "
200+ }
201+
202+ private func updateUIWithCurrentToken( ) {
203+ let accessToken = AccessToken . current
204+ let authenticationToken = AuthenticationToken . current
205+ let profile = Profile . current
206+
207+ accessTokenLabel. text = accessToken? . tokenString ?? Self . missingAccessToken
208+ permissionsLabel. text = accessToken? . permissions
209+ . map { $0. name }
210+ . joined ( separator: " , " )
211+ ?? Self . missingAccessToken
212+ declinedPermissionsLabel. text = accessToken? . declinedPermissions
213+ . map { $0. name }
214+ . joined ( separator: " , " )
215+ ?? Self . missingAccessToken
216+ authenticationTokenLabel. text = authenticationToken? . tokenString
217+ ?? " Authentication Token Unavailable "
218+ nonceLabel. text = authenticationToken? . nonce
219+ ?? " Authentication Token Nonce Unavailable "
220+ nameLabel. text = profile? . name ?? Self . missingProfile
221+ userIdentifierLabel. text = profile? . userID ?? Self . missingProfile
222+ emailLabel. text = profile? . email ?? " Email unavailable "
223+ }
224+
53225}
0 commit comments