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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@thewhateverapp/tile-sdk

v0.10.1

Published

SDK for building interactive tiles on The Whatever App platform

Readme

@thewhateverapp/tile-sdk

⚠️ Beta Release (v0.0.1): This package is in early preview. Requires integration with The Whatever App parent window (thewhatever.app). Use for development and testing.

SDK for building interactive tiles on The Whatever App platform. Provides secure parent-tile communication, authentication, storage, and more.

Features

  • 🔐 Authentication: OAuth-like permission system for accessing user data
  • 💾 Storage: Parent-managed persistent storage (100KB per tile)
  • 📋 Clipboard: Secure clipboard access with user permission
  • 🔄 Navigation: Navigate to full page or open external URLs
  • 📊 Analytics: Track events and user interactions
  • 🎨 React Hooks: Easy-to-use React hooks and components
  • 🔒 Security: Sandboxed iframe with validated message bridge

Installation

npm install @thewhateverapp/tile-sdk

Quick Start

React Component

import { TileProvider, useTile } from '@thewhateverapp/tile-sdk/react';

function MyTileApp() {
  return (
    <TileProvider fallback={<div>Loading...</div>}>
      <TileContent />
    </TileProvider>
  );
}

function TileContent() {
  const tile = useTile();

  const handleClick = async () => {
    // Navigate to full page
    tile.navigateToPage();

    // Track event
    tile.trackEvent('button_click', { action: 'open_page' });
  };

  return (
    <div className="w-full h-full">
      <h1>My Tile</h1>
      <button onClick={handleClick}>Open Full App</button>
    </div>
  );
}

Vanilla JavaScript

import { getTileBridge } from '@thewhateverapp/tile-sdk';

const bridge = getTileBridge();

// Wait for bridge to be ready
await bridge.waitForReady();

// Use bridge APIs
bridge.navigateToPage();
bridge.trackEvent('page_view', { page: 'home' });

Authentication

The tile SDK implements an OAuth-like permission system where apps start with minimal access (user ID only) and must request additional permissions.

Permission Scopes

  • id - User's internal ID (always granted)
  • profile - Username, avatar, display name
  • email - Email address
  • wallet - Connected wallet addresses
  • analytics:read - View user's analytics
  • analytics:write - Track analytics (auto-granted)
  • storage:read - Read user's storage (auto-granted)
  • storage:write - Write to storage (auto-granted)

Request User Authentication

const tile = useTile();

// Request user with specific scopes
const handleLogin = async () => {
  const user = await tile.auth.getUser({
    scopes: ['profile', 'email'],
    reason: 'We need your email to send you updates'
  });

  if (user) {
    console.log('User granted access:', user);
    // { id, username, email, scopes }
  } else {
    console.log('User denied permission');
  }
};

Get Current User

const tile = useTile();

// Get current user (if already authenticated)
const user = await tile.auth.getCurrentUser();

if (user) {
  console.log(`Hello, ${user.username || 'User'}!`);
}

Request Additional Scopes

const tile = useTile();

// Later in the app, request wallet access
const handleConnectWallet = async () => {
  const granted = await tile.auth.requestScopes(['wallet']);

  if (granted) {
    const user = await tile.auth.getCurrentUser();
    console.log('Wallet addresses:', user.walletAddresses);
  }
};

Storage

Tiles get 100KB of persistent storage managed by the parent window.

const tile = useTile();

// Store data
await tile.storage.set('user_preferences', {
  theme: 'dark',
  notifications: true
});

// Retrieve data
const prefs = await tile.storage.get('user_preferences');
console.log('Theme:', prefs.theme);

Clipboard

Clipboard access requires user permission (one-time prompt).

const tile = useTile();

const handleCopy = async () => {
  try {
    await tile.clipboard.write('Hello from my tile!');
    console.log('Copied to clipboard!');
  } catch (error) {
    console.error('User denied clipboard access');
  }
};

Navigation

Navigate to Full Page

const tile = useTile();

// Opens the full page view of your app
tile.navigateToPage();

Open External URL

const tile = useTile();

// Opens URL with user confirmation
tile.openUrl('https://example.com', '_blank');

Analytics

Track user events and interactions.

const tile = useTile();

// Track custom event
tile.trackEvent('button_click', {
  button: 'subscribe',
  location: 'header'
});

// Track page view
tile.trackEvent('page_view', { page: 'home' });

Configuration

Access tile configuration from parent.

const tile = useTile();

if (tile.config) {
  console.log('App ID:', tile.config.tileId);
  console.log('Position:', tile.config.position);
  console.log('Theme:', tile.config.theme);
}

Advanced: Using TileBridge Directly

For non-React apps or advanced use cases:

import { getTileBridge } from '@thewhateverapp/tile-sdk';

const bridge = getTileBridge();

// Wait for ready
const config = await bridge.waitForReady();

// Listen for events
bridge.on('theme:change', (theme) => {
  console.log('Theme changed:', theme);
});

// Request resize (max 512x512)
bridge.requestResize(400, 300);

// Get user with auth
const user = await bridge.getUser({
  scopes: ['profile'],
  reason: 'Show your name'
});

// Storage operations
await bridge.setStorage('key', { data: 'value' });
const data = await bridge.getStorage('key');

// Navigate
bridge.navigateToPage();
bridge.openUrl('https://example.com');

// Track events
bridge.trackEvent('custom_event', { foo: 'bar' });

// Clipboard
await bridge.writeToClipboard('text to copy');

Security Model

Sandboxed Iframe

All tiles run in sandboxed iframes with strict policies:

<iframe
  sandbox="allow-scripts allow-pointer-lock"
  referrerpolicy="no-referrer"
  allow="clipboard-read; clipboard-write"
/>

Origin Validation

All messages are validated against the parent origin:

  • Production: https://thewhatever.app
  • Development: http://localhost:3000

Permission System

  • Apps start with minimal access (user ID only)
  • Additional permissions require explicit user consent
  • Users can revoke permissions anytime in settings
  • Permission grants are audited for security

Best Practices

1. Request Minimal Scopes

❌ Bad:

await tile.auth.getUser({
  scopes: ['profile', 'email', 'wallet']
});

✅ Good:

// Start minimal
await tile.auth.getUser({
  scopes: ['profile']
});

// Request more later when needed
await tile.auth.requestScopes(['email']);

2. Explain Why

await tile.auth.getUser({
  scopes: ['email'],
  reason: 'Send you order confirmation'
});

3. Handle Denials Gracefully

const user = await tile.auth.getUser({ scopes: ['email'] });

if (!user || !user.email) {
  // Provide alternative flow
  return <ManualEmailInput />;
}

4. Check Scopes Before Using

const user = await tile.auth.getCurrentUser();

if (user?.scopes.includes('email')) {
  sendEmail(user.email);
} else {
  console.log('Email permission not granted');
}

API Reference

useTile() Hook

Returns the tile context with all available methods.

Returns: TileContextValue

TileContextValue

interface TileContextValue {
  config: TileConfig | null;
  bridge: TileBridge;
  isReady: boolean;

  // Navigation
  navigateToPage: () => void;
  openUrl: (url: string, target?: '_blank' | '_self') => void;

  // Analytics
  trackEvent: (eventName: string, data?: any) => void;

  // Storage
  storage: {
    get: (key: string) => Promise<any>;
    set: (key: string, value: any) => Promise<void>;
  };

  // Clipboard
  clipboard: {
    write: (text: string) => Promise<void>;
  };

  // Authentication
  auth: {
    getUser: (options?: {
      scopes?: string[];
      reason?: string;
    }) => Promise<UserContext | null>;
    requestScopes: (scopes: string[]) => Promise<boolean>;
    getCurrentUser: () => Promise<UserContext | null>;
  };
}

TileConfig

interface TileConfig {
  tileId: string;
  position: { row: number; col: number };
  tileUrl: string;
  apiEndpoint?: string;
  theme?: 'light' | 'dark';
  debug?: boolean;
}

UserContext

interface UserContext {
  authenticated: boolean;
  userId: string;
  scopes: string[];

  // Only if 'profile' scope granted
  username?: string;
  avatar?: string;
  displayName?: string;

  // Only if 'email' scope granted
  email?: string;

  // Only if 'wallet' scope granted
  walletAddresses?: string[];
}

Examples

Example: Simple Counter Tile

import { TileProvider, useTile } from '@thewhateverapp/tile-sdk/react';
import { useState, useEffect } from 'react';

function CounterTile() {
  return (
    <TileProvider>
      <Counter />
    </TileProvider>
  );
}

function Counter() {
  const tile = useTile();
  const [count, setCount] = useState(0);

  // Load count from storage on mount
  useEffect(() => {
    tile.storage.get('count').then(savedCount => {
      if (savedCount !== undefined) setCount(savedCount);
    });
  }, []);

  // Save count to storage when it changes
  useEffect(() => {
    tile.storage.set('count', count);
  }, [count]);

  const increment = () => {
    setCount(c => c + 1);
    tile.trackEvent('counter_increment', { value: count + 1 });
  };

  return (
    <div className="flex flex-col items-center justify-center h-full">
      <h1 className="text-4xl font-bold">{count}</h1>
      <button onClick={increment} className="mt-4 px-6 py-2 bg-blue-500">
        Increment
      </button>
      <button onClick={() => tile.navigateToPage()} className="mt-2">
        View Full App
      </button>
    </div>
  );
}

Example: User Profile Tile

import { TileProvider, useTile } from '@thewhateverapp/tile-sdk/react';
import { useState, useEffect } from 'react';

function ProfileTile() {
  return (
    <TileProvider>
      <Profile />
    </TileProvider>
  );
}

function Profile() {
  const tile = useTile();
  const [user, setUser] = useState(null);

  const handleLogin = async () => {
    const userData = await tile.auth.getUser({
      scopes: ['profile', 'email'],
      reason: 'Display your profile information'
    });

    if (userData) {
      setUser(userData);
      tile.trackEvent('user_logged_in');
    }
  };

  if (!user) {
    return (
      <div className="flex items-center justify-center h-full">
        <button onClick={handleLogin} className="px-6 py-3 bg-purple-500">
          Sign In
        </button>
      </div>
    );
  }

  return (
    <div className="p-4">
      <img src={user.avatar} className="w-16 h-16 rounded-full" />
      <h2 className="text-xl font-bold mt-2">{user.username}</h2>
      {user.email && <p className="text-gray-600">{user.email}</p>}
      <button onClick={() => tile.navigateToPage()} className="mt-4">
        View Full Profile
      </button>
    </div>
  );
}

TypeScript Support

Full TypeScript support with type definitions included.

import type {
  TileContextValue,
  TileConfig,
  UserContext,
  TileBridge
} from '@thewhateverapp/tile-sdk';

Development

Local Testing

# Install dependencies
npm install

# Build
npm run build

# Run type checking
npm run typecheck

# Lint
npm run lint

Testing with Parent

To test your tile locally, you'll need the parent window running:

# In parent project
npm run dev

# Your tile will be loaded at:
# http://localhost:3000/tile/[your-app-id]

Resources

License

Proprietary - All rights reserved