@pmate/bridge 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/global.d.ts CHANGED
@@ -2,6 +2,8 @@ declare global {
2
2
  interface Window {
3
3
  PmateBridge?: {
4
4
  call: (functionName: string, params?: any) => Promise<any>
5
+ on?: (eventName: string, handler: (payload: any) => void) => () => void
6
+ __emit?: (eventName: string, payload: any) => void
5
7
  __notify: (event: any) => void
6
8
  }
7
9
  isPmate?: boolean
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmate/bridge",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "files": [
5
5
  "src",
6
6
  "global.d.ts"
@@ -1,4 +1,4 @@
1
- import { PropsWithChildren, useEffect, useMemo, useState } from "react"
1
+ import { PropsWithChildren, ReactNode, useEffect, useMemo, useState } from "react"
2
2
  import { PmateBridgeContext } from "./context"
3
3
  import {
4
4
  getFallbackSafeInsets,
@@ -13,11 +13,13 @@ import type { DeviceInfoState, SafeInsetsState } from "./types"
13
13
 
14
14
  export type PmateBridgeProviderProps = PropsWithChildren<{
15
15
  disableSafeAreaVars?: boolean
16
+ loadingFallback?: ReactNode
16
17
  }>
17
18
 
18
19
  export function PmateBridgeProvider({
19
20
  children,
20
21
  disableSafeAreaVars = false,
22
+ loadingFallback = null,
21
23
  }: PmateBridgeProviderProps) {
22
24
  const [safeInsetsState, setSafeInsetsState] = useState<SafeInsetsState>({
23
25
  insets: getFallbackSafeInsets(),
@@ -87,7 +89,12 @@ export function PmateBridgeProvider({
87
89
  [deviceInfoState, isPmate, safeInsetsState]
88
90
  )
89
91
 
92
+ const runtimeReady = !isPmate || safeInsetsState.ready
90
93
  const content = disableSafeAreaVars ? children : <SafeAreaVarsProvider>{children}</SafeAreaVarsProvider>
91
94
 
92
- return <PmateBridgeContext.Provider value={contextValue}>{content}</PmateBridgeContext.Provider>
95
+ return (
96
+ <PmateBridgeContext.Provider value={contextValue}>
97
+ {runtimeReady ? content : loadingFallback}
98
+ </PmateBridgeContext.Provider>
99
+ )
93
100
  }
@@ -1,10 +1,10 @@
1
- import { PropsWithChildren, useEffect } from "react"
1
+ import { PropsWithChildren, useLayoutEffect } from "react"
2
2
  import { useSafeInsets } from "./hooks"
3
3
 
4
4
  export function SafeAreaVarsProvider({ children }: PropsWithChildren) {
5
5
  const { insets } = useSafeInsets()
6
6
 
7
- useEffect(() => {
7
+ useLayoutEffect(() => {
8
8
  if (typeof document === "undefined") {
9
9
  return
10
10
  }
package/src/index.ts CHANGED
@@ -1,4 +1,17 @@
1
1
  export * from "./PmateBridgeProvider"
2
2
  export * from "./SafeAreaVarsProvider"
3
3
  export * from "./hooks"
4
+ export {
5
+ connectBluetoothDevice,
6
+ disconnectBluetoothDevice,
7
+ getBluetoothCapabilities,
8
+ getConnectedBluetoothDevices,
9
+ onBluetoothEvent,
10
+ readBluetooth,
11
+ requestBluetoothPermission,
12
+ scanBluetoothDevices,
13
+ stopBluetoothScan,
14
+ subscribeBluetooth,
15
+ writeBluetooth,
16
+ } from "./internal"
4
17
  export type * from "./types"
package/src/internal.ts CHANGED
@@ -1,8 +1,26 @@
1
- import type { DeviceInfo, PermissionResult, PickedFile, SafeInsets } from "./types"
1
+ import type {
2
+ BluetoothCapabilities,
3
+ BluetoothConnectOptions,
4
+ BluetoothConnection,
5
+ BluetoothDevice,
6
+ BluetoothEventName,
7
+ BluetoothPermissionResult,
8
+ BluetoothReadOptions,
9
+ BluetoothReadResult,
10
+ BluetoothScanOptions,
11
+ BluetoothScanResult,
12
+ BluetoothSubscribeOptions,
13
+ BluetoothWriteOptions,
14
+ DeviceInfo,
15
+ PermissionResult,
16
+ PickedFile,
17
+ SafeInsets,
18
+ } from "./types"
2
19
 
3
20
  type PmateWindow = Window & {
4
21
  PmateBridge?: {
5
22
  call: <T>(functionName: string, params?: unknown) => Promise<T>
23
+ on?: (eventName: string, handler: (payload: unknown) => void) => () => void
6
24
  }
7
25
  isPmate?: boolean
8
26
  }
@@ -30,6 +48,13 @@ async function callBridge<T>(functionName: string, params?: unknown): Promise<T
30
48
  }
31
49
  }
32
50
 
51
+ async function requireBridge<T>(functionName: string, params?: unknown): Promise<T> {
52
+ if (!hasWindow() || !(window as PmateWindow).PmateBridge?.call) {
53
+ throw new Error("Pmate bridge is unavailable")
54
+ }
55
+ return (window as PmateWindow).PmateBridge!.call<T>(functionName, params)
56
+ }
57
+
33
58
  export function getFallbackSafeInsets(): SafeInsets {
34
59
  return { ...ZERO_SAFE_INSETS }
35
60
  }
@@ -73,3 +98,66 @@ export async function pickFileBridge(): Promise<PickedFile[]> {
73
98
  const files = await callBridge<PickedFile[]>("pickFile")
74
99
  return files ?? []
75
100
  }
101
+
102
+ export function getBluetoothCapabilities(): Promise<BluetoothCapabilities> {
103
+ return requireBridge<BluetoothCapabilities>("bluetooth.getCapabilities")
104
+ }
105
+
106
+ export function requestBluetoothPermission(): Promise<BluetoothPermissionResult> {
107
+ return requireBridge<BluetoothPermissionResult>("bluetooth.requestPermission")
108
+ }
109
+
110
+ export function scanBluetoothDevices(options?: BluetoothScanOptions): Promise<BluetoothScanResult> {
111
+ return requireBridge<BluetoothScanResult>("bluetooth.scan", options)
112
+ }
113
+
114
+ export function stopBluetoothScan(): Promise<{ stopped: true }> {
115
+ return requireBridge<{ stopped: true }>("bluetooth.stopScan")
116
+ }
117
+
118
+ export function getConnectedBluetoothDevices(): Promise<{ devices: BluetoothDevice[] }> {
119
+ return requireBridge("bluetooth.getConnectedDevices")
120
+ }
121
+
122
+ export function connectBluetoothDevice(
123
+ deviceId: string,
124
+ options?: BluetoothConnectOptions
125
+ ): Promise<BluetoothConnection> {
126
+ return requireBridge<BluetoothConnection>("bluetooth.connect", { deviceId, options: options ?? null })
127
+ }
128
+
129
+ export function disconnectBluetoothDevice(connectionId: string): Promise<{ disconnected: true }> {
130
+ return requireBridge<{ disconnected: true }>("bluetooth.disconnect", { connectionId })
131
+ }
132
+
133
+ export function writeBluetooth(
134
+ connectionId: string,
135
+ data: string,
136
+ options: BluetoothWriteOptions
137
+ ): Promise<{ written: true }> {
138
+ return requireBridge<{ written: true }>("bluetooth.write", { connectionId, data, options })
139
+ }
140
+
141
+ export function readBluetooth(
142
+ connectionId: string,
143
+ options?: BluetoothReadOptions
144
+ ): Promise<BluetoothReadResult> {
145
+ return requireBridge<BluetoothReadResult>("bluetooth.read", { connectionId, options: options ?? null })
146
+ }
147
+
148
+ export function subscribeBluetooth(
149
+ connectionId: string,
150
+ options: BluetoothSubscribeOptions
151
+ ): Promise<{ subscribed: true }> {
152
+ return requireBridge<{ subscribed: true }>("bluetooth.subscribe", { connectionId, options })
153
+ }
154
+
155
+ export function onBluetoothEvent(
156
+ eventName: BluetoothEventName,
157
+ handler: (payload: unknown) => void
158
+ ): () => void {
159
+ if (!hasWindow() || !(window as PmateWindow).PmateBridge?.on) {
160
+ throw new Error("Pmate bridge event subscription is unavailable")
161
+ }
162
+ return (window as PmateWindow).PmateBridge!.on!(`bluetooth.${eventName}`, handler)
163
+ }
package/src/types.ts CHANGED
@@ -33,3 +33,96 @@ export interface PermissionResult {
33
33
  requested: unknown
34
34
  granted: boolean
35
35
  }
36
+
37
+ export type BluetoothTransport = "ble" | "classic"
38
+ export type BluetoothBinaryEncoding = "base64" | "hex" | "utf-8"
39
+ export type BluetoothEventName =
40
+ | "scanResult"
41
+ | "scanStopped"
42
+ | "connected"
43
+ | "disconnected"
44
+ | "connectionLost"
45
+ | "data"
46
+ | "permissionChanged"
47
+ | "error"
48
+
49
+ export interface BluetoothCapabilities {
50
+ supported: boolean
51
+ enabled: boolean
52
+ ble: boolean
53
+ classic: boolean
54
+ canScan: boolean
55
+ canConnect: boolean
56
+ transports: BluetoothTransport[]
57
+ }
58
+
59
+ export interface BluetoothPermissionResult {
60
+ requested: string[]
61
+ granted: boolean
62
+ missing: string[]
63
+ }
64
+
65
+ export interface BluetoothDevice {
66
+ id: string
67
+ name: string
68
+ kind: "unknown"
69
+ transport: BluetoothTransport
70
+ rssi: number | null
71
+ manufacturer: string | null
72
+ services: string[]
73
+ paired: boolean | null
74
+ }
75
+
76
+ export interface BluetoothScanOptions {
77
+ timeoutMs?: number
78
+ transports?: BluetoothTransport[]
79
+ services?: string[]
80
+ allowDuplicates?: boolean
81
+ }
82
+
83
+ export interface BluetoothScanResult {
84
+ scanId: string
85
+ devices: BluetoothDevice[]
86
+ active: boolean
87
+ }
88
+
89
+ export interface BluetoothConnectOptions {
90
+ transport?: BluetoothTransport
91
+ }
92
+
93
+ export interface BluetoothConnection {
94
+ id: string
95
+ deviceId: string
96
+ transport: BluetoothTransport
97
+ state: "connected"
98
+ }
99
+
100
+ export interface BluetoothWriteOptions {
101
+ service?: string
102
+ characteristic?: string
103
+ encoding: BluetoothBinaryEncoding
104
+ withResponse?: boolean
105
+ }
106
+
107
+ export interface BluetoothReadOptions {
108
+ service?: string
109
+ characteristic?: string
110
+ encoding?: BluetoothBinaryEncoding
111
+ }
112
+
113
+ export type BluetoothSubscribeOptions = BluetoothReadOptions
114
+
115
+ export interface BluetoothReadResult {
116
+ data: string
117
+ encoding: BluetoothBinaryEncoding
118
+ }
119
+
120
+ export interface BluetoothDataEvent {
121
+ connectionId: string
122
+ deviceId: string
123
+ service?: string
124
+ characteristic?: string
125
+ data: string
126
+ encoding: BluetoothBinaryEncoding
127
+ receivedAt: number
128
+ }