Added the OpenIdAjaxTextBox control.
[dotnetoauth.git] / src / DotNetOpenAuth / OpenId / RelyingParty / OpenIdAjaxTextBox.js
blob53f6d4347d6fb2bc0d04333485a53e34d8ee18d5
1 // Options that can be set on the host page:
2 //window.openid_visible_iframe = true; // causes the hidden iframe to show up
3 //window.openid_trace = true; // causes lots of alert boxes
5 function trace(msg) {
6         if (window.openid_trace) {
7                 if (!window.tracediv) {
8                         window.tracediv = document.createElement("ol");
9                         document.body.appendChild(window.tracediv);
10                 }
11                 var el = document.createElement("li");
12                 el.appendChild(document.createTextNode(msg));
13                 window.tracediv.appendChild(el);
14                 //alert(msg);
15         }
18 /// <summary>Removes a given element from the array.</summary>
19 /// <returns>True if the element was in the array, or false if it was not found.</returns>
20 Array.prototype.remove = function(element) {
21         function elementToRemoveLast(a, b) {
22                 if (a == element) { return 1; }
23                 if (b == element) { return -1; }
24                 return 0;
25         }
26         this.sort(elementToRemoveLast);
27         if (this[this.length - 1] == element) {
28                 this.pop();
29                 return true;
30         } else {
31                 return false;
32         }
35 function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url, success_icon_url, failure_icon_url,
36                 throttle, timeout, assertionReceivedCode,
37                 loginButtonText, loginButtonToolTip, retryButtonText, retryButtonToolTip, busyToolTip,
38                 identifierRequiredMessage, loginInProgressMessage,
39                 authenticatedByToolTip, authenticatedAsToolTip, authenticationFailedToolTip,
40                 discoverCallback, discoveryFailedCallback) {
41         box.dnoi_internal = new Object();
42         if (assertionReceivedCode) {
43                 box.dnoi_internal.onauthenticated = function(sender, e) { eval(assertionReceivedCode); }
44         }
46         box.dnoi_internal.originalBackground = box.style.background;
47         box.timeout = timeout;
48         box.dnoi_internal.discoverIdentifier = discoverCallback;
49         box.dnoi_internal.authenticationRequests = new Array();
51         // The possible authentication results
52         var authSuccess = new Object();
53         var authRefused = new Object();
54         var timedOut = new Object();
56         function FrameManager(maxFrames) {
57                 this.queuedWork = new Array();
58                 this.frames = new Array();
59                 this.maxFrames = maxFrames;
61                 /// <summary>Called to queue up some work that will use an iframe as soon as it is available.</summary>
62                 /// <param name="job">
63                 /// A delegate that must return the url to point to iframe to.  
64                 /// Its first parameter is the iframe created to service the request.
65                 /// It will only be called when the work actually begins.
66                 /// </param>
67                 this.enqueueWork = function(job) {
68                         // Assign an iframe to this task immediately if there is one available.
69                         if (this.frames.length < this.maxFrames) {
70                                 this.createIFrame(job);
71                         } else {
72                                 this.queuedWork.unshift(job);
73                         }
74                 };
76                 /// <summary>Clears the job queue and immediately closes all iframes.</summary>
77                 this.cancelAllWork = function() {
78                         trace('Canceling all open and pending iframes.');
79                         while (this.queuedWork.pop());
80                         this.closeFrames();
81                 };
83                 /// <summary>An event fired when a frame is closing.</summary>
84                 this.onJobCompleted = function() {
85                         // If there is a job in the queue, go ahead and start it up.
86                         if (job = this.queuedWork.pop()) {
87                                 this.createIFrame(job);
88                         }
89                 }
91                 this.createIFrame = function(job) {
92                         var iframe = document.createElement("iframe");
93                         if (!window.openid_visible_iframe) {
94                                 iframe.setAttribute("width", 0);
95                                 iframe.setAttribute("height", 0);
96                                 iframe.setAttribute("style", "display: none");
97                         }
98                         iframe.setAttribute("src", job(iframe));
99                         iframe.openidBox = box;
100                         box.parentNode.insertBefore(iframe, box);
101                         this.frames.push(iframe);
102                         return iframe;
103                 };
104                 this.closeFrames = function() {
105                         if (this.frames.length == 0) { return false; }
106                         for (var i = 0; i < this.frames.length; i++) {
107                                 if (this.frames[i].parentNode) { this.frames[i].parentNode.removeChild(this.frames[i]); }
108                         }
109                         while (this.frames.length > 0) { this.frames.pop(); }
110                         return true;
111                 };
112                 this.closeFrame = function(frame) {
113                         if (frame.parentNode) { frame.parentNode.removeChild(frame); }
114                         var removed = this.frames.remove(frame);
115                         this.onJobCompleted();
116                         return removed;
117                 };
118         }
119         
120         box.dnoi_internal.authenticationIFrames = new FrameManager(throttle);
122         box.dnoi_internal.constructButton = function(text, tooltip, onclick) {
123                 var button = document.createElement('button');
124                 button.textContent = text; // Mozilla
125                 button.value = text; // IE
126                 button.title = tooltip != null ? tooltip : '';
127                 button.onclick = onclick;
128                 button.style.visibility = 'hidden';
129                 button.style.position = 'absolute';
130                 button.style.padding = "0px";
131                 button.style.fontSize = '8px';
132                 button.style.top = "1px";
133                 button.style.bottom = "1px";
134                 button.style.right = "2px";
135                 box.parentNode.appendChild(button);
136                 return button;
137         }
139         box.dnoi_internal.constructIcon = function(imageUrl, tooltip, rightSide, visible, height) {
140                 var icon = document.createElement('img');
141                 icon.src = imageUrl;
142                 icon.title = tooltip != null ? tooltip : '';
143                 icon.originalTitle = icon.title;
144                 if (!visible) {
145                         icon.style.visibility = 'hidden';
146                 }
147                 icon.style.position = 'absolute';
148                 icon.style.top = "2px";
149                 icon.style.bottom = "2px"; // for FireFox (and IE7, I think)
150                 if (height) {
151                         icon.style.height = height; // for Chrome and IE8
152                 }
153                 if (rightSide) {
154                         icon.style.right = "2px";
155                 } else {
156                         icon.style.left = "2px";
157                 }
158                 box.parentNode.appendChild(icon);
159                 return icon;
160         }
162         box.dnoi_internal.prefetchImage = function(imageUrl) {
163                 var img = document.createElement('img');
164                 img.src = imageUrl;
165                 img.style.display = 'none';
166                 box.parentNode.appendChild(img);
167                 return img;
168         }
170         function findParentForm(element) {
171                 if (element == null || element.nodeName == "FORM") {
172                         return element;
173                 }
175                 return findParentForm(element.parentNode);
176         };
178         box.parentForm = findParentForm(box);
180         function findOrCreateHiddenField() {
181                 var name = box.name + '_openidAuthData';
182                 var existing = window.document.getElementsByName(name);
183                 if (existing && existing.length > 0) {
184                         return existing[0];
185                 }
187                 var hiddenField = document.createElement('input');
188                 hiddenField.setAttribute("name", name);
189                 hiddenField.setAttribute("type", "hidden");
190                 box.parentForm.appendChild(hiddenField);
191                 return hiddenField;
192         };
194         box.dnoi_internal.loginButton = box.dnoi_internal.constructButton(loginButtonText, loginButtonToolTip, function() {
195                 var discoveryInfo = box.dnoi_internal.authenticationRequests[box.lastDiscoveredIdentifier];
196                 if (discoveryInfo == null) {
197                         trace('Ooops!  Somehow the login button click event was invoked, but no openid discovery information for ' + box.lastDiscoveredIdentifier + ' is available.');
198                         return;
199                 }
200                 // The login button always sends a setup message to the first OP.
201                 var selectedProvider = discoveryInfo[0];
202                 selectedProvider.trySetup();
203                 return false;
204         });
205         box.dnoi_internal.retryButton = box.dnoi_internal.constructButton(retryButtonText, retryButtonToolTip, function() {
206                 box.timeout += 5000; // give the retry attempt 5s longer than the last attempt
207                 box.dnoi_internal.performDiscovery(box.value);
208                 return false;
209         });
210         box.dnoi_internal.openid_logo = box.dnoi_internal.constructIcon(openid_logo_url, null, false, true);
211         box.dnoi_internal.op_logo = box.dnoi_internal.constructIcon('', authenticatedByToolTip, false, false, "16px");
212         box.dnoi_internal.spinner = box.dnoi_internal.constructIcon(spinner_url, busyToolTip, true);
213         box.dnoi_internal.success_icon = box.dnoi_internal.constructIcon(success_icon_url, authenticatedAsToolTip, true);
214         //box.dnoi_internal.failure_icon = box.dnoi_internal.constructIcon(failure_icon_url, authenticationFailedToolTip, true);
216         // Disable the display of the DotNetOpenId logo
217         //box.dnoi_internal.dnoi_logo = box.dnoi_internal.constructIcon(dotnetopenid_logo_url);
218         box.dnoi_internal.dnoi_logo = box.dnoi_internal.openid_logo;
220         box.dnoi_internal.setVisualCue = function(state, authenticatedBy, authenticatedAs) {
221                 box.dnoi_internal.openid_logo.style.visibility = 'hidden';
222                 box.dnoi_internal.dnoi_logo.style.visibility = 'hidden';
223                 box.dnoi_internal.op_logo.style.visibility = 'hidden';
224                 box.dnoi_internal.openid_logo.title = box.dnoi_internal.openid_logo.originalTitle;
225                 box.dnoi_internal.spinner.style.visibility = 'hidden';
226                 box.dnoi_internal.success_icon.style.visibility = 'hidden';
227                 //              box.dnoi_internal.failure_icon.style.visibility = 'hidden';
228                 box.dnoi_internal.loginButton.style.visibility = 'hidden';
229                 box.dnoi_internal.retryButton.style.visibility = 'hidden';
230                 box.title = '';
231                 box.dnoi_internal.state = state;
232                 if (state == "discovering") {
233                         box.dnoi_internal.dnoi_logo.style.visibility = 'visible';
234                         box.dnoi_internal.spinner.style.visibility = 'visible';
235                         box.dnoi_internal.claimedIdentifier = null;
236                         box.title = '';
237                         window.status = "Discovering OpenID Identifier '" + box.value + "'...";
238                 } else if (state == "authenticated") {
239                         var opLogo = box.dnoi_internal.deriveOPFavIcon();
240                         if (opLogo) {
241                                 box.dnoi_internal.op_logo.src = opLogo;
242                                 box.dnoi_internal.op_logo.style.visibility = 'visible';
243                                 box.dnoi_internal.op_logo.title = box.dnoi_internal.op_logo.originalTitle.replace('{0}', authenticatedBy.getHost());
244                         } else {
245                                 box.dnoi_internal.openid_logo.style.visibility = 'visible';
246                                 box.dnoi_internal.openid_logo.title = box.dnoi_internal.op_logo.originalTitle.replace('{0}', authenticatedBy.getHost());
247                         }
248                         box.dnoi_internal.success_icon.style.visibility = 'visible';
249                         box.dnoi_internal.success_icon.title = box.dnoi_internal.success_icon.originalTitle.replace('{0}', authenticatedAs);
250                         box.title = box.dnoi_internal.claimedIdentifier;
251                         window.status = "Authenticated as " + box.value;
252                 } else if (state == "setup") {
253                         var opLogo = box.dnoi_internal.deriveOPFavIcon();
254                         if (opLogo) {
255                                 box.dnoi_internal.op_logo.src = opLogo;
256                                 box.dnoi_internal.op_logo.style.visibility = 'visible';
257                         } else {
258                                 box.dnoi_internal.openid_logo.style.visibility = 'visible';
259                         }
260                         box.dnoi_internal.loginButton.style.visibility = 'visible';
261                         box.dnoi_internal.claimedIdentifier = null;
262                         window.status = "Authentication requires setup.";
263                 } else if (state == "failed") {
264                         box.dnoi_internal.openid_logo.style.visibility = 'visible';
265                         //box.dnoi_internal.failure_icon.style.visibility = 'visible';
266                         box.dnoi_internal.retryButton.style.visibility = 'visible';
267                         box.dnoi_internal.claimedIdentifier = null;
268                         window.status = authenticationFailedToolTip;
269                         box.title = authenticationFailedToolTip;
270                 } else if (state = '' || state == null) {
271                         box.dnoi_internal.openid_logo.style.visibility = 'visible';
272                         box.title = '';
273                         box.dnoi_internal.claimedIdentifier = null;
274                         window.status = null;
275                 } else {
276                         box.dnoi_internal.claimedIdentifier = null;
277                         trace('unrecognized state ' + state);
278                 }
279         }
281         box.dnoi_internal.isBusy = function() {
282                 return box.dnoi_internal.state == 'discovering' || 
283                         box.dnoi_internal.authenticationRequests[box.lastDiscoveredIdentifier].busy();
284         };
286         box.dnoi_internal.canAttemptLogin = function() {
287                 if (box.value.length == 0) return false;
288                 if (box.dnoi_internal.authenticationRequests[box.value] == null) return false;
289                 if (box.dnoi_internal.state == 'failed') return false;
290                 return true;
291         };
293         box.dnoi_internal.getUserSuppliedIdentifierResults = function() {
294                 return box.dnoi_internal.authenticationRequests[box.value];
295         }
297         box.dnoi_internal.isAuthenticated = function() {
298                 return box.dnoi_internal.getUserSuppliedIdentifierResults().findSuccessfulRequest() != null;
299         }
301         box.dnoi_internal.onSubmit = function() {
302                 var hiddenField = findOrCreateHiddenField();
303                 if (box.dnoi_internal.isAuthenticated()) {
304                         // stick the result in a hidden field so the RP can verify it
305                         hiddenField.setAttribute("value", box.dnoi_internal.authenticationRequests[box.value].successAuthData);
306                 } else {
307                         hiddenField.setAttribute("value", '');
308                         if (box.dnoi_internal.isBusy()) {
309                                 alert(loginInProgressMessage);
310                         } else {
311                                 if (box.value.length > 0) {
312                                         // submitPending will be true if we've already tried deferring submit for a login,
313                                         // in which case we just want to display a box to the user.
314                                         if (box.dnoi_internal.submitPending || !box.dnoi_internal.canAttemptLogin()) {
315                                                 alert(identifierRequiredMessage);
316                                         } else {
317                                                 // The user hasn't clicked "Login" yet.  We'll click login for him,
318                                                 // after leaving a note for ourselves to automatically click submit
319                                                 // when login is complete.
320                                                 box.dnoi_internal.submitPending = box.dnoi_internal.submitButtonJustClicked;
321                                                 if (box.dnoi_internal.submitPending == null) {
322                                                         box.dnoi_internal.submitPending = true;
323                                                 }
324                                                 box.dnoi_internal.loginButton.onclick();
325                                                 return false; // abort submit for now
326                                         }
327                                 } else {
328                                         return true;
329                                 }
330                         }
331                         return false;
332                 }
333                 return true;
334         };
336         /// <summary>
337         /// Records which submit button caused this openid box to question whether it
338         /// was ready to submit the user's identifier so that that button can be re-invoked
339         /// automatically after authentication completes.
340         /// </summary>
341         box.dnoi_internal.setLastSubmitButtonClicked = function(evt) {
342                 var button;
343                 if (evt.target) {
344                         button = evt.target;
345                 } else {
346                         button = evt.srcElement;
347                 }
349                 box.dnoi_internal.submitButtonJustClicked = button;
350         };
352         // Find all submit buttons and hook their click events so that we can validate
353         // whether we are ready for the user to postback.
354         var inputs = document.getElementsByTagName('input');
355         for (var i = 0; i < inputs.length; i++) {
356                 var el = inputs[i];
357                 if (el.type == 'submit') {
358                         if (el.attachEvent) {
359                                 el.attachEvent("onclick", box.dnoi_internal.setLastSubmitButtonClicked);
360                         } else {
361                                 el.addEventListener("click", box.dnoi_internal.setLastSubmitButtonClicked, true);
362                         }
363                 }
364         }
366         /// <summary>
367         /// Returns the URL of the authenticating OP's logo so it can be displayed to the user.
368         /// </summary>
369         box.dnoi_internal.deriveOPFavIcon = function() {
370                 var response = box.dnoi_internal.getUserSuppliedIdentifierResults().successAuthData;
371                 if (!response || response.length == 0) return;
372                 var authResult = new Uri(response);
373                 var opUri;
374                 if (authResult.getQueryArgValue("openid.op_endpoint")) {
375                         opUri = new Uri(authResult.getQueryArgValue("openid.op_endpoint"));
376                 } if (authResult.getQueryArgValue("dotnetopenid.op_endpoint")) {
377                         opUri = new Uri(authResult.getQueryArgValue("dotnetopenid.op_endpoint"));
378                 } else if (authResult.getQueryArgValue("openid.user_setup_url")) {
379                         opUri = new Uri(authResult.getQueryArgValue("openid.user_setup_url"));
380                 } else return null;
381                 var favicon = opUri.getAuthority() + "/favicon.ico";
382                 return favicon;
383         };
385         box.dnoi_internal.createDiscoveryInfo = function(discoveryInfo, identifier) {
386                 this.identifier = identifier;
387                 // The claimed identifier may be null if the user provided an OP Identifier.
388                 this.claimedIdentifier = discoveryInfo.claimedIdentifier;
389                 trace('Discovered claimed identifier: ' + this.claimedIdentifier);
391                 // Add extra tracking bits and behaviors.
392                 this.findByEndpoint = function(opEndpoint) {
393                         for (var i = 0; i < this.length; i++) {
394                                 if (this[i].endpoint == opEndpoint) {
395                                         return this[i];
396                                 }
397                         }
398                 };
399                 this.findSuccessfulRequest = function() {
400                         for (var i = 0; i < this.length; i++) {
401                                 if (this[i].result == authSuccess) {
402                                         return this[i];
403                                 }
404                         }
405                 };
406                 this.busy = function() {
407                         for (var i = 0; i < this.length; i++) {
408                                 if (this[i].busy()) {
409                                         return true;
410                                 }
411                         }
412                 };
413                 this.abortAll = function() {
414                         // Abort all other asynchronous authentication attempts that may be in progress.
415                         box.dnoi_internal.authenticationIFrames.cancelAllWork();
416                         for (var i = 0; i < this.length; i++) {
417                                 this[i].abort();
418                         }
419                 };
420                 this.tryImmediate = function() {
421                         if (this.length > 0) {
422                                 for (var i = 0; i < this.length; i++) {
423                                         box.dnoi_internal.authenticationIFrames.enqueueWork(this[i].tryImmediate);
424                                 }
425                         } else {
426                                 box.dnoi_internal.discoveryFailed(null, this.identifier);
427                         }
428                 };
430                 this.length = discoveryInfo.requests.length;
431                 for (var i = 0; i < discoveryInfo.requests.length; i++) {
432                         this[i] = new box.dnoi_internal.createTrackingRequest(discoveryInfo.requests[i], identifier);
433                 }
434         };
436         box.dnoi_internal.createTrackingRequest = function(requestInfo, identifier) {
437                 // It's possible during a postback that discovered request URLs are not available.
438                 this.immediate = requestInfo.immediate ? new Uri(requestInfo.immediate) : null;
439                 this.setup = requestInfo.setup ? new Uri(requestInfo.setup) : null;
440                 this.endpoint = new Uri(requestInfo.endpoint);
441                 this.identifier = identifier;
442                 var self = this; // closure so that delegates have the right instance
444                 this.host = self.endpoint.getHost();
446                 this.getDiscoveryInfo = function() {
447                         return box.dnoi_internal.authenticationRequests[self.identifier];
448                 }
450                 this.busy = function() {
451                         return self.iframe != null || self.popup != null;
452                 };
454                 this.completeAttempt = function() {
455                         if (!self.busy()) return false;
456                         if (self.iframe) {
457                                 trace('iframe hosting ' + self.endpoint + ' now CLOSING.');
458                                 box.dnoi_internal.authenticationIFrames.closeFrame(self.iframe);
459                                 self.iframe = null;
460                         }
461                         if (self.popup) {
462                                 self.popup.close();
463                                 self.popup = null;
464                         }
465                         if (self.timeout) {
466                                 window.clearTimeout(self.timeout);
467                                 self.timeout = null;
468                         }
470                         if (!self.getDiscoveryInfo().busy() && self.getDiscoveryInfo().findSuccessfulRequest() == null) {
471                                 trace('No asynchronous authentication attempt is in progress.  Display setup view.');
472                                 // visual cue that auth failed
473                                 box.dnoi_internal.setVisualCue('setup');
474                         }
476                         return true;
477                 };
479                 this.authenticationTimedOut = function() {
480                         if (self.completeAttempt()) {
481                                 trace(self.host + " timed out");
482                                 self.result = timedOut;
483                         }
484                 };
485                 this.authSuccess = function(authUri) {
486                         if (self.completeAttempt()) {
487                                 trace(self.host + " authenticated!");
488                                 self.result = authSuccess;
489                                 self.response = authUri;
490                                 box.dnoi_internal.authenticationRequests[self.identifier].abortAll();
491                         }
492                 };
493                 this.authFailed = function() {
494                         if (self.completeAttempt()) {
495                                 //trace(self.host + " failed authentication");
496                                 self.result = authRefused;
497                         }
498                 };
499                 this.abort = function() {
500                         if (self.completeAttempt()) {
501                                 trace(self.host + " aborted");
502                                 // leave the result as whatever it was before.
503                         }
504                 };
506                 this.tryImmediate = function(iframe) {
507                         self.abort(); // ensure no concurrent attempts
508                         self.timeout = setTimeout(function() { self.authenticationTimedOut(); }, box.timeout);
509                         trace('iframe hosting ' + self.endpoint + ' now OPENING.');
510                         self.iframe = iframe;
511                         //trace('initiating auth attempt with: ' + self.immediate);
512                         return self.immediate;
513                 };
514                 this.trySetup = function() {
515                         self.abort(); // ensure no concurrent attempts
516                         window.waiting_openidBox = box;
517                         self.popup = window.open(self.setup, 'opLogin', 'status=0,toolbar=0,location=1,resizable=1,scrollbars=1,width=800,height=600');
518                 };
519         };
521         /*****************************************
522         * Flow
523         *****************************************/
525         /// <summary>Called to initiate discovery on some identifier.</summary>
526         box.dnoi_internal.performDiscovery = function(identifier) {
527                 box.dnoi_internal.authenticationIFrames.closeFrames();
528                 box.dnoi_internal.setVisualCue('discovering');
529                 box.lastDiscoveredIdentifier = identifier;
530                 box.dnoi_internal.discoverIdentifier(identifier, box.dnoi_internal.discoveryResult, box.dnoi_internal.discoveryFailed);
531         };
533         /// <summary>Callback that is invoked when discovery fails.</summary>
534         box.dnoi_internal.discoveryFailed = function(message, identifier) {
535                 box.dnoi_internal.setVisualCue('failed');
536                 if (message) { box.title = message; }
537         }
539         /// <summary>Callback that is invoked when discovery results are available.</summary>
540         /// <param name="discoveryResult">The JSON object containing the OpenID auth requests.</param>
541         /// <param name="identifier">The identifier that discovery was performed on.</param>
542         box.dnoi_internal.discoveryResult = function(discoveryResult, identifier) {
543                 // Deserialize the JSON object and store the result if it was a successful discovery.
544                 discoveryResult = eval('(' + discoveryResult + ')');
545                 // Store the discovery results and added behavior for later use.
546                 box.dnoi_internal.authenticationRequests[identifier] = discoveryBehavior = new box.dnoi_internal.createDiscoveryInfo(discoveryResult, identifier);
548                 // Only act on the discovery event if we're still interested in the result.
549                 // If the user already changed the identifier since discovery was initiated,
550                 // we aren't interested in it any more.
551                 if (identifier == box.lastDiscoveredIdentifier) {
552                         discoveryBehavior.tryImmediate();
553                 }
554         }
556         /// <summary>Invoked by RP web server when an authentication has completed.</summary>
557         /// <remarks>The duty of this method is to distribute the notification to the appropriate tracking object.</remarks>
558         box.dnoi_internal.processAuthorizationResult = function(resultUrl) {
559                 self.waiting_openidBox = null;
560                 //trace('processAuthorizationResult ' + resultUrl);
561                 var resultUri = new Uri(resultUrl);
563                 // Find the tracking object responsible for this request.
564                 var discoveryInfo = box.dnoi_internal.authenticationRequests[resultUri.getQueryArgValue('dotnetopenid.userSuppliedIdentifier')];
565                 if (discoveryInfo == null) {
566                         trace('processAuthorizationResult called but no userSuppliedIdentifier parameter was found.  Exiting function.');
567                         return;
568                 }
569                 var opEndpoint = resultUri.getQueryArgValue("openid.op_endpoint") ? resultUri.getQueryArgValue("openid.op_endpoint") : resultUri.getQueryArgValue("dotnetopenid.op_endpoint");
570                 var tracker = discoveryInfo.findByEndpoint(opEndpoint);
571                 //trace('Auth result for ' + tracker.host + ' received:\n' + resultUrl);
573                 if (isAuthSuccessful(resultUri)) {
574                         tracker.authSuccess(resultUri);
576                         discoveryInfo.successAuthData = resultUrl;
577                         var claimed_id = resultUri.getQueryArgValue("openid.claimed_id");
578                         if (claimed_id && claimed_id != discoveryInfo.claimedIdentifier) {
579                                 discoveryInfo.claimedIdentifier = resultUri.getQueryArgValue("openid.claimed_id");
580                                 trace('Authenticated as ' + claimed_id);
581                         }
583                         // visual cue that auth was successful
584                         box.dnoi_internal.claimedIdentifier = discoveryInfo.claimedIdentifier;
585                         box.dnoi_internal.setVisualCue('authenticated', tracker.endpoint, discoveryInfo.claimedIdentifier);
586                         if (box.dnoi_internal.onauthenticated) {
587                                 box.dnoi_internal.onauthenticated(box);
588                         }
589                         if (box.dnoi_internal.submitPending) {
590                                 // We submit the form BEFORE resetting the submitPending so
591                                 // the submit handler knows we've already tried this route.
592                                 if (box.dnoi_internal.submitPending == true) {
593                                         box.parentForm.submit();
594                                 } else {
595                                         box.dnoi_internal.submitPending.click();
596                                 }
597                         }
598                 } else {
599                         tracker.authFailed();
600                 }
602                 box.dnoi_internal.submitPending = null;
603         };
605         function isAuthSuccessful(resultUri) {
606                 if (isOpenID2Response(resultUri)) {
607                         return resultUri.getQueryArgValue("openid.mode") == "id_res";
608                 } else {
609                         return resultUri.getQueryArgValue("openid.mode") == "id_res" && !resultUri.containsQueryArg("openid.user_setup_url");
610                 }
611         };
613         function isOpenID2Response(resultUri) {
614                 return resultUri.containsQueryArg("openid.ns");
615         };
617         box.onblur = function(event) {
618                 var discoveryInfo = box.dnoi_internal.authenticationRequests[box.value];
619                 if (discoveryInfo == null) {
620                         if (box.value.length > 0) {
621                                 box.dnoi_internal.performDiscovery(box.value);
622                         } else {
623                                 box.dnoi_internal.setVisualCue();
624                         }
625                 } else {
626                         if ((priorSuccess = discoveryInfo.findSuccessfulRequest())) {
627                                 box.dnoi_internal.setVisualCue('authenticated', priorSuccess.endpoint, discoveryInfo.claimedIdentifier);
628                         } else {
629                                 discoveryInfo.tryImmediate();
630                         }
631                 }
632                 return true;
633         };
634         box.onkeyup = function(event) {
635                 box.dnoi_internal.setVisualCue();
636                 return true;
637         };
639         box.getClaimedIdentifier = function() { return box.dnoi_internal.claimedIdentifier; };
641         // Restore a previously achieved state (from pre-postback) if it is given.
642         var oldAuth = findOrCreateHiddenField().value;
643         if (oldAuth.length > 0) {
644                 var oldAuthResult = new Uri(oldAuth);
645                 // The control ensures that we ALWAYS have an OpenID 2.0-style claimed_id attribute, even against
646                 // 1.0 Providers via the return_to URL mechanism.
647                 var claimedId = oldAuthResult.getQueryArgValue("dotnetopenid.claimed_id");
648                 var endpoint = oldAuthResult.getQueryArgValue("dotnetopenid.op_endpoint");
649                 // We weren't given a full discovery history, but we can spoof this much from the
650                 // authentication assertion.
651                 box.dnoi_internal.authenticationRequests[box.value] = new box.dnoi_internal.createDiscoveryInfo({
652                         claimedIdentifier: claimedId,
653                         requests: [{ endpoint: endpoint }]
654                 }, box.value);
656                 box.dnoi_internal.processAuthorizationResult(oldAuthResult.toString());
657         }
660 function Uri(url) {
661         this.originalUri = url;
663         this.toString = function() {
664                 return this.originalUri;
665         };
667         this.getAuthority = function() {
668                 var authority = this.getScheme() + "://" + this.getHost();
669                 return authority;
670         }
672         this.getHost = function() {
673                 var hostStartIdx = this.originalUri.indexOf("://") + 3;
674                 var hostEndIndex = this.originalUri.indexOf("/", hostStartIdx);
675                 if (hostEndIndex < 0) hostEndIndex = this.originalUri.length;
676                 var host = this.originalUri.substr(hostStartIdx, hostEndIndex - hostStartIdx);
677                 return host;
678         }
680         this.getScheme = function() {
681                 var schemeStartIdx = this.indexOf("://");
682                 return this.originalUri.substr(this.originalUri, schemeStartIdx);
683         }
685         this.trimFragment = function() {
686                 var hashmark = this.originalUri.indexOf('#');
687                 if (hashmark >= 0) {
688                         return new Uri(this.originalUri.substr(0, hashmark));
689                 }
690                 return this;
691         };
693         this.appendQueryVariable = function(name, value) {
694                 var pair = encodeURI(name) + "=" + encodeURI(value);
695                 if (this.originalUri.indexOf('?') >= 0) {
696                         this.originalUri = this.originalUri + "&" + pair;
697                 } else {
698                         this.originalUri = this.originalUri + "?" + pair;
699                 }
700         };
702         function KeyValuePair(key, value) {
703                 this.key = key;
704                 this.value = value;
705         };
707         this.Pairs = new Array();
709         var queryBeginsAt = this.originalUri.indexOf('?');
710         if (queryBeginsAt >= 0) {
711                 this.queryString = url.substr(queryBeginsAt + 1);
712                 var queryStringPairs = this.queryString.split('&');
714                 for (var i = 0; i < queryStringPairs.length; i++) {
715                         var pair = queryStringPairs[i].split('=');
716                         this.Pairs.push(new KeyValuePair(unescape(pair[0]), unescape(pair[1])))
717                 }
718         };
720         this.getQueryArgValue = function(key) {
721                 for (var i = 0; i < this.Pairs.length; i++) {
722                         if (this.Pairs[i].key == key) {
723                                 return this.Pairs[i].value;
724                         }
725                 }
726         };
728         this.containsQueryArg = function(key) {
729                 return this.getQueryArgValue(key);
730         };
732         this.indexOf = function(args) {
733                 return this.originalUri.indexOf(args);
734         };
736         return this;