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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@dpgradio/creative

v8.0.0

Published

Support package for standalone Javascript applications

Downloads

611

Readme

@dpgradio/creative

Table of contents:

Installation

yarn add @dpgradio/creative

CSS: Fonts and Colors

Note that version 5 of this package provides fonts and colors as pure css. The variables and fonts are no longer available as scss variables/imports.

Example:

@import "@dpgradio/creative/styles/colors/qmusic";
@import "@dpgradio/creative/styles/fonts/qmusic";

body, html {
  background-color: rgb(var(--q-green));
  color: rgb(var(--q-grey) / 0.8);
  font-family: 'BlackBones';
}

Use the following to import the colors and fonts of all brands:

@import "@dpgradio/creative/styles/all";

Config

Usage

Add the following in the main script of your application to retrieve the configuration. The configuration will also be used by other components of this package (e.g. privacy, tracking, etc.).

import { configuration } from '@dpgradio/creative'

// Global config only
await configuration.retrieveConfigForDetectedStation()

// Global and app-specific config
await configuration.retrieveConfigForDetectedStation(appId) // Default, by hostname or query parameter

// Or, if you want to retrieve the config by hostnames only.
await configuration.retrieveConfigByHostname(appId)
// Or, if you want to retrieve the config for a specific station:
await configuration.retrieveConfigForStation(stationId, appId)

// By default the first station ID (stationIdA) is used as the current station of the configuration.
// If you want to use a different station, you can do so by calling setStation:
await configuration.retrieveConfigForStations([stationIdA, stationIdB, stationIdC], appId)
configuration.setStation(stationIdC)

You can change the station later on at any time without having to retrieve the config again. The API client will automatically use the correct station. Privacy and tracking need to be reinitalized after changing the station.

The config can now be used as follows:

import { config } from '@dpgradio/creative'

config('api_base_url') // or: config().api_base_url
config('app.default_theme') // or: config().app.default_theme

Update Schema

Applications can create/modify the configuration schema by creating a script with the following contents:

import { api, configuration } from '@dpgradio/creative'

api.setApiKey('<api-token-here>')

await configuration.replaceSchema('greety', [
  {
    name: 'display_language',
    type: 'options',
    default: 'dutch',
    options: ['dutch', 'english'],
  },
  {
    name: 'author_name',
    type: 'string',
  },
])

(assuming availablity of top-level await and fetch)

Privacy and Tracking

Privacy

Initialization

Without config:

import { privacy } from '@dpgradio/creative'

privacy.initialize(privacyManagerId, websiteUrl, cmpCname)

With config (after initializing the config):

import { privacy } from '@dpgradio/creative'

privacy.initialize()

Get consent string

import { privacy } from '@dpgradio/creative'

const consent = await privacy.waitForConsent()

// Available properties:
consent.consentString
consent.purposes
consent.allowsTargetedAdvertising()

Tracking

import { dataLayer } from '@dpgradio/creative'

dataLayer.initialize() // Pushes gtmStart and currently authenticated user
dataLayer.pushVirtualPageView(brand) // brand is not required when the config is initialized

dataLayer.pushEvent(event, data)

With Mixpanel

import { mixpanel } from '@dpgradio/creative'
import Mixpanel from 'mixpanel-browser'

mixpanel.initialize(Mixpanel, {
    mixpanelId: 'MIXPANEL ID',
})

mixpanel.trackEvent(event, data)

API

With an initialized configuration:

import { api } from '@dpgradio/creative'

const channels = await api.channels.all()

const globalChannels = await api.global().channels.all()

api.setRadioToken(token)
const profile = await api.members.me() // or await api.setRadioToken(token).members.me()

api.setApiKey(key)
await api.global().config.update(appId, schema)

const oldChannels = await api.onVersion('1.4').channels.all()

Without an initialized configuration:

import { Api } from '@dpgradio/creative'

const api = new Api('https://api.qmusic.be')

const channels = await api.channels.all()

Socket

import { socket } from '@dpgradio/creative'

socket.connect(stationId).subscribe('plays').on('play', () => {
  console.log('play')
}, { backlog: 3 }})

// or

socket.join({ station: stationId, entity: 'plays', action: 'play', options: { backlog: 3 } }, (play) => {
  console.log(play)
})

Hybrid

The app interaction layer for use in webviews in the app. Interacts trough a "JavaScript bridge". On iOS it uses message handlers and on Android it uses JavascriptInterface.

import { hyrid } from '@dpgradio/creative'

Information

hybrid.appInfo()

Gives information about the brand, platform, version etc. of the app.

hybrid.isNativeApp()

Returns true if the client is a native app.

hybrid.isVersion({ iOS, Android })

Returns true if the client is a native app and the version is equal to or lower than the given version for the given platform.

Listen for events

The app will emit events that can be listened to. For certain events, such as appLoad it is important to start listening as soon as possible. Because this is sometimes difficult to do, the appLoaded method can be used to wait for the appLoad event. And if the event has already been emitted before the method is called, it will return immediately.

The following events are available:

  • appLoad: The app has loaded. If the user is logged in, it provides their radio token.
  • authenticated: The user has authenticated. It provides the user's radio token.
  • didAppear: The webview is visible. If the user is logged in, it provides their radio token. ⚠️ Test when exactly this event is emitted.
  • didHide: The webview is hidden. ⚠️ Test when exactly this event is emitted.

