SinaWeibo.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. //
  2. // SinaWeibo.m
  3. // sinaweibo_ios_sdk
  4. //
  5. // Created by Wade Cheng on 4/19/12.
  6. // Copyright (c) 2012 SINA. All rights reserved.
  7. //
  8. #import "SinaWeibo.h"
  9. #import "SinaWeiboRequest.h"
  10. #import "SinaWeiboAuthorizeView.h"
  11. #import "SinaWeiboConstants.h"
  12. @interface SinaWeibo ()
  13. @property (nonatomic, copy) NSString *appKey;
  14. @property (nonatomic, copy) NSString *appSecret;
  15. @property (nonatomic, copy) NSString *appRedirectURI;
  16. @end
  17. @implementation SinaWeibo
  18. @synthesize userID;
  19. @synthesize accessToken;
  20. @synthesize expirationDate;
  21. @synthesize refreshToken;
  22. @synthesize ssoCallbackScheme;
  23. @synthesize delegate;
  24. @synthesize appKey;
  25. @synthesize appSecret;
  26. @synthesize appRedirectURI;
  27. #pragma mark - Memory management
  28. /**
  29. * @description 初始化构造函数,返回采用默认sso回调地址构造的SinaWeibo对象
  30. * @param _appKey: 分配给第三方应用的appkey
  31. * @param _appSecrect: 分配给第三方应用的appsecrect
  32. * @param _appRedirectURI: 微博开放平台中授权设置的应用回调页
  33. * @return SinaWeibo对象
  34. */
  35. - (id)initWithAppKey:(NSString *)_appKey appSecret:(NSString *)_appSecrect
  36. appRedirectURI:(NSString *)_appRedirectURI
  37. andDelegate:(id<SinaWeiboDelegate>)_delegate
  38. {
  39. return [self initWithAppKey:_appKey appSecret:_appSecrect appRedirectURI:_appRedirectURI ssoCallbackScheme:nil andDelegate:_delegate];
  40. }
  41. /**
  42. * @description 初始化构造函数,返回采用默认sso回调地址构造的SinaWeibo对象
  43. * @param _appKey: 分配给第三方应用的appkey
  44. * @param _appSecrect: 分配给第三方应用的appsecrect
  45. * @param _ssoCallbackScheme: sso回调地址,此值应与URL Types中定义的保持一致
  46. * 若为nil,则初始化为默认格式 sinaweibosso.your_app_key;
  47. * @param _appRedirectURI: 微博开放平台中授权设置的应用回调页
  48. * @return SinaWeibo对象
  49. */
  50. - (id)initWithAppKey:(NSString *)_appKey appSecret:(NSString *)_appSecrect
  51. appRedirectURI:(NSString *)_appRedirectURI
  52. ssoCallbackScheme:(NSString *)_ssoCallbackScheme
  53. andDelegate:(id<SinaWeiboDelegate>)_delegate
  54. {
  55. if ((self = [super init]))
  56. {
  57. self.appKey = _appKey;
  58. self.appSecret = _appSecrect;
  59. self.appRedirectURI = _appRedirectURI;
  60. self.delegate = _delegate;
  61. if (!_ssoCallbackScheme)
  62. {
  63. _ssoCallbackScheme = [NSString stringWithFormat:@"sinaweibosso.%@://", self.appKey];
  64. }
  65. self.ssoCallbackScheme = _ssoCallbackScheme;
  66. requests = [[NSMutableSet alloc] init];
  67. }
  68. return self;
  69. }
  70. - (void)dealloc
  71. {
  72. delegate = nil;
  73. for (SinaWeiboRequest* _request in requests)
  74. {
  75. _request.sinaweibo = nil;
  76. }
  77. [request disconnect];
  78. [request release], request = nil;
  79. [userID release], userID = nil;
  80. [accessToken release], accessToken = nil;
  81. [expirationDate release], expirationDate = nil;
  82. [appKey release], appKey = nil;
  83. [appSecret release], appSecret = nil;
  84. [appRedirectURI release], appRedirectURI = nil;
  85. [ssoCallbackScheme release], ssoCallbackScheme = nil;
  86. [super dealloc];
  87. }
  88. /**
  89. * @description 清空认证信息
  90. */
  91. - (void)removeAuthData
  92. {
  93. self.accessToken = nil;
  94. self.userID = nil;
  95. self.expirationDate = nil;
  96. NSHTTPCookieStorage* cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage];
  97. NSArray* sinaweiboCookies = [cookies cookiesForURL:
  98. [NSURL URLWithString:@"https://open.weibo.cn"]];
  99. for (NSHTTPCookie* cookie in sinaweiboCookies)
  100. {
  101. [cookies deleteCookie:cookie];
  102. }
  103. }
  104. #pragma mark - Private methods
  105. - (void)requestAccessTokenWithAuthorizationCode:(NSString *)code
  106. {
  107. NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
  108. self.appKey, @"client_id",
  109. self.appSecret, @"client_secret",
  110. @"authorization_code", @"grant_type",
  111. self.appRedirectURI, @"redirect_uri",
  112. code, @"code", nil];
  113. [request disconnect];
  114. [request release], request = nil;
  115. request = [[SinaWeiboRequest requestWithURL:kSinaWeiboWebAccessTokenURL
  116. httpMethod:@"POST"
  117. params:params
  118. delegate:self] retain];
  119. [request connect];
  120. }
  121. - (void)requestDidFinish:(SinaWeiboRequest *)_request
  122. {
  123. [requests removeObject:_request];
  124. _request.sinaweibo = nil;
  125. }
  126. - (void)requestDidFailWithInvalidToken:(NSError *)error
  127. {
  128. if ([delegate respondsToSelector:@selector(sinaweibo:accessTokenInvalidOrExpired:)])
  129. {
  130. [delegate sinaweibo:self accessTokenInvalidOrExpired:error];
  131. }
  132. }
  133. - (void)notifyTokenExpired:(id<SinaWeiboRequestDelegate>)requestDelegate
  134. {
  135. NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
  136. @"Token expired", NSLocalizedDescriptionKey, nil];
  137. NSError *error = [NSError errorWithDomain:kSinaWeiboSDKErrorDomain
  138. code:21315
  139. userInfo:userInfo];
  140. if ([delegate respondsToSelector:@selector(sinaweibo:accessTokenInvalidOrExpired:)])
  141. {
  142. [delegate sinaweibo:self accessTokenInvalidOrExpired:error];
  143. }
  144. if ([requestDelegate respondsToSelector:@selector(request:didFailWithError:)])
  145. {
  146. [requestDelegate request:nil didFailWithError:error];
  147. }
  148. }
  149. - (void)logInDidCancel
  150. {
  151. if ([delegate respondsToSelector:@selector(sinaweiboLogInDidCancel:)])
  152. {
  153. [delegate sinaweiboLogInDidCancel:self];
  154. }
  155. }
  156. - (void)logInDidFinishWithAuthInfo:(NSDictionary *)authInfo
  157. {
  158. NSString *access_token = [authInfo objectForKey:@"access_token"];
  159. NSString *uid = [authInfo objectForKey:@"uid"];
  160. NSString *remind_in = [authInfo objectForKey:@"remind_in"];
  161. NSString *refresh_token = [authInfo objectForKey:@"refresh_token"];
  162. if (access_token && uid)
  163. {
  164. if (remind_in != nil)
  165. {
  166. int expVal = [remind_in intValue];
  167. if (expVal == 0)
  168. {
  169. self.expirationDate = [NSDate distantFuture];
  170. }
  171. else
  172. {
  173. self.expirationDate = [NSDate dateWithTimeIntervalSinceNow:expVal];
  174. }
  175. }
  176. self.accessToken = access_token;
  177. self.userID = uid;
  178. self.refreshToken = refresh_token;
  179. if ([delegate respondsToSelector:@selector(sinaweiboDidLogIn:)])
  180. {
  181. [delegate sinaweiboDidLogIn:self];
  182. }
  183. }
  184. }
  185. - (void)logInDidFailWithErrorInfo:(NSDictionary *)errorInfo
  186. {
  187. NSString *error_code = [errorInfo objectForKey:@"error_code"];
  188. if ([error_code isEqualToString:@"21330"])
  189. {
  190. [self logInDidCancel];
  191. }
  192. else
  193. {
  194. if ([delegate respondsToSelector:@selector(sinaweibo:logInDidFailWithError:)])
  195. {
  196. NSString *error_description = [errorInfo objectForKey:@"error_description"];
  197. NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
  198. errorInfo, @"error",
  199. error_description, NSLocalizedDescriptionKey, nil];
  200. NSError *error = [NSError errorWithDomain:kSinaWeiboSDKErrorDomain
  201. code:[error_code intValue]
  202. userInfo:userInfo];
  203. [delegate sinaweibo:self logInDidFailWithError:error];
  204. }
  205. }
  206. }
  207. #pragma mark - Validation
  208. /**
  209. * @description 判断是否登录
  210. * @return YES为已登录;NO为未登录
  211. */
  212. - (BOOL)isLoggedIn
  213. {
  214. return userID && accessToken && expirationDate;
  215. }
  216. /**
  217. * @description 判断登录是否过期
  218. * @return YES为已过期;NO为未为期
  219. */
  220. - (BOOL)isAuthorizeExpired
  221. {
  222. NSDate *now = [NSDate date];
  223. return ([now compare:expirationDate] == NSOrderedDescending);
  224. }
  225. /**
  226. * @description 判断登录是否有效,当已登录并且登录未过期时为有效状态
  227. * @return YES为有效;NO为无效
  228. */
  229. - (BOOL)isAuthValid
  230. {
  231. return ([self isLoggedIn] && ![self isAuthorizeExpired]);
  232. }
  233. #pragma mark - LogIn / LogOut
  234. /**
  235. * @description 登录入口,当初始化SinaWeibo对象完成后直接调用此方法完成登录
  236. */
  237. - (void)logIn
  238. {
  239. if ([self isAuthValid])
  240. {
  241. if ([delegate respondsToSelector:@selector(sinaweiboDidLogIn:)])
  242. {
  243. [delegate sinaweiboDidLogIn:self];
  244. }
  245. }
  246. else
  247. {
  248. [self removeAuthData];
  249. ssoLoggingIn = NO;
  250. // open sina weibo app
  251. UIDevice *device = [UIDevice currentDevice];
  252. if ([device respondsToSelector:@selector(isMultitaskingSupported)] &&
  253. [device isMultitaskingSupported])
  254. {
  255. NSDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
  256. self.appKey, @"client_id",
  257. self.appRedirectURI, @"redirect_uri",
  258. self.ssoCallbackScheme, @"callback_uri", nil];
  259. // 先用iPad微博打开
  260. NSString *appAuthBaseURL = kSinaWeiboAppAuthURL_iPad;
  261. if (SinaWeiboIsDeviceIPad())
  262. {
  263. NSString *appAuthURL = [SinaWeiboRequest serializeURL:appAuthBaseURL
  264. params:params httpMethod:@"GET"];
  265. ssoLoggingIn = [[UIApplication sharedApplication] openURL:[NSURL URLWithString:appAuthURL]];
  266. }
  267. // 在用iPhone微博打开
  268. if (!ssoLoggingIn)
  269. {
  270. appAuthBaseURL = kSinaWeiboAppAuthURL_iPhone;
  271. NSString *appAuthURL = [SinaWeiboRequest serializeURL:appAuthBaseURL
  272. params:params httpMethod:@"GET"];
  273. ssoLoggingIn = [[UIApplication sharedApplication] openURL:[NSURL URLWithString:appAuthURL]];
  274. }
  275. }
  276. if (!ssoLoggingIn)
  277. {
  278. // open authorize view
  279. NSDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
  280. self.appKey, @"client_id",
  281. @"code", @"response_type",
  282. self.appRedirectURI, @"redirect_uri",
  283. @"mobile", @"display", nil];
  284. SinaWeiboAuthorizeView *authorizeView = \
  285. [[SinaWeiboAuthorizeView alloc] initWithAuthParams:params
  286. delegate:self];
  287. [authorizeView show];
  288. [authorizeView release];
  289. }
  290. }
  291. }
  292. /**
  293. * @description 退出方法,需要退出时直接调用此方法
  294. */
  295. - (void)logOut
  296. {
  297. [self removeAuthData];
  298. if ([delegate respondsToSelector:@selector(sinaweiboDidLogOut:)])
  299. {
  300. [delegate sinaweiboDidLogOut:self];
  301. }
  302. }
  303. #pragma mark - Send request with token
  304. /**
  305. * @description 微博API的请求接口,方法中自动完成token信息的拼接
  306. * @param url: 请求的接口
  307. * @param params: 请求的参数,如发微博所带的文字内容等
  308. * @param httpMethod: http类型,GET或POST
  309. * @param _delegate: 处理请求结果的回调的对象,SinaweiboRequestDelegate类
  310. * @return 完成实际请求操作的SinaWeiboRequest对象
  311. */
  312. - (SinaWeiboRequest *)requestWithURL:(NSString *)url
  313. params:(NSMutableDictionary *)params
  314. httpMethod:(NSString *)httpMethod
  315. delegate:(id<SinaWeiboRequestDelegate>)_delegate
  316. {
  317. if (params == nil)
  318. {
  319. params = [NSMutableDictionary dictionary];
  320. }
  321. if ([self isAuthValid])
  322. {
  323. [params setValue:self.accessToken forKey:@"access_token"];
  324. NSString *fullURL = [kSinaWeiboSDKAPIDomain stringByAppendingString:url];
  325. SinaWeiboRequest *_request = [SinaWeiboRequest requestWithURL:fullURL
  326. httpMethod:httpMethod
  327. params:params
  328. delegate:_delegate];
  329. _request.sinaweibo = self;
  330. [requests addObject:_request];
  331. [_request connect];
  332. return _request;
  333. }
  334. else
  335. {
  336. //notify token expired in next runloop
  337. [self performSelectorOnMainThread:@selector(notifyTokenExpired:)
  338. withObject:_delegate
  339. waitUntilDone:NO];
  340. return nil;
  341. }
  342. }
  343. #pragma mark - SinaWeiboAuthorizeView Delegate
  344. - (void)authorizeView:(SinaWeiboAuthorizeView *)authView didRecieveAuthorizationCode:(NSString *)code
  345. {
  346. [self requestAccessTokenWithAuthorizationCode:code];
  347. }
  348. - (void)authorizeView:(SinaWeiboAuthorizeView *)authView didFailWithErrorInfo:(NSDictionary *)errorInfo
  349. {
  350. [self logInDidFailWithErrorInfo:errorInfo];
  351. }
  352. - (void)authorizeViewDidCancel:(SinaWeiboAuthorizeView *)authView
  353. {
  354. [self logInDidCancel];
  355. }
  356. #pragma mark - SinaWeiboRequest Delegate
  357. - (void)request:(SinaWeiboRequest *)_request didFailWithError:(NSError *)error
  358. {
  359. if (_request == request)
  360. {
  361. if ([delegate respondsToSelector:@selector(sinaweibo:logInDidFailWithError:)])
  362. {
  363. [delegate sinaweibo:self logInDidFailWithError:error];
  364. }
  365. [request release], request = nil;
  366. }
  367. }
  368. - (void)request:(SinaWeiboRequest *)_request didFinishLoadingWithResult:(id)result
  369. {
  370. if (_request == request)
  371. {
  372. NSLog(@"access token result = %@", result);
  373. [self logInDidFinishWithAuthInfo:result];
  374. [request release], request = nil;
  375. }
  376. }
  377. #pragma mark - Application life cycle
  378. /**
  379. * @description 当应用从后台唤起时,应调用此方法,需要完成退出当前登录状态的功能
  380. */
  381. - (void)applicationDidBecomeActive
  382. {
  383. if (ssoLoggingIn)
  384. {
  385. // user open the app manually
  386. // clean sso login state
  387. ssoLoggingIn = NO;
  388. if ([delegate respondsToSelector:@selector(sinaweiboLogInDidCancel:)])
  389. {
  390. [delegate sinaweiboLogInDidCancel:self];
  391. }
  392. }
  393. }
  394. /**
  395. * @description sso回调方法,官方客户端完成sso授权后,回调唤起应用,应用中应调用此方法完成sso登录
  396. * @param url: 官方客户端回调给应用时传回的参数,包含认证信息等
  397. * @return YES
  398. */
  399. - (BOOL)handleOpenURL:(NSURL *)url
  400. {
  401. NSString *urlString = [url absoluteString];
  402. if ([urlString hasPrefix:self.ssoCallbackScheme])
  403. {
  404. if (!ssoLoggingIn)
  405. {
  406. // sso callback after user have manually opened the app
  407. // ignore the request
  408. }
  409. else
  410. {
  411. ssoLoggingIn = NO;
  412. if ([SinaWeiboRequest getParamValueFromUrl:urlString paramName:@"sso_error_user_cancelled"])
  413. {
  414. if ([delegate respondsToSelector:@selector(sinaweiboLogInDidCancel:)])
  415. {
  416. [delegate sinaweiboLogInDidCancel:self];
  417. }
  418. }
  419. else if ([SinaWeiboRequest getParamValueFromUrl:urlString paramName:@"sso_error_invalid_params"])
  420. {
  421. if ([delegate respondsToSelector:@selector(sinaweibo:logInDidFailWithError:)])
  422. {
  423. NSString *error_description = @"Invalid sso params";
  424. NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
  425. error_description, NSLocalizedDescriptionKey, nil];
  426. NSError *error = [NSError errorWithDomain:kSinaWeiboSDKErrorDomain
  427. code:kSinaWeiboSDKErrorCodeSSOParamsError
  428. userInfo:userInfo];
  429. [delegate sinaweibo:self logInDidFailWithError:error];
  430. }
  431. }
  432. else if ([SinaWeiboRequest getParamValueFromUrl:urlString paramName:@"error_code"])
  433. {
  434. NSString *error_code = [SinaWeiboRequest getParamValueFromUrl:urlString paramName:@"error_code"];
  435. NSString *error = [SinaWeiboRequest getParamValueFromUrl:urlString paramName:@"error"];
  436. NSString *error_uri = [SinaWeiboRequest getParamValueFromUrl:urlString paramName:@"error_uri"];
  437. NSString *error_description = [SinaWeiboRequest getParamValueFromUrl:urlString paramName:@"error_description"];
  438. NSDictionary *errorInfo = [NSDictionary dictionaryWithObjectsAndKeys:
  439. error, @"error",
  440. error_uri, @"error_uri",
  441. error_code, @"error_code",
  442. error_description, @"error_description", nil];
  443. [self logInDidFailWithErrorInfo:errorInfo];
  444. }
  445. else
  446. {
  447. NSString *access_token = [SinaWeiboRequest getParamValueFromUrl:urlString paramName:@"access_token"];
  448. NSString *expires_in = [SinaWeiboRequest getParamValueFromUrl:urlString paramName:@"expires_in"];
  449. NSString *remind_in = [SinaWeiboRequest getParamValueFromUrl:urlString paramName:@"remind_in"];
  450. NSString *uid = [SinaWeiboRequest getParamValueFromUrl:urlString paramName:@"uid"];
  451. NSString *refresh_token = [SinaWeiboRequest getParamValueFromUrl:urlString paramName:@"refresh_token"];
  452. NSMutableDictionary *authInfo = [NSMutableDictionary dictionary];
  453. if (access_token) [authInfo setObject:access_token forKey:@"access_token"];
  454. if (expires_in) [authInfo setObject:expires_in forKey:@"expires_in"];
  455. if (remind_in) [authInfo setObject:remind_in forKey:@"remind_in"];
  456. if (refresh_token) [authInfo setObject:refresh_token forKey:@"refresh_token"];
  457. if (uid) [authInfo setObject:uid forKey:@"uid"];
  458. [self logInDidFinishWithAuthInfo:authInfo];
  459. }
  460. }
  461. }
  462. return YES;
  463. }
  464. @end
  465. BOOL SinaWeiboIsDeviceIPad()
  466. {
  467. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 30200
  468. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
  469. {
  470. return YES;
  471. }
  472. #endif
  473. return NO;
  474. }