diff --git a/Controller.h b/Controller.h index c2fd489..8c4f381 100644 --- a/Controller.h +++ b/Controller.h @@ -19,6 +19,8 @@ IBOutlet NSWindow *timelineViewWindow; IBOutlet WebView *mentionsView; IBOutlet NSWindow *mentionsViewWindow; + NSWindow *loginViewWindow; + NSProgressIndicator *loginActivityIndicator; IBOutlet NSMenuItem *globalHotkeyMenuItem; IBOutlet NSImageView *logoLayer; ViewDelegate *viewDelegate; @@ -30,6 +32,8 @@ @property (retain, nonatomic) IBOutlet NSWindow *timelineViewWindow; @property (retain, nonatomic) IBOutlet WebView *mentionsView; @property (retain, nonatomic) IBOutlet NSWindow *mentionsViewWindow; +@property (assign) IBOutlet NSWindow *loginViewWindow; +@property (assign) IBOutlet NSProgressIndicator *loginActivityIndicator; @property (retain, nonatomic) IBOutlet NSMenuItem *globalHotkeyMenuItem; @property (retain, nonatomic) IBOutlet NSImageView *logoLayer; @property (retain, nonatomic) IBOutlet ViewDelegate *viewDelegate; @@ -51,6 +55,10 @@ - (void)storeAccessToken:(NSString *)accessToken secret:(NSString *)secret userId:(NSString *)userId andScreenName:(NSString *)screenName; - (void)loggedIn; +- (IBAction)login:(id)sender; +- (IBAction)logout:(id)sender; + + OSStatus handler(EventHandlerCallRef nextHandler, EventRef theEvent, void* userData); @end diff --git a/Controller.m b/Controller.m index 2030378..9649b2b 100644 --- a/Controller.m +++ b/Controller.m @@ -12,6 +12,8 @@ @implementation Controller +@synthesize loginViewWindow; +@synthesize loginActivityIndicator; @synthesize timelineView, timelineViewWindow, mentionsView, mentionsViewWindow, globalHotkeyMenuItem, viewDelegate; @synthesize logoLayer; @@ -50,26 +52,35 @@ accessToken = [[AccessToken alloc] init]; + //[accessToken setString:nil forKey:@"user_access_token"]; + if (![accessToken stringForKey:@"user_access_token"]) { - [self initOauth]; + [timelineViewWindow performClose:self]; + [mentionsViewWindow performClose:self]; + [self.loginViewWindow makeKeyAndOrderFront:self]; } else { + [timelineViewWindow makeKeyAndOrderFront:self]; [self initWebViews]; } } - (void)initOauth { - NSString *path = [[NSBundle mainBundle] resourcePath]; - NSURL *url = [NSURL fileURLWithPath:path]; - NSString *index_string = [NSString stringWithContentsOfFile:[NSString stringWithFormat:@"%@/index_oauth.html", path] encoding:NSUTF8StringEncoding error:nil]; - - - oauthView = [[WebView alloc] init]; - viewDelegate.oauthView = oauthView; - [[oauthView mainFrame] loadHTMLString:index_string baseURL:url]; - [oauthView setFrameLoadDelegate:viewDelegate]; - [oauthView setPolicyDelegate:viewDelegate]; - [oauthView setUIDelegate:viewDelegate]; - [[oauthView windowScriptObject] setValue:self forKey:@"controller"]; + if (!oauthView) { + NSString *path = [[NSBundle mainBundle] resourcePath]; + NSURL *url = [NSURL fileURLWithPath:path]; + NSString *index_string = [NSString stringWithContentsOfFile:[NSString stringWithFormat:@"%@/index_oauth.html", path] encoding:NSUTF8StringEncoding error:nil]; + + + oauthView = [[WebView alloc] init]; + viewDelegate.oauthView = oauthView; + [[oauthView mainFrame] loadHTMLString:index_string baseURL:url]; + [oauthView setFrameLoadDelegate:viewDelegate]; + [oauthView setPolicyDelegate:viewDelegate]; + [oauthView setUIDelegate:viewDelegate]; + [[oauthView windowScriptObject] setValue:self forKey:@"controller"]; + } else { + [oauthView stringByEvaluatingJavaScriptFromString:@"tentia_oauth.authenticate()"]; + } } - (void)initHotKeys { @@ -123,7 +134,9 @@ } - (void)authentificationSucceded:(id)sender { + [loginActivityIndicator stopAnimation:self]; [self initWebViews]; + [loginViewWindow performClose:self]; } - (void)initWebViews { @@ -245,12 +258,36 @@ [[NSNotificationCenter defaultCenter] postNotificationName:@"authentificationSucceded" object:nil]; } -- (void)loggedIn -{ +- (void)loggedIn { [timelineViewWindow makeKeyAndOrderFront:self]; [[NSNotificationCenter defaultCenter] postNotificationName:@"authentificationSucceded" object:nil]; } +- (IBAction)login:(id)sender { + [loginActivityIndicator startAnimation:self]; + [self initOauth]; +} + +- (IBAction)logout:(id)sender { + [timelineViewWindow performClose:self]; + [mentionsViewWindow performClose:self]; + [self.loginViewWindow makeKeyAndOrderFront:self]; + + [timelineView stringByEvaluatingJavaScriptFromString:@"tentia_instance.logout();"]; + [mentionsView stringByEvaluatingJavaScriptFromString:@"tentia_instance.logout();"]; + + [accessToken setString:nil forKey:@"app_mac_key"]; + [accessToken setString:nil forKey:@"app_mac_key_id"]; + [accessToken setString:nil forKey:@"app_id"]; + [accessToken setString:nil forKey:@"app_mac_algorithm"]; + [accessToken setString:nil forKey:@"user_access_token"]; + [accessToken setString:nil forKey:@"user_mac_key"]; + [accessToken setString:nil forKey:@"user_mac_algorithm"]; + [accessToken setString:nil forKey:@"user_token_type"]; + [accessToken setString:nil forKey:@"api_root"]; + [accessToken setString:nil forKey:@"entity"]; +} + // Mentions window has been visible - (void)windowDidBecomeKey:(NSNotification *)notification { if ([notification object] == mentionsViewWindow) { diff --git a/Core.js b/Core.js index 1f9d3f2..de905f4 100644 --- a/Core.js +++ b/Core.js @@ -57,19 +57,15 @@ Core.prototype.newStatus = function(status, supress_new_with_timeout) { this.is_not_init = true; } +Core.prototype.logout = function() { + this.body.innerHTML = ""; +} + Core.prototype.getItem = function(status) { var _this = this; this.since_id = status.id; this.since_id_entity = status.entity; - if (this.since_time < status.published_at) this.since_time = status.published_at; - - var original_status = null; - /* - if(status.retweeted_status != null) { - var original_status = status; - var status = status.retweeted_status; - }*/ var template = this.getTemplate(); @@ -85,7 +81,6 @@ Core.prototype.getItem = function(status) { } //template.retweet.onclick = function() { template.retweet.className = "hidden"; _this.retweet(status.id_str, template.item); return false; } - //template.image.src = status.user.profile_image_url; template.username.innerText = status.entity; template.username.href = status.entity; // FIXME open profile @@ -100,30 +95,16 @@ Core.prototype.getItem = function(status) { template.username.title = template.username.innerText; template.username.innerText = basic.name; } - if(basic.avatar_url) template.image.src = basic.avatar_url; + if(basic.avatar_url) { + template.image.onerror = function() { template.image.src = 'default-avatar.png' }; + template.image.src = basic.avatar_url; + } } - }); + }, null, false); // do not send auth-headers } }); - - /* - if(original_status != null) { - var retweeted = document.createElement("span") - retweeted.className = "retweeted"; - var retweeted_icon = document.createElement("span"); - retweeted_icon.innerText = " "; - retweeted.appendChild(retweeted_icon); - var retweeted_by = document.createElement("a"); - retweeted_by.innerText = original_status.user.screen_name + " "; - retweeted_by.href = WEBSITE_PATH + original_status.user.screen_name; - retweeted.appendChild(document.createTextNode("@")); - retweeted.appendChild(retweeted_by); - template.in_reply.parentNode.parentNode.insertBefore(retweeted, template.in_reply.parent); - }*/ - /*if(status.in_reply_to_status_id_str != null) template.in_reply.innerText = status.in_reply_to_screen_name; - else */template.in_reply.parentNode.className = "hidden"; - //template.in_reply.href = WEBSITE_PATH + status.in_reply_to_screen_name + "/status/" + status.in_reply_to_status_id_str; + template.in_reply.parentNode.className = "hidden"; template.message.innerHTML = replaceUsernamesWithLinks(replaceURLWithHTMLLinks(status.content.text, status.entities, template.message)); @@ -133,60 +114,17 @@ Core.prototype.getItem = function(status) { time.className = "timeago"; $(time).timeago(); template.ago.appendChild(time); - //template.ago.href = WEBSITE_PATH + status.user.screen_name + "/status/" + status.id_str; // {"type":"Point","coordinates":[57.10803113,12.25854746]} if (status.content && status.content.location && status.content.location.type == "Point") { template.geo.href = "http://maps.google.com/maps?q=" + status.content.location.coordinates[0] + "," + status.content.location.coordinates[1]; template.geo.style.display = ""; } - + template.source.href = status.app.url; template.source.innerHTML = status.app.name; template.source.title = status.app.url; - /* - if(status.entities.media) { - - for(var i=0; iNSImageView NSMenu NSMenuItem + NSProgressIndicator NSTextField NSTextFieldCell + NSUserDefaultsController NSView NSWindowTemplate WebView @@ -93,6 +95,15 @@ + + + Preferences... + , + 1048576 + 2147483647 + + + YES @@ -252,6 +263,24 @@ + + + YES + YES + + + 2147483647 + + + + + + Logout + + 2147483647 + + + @@ -910,11 +939,11 @@ YES - 15 + 7 2 {{641, 502}, {480, 186}} - 611845120 - Login + 1685586944 + Preferences NSWindow @@ -964,7 +993,7 @@ {{194, 82}, {266, 22}} - + _NS:9 YES @@ -976,7 +1005,7 @@ 13 1044 - https://someone.tent.is + https://example.tent.is _NS:9 YES @@ -1060,6 +1089,17 @@ NO + + + 268 + {{373, 55}, {16, 16}} + + + + _NS:945 + 28938 + 100 + {480, 186} @@ -1069,8 +1109,12 @@ {{0, 0}, {2560, 1418}} {10000000000000, 10000000000000} + preferences YES + + YES + @@ -1467,6 +1511,54 @@ 570 + + + loginEntityTextField + + + + 605 + + + + login: + + + + 606 + + + + login: + + + + 609 + + + + logout: + + + + 612 + + + + loginViewWindow + + + + 616 + + + + loginActivityIndicator + + + + 622 + makeKeyAndOrderFront: @@ -1499,6 +1591,30 @@ 569 + + + makeKeyAndOrderFront: + + + + 611 + + + + value: values.entity + + + + + + value: values.entity + value + values.entity + 2 + + + 619 + @@ -1588,6 +1704,8 @@ + + @@ -1779,6 +1897,7 @@ + @@ -2160,6 +2279,7 @@ + @@ -2219,6 +2339,31 @@ + + 602 + + + + + 603 + + + + + 610 + + + + + 613 + + + + + 620 + + + @@ -2325,6 +2470,11 @@ 599.IBPluginDependency 600.IBPluginDependency 601.IBPluginDependency + 602.IBPluginDependency + 603.IBPluginDependency + 610.IBPluginDependency + 613.IBPluginDependency + 620.IBPluginDependency 72.IBPluginDependency 73.IBPluginDependency 79.IBPluginDependency @@ -2409,7 +2559,7 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{202, 175}, {397, 581}} - + com.apple.InterfaceBuilder.CocoaPlugin com.apple.WebKitIBPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -2425,7 +2575,12 @@ com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin - + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin @@ -2456,7 +2611,7 @@ - 601 + 622 @@ -2464,11 +2619,59 @@ Controller NSObject + + YES + + YES + login: + logout: + openNewMessageWindow: + sendTweet: + + + YES + id + id + id + id + + + + YES + + YES + login: + logout: + openNewMessageWindow: + sendTweet: + + + YES + + login: + id + + + logout: + id + + + openNewMessageWindow: + id + + + sendTweet: + id + + + YES YES globalHotkeyMenuItem + loginActivityIndicator + loginViewWindow logoLayer mentionsView mentionsViewWindow @@ -2479,6 +2682,8 @@ YES NSMenuItem + NSProgressIndicator + NSWindow NSImageView WebView NSWindow @@ -2492,6 +2697,8 @@ YES globalHotkeyMenuItem + loginActivityIndicator + loginViewWindow logoLayer mentionsView mentionsViewWindow @@ -2505,6 +2712,14 @@ globalHotkeyMenuItem NSMenuItem + + loginActivityIndicator + NSProgressIndicator + + + loginViewWindow + NSWindow + logoLayer NSImageView diff --git a/NewMessageWindow.m b/NewMessageWindow.m index 5ae0150..c387ba2 100644 --- a/NewMessageWindow.m +++ b/NewMessageWindow.m @@ -34,11 +34,11 @@ // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead. return @"NewMessageWindow"; } -/* + - (NSString *)displayName { - return @"New Tweet"; + return @"New Post"; } -*/ + - (void)windowControllerDidLoadNib:(NSWindowController *) aController { [super windowControllerDidLoadNib:aController]; diff --git a/OauthImplementation.js b/OauthImplementation.js index 7d8de36..06e6880 100644 --- a/OauthImplementation.js +++ b/OauthImplementation.js @@ -19,9 +19,7 @@ function getUrlVars(url) { return vars; } -function OauthImplementation(entity) { - this.entity = entity || "http://jeena.net"; - controller.setString_forKey_(this.entity, "entity"); +function OauthImplementation() { this.app_info = { "id": null, "name": "Tentia", @@ -39,6 +37,12 @@ function OauthImplementation(entity) { this.register_data = null; this.profile = null; this.state = null; + + this.authenticate(); +} + +OauthImplementation.prototype.authenticate = function() { + this.entity = controller.stringForKey_("entity"); this.requestProfileURL(this.entity); } @@ -102,12 +106,19 @@ OauthImplementation.prototype.requestAccessToken = function(responseBody) { }); var those = this; + var http_method = "POST"; var callback = function(resp) { those.requestAccessTokenTicketFinished(resp.responseText); }; - var auth_header = makeAuthHeader(url, "POST", controller.stringForKey_("app_mac_key"), controller.stringForKey_("app_mac_key_id")); - getURL(url, "POST", callback, requestBody, auth_header); + var auth_header = makeAuthHeader( + url, + http_method, + controller.stringForKey_("app_mac_key"), + controller.stringForKey_("app_mac_key_id") + ); + + getURL(url, http_method, callback, requestBody, auth_header); } else { alert("State is not the same: {" + this.state + "} vs {" + urlVars["state"] + "}") @@ -116,10 +127,6 @@ OauthImplementation.prototype.requestAccessToken = function(responseBody) { this.state = null; // reset the state } - - - - OauthImplementation.prototype.requestAccessTokenTicketFinished = function(responseBody) { var access = JSON.parse(responseBody); diff --git a/default.css b/default.css index c404f7e..5fe882a 100644 --- a/default.css +++ b/default.css @@ -6,7 +6,7 @@ html, body { body { font-family: "Lucida Grande", Tahoma, sans-serif; font-size: 11px; - background: #dedede; + background: #dedede url(Icon.png) bottom center no-repeat; } a { diff --git a/hmac-helper.js b/hmac-helper.js index 84eb9a0..53c4f1d 100644 --- a/hmac-helper.js +++ b/hmac-helper.js @@ -6,7 +6,21 @@ function getURL(url, http_method, callback, data, auth_header) { $.ajax({ beforeSend: function(xhr) { if (data) xhr.setRequestHeader("Content-Length", data.length); - if (auth_header) xhr.setRequestHeader("Authorization", auth_header); + + if (auth_header) { // if is_set? auth_header + xhr.setRequestHeader("Authorization", auth_header); + } else { + var user_access_token = controller.stringForKey_("user_access_token"); + if (auth_header !== false && user_access_token) { + auth_header = makeAuthHeader( + url, + http_method, + controller.stringForKey_("user_mac_key"), + user_access_token + ) + xhr.setRequestHeader("Authorization", auth_header); + } + } }, url: url, accepts: "application/vnd.tent.v0+json", @@ -16,10 +30,7 @@ function getURL(url, http_method, callback, data, auth_header) { data: data, processData: false, error: function(xhr, ajaxOptions, thrownError) { - alert("getURL ERROR (" + url + ") (" + http_method + "):"); - alert(xhr.statusText); - alert(ajaxOptions); - alert(thrownError); + alert("getURL " + xhr.statusText + " " + http_method + " (" + url + "): '" + xhr.responseText + "'"); } }); } @@ -30,13 +41,18 @@ function makeAuthHeader(url, http_method, mac_key, mac_key_id) { var nonce = makeid(8); var time_stamp = parseInt((new Date).getTime() / 1000, 10); + var port = url.port(); + if (!port) { + port = url.protocol() == "https" ? "443" : "80"; + } + var normalizedRequestString = "" + time_stamp + '\n' + nonce + '\n' + http_method + '\n' + url.path() + url.search() + url.hash() + '\n' + url.hostname() + '\n' - + url.port() + '\n' + + port + '\n' + '\n' ; var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, mac_key); @@ -69,21 +85,21 @@ function findProfileURL(entity, callback) { var headers = resp.getAllResponseHeaders(); var regex = /Link: <([^>]*)>; rel="https:\/\/tent.io\/rels\/profile"/; // FIXME: parse it! var ret = headers.match(regex); - var profile_url = entity + "/profile"; - if(ret.length > 1) { + var profile_url = null; + if(ret && ret.length > 1) { var profile_url = ret[1]; if (profile_url == "/profile") { profile_url = entity + "/profile"; } } - callback(profile_url); + + if (profile_url) { + callback(profile_url); + } } }, error: function(xhr, ajaxOptions, thrownError) { - alert("getURL ERROR (" + url + ") (" + http_method + "):"); - alert(xhr.statusText); - alert(ajaxOptions); - alert(thrownError); + alert("findProfileURL " + xhr.statusText + " (" + entity + "): " + xhr.responseText); } }); }