@pwa-kit/sdk
v0.1.2
Published
JavaScript SDK for PWAKit native bridge
Maintainers
Readme
@pwa-kit/sdk 
TypeScript SDK for communicating with the PWAKit native iOS bridge. Provides Web API-aligned interfaces for native capabilities like push notifications, badges, haptics, biometrics, HealthKit, StoreKit, and more.
Installation
npm install @pwa-kit/sdkBrowser Bundle
For direct browser usage without a bundler:
<script src="https://cdn.jsdelivr.net/gh/eddmann/pwa-kit@main/sdk/dist/index.global.js"></script>
<script>
const { push, badging, haptics, ios, isNative } = PWAKit;
if (isNative) {
badging.setAppBadge(5);
}
</script>Quick Start
import { push, badging, haptics, ios, isNative } from "@pwa-kit/sdk";
if (isNative) {
// Subscribe to push notifications (PushManager API)
const subscription = await push.subscribe();
console.log("Token:", subscription.token);
// Set app badge (Badging API)
await badging.setAppBadge(5);
// Haptic feedback
await haptics.impact("medium");
// iOS-specific: Face ID / Touch ID
const result = await ios.biometrics.authenticate("Confirm purchase");
}API Structure
The SDK organizes APIs into three categories:
Web API-Aligned Modules
These modules follow Web Platform API conventions:
| Module | Web Standard | Description |
| ------------- | ----------------------------------------------------------------------------------- | ------------------------------- |
| push | PushManager | Push notification registration |
| badging | Badging API | App icon badges |
| vibration | Vibration API | Vibration (uses haptics on iOS) |
| clipboard | Clipboard API | Read/write clipboard |
| share | Web Share API | Native share sheet |
| permissions | Permissions API | Permission management |
Enhanced APIs
Extended functionality beyond web standards:
| Module | Description |
| ---------- | ----------------------------------------------------- |
| haptics | iOS haptic feedback (impact, notification, selection) |
| print | AirPrint functionality |
| platform | Platform detection and device info |
iOS-Specific Modules (Namespaced)
Native iOS features without web equivalents, namespaced under ios.*:
| Module | Description |
| ------------------- | --------------------------------- |
| ios.biometrics | Face ID / Touch ID authentication |
| ios.secureStorage | Keychain storage |
| ios.healthKit | HealthKit data access |
| ios.storeKit | In-app purchases (StoreKit 2) |
| ios.app | App lifecycle and reviews |
| ios.notifications | Local notification scheduling |
Web API Alignment
PWAKit SDK methods are designed to match Web Platform API signatures as closely as possible:
| SDK Module | Web Standard | Alignment |
| ------------- | ----------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| push | PushManager | Exact match - subscribe(), getSubscription(), permissionState() |
| badging | Badging API | Exact match - setAppBadge(), clearAppBadge() |
| vibration | Vibration API | Exact match - vibrate(pattern) (polyfills with haptics on iOS) |
| clipboard | Clipboard API | Text methods - writeText(), readText() |
| share | Web Share API | Match - share(), async canShare() |
| permissions | Permissions API | Match with extension - query() + request() |
Intentional Deviations
These deviations improve the developer experience on mobile:
clipboard.readText()returnsstring | nullinstead of throwing when clipboard is emptyshare.canShare()is async to check native availability (Web API is sync)share.share()returnsShareResultwithcompletedandactivityType(Web returns void)permissions.request()is an extension for pre-prompting (not in Web standard)
Web API-Aligned Modules
Push (PushManager)
import { push } from "@pwa-kit/sdk";
// Subscribe to push notifications
const subscription = await push.subscribe();
console.log("Token:", subscription.token);
console.log("Endpoint:", subscription.endpoint); // apns://token
// Get existing subscription
const existing = await push.getSubscription();
if (existing) {
console.log("Already subscribed");
}
// Check permission state
const state = await push.permissionState();
// 'granted' | 'denied' | 'prompt'Badging (Badging API)
import { badging } from "@pwa-kit/sdk";
// Set badge count
await badging.setAppBadge(5);
// Clear badge
await badging.clearAppBadge();Vibration (Vibration API)
import { vibration } from "@pwa-kit/sdk";
// Vibrate (triggers haptic on iOS)
await vibration.vibrate(100);
await vibration.vibrate([100, 50, 100]); // PatternClipboard (Clipboard API)
import { clipboard } from "@pwa-kit/sdk";
// Write text
await clipboard.writeText("Hello, World!");
// Read text
const text = await clipboard.readText();Share (Web Share API)
import { share } from "@pwa-kit/sdk";
// Share URL
await share.share({
title: "Check this out",
url: "https://example.com",
});
// Share with files
await share.share({
title: "Document",
files: [
{
name: "report.pdf",
type: "application/pdf",
data: "base64encodeddata...",
},
],
});
// Check availability
const canShare = await share.canShare();Permissions (Permissions API)
import { permissions } from "@pwa-kit/sdk";
// Query permission state
const status = await permissions.query({ name: "camera" });
console.log("Camera:", status.state); // 'granted' | 'denied' | 'prompt'
// Request permission
const result = await permissions.request({ name: "geolocation" });
// Supported permissions: 'camera', 'geolocation', 'microphone'Enhanced APIs
Haptics
Native iOS haptic feedback with impact, notification, and selection patterns.
import { haptics } from "@pwa-kit/sdk";
// Impact feedback
await haptics.impact("light"); // light, medium, heavy, soft, rigid
await haptics.impact("medium");
await haptics.impact("heavy");
// Notification feedback
await haptics.notification("success"); // success, warning, error
await haptics.notification("error");
// Selection feedback
await haptics.selection();AirPrint support for the current page.
import { print } from "@pwa-kit/sdk";
const result = await print.print();
if (result.success) {
console.log("Print job submitted");
}Platform
Platform detection and device information.
import { platform } from "@pwa-kit/sdk";
const info = await platform.getInfo();
// {
// platform: 'ios',
// version: '17.0',
// isNative: true,
// appVersion: '1.0.0',
// buildNumber: '42',
// deviceModel: 'iPhone15,2'
// }iOS-Specific Modules
These modules are namespaced under ios.* as they don't have web equivalents.
ios.biometrics
Face ID and Touch ID authentication.
import { ios } from "@pwa-kit/sdk";
// Check availability
const availability = await ios.biometrics.isAvailable();
// { available: true, biometryType: 'faceId' }
// Authenticate
const result = await ios.biometrics.authenticate("Confirm your identity");
if (result.success) {
// Authentication successful
}ios.secureStorage
Keychain-backed secure storage that persists across app reinstalls.
import { ios } from "@pwa-kit/sdk";
// Store value
await ios.secureStorage.set("auth_token", "secret123");
// Retrieve value
const token = await ios.secureStorage.get("auth_token");
// Check if key exists
const exists = await ios.secureStorage.has("auth_token");
// Delete value
await ios.secureStorage.delete("auth_token");ios.healthKit
Access health and fitness data via Apple HealthKit.
import { ios } from "@pwa-kit/sdk";
// Check availability
const { available } = await ios.healthKit.isAvailable();
// Request authorization
const auth = await ios.healthKit.requestAuthorization({
read: ["stepCount", "heartRate"],
readWorkouts: true,
readSleep: true,
});
// Query steps
const steps = await ios.healthKit.querySteps({
startDate: "2024-01-01T00:00:00Z",
endDate: "2024-01-31T23:59:59Z",
});
// Query heart rate
const heartRate = await ios.healthKit.queryHeartRate({
startDate: "2024-01-01T00:00:00Z",
endDate: "2024-01-31T23:59:59Z",
});
// Query workouts
const workouts = await ios.healthKit.queryWorkouts({
startDate: "2024-01-01T00:00:00Z",
endDate: "2024-01-31T23:59:59Z",
limit: 10,
});
// Query sleep
const sleep = await ios.healthKit.querySleep({
startDate: "2024-01-01T00:00:00Z",
endDate: "2024-01-31T23:59:59Z",
});
// Save workout
await ios.healthKit.saveWorkout({
activityType: "running",
startDate: "2024-01-15T08:00:00Z",
endDate: "2024-01-15T08:30:00Z",
duration: 1800,
totalEnergyBurned: 350,
totalDistance: 5000,
});ios.storeKit
In-app purchases via StoreKit 2.
import { ios } from "@pwa-kit/sdk";
// Get products
const products = await ios.storeKit.getProducts(["premium", "coins_100"]);
for (const product of products) {
console.log(`${product.displayName}: ${product.displayPrice}`);
}
// Purchase
const result = await ios.storeKit.purchase("premium");
if (result.success) {
console.log("Transaction ID:", result.transactionId);
}
// Restore purchases
await ios.storeKit.restore();
// Get entitlements
const entitlements = await ios.storeKit.getEntitlements();
console.log("Owned:", entitlements.ownedProductIds);
// Check ownership
const hasPremium = await ios.storeKit.isOwned("premium");ios.app
App lifecycle and utilities.
import { ios } from "@pwa-kit/sdk";
// Get app version
const version = await ios.app.getVersion();
// { version: '1.0.0', build: '42' }
// Request App Store review
await ios.app.requestReview();
// Open app settings
await ios.app.openSettings();ios.notifications
Local notification scheduling. Schedule one-off or recurring notifications that fire even when the app is in the background.
import { ios } from "@pwa-kit/sdk";
// Schedule a notification in 60 seconds
await ios.notifications.schedule({
id: "reminder-123",
title: "Time for a break",
body: "You have been working for an hour",
trigger: { type: "timeInterval", seconds: 60 },
});
// Schedule a repeating notification (minimum 60 seconds)
await ios.notifications.schedule({
id: "hourly",
title: "Hourly check-in",
trigger: { type: "timeInterval", seconds: 3600, repeats: true },
});
// Schedule at a specific date
await ios.notifications.schedule({
id: "meeting",
title: "Meeting starts",
body: "Project sync in conference room A",
trigger: { type: "date", date: new Date("2024-12-25T10:00:00") },
});
// Schedule a daily recurring notification (9 AM)
await ios.notifications.schedule({
id: "daily-reminder",
title: "Good morning!",
body: "Start your day with a review",
trigger: { type: "calendar", hour: 9, minute: 0, repeats: true },
});
// Include badge, sound, and custom data
await ios.notifications.schedule({
id: "full-example",
title: "New message",
body: "You have a new message",
subtitle: "From John",
badge: 1,
sound: "default",
data: { messageId: "123", senderId: "john" },
trigger: { type: "timeInterval", seconds: 5 },
});
// Cancel a specific notification
await ios.notifications.cancel("reminder-123");
// Cancel all scheduled notifications
await ios.notifications.cancelAll();
// Get all pending notifications
const pending = await ios.notifications.getPending();
for (const notification of pending) {
console.log(`${notification.id}: ${notification.title}`);
if (notification.nextTriggerDate) {
console.log(` Next: ${notification.nextTriggerDate}`);
}
}Trigger Types:
| Type | Description | Example |
| -------------- | -------------------------- | --------------------------------------------------------- |
| timeInterval | Fire after N seconds | { type: 'timeInterval', seconds: 60, repeats: false } |
| date | Fire at specific date/time | { type: 'date', date: new Date('2024-12-25T10:00:00') } |
| calendar | Fire on calendar match | { type: 'calendar', hour: 9, minute: 0, repeats: true } |
Calendar Trigger Components:
hour(0-23),minute(0-59),second(0-59)weekday(1=Sunday, 7=Saturday)day(1-31),month(1-12),year
Note: iOS limits apps to 64 scheduled local notifications. Use getPending() to monitor your notification count.
Detection Utilities
import {
isNative,
getPlatformInfo,
hasMessageHandlers,
} from "@pwa-kit/sdk";
// Quick check if running in native app
if (isNative) {
// Use native features
}
// Full platform info
const info = getPlatformInfo();
// {
// isNative: true,
// hasMessageHandlers: true,
// hasPWAKitUserAgent: true,
// platform: 'ios',
// userAgent: '...'
// }
// Check for webkit message handlers
if (hasMessageHandlers()) {
// Bridge is available
}Bridge API
For advanced usage, access the bridge directly.
import { bridge } from "@pwa-kit/sdk";
// Call any native module
const response = await bridge.call("myModule", "myAction", { key: "value" });
// Listen to native events
window.addEventListener("pwa:push", (event) => {
console.log("Push notification:", event.detail);
});
window.addEventListener("pwa:lifecycle", (event) => {
console.log("Lifecycle event:", event.detail);
});Custom Modules
You can extend PWAKit by creating your own native Swift modules that can be called from JavaScript.
Quick Example
Swift Module:
public struct MyModule: PWAModule {
public static let moduleName = "myModule"
public static let supportedActions = ["doSomething"]
public init() {}
public func handle(
action: String,
payload: AnyCodable?,
context: ModuleContext
) async throws -> AnyCodable? {
try validateAction(action)
switch action {
case "doSomething":
let value = payload?["value"]?.stringValue ?? "default"
return AnyCodable([
"result": AnyCodable("Processed: \(value)")
])
default:
throw BridgeError.unknownAction(action)
}
}
}JavaScript Usage:
import { bridge } from "@pwa-kit/sdk";
const result = await bridge.call("myModule", "doSomething", { value: "test" });
console.log(result.result); // "Processed: test"See the Custom Modules Guide for complete documentation including:
- The
PWAModuleprotocol - Registration and feature flags
- Working with UIKit and @MainActor
- Error handling patterns
- Complete Hello World example
TypeScript
Full TypeScript support with type definitions for all modules.
import type {
// Push types
PushSubscription,
PushPermissionState,
// Permission types
PermissionName,
PermissionState,
PermissionStatus,
// iOS types
BiometryType,
BiometricAvailability,
AuthenticationResult,
ProductInfo,
PurchaseResult,
HealthSample,
WorkoutData,
// Local notification types
NotificationOptions,
NotificationTrigger,
TimeIntervalTrigger,
DateTrigger,
CalendarTrigger,
PendingNotification,
// Platform types
PlatformInfo,
PlatformDetectionInfo,
} from "@pwa-kit/sdk";Browser Compatibility
The SDK safely handles non-native environments:
import { isNative, push, haptics } from "@pwa-kit/sdk";
if (isNative) {
// Running in PWAKit native app
await push.subscribe();
await haptics.impact("medium");
} else {
// Running in browser - provide fallbacks
console.log("Native features unavailable");
// Use web APIs or graceful degradation
}License
MIT
