@schibsted/sps-sdk
v2.17.0-alpha.1
Published
SPS SDK for node.js
Maintainers
Keywords
Readme
SPS SDK
The SPS SDK library provides convenient access to the SPS API from applications written in client-side JavaScript.
Pulse origin object and tracking data are inferred from the poster and included in the response.
Table of Contents
Requirements
- Node.js 18 or higher.
Installation
Install the package with:
npm install @snoam/sps-sdk
# or
yarn add @snoam/sps-sdkUsage
Configuration
You can configure the SDK by passing options to the constructor:
// Will use https://test-cm.vg.no/v3/dp/ to fetch content
const sps = new SPS({
domain: "vg.no",
publication: SpsPublication.DP,
environment: "staging",
});[!WARNING] Do not use any other environment than
productionorstagingin production code. The-testenvironments are only for testing purposes and should not be used in production.
| Option | Default | Description | Example |
|-------------|----------------|--------------------------------------------------------------------------------|-------------------|
| publication | | The SPS publication to fetch content from. | SpsPublication.VG |
| domain | schibsted.tech | The domain you are requesting from. Must be set for cookie forwarding to work. | "aftenposten.no" |
| environment | production | Determines which endpoint is called. See endpoint.ts. | "staging" |
Fetching a Poster
import SPS, { SpsPublication } from '@snoam/sps-sdk';
// Will use https://cm.aftenposten.no/v3/aftenposten/ to fetch content
const sps = new SPS({
domain: "aftenposten.no",
publication: SpsPublication.AFTENPOSTEN,
});
const poster = await sps.getPoster("salesposter", {
params: {
section: "sport",
articleType: "subscription",
articleId,
tags: ["fotball", "eliteserien"],
},
tracking: {
contentId: articleId,
},
generateLoginUrl: ({ campaign }) => `https://login.aftenposten.no?utm_campaign=${campaign}`,
});
if (!poster) {
// No content
} else {
const { content, placementType, campaign, contentName, abTestGroup, segmentNames, trackingData, pulseOrigin, pulseObject } = poster;
// Render poster
}Fetching a Decision
import SPS, { SpsPublication } from '@snoam/sps-sdk';
// Will use https://cm.vg.no/v3/vg/ to fetch content
const sps = new SPS({
domain: "vg.no",
publication: SpsPublication.VG,
});
const decision = await sps.getDecision("foldup", {
params: {
section: "sport",
wordCount: 100,
},
});
if (decision.contentKey) {
const posterResponse = await sps.getPosterByDecision(decision, {
tracking: {
contentId: articleId,
},
generateLoginUrl: () => loginUrl,
});
const { content, placementType, campaign, contentName, abTestGroup, trackingData, pulseOrigin, pulseObject } = posterResponse;
}Decisioning params
The params passed to getPoster and getDecision correspond with the nodes in the flows in Salesposter Manager. These are used to determine which content to return.
Note that only the blue nodes are sent as params, while the remaining nodes are deduced internally in SPS.
None of the params are required for most placements, but depend on which will be used in the flow for a specific placement and publication. Here are some commonly used params (others can be used as well):
| Property | Type | Required for salesposters | Description |
|---------------|------------|-----------------------------|-----------------------------------------------------------------------------------------------------------------------------|
| articleId | string | ✅ | The ID of the item displaying the salesposter. |
| articleType | string | ✅ | Type of content the salesposter is displayed on. F.ex. "subscription" or "metered" |
| tags | string[] | ✅ | Article tags |
| section | string | ✅ | Article (or frontpage) category. F.ex. "sport" or "news" |
| refId | string | ❌ | To override normal flows with specific references to origins. F.ex. Vink salesposters when coming from the Vink landingpage |
[!NOTE] Only
salesposterplacements have required params. These are marked with a ✅ above.
Dismiss
The dismiss functionality is used to hide a placement when an element is clicked inside the placement. For example, this is used when a user clicks "X" on a bottombar or clicks a link in a foldup.
To enable this, you need to have at least one element in the placement with the data-dismiss-on-click attribute. When this element is clicked, the placement will be hidden, and data is stored in local storage to prevent the placement from being shown again for a certain period of time.
Example:
const sps = new SPS({
domain: "aftenposten.no",
publication: SpsPublication.AFTENPOSTEN,
})
const poster = await sps.getPoster("bottombar", {
params: {},
}, {
elementId: "id-of-iframe",
dismissTime: "1day" //possible values is | "1day" | "2days"| "3days"| "4days"| "5days"| "6days"| "1week"| "2week"| "1month"
});
<iframe id="id-of-iframe" />HTML Variables
The HTML content returned from SPS may include the variables {{redirect_uri_url_encoded}} and {{loginUrl}}.
{{redirect_uri_url_encoded}}: This variable defaults to the encoded URL of the current page. To override the redirect URL, you can specify a custom URL by implementing agenerateRedirectUrlfunction and passing it to thegetPosterorgetPosterByDecisionmethods.{{loginUrl}}: This variable does not have a default value. You must provide it by implementing agenerateLoginUrlfunction. This function can utilize data from the SPS response, but it should also include additional parameters, such asclient_id.
const poster = await sps.getPoster("salesposter", {
...
generateLoginUrl: ({ campaign }) =>
`https://payment.schibsted.no/authn/?client_id=123abc321&utm_campaign=${campaign}`,
generateRedirectUrl: () => "customReturnUrl",
});These variables can also be included in the HTML of the fallback content.
Fallback Poster
By default, the SDK will throw an error if the SPS API returns an error or an invalid poster response. You can prevent this by providing a fallback poster in the getPoster and getDecision methods.
You can use placeholders {{redirect_uri_url_encoded}} and {{loginUrl}} in the fallback content.
const posterResponse = await sps.getPoster("foldup", {
...
fallbackPoster: {
content: `
<div>
<p>Fallback Content</p>
<a href="https://kampanje.aftenposten.no/bliabonnent-fulltilgang?redirect_uri={{redirect_uri_url_encoded}}">
Kjøp
</a>
<a href="{{loginUrl}}">
Logg inn
</a>
</div>
`,
campaign: "fallback-campaign",
contentName: "fallback-content",
},
});If you always need to show a poster, you can set the showFallbackPosterOnNoContent option to true when calling the getPoster or getDecision methods. This will ensure that the fallback poster is displayed even when there is no content available.
Custom Types
You can pass a custom type to the getPoster and the getDecision method to enforce stricter typing:
type FoldupProps = {
section: "sport" | "news" | "culture";
wordCount: number;
payWall: boolean;
};
const posterResponse = await sps.getPoster<FoldupProps>("foldup", {
params: {
section: "sport",
wordCount: 100,
payWall: true,
},
});Tracking
Pulse Proxy View and Click events
The SDK can automatically generate Pulse tracking events for HTML type content. This utilizes the Pulse Tracker Proxy to send tracking events from a child iframe to a parent window, where the Pulse Tracker sends the event to Pulse.
The Tracker needs to be exposed to Proxy Clients in the parent frame with direct access to the Pulse Tracker:
import PulseTracker from '@schibsted/pulse-sdk';
// Create tracker instance (use your existing tracker implementation if you have it)
const tracker = new PulseTracker("aftenposten");
// [Important!] Expose the tracker to child frames
tracker.exposeToProxyClients();Once this is done, the scripts in the poster contents will run the tracking on View and on Click.
See package docs for detailed info.
Custom Tracking Data
You can customize the tracking behavior by providing a tracking object in the getPoster and getPosterByDecision methods.
The SDK automatically creates a pulse object and pulse origin object, and includes it in the response for all placements. The pulse.origin object is generated based on utm tags and poster data, and should be assigned to Pulse on the SalesPoster View events. This includes generating the x_domain_id and injecting the x_env_id if available.
const { content, trackingData, pulseOrigin, pulseObject, ...rest } = await sps.getPoster("foldup", {
params: {
section: "sport",
},
generateLoginUrl: () => loginUrl,
tracking: {
contentId: "article-123",
contentType: "other",
additionalParams: {
foo: "bar",
},
utmTagOverrides: {
utm_term: "sport,other,another"
}
},
});| Property | Type | Required | Description |
|--------------------|--------------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| contentId | string | ✅ | The ID of the item displaying the salesposter (usually article ID or stream ID). |
| contentType | ContentType | ❌ | Type of content the salesposter is displayed on. Defaults to "article". |
| additionalParams | Record<string, string> | ❌ | Used to pass any additional parameters to the sales poster as query parameters. |
| appendSaleslead | boolean | ❌ | If true, -lead will be postfixed to the utm_content parameter. |
| paywallType | string | ❌ | The paywall type, used for utm_medium if no other utm_medium is provided.Example: "metered", "subscription".Note: Potentially deprecated. |
| utmUrlDefault | string | ❌ | Override the default utm_url parameter to something other than the current URL.Still overridden by search params or utmTagOverrides if present. |
| utmTagOverrides | UtmTags | ❌ | Override UTM parameters or provide them when missing from window.location.href. |
| pulseObject | PulseObject | ❌ | Source details for generating pulse.object. Details vary based on type |
Utm Tags
UTM tags are overridden with query params in the page url, and can also be specifically overridden with utmTagOverrides property in the tracking object.
If no overrides are provided, the UTM tags will be deduced the following way:
| UTM Tag | Source |
|----------------|---------------------------------------------------------------------------------------------------------|
| utm_source | Deduced from document.referrer, concatenated with -front, -article depending on location. |
| utm_medium | Set to paywallType if it is provided to trackingData, otherwise placementType from the poster data. |
| utm_campaign | Set to campaign from the poster data. |
| utm_content | Set to articleType from the params passed to getPoster |
| utm_term | Only from query params/ override |
| utm_url | Only from query params/ override |
[!NOTE]
utm_channelis not a supported UTM tag, and will not be set.utm_mediumis the value used for Pulse origin channel.
Getting Tracking Params Without Poster
It is also possible to only get the tracking parameters without fetching a poster. This can be useful to create purchase links and tracking for other purposes. Here, you need to manually pass window and document.
import { createTrackingParams, addSearchParams } from 'sps-sdk';
const { pulseOrigin, trackingData, searchParams } = createTrackingParams({
contentId: 'abc123',
contentType: 'article',
paywallType: 'subscription',
document,
window,
}, {
placementType: 'salesposter',
articleType: 'subscription',
campaign: 'spring-sale'
});
const subscriptionUrl = `${addSearchParams(canonicalSubscriptionUrl, searchParams)}&redirect_uri=${encodedUrl}`;Development
Setup
nvm use
yarn installBuild
yarn buildLink
Use yarn link to link the package to your local npm registry to avoid having to publish alpha versions when testing the package in other projects.
Every time you make changes to the code, you need to run yarn build.
yarn linkLint
yarn lintTesting
yarn test[!NOTE] Some of the tests assume that the SPS SDK is built and available in the
distdirectory. Make sure to runyarn buildbefore running the tests.
Deployment
This project uses semantic-release to automate versioning and publishing. Versions are determined automatically based on commit messages following the Conventional Commits specification.
To Deploy a New Version of the Package:
- Open a PR to the
releasebranch with a title following Conventional Commits format - Merge the PR — semantic-release will automatically:
- Determine the version bump based on commits
- Update
CHANGELOG.md - Bump the version in
package.json - Create a GitHub release and tag
- Publish to npm
Valid PR Title Examples:
| PR Title | Version Bump |
|----------|--------------|
| fix: handle null response [CBT-12345] | Patch (1.0.0 → 1.0.1) |
| fix(tracking): correct utm_source [CBT-12346] | Patch (1.0.0 → 1.0.1) |
| feat: add new tracking parameter [CBT-12347] | Minor (1.0.0 → 1.1.0) |
| feat!: redesign API response format [CBT-12348] | Major (1.0.0 → 2.0.0) |
To Publish an Alpha or Beta Version:
- Squash your branch into the
alphaorbetabranch - Commit the changes as a single commit, using a Conventional Commits formatted commit message
- Push the branch/ merge the pr
- This will trigger a pre-release build and publish an alpha or beta version to npm with the corresponding dist-tag.