hybrid.on(event, callback, once)

Listen for an event. If once is true, the callback will only be called once.

hybrid.one(event, callback)

Listen for an event. The callback will only be called once.

hybrid.appLoaded()

This method returns a promise that resolves when the appLoad event is emitted. If the event has already been emitted before the method is called, it will return immediately.

If the user is logged in, the user's radio token is returned.

Example usage:

const radioToken = await hybrid.appLoaded()
await api.members.me()

Actions

To initiate actions on the app side, we provide the methods below.

Under the hood these actions are called using the following method, which can also be used directly in case you need to call an action that is not available as a method:

hybrid.call(method, options)

You may encounter the method "navigateTo" in older code. This method is now deprecated and replaced by hybrid.openUrl and hybrid.openPermalink.

hybrid.openUrl(url, { mode })

Opens an external URL in a mode of choice. This is not meant to open URLs on our own domains, e.g. https://qmusic.be/this-is-a-cool-article. For that, use hybrid.openPermalink.

Supported modes:

  • seque: Opens the URL in a windows that slides from the right, pushing onto the current page.
  • overlay: Opens the URL in a full-screen modal that slides up from the bottom.
  • in-app-browser: Opens the URL in an in-app browser with navigation controls. No hybrid functionality supported.
  • external-browser: Opens the URL in the default browser. No hybrid functionality supported.

hybrid.openPermalink(permalink)

Opens a permalink of e.g. an article. The article will be shown in a regular seque style.

⚠️ As of October 2023, not yet supported by the apps.

hybrid.showAuthentication()

Shows an authentication dialog.

On Android this is shown regardless of the login status, on iOS it's only shown when the user is not logged in.

hybrid.changeHeight(height, { animated })

Changes the height of the webview.

Authentication

import { authentication } from '@dpgradio/creative'

// Call once, at least before requiring authentication somewhere
authentication.initialize()

// Main usage: prompts for login if needed and sets the token in the API client
await authentication.require()


// Potentially helpful methods (but not required for typical usage)
authentication.isLoggedIn()
authentication.onRadioTokenChange(callback)
authentication.onLogin(callback)
authentication.askForLogin() // Does the same as require(), but does not await the result
authentication.radioToken

Sharing Generator

Example:

import { Shareable, ImageGeneratorProperties } from '@dpgradio/creative/share'

const shareable = new Shareable()
  .withTitle(`${this.name} is mijn seventies match!`)
  .withDescription('Ontdek wie jouw seventies match is in de Generation Quiz van Joe!')
  .withMessageText(`Mijn seventies match is ${this.name}, wat is die van jou?`)
  .redirectTo('https://article_url.com')
  .fromDomain('joe.be')

// Facebook
const image = new ImageGeneratorProperties('https://static.qmusic.be/acties/joe-70s-quiz-share-fb/index.html')
  .withDimensions(1200, 630)
  .withPayload({ results: this.matches });
       
(await shareable.generateUsingImage(image)).openFacebookUrl()

// Instagram
const image = new ImageGeneratorProperties('https://static.qmusic.be/acties/joe-70s-quiz-share-fb/index.html')
  .withDimensions(1080, 1920)
  .withPayload({ results: this.matches });
       
(await shareable.generateUsingImage(image)).openInstagramUrl()

// Whatsapp
const image = new ImageGeneratorProperties('https://static.qmusic.be/acties/joe-70s-quiz-share-fb/index.html')
  .withDimensions(1200, 630)
  .withPayload({ results: this.matches });
       
(await shareable.generateUsingImage(image)).openWhatsappUrl()

CSP

Sometimes it's necessary to set strict Content Security Policy (CSP) headers. When that's the case a nicety of some of the methods in this package is that they support a random nonce to be passed in so extra sources (GTM, Privacy gate) don't need to be explicitly whitelisted (since they might be unknown at the time of development). Our datalayer and privacy gate initializers have an optional nonce parameter that can be passed in.

import { privacy, dataLayer } from '@dpgradio/creative'

privacy.initialize(privacyManagerId, websiteUrl, cmpCname, { nonce: "abc1234" })
dataLayer.initialize({ nonce: "abc1234" })

Which then allows you to whitelist that nonce in your CSP header

Content-Security-Policy: script-src 'nonce-abc1234'

!! Take note that this nonce need to be random on every request, as else this security is useless !!

Utilities

This package provides a number of utility functions.

| Function | Description | | --------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | loadScript(url, { timeout }) | Dynammically loads a JS script. | | openExternalUrl(url) | Opens external URL as separate from the current page as possible on web/app. | | tap(value, callback) | Invokes callback with the value and then returns value. | | cdnImageUrl(endpoint[, size]) | Get a full image URL for an endpoint in a given size (w480, w800, w1200, w2400). | | cdnUrl(endpoint) | Get a full CDN URL for a given endpoint. | | removePhoneNumberCountryPrefix(phoneNumber, [, prefix]) | Removes a country prefix from a phone number based on the station config country_code. | | onLocalStorageChange(key, callback) | Calls callback when the value of key in localStorage changes. | | decodeRadioToken(token) | Decodes a JWT radio token. |