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

@bernierllc/social-media-linkedin

v1.0.4

Published

LinkedIn API integration service with OAuth 2.0, content posting (posts, articles, documents), and rate limiting

Readme

@bernierllc/social-media-linkedin

LinkedIn API integration service with OAuth 2.0, content posting (posts, articles, documents), and rate limiting.

Installation

npm install @bernierllc/social-media-linkedin

Usage

import { LinkedInService } from '@bernierllc/social-media-linkedin';

// Create a new LinkedIn service instance
const linkedin = new LinkedInService({
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  callbackUrl: 'https://your-app.com/callback',
  scopes: ['openid', 'profile', 'w_member_social']
});

// Generate authorization URL for OAuth flow
const authUrl = linkedin.getAuthorizationUrl();
// Redirect user to authUrl...

// After user authorizes, handle the callback
const authResult = await linkedin.handleCallback(code, state);
if (authResult.success) {
  console.log('Authenticated as:', authResult.profile?.name);
}

// Post an update
const postResult = await linkedin.postUpdate({
  text: 'Hello LinkedIn! 👋',
  visibility: 'PUBLIC'
});

if (postResult.success) {
  console.log('Posted:', postResult.postUrn);
}

API Reference

LinkedInService

Main service class for LinkedIn API integration.

Constructor

new LinkedInService(config: LinkedInConfig)

Config Options:

  • clientId (required): LinkedIn application client ID
  • clientSecret (required): LinkedIn application client secret
  • callbackUrl (required): OAuth callback URL
  • scopes: OAuth scopes to request (default: ['openid', 'profile', 'w_member_social'])
  • rateLimitConfig: Rate limiting configuration
  • tokenStorage: Token storage interface for persistence

Authentication Methods

getAuthorizationUrl(callbackUrl?: string): string

Generate OAuth 2.0 authorization URL for user consent.

const authUrl = linkedin.getAuthorizationUrl();
// Redirect user to authUrl
handleCallback(code: string, state: string): Promise<LinkedInAuthResult>

Handle OAuth callback and exchange authorization code for tokens.

const result = await linkedin.handleCallback(code, state);
if (result.success) {
  console.log('User:', result.profile);
  console.log('Tokens:', result.tokens);
}
authenticate(credentials: LinkedInCredentials): Promise<LinkedInAuthResult>

Authenticate with existing access token.

const result = await linkedin.authenticate({
  accessToken: 'existing-token',
  expiresAt: 1234567890
});
refreshAccessToken(): Promise<LinkedInAuthResult>

Refresh the access token using refresh token.

const result = await linkedin.refreshAccessToken();
revokeAccess(): Promise<boolean>

Revoke access and clear tokens.

const revoked = await linkedin.revokeAccess();
isAuthenticated(): boolean

Check if currently authenticated with valid token.

if (linkedin.isAuthenticated()) {
  // Can make API calls
}

Content Posting Methods

postUpdate(content: LinkedInContent): Promise<LinkedInPostResult>

Post an update to LinkedIn.

const result = await linkedin.postUpdate({
  text: 'Check out this post!',
  visibility: 'PUBLIC',
  media: [{
    type: 'image',
    data: imageBuffer,
    mimeType: 'image/jpeg',
    altText: 'Description of the image'
  }]
});
postArticle(article: LinkedInArticle): Promise<LinkedInArticleResult>

Post an article to LinkedIn.

const result = await linkedin.postArticle({
  title: 'My Article Title',
  body: '<p>Article content in HTML</p>',
  description: 'Brief description',
  visibility: 'PUBLIC'
});
shareDocument(document: LinkedInDocument): Promise<LinkedInPostResult>

Share a document (PDF, etc.) to LinkedIn.

const result = await linkedin.shareDocument({
  title: 'My Document',
  text: 'Check out this document!',
  file: {
    type: 'document',
    data: pdfBuffer,
    mimeType: 'application/pdf',
    fileName: 'document.pdf'
  }
});
deletePost(postUrn: string): Promise<boolean>

