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

@alpine-iq/third-party-loops

v0.1.2

Published

Display Alpine IQ sponsored ad placements (loops) in your app. For third-party integration partners.

Readme

@alpine-iq/third-party-loops

Display Alpine IQ sponsored ad placements ("loops") in your app or website. This package is intended for third-party integration partners (Jane, Dutchie, DigitalAwesome, etc.) and exposes only what you need — no access to the broader Alpine IQ internal library is required.

Prerequisites

Before integrating, you will need the following from your Alpine IQ account manager:

  • API URL — the base URL for the Alpine IQ API
  • Vendor name — your integration identifier (e.g. jane, dutchie); used in the vendor ads endpoint
  • Publisher UID — your organization's unique identifier in the Alpine IQ system

If you don't have these, reach out to your Alpine IQ contact before proceeding.


Install

npm install @alpine-iq/third-party-loops
# or
yarn add @alpine-iq/third-party-loops
# or
pnpm add @alpine-iq/third-party-loops

Peer dependencies

The following must already be installed in your project:

npm install react react-dom

| Package | Version | |---|---| | react | ^18.2.0 | | react-dom | ^18.2.0 |

Required styles

The widget requires Alpine IQ UI styles to render correctly. Import them once in your app's entry point:

import '@alpine-iq/third-party-loops/styles/main.css'
import '@alpine-iq/third-party-loops/styles/animations.css'

If you are using a CSS bundler or framework that doesn't support bare CSS imports, copy the contents of these files into your own stylesheet. Contact your Alpine IQ account manager if you need the raw CSS files.


Quick start

1. Initialize the API client

Call initApiClient once at your app's startup, before any widget renders. Calling it multiple times is safe — subsequent calls are ignored. apiUrl and vendorName are required; without vendorName the widget cannot fetch ads and will render nothing.

import { initApiClient } from '@alpine-iq/third-party-loops'

initApiClient({
  apiUrl: 'https://api.alpineiq.com',  // provided by Alpine IQ
  vendorName: 'your-vendor-name',      // e.g. jane, dutchie — provided by Alpine IQ (required)
})

2. Render the widget

import { LoopsWidget } from '@alpine-iq/third-party-loops'

function ProductPage() {
  return (
    <LoopsWidget uid="your-publisher-uid" />
  )
}

That's it. The widget fetches and renders the appropriate ads automatically, and handles impression tracking with no additional setup.


React usage

Basic example

import { LoopsWidget } from '@alpine-iq/third-party-loops'

function StorePage() {
  return (
    <LoopsWidget
      uid="your-publisher-uid"
      variant="cover-card"
      onAdsLoaded={(ads) => console.log(`${ads.length} ad(s) loaded`)}
    />
  )
}

With a known user (personalized ads)

If the current user is logged in and you have their Alpine IQ contact ID, pass it via contactID to enable personalized ad targeting:

<LoopsWidget
  uid="your-publisher-uid"
  contactID={currentUser.aiqContactId}
/>

Without contactID, ads are served anonymously and are not personalized.

With a loading state

Use onLoadingChange to show a skeleton or placeholder while ads are being fetched:

const [loading, setLoading] = React.useState(true)

<>
  {loading && <MySkeletonPlaceholder />}
  <LoopsWidget
    uid="your-publisher-uid"
    onLoadingChange={setLoading}
  />
</>

Props

| Prop | Type | Required | Default | Description | |------|------|----------|---------|-------------| | uid | string | Yes | — | Your publisher UID, provided by Alpine IQ. | | variant | 'cover-card' \| 'padded-card' | No | 'cover-card' | Visual style of the ad card. | | contactID | string | No | — | Alpine IQ contact ID of the current logged-in user. Enables personalized ads. Omit for anonymous/guest users. | | categories | string[] | No | — | Filter ads to specific product categories. Pass a memoized array to avoid unnecessary refetches. | | limit | number | No | — | Maximum number of ads to display. | | customStyles | string | No | — | Raw CSS string injected into the widget's container. Use for minor layout overrides. | | onAdsLoaded | (ads: AdTemplateOut[]) => void | No | — | Called when the ad fetch completes. Receives the loaded ads (empty array if none). | | onLoadingChange | (loading: boolean) => void | No | — | Called with true when a fetch starts and false when it finishes (or fails). | | onError | (error: Error) => void | No | — | Called when the ad fetch fails. Use to log errors or show fallback content. | | disableImpressionTracking | boolean | No | false | Set to true to disable view/click tracking. Useful for testing or staging environments. | | useMockAds | boolean | No | false | Set to true to render built-in sample ads without calling the API. Useful for local development and testing your integration layout before going live. Impression tracking is automatically disabled. |


