@okta-dfuhriman/okta-auth-js
Version:
The Okta Auth SDK
161 lines (137 loc) • 4.64 kB
text/typescript
/*!
* Copyright (c) 2015-present, 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 {
OAuthTransactionMeta,
OAuthStorageManagerInterface,
} from '../../oidc';
import {
ServiceManagerInterface,
ServiceInterface,
ServiceManagerOptions,
OktaAuthCoreInterface,
OktaAuthCoreOptions
} from '../types';
import { AutoRenewService, SyncStorageService, LeaderElectionService } from '../../services';
import { removeNils } from '../../util';
const AUTO_RENEW = 'autoRenew';
const SYNC_STORAGE = 'syncStorage';
const LEADER_ELECTION = 'leaderElection';
export class ServiceManager
<
M extends OAuthTransactionMeta,
S extends OAuthStorageManagerInterface<M>,
O extends OktaAuthCoreOptions
>
implements ServiceManagerInterface
{
private sdk: OktaAuthCoreInterface<M, S, O>;
private options: ServiceManagerOptions;
private services: Map<string, ServiceInterface>;
private started: boolean;
private static knownServices = [AUTO_RENEW, SYNC_STORAGE, LEADER_ELECTION];
private static defaultOptions = {
autoRenew: true,
autoRemove: true,
syncStorage: true
};
constructor(sdk: OktaAuthCoreInterface<M, S, O>, options: ServiceManagerOptions = {}) {
this.sdk = sdk;
this.onLeader = this.onLeader.bind(this);
// TODO: backwards compatibility, remove in next major version - OKTA-473815
const { autoRenew, autoRemove, syncStorage } = sdk.tokenManager.getOptions();
options.electionChannelName = options.electionChannelName || options.broadcastChannelName;
this.options = Object.assign({},
ServiceManager.defaultOptions,
{ autoRenew, autoRemove, syncStorage },
{
electionChannelName: `${sdk.options.clientId}-election`,
syncChannelName: `${sdk.options.clientId}-sync`,
},
removeNils(options)
);
this.started = false;
this.services = new Map();
ServiceManager.knownServices.forEach(name => {
const svc = this.createService(name);
if (svc) {
this.services.set(name, svc);
}
});
}
private async onLeader() {
if (this.started) {
// Start services that requires leadership
await this.startServices();
}
}
isLeader() {
return (this.getService(LEADER_ELECTION) as LeaderElectionService)?.isLeader();
}
isLeaderRequired() {
return [...this.services.values()].some(srv => srv.canStart() && srv.requiresLeadership());
}
async start() {
if (this.started) {
return; // noop if services have already started
}
await this.startServices();
this.started = true;
}
async stop() {
await this.stopServices();
this.started = false;
}
getService(name: string): ServiceInterface | undefined {
return this.services.get(name);
}
private async startServices() {
for (const [name, srv] of this.services.entries()) {
if (this.canStartService(name, srv)) {
await srv.start();
}
}
}
private async stopServices() {
for (const srv of this.services.values()) {
await srv.stop();
}
}
// eslint-disable-next-line complexity
private canStartService(name: string, srv: ServiceInterface): boolean {
let canStart = srv.canStart() && !srv.isStarted();
// only start election if a leader is required
if (name === LEADER_ELECTION) {
canStart &&= this.isLeaderRequired();
} else if (srv.requiresLeadership()) {
canStart &&= this.isLeader();
}
return canStart;
}
private createService(name: string): ServiceInterface {
const tokenManager = this.sdk.tokenManager;
let service: ServiceInterface;
switch (name) {
case LEADER_ELECTION:
service = new LeaderElectionService({...this.options, onLeader: this.onLeader});
break;
case AUTO_RENEW:
service = new AutoRenewService(tokenManager, {...this.options});
break;
case SYNC_STORAGE:
service = new SyncStorageService(tokenManager, {...this.options});
break;
default:
throw new Error(`Unknown service ${name}`);
}
return service;
}
}