npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@schibsted/sps-sdk

v2.17.0-alpha.1

Published

SPS SDK for node.js

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

  1. Requirements
  2. Installation
  3. Usage
  1. HTML Variables
  2. Fallback Poster
  3. Custom Types
  4. Tracking
  1. Development

Requirements

  • Node.js 18 or higher.

Installation

Install the package with:

npm install @snoam/sps-sdk
# or
yarn add @snoam/sps-sdk

Usage

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 production or staging in production code. The -test environments 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 salesposter placements 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 a generateRedirectUrl function and passing it to the getPoster or getPosterByDecision methods.
  • {{loginUrl}}: This variable does not have a default value. You must provide it by implementing a generateLoginUrl function. This function can utilize data from the SPS response, but it should also include additional parameters, such as client_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_channel is not a supported UTM tag, and will not be set. utm_medium is 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 install

Build

yarn build

Link

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 link

Lint

yarn lint

Testing

yarn test

[!NOTE] Some of the tests assume that the SPS SDK is built and available in the dist directory. Make sure to run yarn build before 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:

  1. Open a PR to the release branch with a title following Conventional Commits format
  2. 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:

  1. Squash your branch into the alpha or beta branch
  2. Commit the changes as a single commit, using a Conventional Commits formatted commit message
  3. 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.