blob: e8e653bfdc1bfdfdab9d6143c5e3082960899a80 [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001//Copyright 2014-2015 Google Inc. All rights reserved.
2
3//Use of this source code is governed by a BSD-style
4//license that can be found in the LICENSE file or at
5//https://developers.google.com/open-source/licenses/bsd
6
7// ref: https://github.com/google/u2f-ref-code/blob/master/u2f-gae-demo/war/js/u2f-api.js
8
9/**
10 * @fileoverview The U2F api.
11 */
12'use strict';
13
14/**
15 * Modification:
16 * Wrap implementation so that we can exit if window.u2f is already supplied by the browser (see below).
17 */
18(function (root) {
19 /**
20 * Modification:
21 * Only continue load this library if window.u2f is not already supplied by the browser.
22 */
23 var browserImplementsU2f = !!((typeof root.u2f !== 'undefined') && root.u2f.register);
24
25 if (browserImplementsU2f) {
26 root.u2f.isSupported = true;
27 return;
28 }
29
30 /**
31 * Namespace for the U2F api.
32 * @type {Object}
33 */
34 var u2f = root.u2f || {};
35
36 /**
37 * Modification:
38 * Check if browser supports U2F API before this wrapper was added.
39 */
40 u2f.isSupported = !!(((typeof u2f !== 'undefined') && u2f.register) || ((typeof chrome !== 'undefined') && chrome.runtime));
41
42 /**
43 * FIDO U2F Javascript API Version
44 * @number
45 */
46 var js_api_version;
47
48 /**
49 * The U2F extension id
50 * @const {string}
51 */
52 // The Chrome packaged app extension ID.
53 // Uncomment this if you want to deploy a server instance that uses
54 // the package Chrome app and does not require installing the U2F Chrome extension.
55 u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
56 // The U2F Chrome extension ID.
57 // Uncomment this if you want to deploy a server instance that uses
58 // the U2F Chrome extension to authenticate.
59 // u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne';
60
61
62 /**
63 * Message types for messsages to/from the extension
64 * @const
65 * @enum {string}
66 */
67 u2f.MessageTypes = {
68 'U2F_REGISTER_REQUEST': 'u2f_register_request',
69 'U2F_REGISTER_RESPONSE': 'u2f_register_response',
70 'U2F_SIGN_REQUEST': 'u2f_sign_request',
71 'U2F_SIGN_RESPONSE': 'u2f_sign_response',
72 'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request',
73 'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response'
74 };
75
76
77 /**
78 * Response status codes
79 * @const
80 * @enum {number}
81 */
82 u2f.ErrorCodes = {
83 'OK': 0,
84 'OTHER_ERROR': 1,
85 'BAD_REQUEST': 2,
86 'CONFIGURATION_UNSUPPORTED': 3,
87 'DEVICE_INELIGIBLE': 4,
88 'TIMEOUT': 5
89 };
90
91
92 /**
93 * A message for registration requests
94 * @typedef {{
95 * type: u2f.MessageTypes,
96 * appId: ?string,
97 * timeoutSeconds: ?number,
98 * requestId: ?number
99 * }}
100 */
101 u2f.U2fRequest;
102
103
104 /**
105 * A message for registration responses
106 * @typedef {{
107 * type: u2f.MessageTypes,
108 * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse),
109 * requestId: ?number
110 * }}
111 */
112 u2f.U2fResponse;
113
114
115 /**
116 * An error object for responses
117 * @typedef {{
118 * errorCode: u2f.ErrorCodes,
119 * errorMessage: ?string
120 * }}
121 */
122 u2f.Error;
123
124 /**
125 * Data object for a single sign request.
126 * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}}
127 */
128 u2f.Transport;
129
130
131 /**
132 * Data object for a single sign request.
133 * @typedef {Array<u2f.Transport>}
134 */
135 u2f.Transports;
136
137 /**
138 * Data object for a single sign request.
139 * @typedef {{
140 * version: string,
141 * challenge: string,
142 * keyHandle: string,
143 * appId: string
144 * }}
145 */
146 u2f.SignRequest;
147
148
149 /**
150 * Data object for a sign response.
151 * @typedef {{
152 * keyHandle: string,
153 * signatureData: string,
154 * clientData: string
155 * }}
156 */
157 u2f.SignResponse;
158
159
160 /**
161 * Data object for a registration request.
162 * @typedef {{
163 * version: string,
164 * challenge: string
165 * }}
166 */
167 u2f.RegisterRequest;
168
169
170 /**
171 * Data object for a registration response.
172 * @typedef {{
173 * version: string,
174 * keyHandle: string,
175 * transports: Transports,
176 * appId: string
177 * }}
178 */
179 u2f.RegisterResponse;
180
181
182 /**
183 * Data object for a registered key.
184 * @typedef {{
185 * version: string,
186 * keyHandle: string,
187 * transports: ?Transports,
188 * appId: ?string
189 * }}
190 */
191 u2f.RegisteredKey;
192
193
194 /**
195 * Data object for a get API register response.
196 * @typedef {{
197 * js_api_version: number
198 * }}
199 */
200 u2f.GetJsApiVersionResponse;
201
202
203 //Low level MessagePort API support
204
205 /**
206 * Sets up a MessagePort to the U2F extension using the
207 * available mechanisms.
208 * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
209 */
210 u2f.getMessagePort = function (callback) {
211 if (typeof chrome != 'undefined' && chrome.runtime) {
212 // The actual message here does not matter, but we need to get a reply
213 // for the callback to run. Thus, send an empty signature request
214 // in order to get a failure response.
215 var msg = {
216 type: u2f.MessageTypes.U2F_SIGN_REQUEST,
217 signRequests: []
218 };
219 chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function () {
220 if (!chrome.runtime.lastError) {
221 // We are on a whitelisted origin and can talk directly
222 // with the extension.
223 u2f.getChromeRuntimePort_(callback);
224 } else {
225 // chrome.runtime was available, but we couldn't message
226 // the extension directly, use iframe
227 u2f.getIframePort_(callback);
228 }
229 });
230 } else if (u2f.isAndroidChrome_()) {
231 u2f.getAuthenticatorPort_(callback);
232 } else if (u2f.isIosChrome_()) {
233 u2f.getIosPort_(callback);
234 } else {
235 // chrome.runtime was not available at all, which is normal
236 // when this origin doesn't have access to any extensions.
237 u2f.getIframePort_(callback);
238 }
239 };
240
241 /**
242 * Detect chrome running on android based on the browser's useragent.
243 * @private
244 */
245 u2f.isAndroidChrome_ = function () {
246 var userAgent = navigator.userAgent;
247 return userAgent.indexOf('Chrome') != -1 &&
248 userAgent.indexOf('Android') != -1;
249 };
250
251 /**
252 * Detect chrome running on iOS based on the browser's platform.
253 * @private
254 */
255 u2f.isIosChrome_ = function () {
256 return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1;
257 };
258
259 /**
260 * Connects directly to the extension via chrome.runtime.connect.
261 * @param {function(u2f.WrappedChromeRuntimePort_)} callback
262 * @private
263 */
264 u2f.getChromeRuntimePort_ = function (callback) {
265 var port = chrome.runtime.connect(u2f.EXTENSION_ID,
266 { 'includeTlsChannelId': true });
267 setTimeout(function () {
268 callback(new u2f.WrappedChromeRuntimePort_(port));
269 }, 0);
270 };
271
272 /**
273 * Return a 'port' abstraction to the Authenticator app.
274 * @param {function(u2f.WrappedAuthenticatorPort_)} callback
275 * @private
276 */
277 u2f.getAuthenticatorPort_ = function (callback) {
278 setTimeout(function () {
279 callback(new u2f.WrappedAuthenticatorPort_());
280 }, 0);
281 };
282
283 /**
284 * Return a 'port' abstraction to the iOS client app.
285 * @param {function(u2f.WrappedIosPort_)} callback
286 * @private
287 */
288 u2f.getIosPort_ = function (callback) {
289 setTimeout(function () {
290 callback(new u2f.WrappedIosPort_());
291 }, 0);
292 };
293
294 /**
295 * A wrapper for chrome.runtime.Port that is compatible with MessagePort.
296 * @param {Port} port
297 * @constructor
298 * @private
299 */
300 u2f.WrappedChromeRuntimePort_ = function (port) {
301 this.port_ = port;
302 };
303
304 /**
305 * Format and return a sign request compliant with the JS API version supported by the extension.
306 * @param {Array<u2f.SignRequest>} signRequests
307 * @param {number} timeoutSeconds
308 * @param {number} reqId
309 * @return {Object}
310 */
311 u2f.formatSignRequest_ =
312 function (appId, challenge, registeredKeys, timeoutSeconds, reqId) {
313 if (js_api_version === undefined || js_api_version < 1.1) {
314 // Adapt request to the 1.0 JS API
315 var signRequests = [];
316 for (var i = 0; i < registeredKeys.length; i++) {
317 signRequests[i] = {
318 version: registeredKeys[i].version,
319 challenge: challenge,
320 keyHandle: registeredKeys[i].keyHandle,
321 appId: appId
322 };
323 }
324 return {
325 type: u2f.MessageTypes.U2F_SIGN_REQUEST,
326 signRequests: signRequests,
327 timeoutSeconds: timeoutSeconds,
328 requestId: reqId
329 };
330 }
331 // JS 1.1 API
332 return {
333 type: u2f.MessageTypes.U2F_SIGN_REQUEST,
334 appId: appId,
335 challenge: challenge,
336 registeredKeys: registeredKeys,
337 timeoutSeconds: timeoutSeconds,
338 requestId: reqId
339 };
340 };
341
342 /**
343 * Format and return a register request compliant with the JS API version supported by the extension..
344 * @param {Array<u2f.SignRequest>} signRequests
345 * @param {Array<u2f.RegisterRequest>} signRequests
346 * @param {number} timeoutSeconds
347 * @param {number} reqId
348 * @return {Object}
349 */
350 u2f.formatRegisterRequest_ =
351 function (appId, registeredKeys, registerRequests, timeoutSeconds, reqId) {
352 if (js_api_version === undefined || js_api_version < 1.1) {
353 // Adapt request to the 1.0 JS API
354 for (var i = 0; i < registerRequests.length; i++) {
355 registerRequests[i].appId = appId;
356 }
357 var signRequests = [];
358 for (var i = 0; i < registeredKeys.length; i++) {
359 signRequests[i] = {
360 version: registeredKeys[i].version,
361 challenge: registerRequests[0],
362 keyHandle: registeredKeys[i].keyHandle,
363 appId: appId
364 };
365 }
366 return {
367 type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
368 signRequests: signRequests,
369 registerRequests: registerRequests,
370 timeoutSeconds: timeoutSeconds,
371 requestId: reqId
372 };
373 }
374 // JS 1.1 API
375 return {
376 type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
377 appId: appId,
378 registerRequests: registerRequests,
379 registeredKeys: registeredKeys,
380 timeoutSeconds: timeoutSeconds,
381 requestId: reqId
382 };
383 };
384
385
386 /**
387 * Posts a message on the underlying channel.
388 * @param {Object} message
389 */
390 u2f.WrappedChromeRuntimePort_.prototype.postMessage = function (message) {
391 this.port_.postMessage(message);
392 };
393
394
395 /**
396 * Emulates the HTML 5 addEventListener interface. Works only for the
397 * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage.
398 * @param {string} eventName
399 * @param {function({data: Object})} handler
400 */
401 u2f.WrappedChromeRuntimePort_.prototype.addEventListener =
402 function (eventName, handler) {
403 var name = eventName.toLowerCase();
404 if (name == 'message' || name == 'onmessage') {
405 this.port_.onMessage.addListener(function (message) {
406 // Emulate a minimal MessageEvent object
407 handler({ 'data': message });
408 });
409 } else {
410 console.error('WrappedChromeRuntimePort only supports onMessage');
411 }
412 };
413
414 /**
415 * Wrap the Authenticator app with a MessagePort interface.
416 * @constructor
417 * @private
418 */
419 u2f.WrappedAuthenticatorPort_ = function () {
420 this.requestId_ = -1;
421 this.requestObject_ = null;
422 }
423
424 /**
425 * Launch the Authenticator intent.
426 * @param {Object} message
427 */
428 u2f.WrappedAuthenticatorPort_.prototype.postMessage = function (message) {
429 var intentUrl =
430 u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
431 ';S.request=' + encodeURIComponent(JSON.stringify(message)) +
432 ';end';
433 document.location = intentUrl;
434 };
435
436 /**
437 * Tells what type of port this is.
438 * @return {String} port type
439 */
440 u2f.WrappedAuthenticatorPort_.prototype.getPortType = function () {
441 return "WrappedAuthenticatorPort_";
442 };
443
444
445 /**
446 * Emulates the HTML 5 addEventListener interface.
447 * @param {string} eventName
448 * @param {function({data: Object})} handler
449 */
450 u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function (eventName, handler) {
451 var name = eventName.toLowerCase();
452 if (name == 'message') {
453 var self = this;
454 /* Register a callback to that executes when
455 * chrome injects the response. */
456 window.addEventListener(
457 'message', self.onRequestUpdate_.bind(self, handler), false);
458 } else {
459 console.error('WrappedAuthenticatorPort only supports message');
460 }
461 };
462
463 /**
464 * Callback invoked when a response is received from the Authenticator.
465 * @param function({data: Object}) callback
466 * @param {Object} message message Object
467 */
468 u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ =
469 function (callback, message) {
470 var messageObject = JSON.parse(message.data);
471 var intentUrl = messageObject['intentURL'];
472
473 var errorCode = messageObject['errorCode'];
474 var responseObject = null;
475 if (messageObject.hasOwnProperty('data')) {
476 responseObject = /** @type {Object} */ (
477 JSON.parse(messageObject['data']));
478 }
479
480 callback({ 'data': responseObject });
481 };
482
483 /**
484 * Base URL for intents to Authenticator.
485 * @const
486 * @private
487 */
488 u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
489 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE';
490
491 /**
492 * Wrap the iOS client app with a MessagePort interface.
493 * @constructor
494 * @private
495 */
496 u2f.WrappedIosPort_ = function () { };
497
498 /**
499 * Launch the iOS client app request
500 * @param {Object} message
501 */
502 u2f.WrappedIosPort_.prototype.postMessage = function (message) {
503 var str = JSON.stringify(message);
504 var url = "u2f://auth?" + encodeURI(str);
505 location.replace(url);
506 };
507
508 /**
509 * Tells what type of port this is.
510 * @return {String} port type
511 */
512 u2f.WrappedIosPort_.prototype.getPortType = function () {
513 return "WrappedIosPort_";
514 };
515
516 /**
517 * Emulates the HTML 5 addEventListener interface.
518 * @param {string} eventName
519 * @param {function({data: Object})} handler
520 */
521 u2f.WrappedIosPort_.prototype.addEventListener = function (eventName, handler) {
522 var name = eventName.toLowerCase();
523 if (name !== 'message') {
524 console.error('WrappedIosPort only supports message');
525 }
526 };
527
528 /**
529 * Sets up an embedded trampoline iframe, sourced from the extension.
530 * @param {function(MessagePort)} callback
531 * @private
532 */
533 u2f.getIframePort_ = function (callback) {
534 // Create the iframe
535 var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID;
536 var iframe = document.createElement('iframe');
537 iframe.src = iframeOrigin + '/u2f-comms.html';
538 iframe.setAttribute('style', 'display:none');
539 document.body.appendChild(iframe);
540
541 var channel = new MessageChannel();
542 var ready = function (message) {
543 if (message.data == 'ready') {
544 channel.port1.removeEventListener('message', ready);
545 callback(channel.port1);
546 } else {
547 console.error('First event on iframe port was not "ready"');
548 }
549 };
550 channel.port1.addEventListener('message', ready);
551 channel.port1.start();
552
553 iframe.addEventListener('load', function () {
554 // Deliver the port to the iframe and initialize
555 iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]);
556 });
557 };
558
559
560 //High-level JS API
561
562 /**
563 * Default extension response timeout in seconds.
564 * @const
565 */
566 u2f.EXTENSION_TIMEOUT_SEC = 30;
567
568 /**
569 * A singleton instance for a MessagePort to the extension.
570 * @type {MessagePort|u2f.WrappedChromeRuntimePort_}
571 * @private
572 */
573 u2f.port_ = null;
574
575 /**
576 * Callbacks waiting for a port
577 * @type {Array<function((MessagePort|u2f.WrappedChromeRuntimePort_))>}
578 * @private
579 */
580 u2f.waitingForPort_ = [];
581
582 /**
583 * A counter for requestIds.
584 * @type {number}
585 * @private
586 */
587 u2f.reqCounter_ = 0;
588
589 /**
590 * A map from requestIds to client callbacks
591 * @type {Object.<number,(function((u2f.Error|u2f.RegisterResponse))
592 * |function((u2f.Error|u2f.SignResponse)))>}
593 * @private
594 */
595 u2f.callbackMap_ = {};
596
597 /**
598 * Creates or retrieves the MessagePort singleton to use.
599 * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
600 * @private
601 */
602 u2f.getPortSingleton_ = function (callback) {
603 if (u2f.port_) {
604 callback(u2f.port_);
605 } else {
606 if (u2f.waitingForPort_.length == 0) {
607 u2f.getMessagePort(function (port) {
608 u2f.port_ = port;
609 u2f.port_.addEventListener('message',
610 /** @type {function(Event)} */(u2f.responseHandler_));
611
612 // Careful, here be async callbacks. Maybe.
613 while (u2f.waitingForPort_.length)
614 u2f.waitingForPort_.shift()(u2f.port_);
615 });
616 }
617 u2f.waitingForPort_.push(callback);
618 }
619 };
620
621 /**
622 * Handles response messages from the extension.
623 * @param {MessageEvent.<u2f.Response>} message
624 * @private
625 */
626 u2f.responseHandler_ = function (message) {
627 var response = message.data;
628 var reqId = response['requestId'];
629 if (!reqId || !u2f.callbackMap_[reqId]) {
630 console.error('Unknown or missing requestId in response.');
631 return;
632 }
633 var cb = u2f.callbackMap_[reqId];
634 delete u2f.callbackMap_[reqId];
635 cb(response['responseData']);
636 };
637
638 /**
639 * Dispatches an array of sign requests to available U2F tokens.
640 * If the JS API version supported by the extension is unknown, it first sends a
641 * message to the extension to find out the supported API version and then it sends
642 * the sign request.
643 * @param {string=} appId
644 * @param {string=} challenge
645 * @param {Array<u2f.RegisteredKey>} registeredKeys
646 * @param {function((u2f.Error|u2f.SignResponse))} callback
647 * @param {number=} opt_timeoutSeconds
648 */
649 u2f.sign = function (appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
650 if (js_api_version === undefined) {
651 // Send a message to get the extension to JS API version, then send the actual sign request.
652 u2f.getApiVersion(
653 function (response) {
654 js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version'];
655 console.log("Extension JS API Version: ", js_api_version);
656 u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
657 });
658 } else {
659 // We know the JS API version. Send the actual sign request in the supported API version.
660 u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds);
661 }
662 };
663
664 /**
665 * Dispatches an array of sign requests to available U2F tokens.
666 * @param {string=} appId
667 * @param {string=} challenge
668 * @param {Array<u2f.RegisteredKey>} registeredKeys
669 * @param {function((u2f.Error|u2f.SignResponse))} callback
670 * @param {number=} opt_timeoutSeconds
671 */
672 u2f.sendSignRequest = function (appId, challenge, registeredKeys, callback, opt_timeoutSeconds) {
673 u2f.getPortSingleton_(function (port) {
674 var reqId = ++u2f.reqCounter_;
675 u2f.callbackMap_[reqId] = callback;
676 var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
677 opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
678 var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId);
679 port.postMessage(req);
680 });
681 };
682
683 /**
684 * Dispatches register requests to available U2F tokens. An array of sign
685 * requests identifies already registered tokens.
686 * If the JS API version supported by the extension is unknown, it first sends a
687 * message to the extension to find out the supported API version and then it sends
688 * the register request.
689 * @param {string=} appId
690 * @param {Array<u2f.RegisterRequest>} registerRequests
691 * @param {Array<u2f.RegisteredKey>} registeredKeys
692 * @param {function((u2f.Error|u2f.RegisterResponse))} callback
693 * @param {number=} opt_timeoutSeconds
694 */
695 u2f.register = function (appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
696 if (js_api_version === undefined) {
697 // Send a message to get the extension to JS API version, then send the actual register request.
698 u2f.getApiVersion(
699 function (response) {
700 js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version'];
701 console.log("Extension JS API Version: ", js_api_version);
702 u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
703 callback, opt_timeoutSeconds);
704 });
705 } else {
706 // We know the JS API version. Send the actual register request in the supported API version.
707 u2f.sendRegisterRequest(appId, registerRequests, registeredKeys,
708 callback, opt_timeoutSeconds);
709 }
710 };
711
712 /**
713 * Dispatches register requests to available U2F tokens. An array of sign
714 * requests identifies already registered tokens.
715 * @param {string=} appId
716 * @param {Array<u2f.RegisterRequest>} registerRequests
717 * @param {Array<u2f.RegisteredKey>} registeredKeys
718 * @param {function((u2f.Error|u2f.RegisterResponse))} callback
719 * @param {number=} opt_timeoutSeconds
720 */
721 u2f.sendRegisterRequest = function (appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) {
722 u2f.getPortSingleton_(function (port) {
723 var reqId = ++u2f.reqCounter_;
724 u2f.callbackMap_[reqId] = callback;
725 var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
726 opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
727 var req = u2f.formatRegisterRequest_(
728 appId, registeredKeys, registerRequests, timeoutSeconds, reqId);
729 port.postMessage(req);
730 });
731 };
732
733
734 /**
735 * Dispatches a message to the extension to find out the supported
736 * JS API version.
737 * If the user is on a mobile phone and is thus using Google Authenticator instead
738 * of the Chrome extension, don't send the request and simply return 0.
739 * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback
740 * @param {number=} opt_timeoutSeconds
741 */
742 u2f.getApiVersion = function (callback, opt_timeoutSeconds) {
743 u2f.getPortSingleton_(function (port) {
744 // If we are using Android Google Authenticator or iOS client app,
745 // do not fire an intent to ask which JS API version to use.
746 if (port.getPortType) {
747 var apiVersion;
748 switch (port.getPortType()) {
749 case 'WrappedIosPort_':
750 case 'WrappedAuthenticatorPort_':
751 apiVersion = 1.1;
752 break;
753
754 default:
755 apiVersion = 0;
756 break;
757 }
758 callback({ 'js_api_version': apiVersion });
759 return;
760 }
761 var reqId = ++u2f.reqCounter_;
762 u2f.callbackMap_[reqId] = callback;
763 var req = {
764 type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST,
765 timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ?
766 opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC),
767 requestId: reqId
768 };
769 port.postMessage(req);
770 });
771 };
772
773 /**
774 * Modification:
775 * Assign u2f back to window (root) scope.
776 */
777 root.u2f = u2f;
778}(this));