Browser SDK
This page describes how to install and use the JavaScript client-side SDK
Breaking changes in 2.0 and 2.1
If you are about to upgrade Castle.js from a 1.x or 2.0.x version, please see the chapter on how to deal with the breaking changes that were introduced for these versions.
Introduction
The Browser SDK is a central component of the Castle integration, and provides device fingerprinting, behavioral analysis, and client-side event monitoring.
All the client-side SDKs, have two main purposes:
- Create request tokens. Generate a unique token to be passed to your server and used when calling Castle's Risk and Filter APIs.
- Tracking client-side events. Track important in-app events directly from the client to the Castle API.
The script is designed to work in most known browser starting from Internet Explorer 5 and up. Please see the Minimal Requirements section in the NPM package docs for more details.
Castle and cookie types
Castle.js doesn’t set third-party cookies and only sets first-party cookies. Eg. Google's Third-Party Cookies deprecation will also require no adjustments on your end.
Installation
The recommended way of installing Castle.js in your app is via the NPM package and using either npm
or yarn
:
npm install @castleio/castle-js
yarn add @castleio/castle-js
"dependencies": {
"@castleio/castle-js": "^2.1.15",
…
}
# Check this file for the latest version (dist/tarball)
https://registry.npmjs.org/@castleio/castle-js/latest
# And then pull the tgz file:
https://registry.npmjs.org/@castleio/castle-js/-/castle-js-2.1.15.tgz
Configuration
Once installed, the SDK needs to be configured using your Publishable Key, which can be found in the Dashboard for users with administrator access.
Initialize the SDK as early as possible
The SDK should be initialized immediately at page load. The more time that passes from initialization to when you eventually create a request token (next section), the more data the agent can collect, which in turn improves accuracy.
import * as Castle from '@castleio/castle-js'
var castle = Castle.configure({ pk: '<YOUR_PUBLISHABLE_KEY>' });
import '@castleio/castle-js/dist/castle.browser.js'
Castle.configure({ pk: '<YOUR_PUBLISHABLE_KEY>' });
option | description | default |
---|---|---|
pk | Required. Publishable Key | null |
window | Overrides the browser window object. Useful e.g. when running in a test environment with a mocked DOM, like JSDOM. | window |
timeout | request timeout for page, form, custom events. | 1000 ms |
storage | container for the namespace ("name") and the expiration time in seconds ("expireIn"), used for storing castle data in the localStorage and/or in the cookies. (use name in case of conflicts with other vendors) | {"name":"__cuid", "expireIn": 34560000} |
throttling | Sends multiple custom events in a single request when they are tracked with very short intervals (less than 500ms apart). We recommend leaving this setting set to true | true |
Creating request tokens
Once Castle.js is running on your web pages, you need to ensure that the request_token
value generated by Castle.js is passed to your application server, where the Castle server-side SDK will be able to extract the request_token
value.
Adblock-safe
Many analytics solution rely on the client-side SDK performing outgoing API requests, which results in them getting blocked by adblockers and privacy plugins. Castle.js won't make such requests, resulting in much higher accuracy.
Request tokens don't live forever
A new request token value should to be generated for each request to your backend. A request token will expire after 120 seconds and should only be used during a single request to your backend. The reason for this is that Castle.js continuously monitors behavior in the user session and the data will need to be fresh when processed by the Castle APIs. A scalable approach is to implement the token generation as a client-side middleware which generates a new request token with each request to your backend.
castle.createRequestToken().then( (requestToken) => {
// `token` contains a unique, one-time use, string that should be passed
// in the request to the server, eg. in a form post.
});
// or
const requestToken = await castle.createRequestToken();
The createRequestToken
method doesn't accept any options, and it generates tokens immediately in a non-blocking way under normal conditions. In the edge case where the method is called immediately after page load, it can take up to a maximum of 300 ms to collect all the device data, but this edge-case should only happen in bot scenarios.
Examples on how to pass the request token
var myForm = document.getElementById('loginForm');
myForm.addEventListener(
'submit',
function (e) { castle.injectTokenOnSubmit(e) }
);
var myHeaders = new Headers();
var token = await castle.createRequestToken();
myHeaders.append('X-Castle-Request-Token', token);
fetch('https://example.com/login', {
method: 'POST',
mode: 'no-cors',
headers: myHeaders,
body: JSON.stringify(loginData)
})
// Note: It's recommended that you use the form submit helper if you can
var myForm = document.getElementById('loginForm');
myForm.addEventListener(
'submit',
function (e) {
e.preventDefault();
// get or insert a hidden field with the Castle request token
var hiddenField;
for (var i = 0; i < myForm.childNodes.length; i++) {
var node = myForm.childNodes[i];
if (node.getAttribute && node.getAttribute('name') === 'castle_request_token') {
hiddenField = node;
break;
}
}
if(!hiddenField) {
hiddenField = document.createElement('input');
hiddenField.setAttribute('type', 'hidden');
hiddenField.setAttribute('name', 'castle_request_token');
myForm.appendChild(hiddenField);
}
castle.createRequestToken().then(function (token) {
hiddenField.value = token;
myForm.submit();
});
return false;
}
);
Tracking client-side events
API calls and requests per second limits
Tracking client-side events count towards your API calls and request per second (RPS) limits, as defined in your current plan. Contact us about our Enterprise plan if you are interested in pricing based on Monthly Tracked Users (MTU).
Building the user object
The Browser SDK offer three methods of sending data: page
, form
and custom
(described below) depending on which type of action the user performs. Common for these calls is that a user
object needs to be provided. The contents of the user
object are the same as what you'll send to the Risk and Filter APIs.
var user = {
id: 'ca1242f498', // required, and should be a string
email: '[email protected]', // optional
phone: '+1415232183', // optional
name: 'Michael Brown',
registered_at: '2012-12-02T00:30:08.276Z',
traits: {
plan: 'premium'
}
}
// The `user` object is required for all event tracking methods
castle.page({user: user});
Securing the user object
In order to prevent user information from being spoofed by a bad actor, it is recommended to send the user information as a signed JWT when Castle.js is used in production.
From your backend code, you need to encode the user as a JWT and sign it using your API secret key. Then, when Castle receives the JWT, the integrity of the user data will be verified to ensure that the data isn't being tampered with. Below is an example of how to generate a JWT using the Ruby language:
var userJwt = "<%= JWT.encode({ id: current_user.id.to_s, email: current_user.email, registered_at: current_user.registered_at }, ENV.fetch('CASTLE_API_SECRET'), 'HS256') %>";
// Then use the `userJwt` argument instead of `user` when using any of the tracking methods
castle.page({userJwt: userJwt});
Please note that the user ID needs to be sent as a string
castle.page()
castle.page()
Usage
// The page method needs to be called with the currently logged in user
castle.page(options);
// Fully customizable example:
castle.page({
userJwt: userJwt, // or `user` if you're not using secure mode
url: 'http://castle.io',
name: 'User Page',
referrer: null
}).then( (response) => {
console.log(response)
})
option | description | default |
---|---|---|
user | Required unless userJwt is used. The user object, as described above | none |
userJwt | Required unless user is used. JWT string containing user information and a signature | none |
name | Optional. Name of the page that the user is viewing. | Extracted automatically from document.title |
url | Optional. URL of the page that the user is viewing | Extracted automatically from document.location.href |
referrer | Optional. URL of the referrer from the user came from | Extracted automatically from document.referrer |
page
function is async and returns the promise which is resolved in max 1000ms (timeout can be configured in the configure method)
Additionally, all the page events on the page are debounced with 300ms (to avoid quickly changed urls for SPA frameworks)
Event promise can be resolved with one of the values:
response | descriptin |
---|---|
true | event sent successfully |
false | wrong configuration or data is invalid |
null | timeout or debounce appeared |
castle.form()
castle.form()
Usage
castle.form({
userJwt: userJwt, // or `user` if you're not using secure mode
name: 'Update Profile',
values: {
first_name: 'John',
newsletter: false
}
});
option | description | default |
---|---|---|
user | Required unless userJwt is used. The user object, as described above | none |
userJwt | Required unless user is used. JWT string containing user information and a signature | none |
name | Required. Name of the form that the user is submitting. | none |
values | Optional. Object with the input values of the form that the user is submitting | none |
form
function is async and returns the promise which is resolved in max 1000ms (timeout can be configured in the configure method)
Event promise can be resolved with one of the values:
response | description |
---|---|
true | event sent successfully |
false | wrong configuration or data is invalid |
null | timeout appeared |
castle.custom()
castle.custom()
Usage
castle.custom({
userJwt: userJwt, // or `user` if you're not using secure mode
name: 'Added to cart',
properties: {
product: 'iPhone 13 Pro',
price: 1099.99
}
});
option | description | default |
---|---|---|
user | Required unless userJwt is used. The user object, as described above | none |
userJwt | Required unless user is used. JWT string containing user information and a signature | none |
name | Required. Name of the event that is tracked. | none |
properties | Optional. Object with extra properties to attach to the event. | none |
custom
function is async and returns the promise which is resolved in max 1000ms (timeout can be configured in the configure method)
Event promise can be resolved with one of the values:
response | description |
---|---|
true | event sent successfully |
false | wrong configuration or data is invalid |
null | timeout appeared |
Helper methods
Castle.injectTokenOnSubmit()
Castle.injectTokenOnSubmit()
Global helper method that can be used directly on a form to inject the Castle request token as a hidden parameter called castle_request_token
Usage
<form onsubmit="Castle.injectTokenOnSubmit(event)">
<input type="email" name="email">
<input type="password" name="password">
</form>
parameters | description | default |
---|---|---|
1. event |
| |
2. onDone(event) | a function called after injecting the token | function(event) { |
Castle.formEventOnSubmit()
Castle.formEventOnSubmit()
Global helper method that can be used directly on a form to generate form events based on the data-castle-
attributes
Usage
<form data-castle-name="Update Profile" onsubmit="Castle.formEventOnSubmit(event, user)" action="/">
<input type="text" name="email" placeholder="email" data-castle-value="email">
</form>
This will trigger form event like below before the submit:
castle.form({
user: user,
name: 'Update Profile',
values: {
email: '<email>',
}
});
parameters | description | default |
---|---|---|
event |
| |
user |
| |
options | - | {} |
onDone(event) | a function called after sending the | function(event) { |
HTML data attributes
attribute | description | element |
---|---|---|
data-castle-name | attribute describing name of the form event | on the form element |
data-caste-value | attribute describing name of the form values names | on the input, select or textarea element |
Breaking changes
Upgrading from 1.x to 2.0.x
1.x
Legacy package: https://www.npmjs.com/package/castle.js.
require "castle.js"
_castle('setAppId', "YOUR_APP_ID")
_castle('getClientId')
2.0.x
New package:
import * as Castle from "@castleio/castle-js"
- Renamed the main object from
_castle
toCastle
in the new module, but kept the_castle
in the CDN version as well as the browser-specific module@castleio/castle-js/dist/castle.browser.js
- Introduced the concept of request tokens that need to be generated for each call to Castle's server-side API.
Castle.createRequestToken().then( (requestToken) => {
});
// or
const token = await Castle.createRequestToken();
- Request tokens can no longer be retrieved from the cookie string.
- All the following methods were removed:
autoForwardClientId
, autoTrack
, catchHistoryErrors
, identify
, setUserId
, setAccount
, setKey
, setAccount
, sessionId
, reset
, page
, trackPageView
, setTrackerUrl
Page views (and more) returned in 2.1
The old way of sending page views were deprecated with 2.0, but recently returned in a more powerful version were you can track any events from the frontend.
Upgrading from 2.0.x to 2.1.x or later
2.0.x
Castle.configure(YOUR_CASTLE_APP_ID);
2.1.x or later
-
Removed the
_castle
object for all versions and now only relying onCastle
. -
Switched to use the Publishable Key that can be found in the same place as the now deprecated App ID.
Castle.configure({pk: YOUR_PUBLISHABLE_KEY});
-
onFormSubmit
has been renamed toinjectTokenOnSubmit
-
_castle
global method is no longer supported and has been replaced withCastle
(for the CDN and castle.browser.js versions) check docs -
CDN version no longer needs
appID
in the url and requires<script>Castle.configure({pk: YOUR_CASTLE_PUBLISHABLE_KEY});</script>
to be added. The CDN version can't be used for generating request tokens, but only for tracking client-side events. -
Introduced
page
,form
, andcustom
methods for client-side event tracking
Updated about 2 months ago