UNPKG

@okta-dfuhriman/okta-auth-js

Version:
238 lines (208 loc) 7.49 kB
/*! * Copyright (c) 2015-2016, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") * * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and limitations under the License. */ import config from 'config/config.json'; import fetch from 'cross-fetch'; import country from 'nls/country.json'; import login from 'nls/login.json'; import Q from 'q'; import { _ } from 'okta'; import BrowserFeatures from 'util/BrowserFeatures'; import Logger from 'util/Logger'; const STORAGE_KEY = 'osw.languages'; declare type i18nOptions = { [localeOrCountry: string]: { [i18nKey: string]: string; } } declare type Assets = { baseUrl: string; rewrite(origPath: string): string; } /** * Converts options to our internal format, which distinguishes between * login and country bundles. * * Example options.i18n passed in by the developer: * { * 'en': { * 'needhelp': 'need help override', * 'primaryauth.title': 'new sign in text', * 'country.JP' = 'Japan, Japan' * } * } * * Parsed: * { * 'en': { * 'login': { * 'needhelp': 'need help override', * 'primaryauth.title': 'new sign in text', * }, * 'country': { * 'JP': 'Japan, Japan' * } * } * } */ function parseOverrides(i18n) { if (!i18n) { return {}; } const i18nWithLowerCaseKeys = {}; _.each(_.keys(i18n), function(key) { i18nWithLowerCaseKeys[key.toLowerCase()] = i18n[key]; }); return _.mapObject(i18nWithLowerCaseKeys, function(props) { const mapped = { login: {}, country: {} }; if (!_.isObject(props)) { throw new Error('Invalid format for "i18n"'); } _.each(props, function(val, key) { const split = key.split(/^country\./); if (split.length > 1) { mapped.country[split[1]] = val; } else { mapped.login[split[0]] = val; } }); return mapped; }); } // Caching: We only bundle English by default in the Sign-In Widget. Other // languages are loaded on demand and cached in localStorage. These languages // are tied to the version of the widget - when it bumps, we reset the cache. function getCachedLanguages() { let storage = JSON.parse(localStorage.getItem(STORAGE_KEY)); if (!storage || storage.version !== config.version) { storage = { version: config.version, }; } return storage; } function addLanguageToCache(language, loginJson, countryJson) { const current = getCachedLanguages(); current[language] = { login: loginJson, country: countryJson, }; localStorage.setItem(STORAGE_KEY, JSON.stringify(current)); } // By default, the assets.bundleUrl is tied to the Okta CDN. // // There are two overrides available for modifying where we load the asset // bundles from: // // 1. assets.baseUrl // // This is the base path the OSW pulls assets from, which in this case is // the Okta CDN. Override this config option if you want to host the // files on your own domain, or if you're using a new version of the // widget whose language files haven't been published to the CDN yet. // // 2. assets.rewrite // // This is a function that can be used to modify the path + fileName of // the bundle we're loading, relative to the baseUrl. When called, it // will pass the current path, and expect the new path to be returned. // This is useful, for example, if your build process has an extra // cachebusting step, i.e: // // function rewrite(file) { // // file: /labels/json/login_ja.json // return file.replace('.json', '.' + md5file(file) + '.json'); // } // // Note: Most developers will not need to use these overrides - the default // is to use the Okta CDN and to use the same path + file structure the // widget module publishes by default. function fetchJson(bundle, language, assets) { // Our bundles use _ to separate country and region, i.e: // zh-CN -> zh_CN const languageCode = language.replace('-', '_'); const path = assets.rewrite(encodeURI(`/labels/json/${bundle}_${languageCode}.json`)); const headers = { Accept: 'application/json', 'Content-Type': 'text/plain', }; const mode = 'no-cors'; return fetch(assets.baseUrl + path, { method: 'GET', headers, mode, }) .then(res => res.text()) .then(txt => JSON.parse(txt)); } function getBundles(language, assets, supportedLanguages) { // Two special cases: // 1. Default language is already bundled with the widget // 2. If the language is not in our config file, it means that they've // probably defined it on their own. if (language === config.defaultLanguage || !_.contains(supportedLanguages, language)) { return Q({}); } //local storage does not work correctly with android web views (embeded browers) //so skip the caching and just make the request to get the local info const localStorageIsSupported = !BrowserFeatures.localStorageIsNotSupported(); if (localStorageIsSupported) { const cached = getCachedLanguages(); if (cached[language]) { return Q(cached[language]); } } return Q.all([fetchJson('login', language, assets), fetchJson('country', language, assets)]) .spread(function(loginJson, countryJson) { if (localStorageIsSupported) { addLanguageToCache(language, loginJson, countryJson); } return { login: loginJson, country: countryJson }; }) .catch(function() { // If there is an error, this will default to the bundled language and // we will no longer try to load the language this session. Logger.warn('Unable to load language: ' + language); return {}; }); } export default { login: login, country: country, // Courage components within the sign in widget point to courage bundle to look // up i18nkeys. Since we dont have courage.properties inside the sign in widget // we are pointing courage bundle to login. courage: login, currentLanguage: null, isLoaded: function(language: string): boolean { return this.currentLanguage === language; }, remove: function(): void { this.currentLanguage = null; }, loadLanguage: function(language: string, overrides: i18nOptions, assets: Assets, supportedLanguages: string[]): Q.Promise<void> { const parsedOverrides = parseOverrides(overrides); const lowerCaseLanguage = language.toLowerCase(); return getBundles(language, assets, supportedLanguages).then(bundles => { // Always extend from the built in defaults in the event that some // properties are not translated this.login = _.extend({}, login, bundles.login); this.country = _.extend({}, country, bundles.country); this.courage = _.extend({}, login, bundles.login); if (parsedOverrides[lowerCaseLanguage]) { _.extend(this.login, parsedOverrides[lowerCaseLanguage]['login']); _.extend(this.country, parsedOverrides[lowerCaseLanguage]['country']); _.extend(this.courage, parsedOverrides[lowerCaseLanguage]['login']); } this.currentLanguage = language; }); }, };