Non-React / script tag usage

If your app is not built with React, you can mount the widget using the global window.__AIQLoopsCore API. Use the IIFE bundle (which includes React so no extra scripts are required), then call mountReact:

<div id="aiq-loops-root"></div>
<!-- When installed via npm, the IIFE is at the package root (publish root is dist/) -->
<script src="node_modules/@alpine-iq/third-party-loops/third-party-loops.iife.js"></script>
<script>
  // Initialize the API client first
  window.__AIQLoopsCore.initApiClient({
    apiUrl: 'https://api.alpineiq.com',
    vendorName: 'your-vendor-name',
  })

  // Mount the widget
  const instance = window.__AIQLoopsCore.mountReact({
    element: '#aiq-loops-root',
    uid: 'your-publisher-uid',
    variant: 'cover-card',
  })

  // Optional: react when ads load
  instance.ads.then((ads) => {
    console.log(`${ads.length} ad(s) loaded`)
  })
</script>

To unmount (e.g. on page navigation):

window.__AIQLoopsCore.unmountReact(instance)

MountOptions

| Option | Type | Required | Description | |---|---|---|---| | element | string \| HTMLElement | Yes | CSS selector string or DOM element to mount into. | | uid | string | Yes | Your publisher UID. | | variant | 'cover-card' \| 'padded-card' | No | Visual style. Default 'cover-card'. | | contactID | string | No | Alpine IQ contact ID of the logged-in user. | | categories | string[] | No | Filter ads by category. | | limit | number | No | Max number of ads. | | customStyles | string | No | Raw CSS for the widget container. | | disableImpressionTracking | boolean | No | Disable impression tracking. Default false. | | onError | (error: Error) => void | No | Called when the ad fetch fails. | | useMockAds | boolean | No | Render built-in sample ads without calling the API. Default false. |


Advanced: calling the API directly

The plugin exports the underlying API helpers if you need to fetch ads or track impressions outside of the widget component (e.g. server-side rendering, custom UI, prefetching):

import {
  initApiClient,
  getAdsForUserAndLocation,
  createAdImpressionResponse,
  submitAdImpressionType,
} from '@alpine-iq/third-party-loops'

// Fetch ads manually (vendor placement is implied by initApiClient; no adLocation param)
const ads = await getAdsForUserAndLocation(
  'your-publisher-uid',
  contactID,    // optional
  categories,   // optional
  limit,        // optional
  refURL        // optional; defaults to current page URL in browser
)

// Or fetch built-in sample ads for testing (no API call, no initApiClient needed)
const mockAds = await getAdsForUserAndLocation(
  'any-uid',
  undefined, undefined, undefined, undefined,
  { useMockAds: true }
)

// Track that an ad was viewed
const impression = await createAdImpressionResponse(ads[0].impressionTracker)

// Track a specific event (e.g. a click)
await submitAdImpressionType(impression, 'click')

getAdsForUserAndLocation will throw if the API request fails. Wrap in try/catch if you need to handle errors. createAdImpressionResponse and submitAdImpressionType silently swallow errors — they will never throw.


TypeScript

The package ships with full type declarations. Useful types you can import:

import type {
  InitApiClientConfig,
  LoopsWidgetProps,
  MountOptions,
  MountResult,
} from '@alpine-iq/third-party-loops'

Troubleshooting

The widget renders nothing.

  • Check the browser console for any API errors.
  • Confirm initApiClient was called before the widget mounted, with the correct apiUrl and vendorName.
  • Make sure the uid is correct — an unrecognized UID will return zero ads.

Styles look broken or unstyled.

  • Confirm you've imported both CSS files at your app's entry point (see Required styles).

Ads are not personalized.

  • Pass the contactID prop with the user's Alpine IQ contact ID. Without it, the anonymous endpoint is used.

I want to test without recording real impressions.

  • Pass disableImpressionTracking={true} to the widget.

I want to test with sample ads before going live.

  • Pass useMockAds={true} to the widget. This renders built-in sample ads (image + video) without calling the API, so you don't need initApiClient or a valid UID. Impression tracking is automatically disabled.

Support

Contact your Alpine IQ account manager or integration support team for API URL, vendor name, UIDs, and placement guidance.