Delete a post by URN.

const deleted = await linkedin.deletePost('urn:li:share:123456');

Scheduling Methods

schedulePost(content: LinkedInContent, scheduledTime: Date): LinkedInScheduleResult

Schedule a post for future publishing.

const result = linkedin.schedulePost(
  { text: 'Scheduled post!' },
  new Date('2025-01-15T10:00:00Z')
);
cancelScheduledPost(scheduleId: string): boolean

Cancel a scheduled post.

const cancelled = linkedin.cancelScheduledPost('sched_123');
listScheduledPosts(): LinkedInScheduledPost[]

List all scheduled posts.

const scheduled = linkedin.listScheduledPosts();

Media Methods

uploadMedia(media: LinkedInMedia): Promise<LinkedInMediaUploadResult>

Upload media to LinkedIn.

const result = await linkedin.uploadMedia({
  type: 'image',
  data: imageBuffer,
  mimeType: 'image/png'
});

if (result.success) {
  console.log('Asset URN:', result.assetUrn);
}

Rate Limiting

getRateLimitStatus(endpoint?: string): LinkedInRateLimitStatus | undefined

Get current rate limit status.

const status = linkedin.getRateLimitStatus('POST /ugcPosts');
console.log('Remaining:', status?.remaining);
console.log('Resets at:', new Date(status?.resetAt * 1000));

Types

LinkedInContent

interface LinkedInContent {
  text: string;                    // Up to 3000 characters
  media?: LinkedInMedia[];         // Optional media attachments
  link?: LinkedInLink;             // Optional link preview
  visibility?: LinkedInVisibility; // PUBLIC, CONNECTIONS, or LOGGED_IN
  organizationUrn?: string;        // For company page posts
}

LinkedInMedia

interface LinkedInMedia {
  type: 'image' | 'video' | 'document';
  data?: Buffer;       // Media data
  url?: string;        // External URL
  mimeType?: string;   // MIME type
  fileName?: string;   // File name
  altText?: string;    // Accessibility text
  size?: number;       // File size in bytes
}

LinkedInArticle

interface LinkedInArticle {
  title: string;                   // Up to 200 characters
  body: string;                    // HTML content
  description?: string;            // Summary/excerpt
  coverImage?: LinkedInMedia;      // Cover image
  visibility?: LinkedInVisibility;
  organizationUrn?: string;
}

OAuth Scopes

| Scope | Description | |-------|-------------| | openid | OpenID Connect (required) | | profile | Basic profile information | | email | Email address | | w_member_social | Post on behalf of user | | r_liteprofile | Read lite profile | | r_organization_social | Read company page content | | w_organization_social | Post to company pages |

Rate Limiting

The service automatically handles rate limits:

const linkedin = new LinkedInService({
  // ... other config
  rateLimitConfig: {
    enabled: true,
    strategy: 'wait',  // or 'fail'
    maxWaitMs: 60000   // Max wait time before failing
  }
});

Token Storage

Implement the LinkedInTokenStorage interface for token persistence:

const tokenStorage: LinkedInTokenStorage = {
  save: async (tokens) => {
    await db.saveTokens(tokens);
  },
  load: async () => {
    return await db.loadTokens();
  },
  clear: async () => {
    await db.clearTokens();
  }
};

const linkedin = new LinkedInService({
  // ... other config
  tokenStorage
});

Media Limits

| Type | Max Size | |------|----------| | Image | 8 MB | | Video | 200 MB | | Document | 100 MB |

Integration Status

  • Logger: planned - Will integrate with @bernierllc/logger
  • Docs-Suite: ready - Markdown documentation
  • NeverHub: planned - Event publishing for linkedin.post.published

See Also

License

Copyright (c) 2025 Bernier LLC. All rights reserved.