
Security News
OWASP 2025 Top 10 Adds Software Supply Chain Failures, Ranked Top Community Concern
OWASP’s 2025 Top 10 introduces Software Supply Chain Failures as a new category, reflecting rising concern over dependency and build system risks.
@tenfold/web-client-sdk
Advanced tools
Use this package to utilize tenfold features and build your own integration app / UI on top of it. The package is designed to be used in browser alike environments (window object required).
Node - defined in package.json - Node.js 20.11.1
You can use nvm or fnm to install this version.
There is a single peer dependency -> rxjs. Usage of this package requires knowledge of rxjs (https://rxjs-dev.firebaseapp.com/guide/overview) and concept of Observables.
Tenfold SDK Client Library is written with Typescript. This package includes TypeScript declarations for the Tenfold SDK Client Library. We support projects using TypeScript versions >= 4.1.
Use npm or yarn to install the Tenfold SDK Client Library:
> npm install @tenfold/web-client-sdk
or
> yarn add @tenfold/web-client-sdk
To utilize Tenfold SDK Client Library you will need to instantiate a WebClient. It appends to DOM an extra iFrame that encapsulates most of the logic and state and communicates with this iFrame to dispatch actions and listen for data changes via rxjs observables.
To start using Tenfold SDK Client Library you will need to use WebClient.
import { WebClient as TenfoldWebClient } from "@tenfold/web-client-sdk";
const webClient = new TenfoldWebClient({
sharedWorker: false,
iframeDivId: "some-sdk-custom-id",
sdkUrl: "https://app.tenfold.com/sdk.html",
});
<html>
<head>
...
</head>
<body>
<div id="some-sdk-custom-id"></div>
</body>
</html>
When you instantiate WebClient you may pass an optional configuration object. All these properties are optional.
sdkUrl option (optional)As described in Architecture overview, WebClient requires an iFrame to communicate with.
As a default value we pass https://app.tenfold.com/v${sdkVersion}-sdk/sdk.html. As Tenfold,
we provide per each sdk version matching iframe.src. So if you have
@tenfoldweb-client-sdk@1.0.24 and you don't define this config property, WebClient should
attach to your DOM a div with an iframe that src is https://app.tenfold.com/v1.0.24-sdk/sdk.html. This may be exposed as a
configuration property for testing purposes but this is not recommended. Latest version is hosted at
https://app.tenfold.com/sdk.html.
iframeDivId option (optional)You can pass id attribute value of your div in HTML. The default value is __tenfold__sdk__iframe__. You can only define <div/> tags for this purpose in your code
to add an iFrame to dedicated <div/> in DOM. For both - default or custom provided iframeDivId - WebClient will look into the DOM and if it finds <div/> with iframeDivId value it will insert iframe with src attribute and sdkUrl value. If it doesn't find a <div/> it will create one and prepend to document.body and then insert an iFrame.
sharedWorker option (optional)Use a shared worker (SW) (https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker) for underlying mechanism in iFrame. Default value is true.
Shared worker is not implemented in all browsers, so even if this
configuration option is enabled we cannot ensure that it will bootstrap in this mode.
WebClient will fallback to non shared worker mode in that case.
This mechanism gives a better UX in your app and better performance. Entire logic and state management in iFrame is bootstrapped only once and iFrame is only a proxy to SW. When SW is disabled, each iFrame bootstraps it's own instance of logic and state, so the UX accross many tabs in browser will not be exact the same. We recommend to check the behavior of your implementation with both values.
sharedWorkerContext option (optional)Only applies when the Shared Worker (SW) is enabled and implemented in the runtime environment. It defines a context for SW, so many diffrent apps using this SDK will not attach
to the same SW. As a default it's sdk_${window.location.origin}. So different apps (under diffrent domains) should not attach to the same SW. You can define your own SW context by passing this option. In Chrome you can inspect your SWs under chrome://inspect/#workers.
When you create a new instance of WebClient, it's important to remember to destroy it too.
If you create it once for entire life of your app you can skip this, however, if you create it
in some module and your intention is to clean it up when you destroy that module - use destroy method. It ensures that all inner rxjs subscriptions will be completed and iframe
injected into given div will be removed from DOM. Your code should look like:
yourDestroyFn() {
if (this.webClient) {
this.webClient.destroy();
this.webClient = null;
}
}
It's an observable that will notify you that everything is established and works
(communication with iframe i.e.). You should wait with any manipulations via WebClient
until isReady$ emits true value.
webClient.isReady$
.pipe(
takeUntil(this.isDestroyed$),
filter((isReady) => !!isReady),
tap(() => {
console.log("WebClient and Iframe are ready!");
})
)
.subscribe();
It's an observable that will notify you that user is authenticated to Tenfold.
webClient.isAuthenticated$
.pipe(
takeUntil(this.isDestroyed$),
filter((isAuthenticated) => !!isAuthenticated),
tap(() => {
console.log("User is authenticated to Tenfold!");
})
)
.subscribe();
WebClient has several domain services. Those are responsible for dispatching some actions to iframe (via methods) and listen for data state changes from iframe (via rxjs observables).
All of the domain services are only available after the isReady$ observable emits true value, before that those will be undefined.
It exposes isReady$ observable that indicates iframe's services to be ready when emits true.
WebClient.isReady$ is better to use, cause ensures that both sides of communication are ready
to use. Exposes also setEnvironment mainly to set environmentUrl that we want to use to communicate with Tenfold APIs (for switching between dev and prod envs). setEnvironment method
can be used only when isReady$ emits true value and user is authenticated - otherwise will
throw an error. currentEnvironment$ exposes observable with a current state of env configuration - the most important property of this state is environmentUrl of course, which
indicates which one is currently set.
webClient.env.currentEnvironment$
.pipe(
takeUntil(this.isDestroyed$),
map(({ environmentUrl }) => environmentUrl),
tap((environmentUrl) => {
console.log("environmentUrl changed to:", environmentUrl);
})
)
.subscribe();
webClient.env.setEnvironment({ environmentUrl: "https://api.tenfold.com" });
You can listen for user data (can be User or undefined depends on WebClient.isAuthenticated$ value):
webClient.auth.user$
.pipe(
takeUntil(this.isDestroyed$),
filter((user) => !!user),
map(({ username }) => username),
tap((username) => {
console.log("user name is:", username);
})
)
.subscribe();
You can also get phoneSystem$ returns string or null (just a sugar selector on top of user$ one):
webClient.auth.phoneSystem$
.pipe(
takeUntil(this.isDestroyed$),
filter((phoneSystem) => !!phoneSystem),
tap((phoneSystem) => {
console.log("Your phone system is:", phoneSystem);
})
)
.subscribe();
It exposes login method for login with credentials:
await webClient.auth.login({
username: "someone@tenfold.com",
password: "********",
});
You can logout at any time:
await webClient.auth.logout();
You can also implement SSO flow:
const identifier = "john.smith@tenfold.com";
const loginType = "openid_flow"; // 'saml_flow' | 'openid_flow';
webClient.auth
.startSSOFlow(identifier, loginType)
.pipe(
tap(({ redirectTo }) => {
window.open(redirectTo);
})
)
.subscribe();
startSSOFlow(identifier, loginType) takes two params.
identifier is SSO domain or username. loginType is "saml_flow" when you want to perform domain login, "openid_flow" when you want to
perform open id connect login. Underneath it starts long polling for
user authentication status change (it stops when user becomes authenticated or call startSSOFlow once again).
In login flow sometimes not only tenfold authentication is required - you need to
perform a login specific for a given phone system. You can do this with login method:
await webClient.agentStatus.login({
agentId,
password,
extension,
});
To perform that you can check for getAgentSettings to get some data to choose from:
webClient.agentStatus
.getAgentSettings()
.pipe(
takeUntil(this.isDestroyed$),
tap((agentSettings) => {
console.log("Your prefill agent id is:", agentSettings?.agentId);
console.log(
"If your hasPassword is truthy you can set your password to '▪▪▪▪▪▪▪▪'",
agentSettings?.hasPassword
);
console.log(
"Your preferredExtension is:",
agentSettings?.preferredExtension
);
})
)
.subscribe();
After you login you can implement widget with agent status selection:
webClient.agentStatus.agentStatuses$
.pipe(
takeUntil(this.isDestroyed$),
tap((agentStatuses) => {
console.log(
"Your options list of agentStatuses is:",
agentStatuses
);
})
)
.subscribe();
webClient.agentStatus.currentAgentStatus$
.pipe(
takeUntil(this.isDestroyed$),
tap((currentAgentStatus) => {
console.log("Your current agentStatus is:", currentAgentStatus);
})
)
.subscribe();
const agentStatusIJustChose = {
id: "some-id-of-option-from-above-agentStatuses",
type: "busy",
};
if (!agentStatusIJustChose.readOnly) {
await webClient.agentStatus.save(agentStatusIJustChose);
}
You can always get current agent login data with getAgentData and use it for logout.
const agentData = await webClient.agentStatus.getAgentData();
webClient.agentStatus.logout(agentData);
IMPORTANT: to implement this flow you will need some parts from WebClient.callControls.
In Tenfold we have an Interaction that is a common type for call, chat, etc. To handle data related to this important scope we have
WebClient.interaction domain service.
You can listen for interactions collection changes with webClient.interaction.interactions$ observable. There is also webClient.interaction.newInteraction$ that emits a new Interaction only when
it's new one in webClient.interaction.interactions$. We expose also
a sugar observable on top of webClient.interaction.newInteraction$
that distinguish call and chat accordingly: webClient.interaction.newCall$ and webClient.interaction.newChat$.
We also track changes and in case if any interaction in collection change we emit a value via webClient.interaction.interactionChange$.
It's suitable to handle changes of each one in time.
There is also a sugar observable webClient.interaction.interactionStatusChange$ that emits { id: string, status: InteractionStatus } when for any Interaction from collection its
status has changed.
webClient.interaction.interactions$
.pipe(
takeUntil(this.isDestroyed$),
tap((interactions) => {
console.log(
"Here is my always up to date list of latest interactions:",
interactions
);
})
)
.subscribe();
You can check with webClient.callControls.callControlsEnabled$ observable if
call controls for your agent are enabled.
To check if you have an agent with phone system that requires login you can
use webClient.callControls.hasSessionCreationPhoneSystem$ observable or webClient.callControls.hasSessionCreationPhoneSystem getter.
To check if you have an agent that requires login you can
use webClient.callControls.hasAgentLogin$ observable or webClient.callControls.hasAgentLogin getter.
The easiest and most safe solution to check if login is required (as combination of two above) is to use webClient.callControls.hasSessionCreation$ observable or webClient.callControls.hasSessionCreation getter. This one is the one you need to
implement the proper agent login flow. Check: WebClient.agentStatus domain service.
To check if the session is active you can use webClient.callControls.sessionActive$ observable or webClient.callControls.hasSessionActive getter.
The easiest way to logout on agent level is to use webClient.callControls.destroySession. It will logout you from agent login session, but not from Tenfold.
If you have webClient.callControls.hasSessionActive truthy and you would like to
perform full logout:
await webClient.callControls.logout();
await webClient.auth.logout();
webClient.callControls has a bunch of methods responsible for this domain like:
dial(phoneNumber: string, source: CTDRequestSource, pbxOptions?: DialPbxOptions)
with a parameters to setup any dial you want like:
const phoneNumber = "123456789";
webClient.callControls.dial(phoneNumber, "dialer", { type: "external" });
As name of this service suggests it's oriented on Interaction of call type.
By tracking webClient.interaction.newCall$ you can
answerCall(call: Call),
hangupCall(call: Call, force?: boolean),
switchCall(call: Call),
holdCall(call: Call),
muteCall(call: Call),
unmuteCall(call: Call),
startRecording(call: Call),
stopRecording(call: Call),
retrieveCall(call: Call),
sendDtmf(call: Call, sequence: string).
All above are self explanatory.
This domain service is responsible for checking availability for features defined in Tenfold. Depends on organization you belong and account configuration you can have access to only part of features.
The most basic observable to check tenfold feature setting is onFeature.
webClient.features
.onFeature("socialProfile")
.pipe(
takeUntil(this.isDestroyed$),
tap((feature: Feature) => {
this.socialProfilesEnabled = feature && feature.status === "active";
})
)
.subscribe();
Sugar observable for status extraction is onFeatureStatus observable.
Sugar observable for property chain of feature is onFeaturePreference<T>(featureName: string, path: string, defaultValue: T) observable.
Features that are user related can be found under onUserFeature.
Features that are organization related can be found under onOrganizationFeature.
On phone system level to check for feature use: webClient.features.onIntegrationCapability.
Sugar observable for supports extraction is: webClient.features.onIntegrationCapabilitySupport.
Combination of onFeatureStatus and onIntegrationCapabilitySupport is: onIntegrationCapabilityAndFeatureSupport (logical AND).
We expose agentDirectory to let navigate through tree of agent-alike objects.
Method fetchAgentDirectory(search: string) is for getting a config object that you can
utilize to navigate through the tree. search param is initial value for search$ pusher.
Returned config has also result$ and fetchPath$.
const directoryConfig = webClient.agentDirectory.fetchAgentDirectory('');
directoryConfig.results$.pipe(
takeUntil(this.isDestroyed$),
tap((results) => {
console.log(
"Fresh results list for current search$ and fetchPath$ values:",
results
);
})
).subscribe();
clickOnAgentItem(item: AgentDirectoryItem) {
directoryConfig.fetchPath$.next(item.childrenPath);
}
directoryConfig.search$.next('John Smith');
This one is helpful for usage of transfers (see next section).
This domain service supports concept of transfers in Tenfold.
You should use it when you want implement a logic, that your agent is on call
with some customer and want to:
No matter which scenario you want to implement, you need to set target agent that you want
transfer in-progress call to. For this purpose use webClient.transfers.setAgent.
selectAgent(result: AgentDirectoryItem) {
const transferAgent = {
name: result.label,
agentStatus: result?.data?.state,
pointsOfContact: result.pointsOfContact,
hasPrimaryExtension: false,
} as TransferAgent;
await this.connectorService.getSDKService().transfers.setAgent(this.interaction.id, transferAgent);
}
The above code is for case when you select agent after using webClient.agentDirectory.fetchAgentDirectory.
For scenario 1. you just need to use webClient.transfers.blindTransfer. It will redirect call
to selected agent and end it with you.
For scenaro 2. you want to use webClient.transfers.handleInternalCall.
webClient.transfers.handleInternalCall.webClient.transfers.mergeTransfer.webClient.transfers.completeWarmTransfer.webClient.transfers.handOverCall.webClient.callControls.switchCall. It works like toggle mechanism from one to another.webClient.transfers.transfers$ is an observable that exposes state of all transfers as map
where key is Interaction.id and value InteractionTransferState. It keeps a state with data like:
agent, internalCall and transferStage.
If you focus on single interaction that is in progress you might be intrested in that kind of extraction of transfer state for your interaction:
interactionTransferSubState$() {
return combineLatest([
this.interaction$,
this.transfers$,
]).pipe(
takeUntil(this.destroySubject$),
map(([call, transfers]) => {
return !!call ? transfers[call.id] : null;
}),
);
}
FAQs
Tenfold Web Client SDK
We found that @tenfold/web-client-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 13 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OWASP’s 2025 Top 10 introduces Software Supply Chain Failures as a new category, reflecting rising concern over dependency and build system risks.

Research
/Security News
Socket researchers discovered nine malicious NuGet packages that use time-delayed payloads to crash applications and corrupt industrial control systems.

Security News
Socket CTO Ahmad Nassri discusses why supply chain attacks now target developer machines and what AI means for the future of enterprise security.