tempest-react-sdk 0.51.0 → 0.53.0
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/README.md +35 -5
- package/dist/audio/audio-bus.cjs +2 -0
- package/dist/audio/audio-bus.cjs.map +1 -0
- package/dist/audio/audio-bus.js +116 -0
- package/dist/audio/audio-bus.js.map +1 -0
- package/dist/audio/use-audio-bus.cjs +2 -0
- package/dist/audio/use-audio-bus.cjs.map +1 -0
- package/dist/audio/use-audio-bus.js +11 -0
- package/dist/audio/use-audio-bus.js.map +1 -0
- package/dist/auth/refresh-queue.cjs +1 -1
- package/dist/auth/refresh-queue.cjs.map +1 -1
- package/dist/auth/refresh-queue.js +6 -6
- package/dist/auth/refresh-queue.js.map +1 -1
- package/dist/components/RangeSlider/RangeSlider.cjs +1 -1
- package/dist/components/RangeSlider/RangeSlider.cjs.map +1 -1
- package/dist/components/RangeSlider/RangeSlider.js +23 -23
- package/dist/components/RangeSlider/RangeSlider.js.map +1 -1
- package/dist/components/Scheduler/Scheduler.cjs +1 -1
- package/dist/components/Scheduler/Scheduler.cjs.map +1 -1
- package/dist/components/Scheduler/Scheduler.js +1 -1
- package/dist/components/Scheduler/Scheduler.js.map +1 -1
- package/dist/components/Scheduler/Scheduler.module.cjs.map +1 -1
- package/dist/components/Scheduler/Scheduler.module.js.map +1 -1
- package/dist/components/Slider/Slider.cjs +1 -1
- package/dist/components/Slider/Slider.cjs.map +1 -1
- package/dist/components/Slider/Slider.js +9 -9
- package/dist/components/Slider/Slider.js.map +1 -1
- package/dist/forms/FormField.cjs +1 -1
- package/dist/forms/FormField.cjs.map +1 -1
- package/dist/forms/FormField.js +23 -21
- package/dist/forms/FormField.js.map +1 -1
- package/dist/http/describe-api-error.cjs +1 -1
- package/dist/http/describe-api-error.cjs.map +1 -1
- package/dist/http/describe-api-error.js +4 -2
- package/dist/http/describe-api-error.js.map +1 -1
- package/dist/http/use-describe-api-error.cjs +1 -1
- package/dist/http/use-describe-api-error.cjs.map +1 -1
- package/dist/http/use-describe-api-error.js +3 -2
- package/dist/http/use-describe-api-error.js.map +1 -1
- package/dist/offline/create-offline-database.cjs +2 -0
- package/dist/offline/create-offline-database.cjs.map +1 -0
- package/dist/offline/create-offline-database.js +29 -0
- package/dist/offline/create-offline-database.js.map +1 -0
- package/dist/offline/create-offline-store.cjs +1 -1
- package/dist/offline/create-offline-store.cjs.map +1 -1
- package/dist/offline/create-offline-store.js +29 -25
- package/dist/offline/create-offline-store.js.map +1 -1
- package/dist/query/QueryProvider.cjs +1 -1
- package/dist/query/QueryProvider.cjs.map +1 -1
- package/dist/query/QueryProvider.js +13 -11
- package/dist/query/QueryProvider.js.map +1 -1
- package/dist/query/foreign-client-warning.cjs +2 -0
- package/dist/query/foreign-client-warning.cjs.map +1 -0
- package/dist/query/foreign-client-warning.js +12 -0
- package/dist/query/foreign-client-warning.js.map +1 -0
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +695 -41
- package/dist/tempest-react-sdk.js +52 -46
- package/dist/utils/duplicate-instance.cjs +2 -0
- package/dist/utils/duplicate-instance.cjs.map +1 -0
- package/dist/utils/duplicate-instance.js +6 -0
- package/dist/utils/duplicate-instance.js.map +1 -0
- package/dist/utils/format.cjs +1 -1
- package/dist/utils/format.cjs.map +1 -1
- package/dist/utils/format.js +8 -3
- package/dist/utils/format.js.map +1 -1
- package/dist/webrtc/opus-sdp.cjs +3 -0
- package/dist/webrtc/opus-sdp.cjs.map +1 -0
- package/dist/webrtc/opus-sdp.js +94 -0
- package/dist/webrtc/opus-sdp.js.map +1 -0
- package/dist/webrtc/sender-bitrate.cjs +2 -0
- package/dist/webrtc/sender-bitrate.cjs.map +1 -0
- package/dist/webrtc/sender-bitrate.js +15 -0
- package/dist/webrtc/sender-bitrate.js.map +1 -0
- package/dist/ws/create-web-socket.cjs +1 -1
- package/dist/ws/create-web-socket.cjs.map +1 -1
- package/dist/ws/create-web-socket.js +123 -47
- package/dist/ws/create-web-socket.js.map +1 -1
- package/dist/ws/resilience.cjs +2 -0
- package/dist/ws/resilience.cjs.map +1 -0
- package/dist/ws/resilience.js +21 -0
- package/dist/ws/resilience.js.map +1 -0
- package/dist/ws/use-web-socket.cjs +1 -1
- package/dist/ws/use-web-socket.cjs.map +1 -1
- package/dist/ws/use-web-socket.js +37 -23
- package/dist/ws/use-web-socket.js.map +1 -1
- package/package.json +1 -1
|
@@ -285,49 +285,55 @@ import { createAudioPlayer as qo, playAudio as Jo, stopAudio as Yo } from "./aud
|
|
|
285
285
|
import { useAudio as Xo } from "./audio/use-audio.js";
|
|
286
286
|
import { createSfxPool as Zo } from "./audio/sfx-pool.js";
|
|
287
287
|
import { useSfxPool as Qo } from "./audio/use-sfx-pool.js";
|
|
288
|
-
import {
|
|
289
|
-
import {
|
|
290
|
-
import {
|
|
291
|
-
import {
|
|
292
|
-
import {
|
|
293
|
-
import {
|
|
294
|
-
import {
|
|
295
|
-
import {
|
|
296
|
-
import {
|
|
297
|
-
import {
|
|
298
|
-
import {
|
|
299
|
-
import {
|
|
300
|
-
import {
|
|
301
|
-
import {
|
|
302
|
-
import {
|
|
303
|
-
import {
|
|
304
|
-
import {
|
|
305
|
-
import {
|
|
306
|
-
import {
|
|
307
|
-
import {
|
|
308
|
-
import {
|
|
309
|
-
import {
|
|
310
|
-
import {
|
|
311
|
-
import {
|
|
312
|
-
import {
|
|
313
|
-
import {
|
|
314
|
-
import {
|
|
315
|
-
import {
|
|
316
|
-
import {
|
|
317
|
-
import {
|
|
318
|
-
import {
|
|
319
|
-
import {
|
|
320
|
-
import {
|
|
321
|
-
import {
|
|
322
|
-
import {
|
|
323
|
-
import {
|
|
324
|
-
import {
|
|
325
|
-
import {
|
|
326
|
-
import {
|
|
327
|
-
import {
|
|
328
|
-
import {
|
|
329
|
-
import {
|
|
330
|
-
import {
|
|
331
|
-
import {
|
|
332
|
-
import {
|
|
333
|
-
export { Ar as AIChat, Dr as AIChatComposer, kr as AIChatTurn, dr as ALL_BARCODE_FORMATS, Bi as API_ERROR_OFFLINE_KEY, Vi as API_ERROR_VALIDATION_KEY, er as AUDIO_MIME_CANDIDATES, Ca as AccessControlProvider, t as Accordion, n as Alert, r as AppBar, Oo as AppProviders, io as AppRouter, o as AppShell, s as AspectRatio, Qn as AudioPlayer, ur as AudioRecorder, aa as AuthGuard, c as Avatar, l as AvatarGroup, i as BREAKPOINTS, u as Badge, d as Banner, Fr as BarList, yr as BarcodeScanner, f as BottomNavigation, p as BottomSheet, m as Breadcrumbs, oo as BrowserRouter, h as Button, ja as CACHE_TIME, bs as CEPInput, xs as CNPJInput, Ss as CPFInput, Mn as Calendar, Ea as Can, g as Card, Rn as Carousel, _ as Center, Yn as Chat, Jn as ChatComposer, v as Checkbox, y as ChipInput, hn as ClickOutside, ln as CodeBlock, En as Collapsible, b as Combobox, kn as Command, gn as ConditionalWrapper, S as ConfirmDialog, I as Container, Dn as ContextMenu, Es as Controller, cn as CopyButton, Hi as DEFAULT_API_ERROR_STRINGS, fr as DEFAULT_BARCODE_FORMATS, Gs as DEFAULT_CAR_SPEED_KMH, $i as DEFAULT_CHUNK_SIZE, Ks as DEFAULT_CIRCUITY_FACTOR, qs as DEFAULT_MODE_DURATION_FACTORS, ua as DEFAULT_PUB_KEY_CRED_PARAMS, Fc as DIVERGING_STEP_COUNT, bn as DataList, zn as DataTable, w as DatePicker, Fn as DateRangePicker, xn as DescriptionList, T as Divider, E as Drawer, D as DropdownMenu, O as Dropzone, Bs as EARTH_RADIUS_KM, k as EmptyState, Do as ErrorBoundary, A as ErrorState, vn as ErrorText, kc as FeatureFlagsProvider, j as FileUpload, Br as FilterBar, Vn as FloatingActionButton, _n as For, M as Form, N as FormActions, ds as FormField, Ds as FormProvider, P as FormRow, F as FormSection, ka as GoogleSignIn, L as Grid, so as HashRouter, St as Hide, On as HoverCard, Gi as I18nProvider, yn as Image, Jt as ImageCropper, C as Input, B as InstallBanner, V as InstallButton, rt as Kanban, at as Kbd, Tn as Label, Kn as Lightbox, co as Link, Bn as ListTile, Or as Markdown, Mr as Masonry, lo as MemoryRouter, Ln as Menubar, x as Modal, ot as ModalsProvider, dn as Money, Cs as MoneyInput, Pn as MultiSelect, lt as NProgressBar, uo as NavLink, ct as Navbar, fo as Navigate, In as NavigationMenu, Hn as NavigationRail, Qt as NotificationCenter, Ic as ORDINAL_START_STEP, Ze as OfflineIndicator, po as Outlet, dt as Page, ft as Pagination, da as PasskeyError, pt as PasswordInput, ws as PhoneInput, ht as PinInput, gt as Popover, mn as Portal, _t as Progress, rn as QRCapacityError, sn as QRCode, Ia as QueryProvider, Ma as REFETCH_TIME, vt as Radio, yt as RadioGroup, bt as RangeSlider, xt as RatingStars, Wn as RefreshIndicator, un as RelativeTime, jn as Resizable, mo as Route, ro as RouteGuard, ho as Routes, Lc as SEQUENTIAL_STEP_COUNT, Na as STALE_TIME, wt as SafeArea, Xt as Scheduler, An as ScrollArea, Tt as SearchBar, Et as SegmentedControl, Dt as Select, Ct as Show, Ot as Sidebar, qn as SignaturePad, kt as Skeleton, Nn as Slider, At as Spacer, Yt as Sparkline, jt as Spinner, R as Stack, Mt as Stat, Nt as Stepper, Rt as StepperInput, zt as Switch, tt as SyncStatusBadge, Vt as Table, Ht as Tabs, Bt as Tag, wc as TelemetryProvider, Fi as TempestApiError, Ja as TempestDataProvider, Ut as Textarea, To as ThemeProvider, Un as TimePicker, Wt as Timeline, Kt as ToastProvider, Sn as Toggle, Cn as ToggleGroup, wn as ToggleGroupItem, Gt as Tooltip, Nr as Tour, ic as TrajectoryMap, jr as Transfer, fn as TreeView, pn as TruncateText, nt as UpdatePrompt, en as VirtualList, nn as VirtualTable, et as VisuallyHidden, No as WebPushClient, Po as WebPushPermissionDeniedError, Fo as WebPushUnsupportedError, Gn as Wizard, br as aiChatStrings, se as announce, Jr as applyFilters, it as applyKanbanMove, ac as applyTheme, gi as assertNever, fa as base64UrlToBytes, Vs as bearingDeg, cr as blobToWav, Ys as boundingBox, Xs as boundsCenter, Pi as buildApiUrl, Pr as buildBarListRows, Ne as buildOpenInChromeIntent, pa as bytesToBase64Url, Vc as cachedResponseBytes, ni as camelCase, ri as capitalize, ci as chunk, Pt as clamp, Fs as clampLatitude, ma as classifyPasskeyError, ce as clearAnnouncer, Go as clearCaches, e as cn, tn as compareValues, Qr as compressToString, $r as compressedStorage, ei as compressedStorageCodec, Sc as consoleSink, Ec as consoleTelemetryAdapter, sc as contrastRatio, zi as createApiClient, qo as createAudioPlayer, tr as createAudioRecorder, ia as createAuthStore, pr as createBarcodeDetector, cc as createColorScale, qa as createDataProvider, ko as createEventStream, Nc as createGrowthBookFeatureFlagsAdapter, Wi as createI18n, Mc as createInMemoryFlags, Wc as createInferenceProfiler, Xr as createJsonStorage, Pc as createLaunchDarklyFeatureFlagsAdapter, ir as createLevelMeter, ea as createLocalUploadStorage, Cc as createLogger, $n as createMediaRecorder, tc as createOSRMBackend, Ga as createOfflineStore, ss as createOfflineSync, Bo as createPartialResponse, ha as createPasskeyClient, nc as createPositionTracker, Oc as createPostHogTelemetryAdapter, Pa as createQueryKeys, la as createRefreshQueue, ta as createResumableUpload, Da as createRoleAccessControl, wo as createSelectors, Dc as createSentryTelemetryAdapter, Zo as createSfxPool, Co as createStore, Sa as createTempestAuth, gc as createTheme, $o as createVideoRecorder, Ns as createWebSocket, xi as debounce, oa as decodeJWT, ti as decompressFromString, fi as deepMerge, ao as defineRoutes, Ui as describeApiError, Ir as describeFilter, Rc as divergingScale, Mi as downloadCsv, La as emptyOffsetPage, an as encodeQR, lr as encodeWav, mt as estimatePasswordStrength, Ue as estimateStorage, Js as estimateTravel, Zs as expandBounds, Lr as filtersFromSearchParams, Yr as filtersToQueryParams, Rr as filtersToSearchParams, Qs as fitProjection, Ft as formatBytes, hs as formatCEP, gs as formatCNPJ, Vr as formatCPF, It as formatCompactNumber, Hr as formatCurrency, Ur as formatDate, Wr as formatDateForInput, Gr as formatDateTime, Uc as formatDurationMs, Kr as formatPercent, qr as formatPhone, Qi as generateIdempotencyKey, bc as getInitialTheme, mr as getSupportedBarcodeFormats, vc as getThemePreset, li as groupBy, Hs as haversineKm, lc as hexToOklch, uc as hexToRgb, dc as hexToRgbaString, cs as higherVersionWins, Ko as inspectCaches, Uo as installBackgroundSync, Lo as installNotificationClickHandler, Vo as installPrecache, Ro as installPushHandler, Ho as installRuntimeCache, zo as installSkipWaitingListener, Pe as isAndroid, Fe as isAndroidWithoutPromptApi, Ii as isApiError, Xn as isAudioOutputSelectionSupported, nr as isAudioRecordingSupported, hr as isBarcodeDetectionSupported, ga as isConditionalMediationAvailable, Is as isCoordinate, Ra as isCursorPage, _i as isDefined, pi as isEmpty, xr as isGenerating, Ie as isIOS, sa as isJWTExpired, or as isMediaCaptureSupported, vi as isNumber, za as isOffsetPage, _a as isPasskeySupported, yi as isPlainObject, va as isPlatformAuthenticatorAvailable, jo as isPushSupported, Li as isRetriableStatus, rs as isScreenCaptureSupported, ki as isShareSupported, as as isSpeechRecognitionSupported, Le as isStandalone, bi as isString, Ls as isValidLatitude, Rs as isValidLongitude, es as isVideoRecordingSupported, ii as kebabCase, Sr as lastAssistantId, ls as lastWriteWins, ca as lazyWithRetry, Xa as listQueryKey, on as matrixToPath, Si as memoizeOne, Ke as moveItem, gr as normalizeBarcode, zs as normalizeLongitude, ut as nprogress, fc as oklchToHex, mi as omit, Ci as once, Za as oneQueryKey, zr as operatorsFor, Xi as parseResponse, Us as pathLengthKm, Lt as percentOf, Oa as permissionsFromToken, Ka as persistQueryClientOffline, hi as pick, rr as pickAudioMimeType, ts as pickVideoMimeType, Jo as playAudio, ai as pluralize, $s as projectMercator, Di as randomId, ui as range, Hc as readDeviceProfile, oc as readThemeToken, pc as readableForeground, go as redirect, Wo as registerPeriodicSync, ze as registerServiceWorker, mc as relativeLuminance, Zt as relativeTime, Ua as removeById, We as requestPersistentStorage, Ri as retry, hc as rgbToHex, Cr as roleLabel, zc as scaleSteps, Bc as sequentialScale, Zn as setAudioOutput, Ai as share, ji as shareOrDownloadBlob, Fa as shouldRetryQuery, Be as skipWaiting, Ti as sleep, oi as slugify, Yo as stopAudio, Zr as storage, wr as tailSignature, _c as themeContrast, xc as themeInitScript, yc as themePresets, wi as throttle, Ni as toCsv, Ws as toRadians, si as truncate, Tr as turnTime, di as uniqueBy, _s as unmask, ec as unprojectMercator, Ve as unregisterAllServiceWorkers, na as uploadFingerprint, Zi as uploadWithProgress, Wa as upsertById, Mo as urlBase64ToUint8Array, wa as useAccessControl, le as useAnnounce, X as useAsync, Xo as useAudio, ar as useAudioRecorder, vr as useBarcodeScanner, z as useBeforeInstallPrompt, a as useBreakpoint, Ta as useCan, ke as useClickOutside, W as useClientFilter, te as useClipboard, Se as useCountdown, xe as useCounter, Qa as useCreate, Va as useCursorQuery, Ya as useDataProvider, H as useDebounce, de as useDeepMemo, $a as useDelete, Yi as useDescribeApiError, ye as useDisclosure, we as useDocumentTitle, Q as useDocumentVisibility, us as useErrorHandler, q as useEventListener, Ao as useEventStream, Te as useFavicon, Ac as useFeatureFlag, Os as useFieldArray, jc as useFlagValue, oe as useFocusTrap, ks as useForm, As as useFormContext, js as useFormState, ie as useGeolocation, _e as useHover, Ki as useI18n, re as useIdle, Re as useInstallPrompt, $ as useIntersectionObserver, pe as useInterval, Ae as useIsFirstRender, ne as useKeyboardShortcut, K as useLatestRef, eo as useList, be as useListState, J as useLocalStorage, _o as useLocation, ve as useLongPress, Me as useLongPressHandlers, Ee as useMap, vo as useMatch, Xe as useMediaDevices, Ye as useMediaPermission, G as useMediaQuery, sr as useMicrophone, st as useModals, yo as useNavigate, $t as useNotificationInbox, Aa as useOAuthCallback, je as useObjectUrl, Ha as useOfflineMutation, Qe as useOfflineSync, to as useOne, Z as useOnline, qi as useOptionalI18n, Ba as usePaginatedQuery, U as usePagination, bo as useParams, ya as usePasskeyCapabilities, ba as usePasskeyRegistration, xa as usePasskeySignIn, ra as usePoll, rc as usePositionTracker, fe as usePrevious, Io as usePushSubscription, Oe as useQueue, ee as useResizeObserver, xo as useRouteError, is as useScreenCapture, ae as useScrollLock, Je as useScrollOverflow, So as useSearchParams, He as useServiceWorkerUpdate, De as useSet, Qo as useSfxPool, qe as useSortable, os as useSpeechRecognition, ue as useStableCallback, Ge as useStorageEstimate, $e as useSyncStatus, Tc as useTelemetry, Eo as useTheme, he as useThrottle, me as useTimeout, qt as useToast, Y as useToggle, _r as useTorch, Ji as useTranslate, Ce as useTypewriter, no as useUpdate, Ts as useViaCEP, ns as useVideoRecorder, Ms as useWatch, Ps as useWebSocket, ge as useWindowSize, ms as useZodForm, vs as validateCNPJ, ys as validateCPF, fs as validateForm, Er as visibleTurns, Ei as withTimeout, Oi as writeXlsx, ps as zodResolver };
|
|
288
|
+
import { DEFAULT_MAX_GAIN as $o, createAudioBus as es } from "./audio/audio-bus.js";
|
|
289
|
+
import { useAudioBus as ts } from "./audio/use-audio-bus.js";
|
|
290
|
+
import { createVideoRecorder as ns, isVideoRecordingSupported as rs, pickVideoMimeType as is } from "./capture/video-recorder.js";
|
|
291
|
+
import { useVideoRecorder as as } from "./capture/use-video-recorder.js";
|
|
292
|
+
import { isScreenCaptureSupported as os, useScreenCapture as ss } from "./capture/use-screen-capture.js";
|
|
293
|
+
import { isSpeechRecognitionSupported as cs, useSpeechRecognition as ls } from "./capture/use-speech-recognition.js";
|
|
294
|
+
import { createOfflineDatabase as us } from "./offline/create-offline-database.js";
|
|
295
|
+
import { createOfflineSync as ds } from "./offline/create-offline-sync.js";
|
|
296
|
+
import { higherVersionWins as fs, lastWriteWins as ps } from "./offline/conflict.js";
|
|
297
|
+
import { useErrorHandler as ms } from "./error-boundary/use-error-handler.js";
|
|
298
|
+
import { FormField as hs } from "./forms/FormField.js";
|
|
299
|
+
import { validateForm as gs } from "./forms/validate-form.js";
|
|
300
|
+
import { zodResolver as _s } from "./forms/zod-resolver.js";
|
|
301
|
+
import { useZodForm as vs } from "./forms/use-zod-form.js";
|
|
302
|
+
import { formatCEP as ys, formatCNPJ as bs, unmask as xs, validateCNPJ as Ss, validateCPF as Cs } from "./forms/br-validators.js";
|
|
303
|
+
import { CEPInput as ws, CNPJInput as Ts, CPFInput as Es, MoneyInput as Ds, PhoneInput as Os } from "./forms/masked-inputs.js";
|
|
304
|
+
import { useViaCEP as ks } from "./forms/use-viacep.js";
|
|
305
|
+
import { Controller as As, FormProvider as js, useFieldArray as Ms, useForm as Ns, useFormContext as Ps, useFormState as Fs, useWatch as Is } from "./forms/index.js";
|
|
306
|
+
import { HEARTBEAT_CLOSE_CODE as Ls, isRejectionCloseCode as Rs } from "./ws/resilience.js";
|
|
307
|
+
import { createWebSocket as zs } from "./ws/create-web-socket.js";
|
|
308
|
+
import { useWebSocket as Bs } from "./ws/use-web-socket.js";
|
|
309
|
+
import { setTunedLocalDescription as Vs, tuneOpus as Hs } from "./webrtc/opus-sdp.js";
|
|
310
|
+
import { setSenderBitrate as Us } from "./webrtc/sender-bitrate.js";
|
|
311
|
+
import { clampLatitude as Ws, isCoordinate as Gs, isValidLatitude as Ks, isValidLongitude as qs, normalizeLongitude as Js } from "./geo/types.js";
|
|
312
|
+
import { EARTH_RADIUS_KM as Ys, bearingDeg as Xs, haversineKm as Zs, pathLengthKm as Qs, toRadians as $s } from "./geo/distance.js";
|
|
313
|
+
import { DEFAULT_CAR_SPEED_KMH as ec, DEFAULT_CIRCUITY_FACTOR as tc, DEFAULT_MODE_DURATION_FACTORS as nc, estimateTravel as rc } from "./geo/estimate.js";
|
|
314
|
+
import { boundingBox as ic, boundsCenter as ac, expandBounds as oc } from "./geo/bounds.js";
|
|
315
|
+
import { fitProjection as sc, projectMercator as cc, unprojectMercator as lc } from "./geo/projection.js";
|
|
316
|
+
import { createOSRMBackend as uc } from "./geo/routing.js";
|
|
317
|
+
import { createPositionTracker as dc } from "./geo/create-position-tracker.js";
|
|
318
|
+
import { usePositionTracker as fc } from "./geo/use-position-tracker.js";
|
|
319
|
+
import { TrajectoryMap as pc } from "./geo/TrajectoryMap.js";
|
|
320
|
+
import { applyTheme as mc, readThemeToken as hc } from "./theme/apply-theme.js";
|
|
321
|
+
import { contrastRatio as gc, createColorScale as _c, hexToOklch as vc, hexToRgb as yc, hexToRgbaString as bc, oklchToHex as xc, readableForeground as Sc, relativeLuminance as Cc, rgbToHex as wc } from "./theme/color.js";
|
|
322
|
+
import { createTheme as Tc, themeContrast as Ec } from "./theme/create-theme.js";
|
|
323
|
+
import { getThemePreset as Dc, themePresets as Oc } from "./theme/theme-presets.js";
|
|
324
|
+
import { getInitialTheme as kc, themeInitScript as Ac } from "./theme/initial-theme.js";
|
|
325
|
+
import { consoleSink as jc, createLogger as Mc } from "./logger/logger.js";
|
|
326
|
+
import { TelemetryProvider as Nc, useTelemetry as Pc } from "./telemetry/TelemetryProvider.js";
|
|
327
|
+
import { consoleTelemetryAdapter as Fc } from "./telemetry/console-adapter.js";
|
|
328
|
+
import { createSentryTelemetryAdapter as Ic } from "./telemetry/sentry-adapter.js";
|
|
329
|
+
import { createPostHogTelemetryAdapter as Lc } from "./telemetry/posthog-adapter.js";
|
|
330
|
+
import { FeatureFlagsProvider as Rc, useFeatureFlag as zc, useFlagValue as Bc } from "./feature-flags/FeatureFlagsProvider.js";
|
|
331
|
+
import { createInMemoryFlags as Vc } from "./feature-flags/in-memory-adapter.js";
|
|
332
|
+
import { createGrowthBookFeatureFlagsAdapter as Hc } from "./feature-flags/growthbook-adapter.js";
|
|
333
|
+
import { createLaunchDarklyFeatureFlagsAdapter as Uc } from "./feature-flags/launchdarkly-adapter.js";
|
|
334
|
+
import { DIVERGING_STEP_COUNT as Wc, ORDINAL_START_STEP as Gc, SEQUENTIAL_STEP_COUNT as Kc, divergingScale as qc, scaleSteps as Jc, sequentialScale as Yc } from "./charts/scales.js";
|
|
335
|
+
import { cachedResponseBytes as Xc } from "./perf/cache-size.js";
|
|
336
|
+
import { readDeviceProfile as Zc } from "./perf/device.js";
|
|
337
|
+
import { formatDurationMs as Qc } from "./perf/format.js";
|
|
338
|
+
import { createInferenceProfiler as $c } from "./perf/profiler.js";
|
|
339
|
+
export { Ar as AIChat, Dr as AIChatComposer, kr as AIChatTurn, dr as ALL_BARCODE_FORMATS, Bi as API_ERROR_OFFLINE_KEY, Vi as API_ERROR_VALIDATION_KEY, er as AUDIO_MIME_CANDIDATES, Ca as AccessControlProvider, t as Accordion, n as Alert, r as AppBar, Oo as AppProviders, io as AppRouter, o as AppShell, s as AspectRatio, Qn as AudioPlayer, ur as AudioRecorder, aa as AuthGuard, c as Avatar, l as AvatarGroup, i as BREAKPOINTS, u as Badge, d as Banner, Fr as BarList, yr as BarcodeScanner, f as BottomNavigation, p as BottomSheet, m as Breadcrumbs, oo as BrowserRouter, h as Button, ja as CACHE_TIME, ws as CEPInput, Ts as CNPJInput, Es as CPFInput, Mn as Calendar, Ea as Can, g as Card, Rn as Carousel, _ as Center, Yn as Chat, Jn as ChatComposer, v as Checkbox, y as ChipInput, hn as ClickOutside, ln as CodeBlock, En as Collapsible, b as Combobox, kn as Command, gn as ConditionalWrapper, S as ConfirmDialog, I as Container, Dn as ContextMenu, As as Controller, cn as CopyButton, Hi as DEFAULT_API_ERROR_STRINGS, fr as DEFAULT_BARCODE_FORMATS, ec as DEFAULT_CAR_SPEED_KMH, $i as DEFAULT_CHUNK_SIZE, tc as DEFAULT_CIRCUITY_FACTOR, $o as DEFAULT_MAX_GAIN, nc as DEFAULT_MODE_DURATION_FACTORS, ua as DEFAULT_PUB_KEY_CRED_PARAMS, Wc as DIVERGING_STEP_COUNT, bn as DataList, zn as DataTable, w as DatePicker, Fn as DateRangePicker, xn as DescriptionList, T as Divider, E as Drawer, D as DropdownMenu, O as Dropzone, Ys as EARTH_RADIUS_KM, k as EmptyState, Do as ErrorBoundary, A as ErrorState, vn as ErrorText, Rc as FeatureFlagsProvider, j as FileUpload, Br as FilterBar, Vn as FloatingActionButton, _n as For, M as Form, N as FormActions, hs as FormField, js as FormProvider, P as FormRow, F as FormSection, ka as GoogleSignIn, L as Grid, Ls as HEARTBEAT_CLOSE_CODE, so as HashRouter, St as Hide, On as HoverCard, Gi as I18nProvider, yn as Image, Jt as ImageCropper, C as Input, B as InstallBanner, V as InstallButton, rt as Kanban, at as Kbd, Tn as Label, Kn as Lightbox, co as Link, Bn as ListTile, Or as Markdown, Mr as Masonry, lo as MemoryRouter, Ln as Menubar, x as Modal, ot as ModalsProvider, dn as Money, Ds as MoneyInput, Pn as MultiSelect, lt as NProgressBar, uo as NavLink, ct as Navbar, fo as Navigate, In as NavigationMenu, Hn as NavigationRail, Qt as NotificationCenter, Gc as ORDINAL_START_STEP, Ze as OfflineIndicator, po as Outlet, dt as Page, ft as Pagination, da as PasskeyError, pt as PasswordInput, Os as PhoneInput, ht as PinInput, gt as Popover, mn as Portal, _t as Progress, rn as QRCapacityError, sn as QRCode, Ia as QueryProvider, Ma as REFETCH_TIME, vt as Radio, yt as RadioGroup, bt as RangeSlider, xt as RatingStars, Wn as RefreshIndicator, un as RelativeTime, jn as Resizable, mo as Route, ro as RouteGuard, ho as Routes, Kc as SEQUENTIAL_STEP_COUNT, Na as STALE_TIME, wt as SafeArea, Xt as Scheduler, An as ScrollArea, Tt as SearchBar, Et as SegmentedControl, Dt as Select, Ct as Show, Ot as Sidebar, qn as SignaturePad, kt as Skeleton, Nn as Slider, At as Spacer, Yt as Sparkline, jt as Spinner, R as Stack, Mt as Stat, Nt as Stepper, Rt as StepperInput, zt as Switch, tt as SyncStatusBadge, Vt as Table, Ht as Tabs, Bt as Tag, Nc as TelemetryProvider, Fi as TempestApiError, Ja as TempestDataProvider, Ut as Textarea, To as ThemeProvider, Un as TimePicker, Wt as Timeline, Kt as ToastProvider, Sn as Toggle, Cn as ToggleGroup, wn as ToggleGroupItem, Gt as Tooltip, Nr as Tour, pc as TrajectoryMap, jr as Transfer, fn as TreeView, pn as TruncateText, nt as UpdatePrompt, en as VirtualList, nn as VirtualTable, et as VisuallyHidden, No as WebPushClient, Po as WebPushPermissionDeniedError, Fo as WebPushUnsupportedError, Gn as Wizard, br as aiChatStrings, se as announce, Jr as applyFilters, it as applyKanbanMove, mc as applyTheme, gi as assertNever, fa as base64UrlToBytes, Xs as bearingDeg, cr as blobToWav, ic as boundingBox, ac as boundsCenter, Pi as buildApiUrl, Pr as buildBarListRows, Ne as buildOpenInChromeIntent, pa as bytesToBase64Url, Xc as cachedResponseBytes, ni as camelCase, ri as capitalize, ci as chunk, Pt as clamp, Ws as clampLatitude, ma as classifyPasskeyError, ce as clearAnnouncer, Go as clearCaches, e as cn, tn as compareValues, Qr as compressToString, $r as compressedStorage, ei as compressedStorageCodec, jc as consoleSink, Fc as consoleTelemetryAdapter, gc as contrastRatio, zi as createApiClient, es as createAudioBus, qo as createAudioPlayer, tr as createAudioRecorder, ia as createAuthStore, pr as createBarcodeDetector, _c as createColorScale, qa as createDataProvider, ko as createEventStream, Hc as createGrowthBookFeatureFlagsAdapter, Wi as createI18n, Vc as createInMemoryFlags, $c as createInferenceProfiler, Xr as createJsonStorage, Uc as createLaunchDarklyFeatureFlagsAdapter, ir as createLevelMeter, ea as createLocalUploadStorage, Mc as createLogger, $n as createMediaRecorder, uc as createOSRMBackend, us as createOfflineDatabase, Ga as createOfflineStore, ds as createOfflineSync, Bo as createPartialResponse, ha as createPasskeyClient, dc as createPositionTracker, Lc as createPostHogTelemetryAdapter, Pa as createQueryKeys, la as createRefreshQueue, ta as createResumableUpload, Da as createRoleAccessControl, wo as createSelectors, Ic as createSentryTelemetryAdapter, Zo as createSfxPool, Co as createStore, Sa as createTempestAuth, Tc as createTheme, ns as createVideoRecorder, zs as createWebSocket, xi as debounce, oa as decodeJWT, ti as decompressFromString, fi as deepMerge, ao as defineRoutes, Ui as describeApiError, Ir as describeFilter, qc as divergingScale, Mi as downloadCsv, La as emptyOffsetPage, an as encodeQR, lr as encodeWav, mt as estimatePasswordStrength, Ue as estimateStorage, rc as estimateTravel, oc as expandBounds, Lr as filtersFromSearchParams, Yr as filtersToQueryParams, Rr as filtersToSearchParams, sc as fitProjection, Ft as formatBytes, ys as formatCEP, bs as formatCNPJ, Vr as formatCPF, It as formatCompactNumber, Hr as formatCurrency, Ur as formatDate, Wr as formatDateForInput, Gr as formatDateTime, Qc as formatDurationMs, Kr as formatPercent, qr as formatPhone, Qi as generateIdempotencyKey, kc as getInitialTheme, mr as getSupportedBarcodeFormats, Dc as getThemePreset, li as groupBy, Zs as haversineKm, vc as hexToOklch, yc as hexToRgb, bc as hexToRgbaString, fs as higherVersionWins, Ko as inspectCaches, Uo as installBackgroundSync, Lo as installNotificationClickHandler, Vo as installPrecache, Ro as installPushHandler, Ho as installRuntimeCache, zo as installSkipWaitingListener, Pe as isAndroid, Fe as isAndroidWithoutPromptApi, Ii as isApiError, Xn as isAudioOutputSelectionSupported, nr as isAudioRecordingSupported, hr as isBarcodeDetectionSupported, ga as isConditionalMediationAvailable, Gs as isCoordinate, Ra as isCursorPage, _i as isDefined, pi as isEmpty, xr as isGenerating, Ie as isIOS, sa as isJWTExpired, or as isMediaCaptureSupported, vi as isNumber, za as isOffsetPage, _a as isPasskeySupported, yi as isPlainObject, va as isPlatformAuthenticatorAvailable, jo as isPushSupported, Rs as isRejectionCloseCode, Li as isRetriableStatus, os as isScreenCaptureSupported, ki as isShareSupported, cs as isSpeechRecognitionSupported, Le as isStandalone, bi as isString, Ks as isValidLatitude, qs as isValidLongitude, rs as isVideoRecordingSupported, ii as kebabCase, Sr as lastAssistantId, ps as lastWriteWins, ca as lazyWithRetry, Xa as listQueryKey, on as matrixToPath, Si as memoizeOne, Ke as moveItem, gr as normalizeBarcode, Js as normalizeLongitude, ut as nprogress, xc as oklchToHex, mi as omit, Ci as once, Za as oneQueryKey, zr as operatorsFor, Xi as parseResponse, Qs as pathLengthKm, Lt as percentOf, Oa as permissionsFromToken, Ka as persistQueryClientOffline, hi as pick, rr as pickAudioMimeType, is as pickVideoMimeType, Jo as playAudio, ai as pluralize, cc as projectMercator, Di as randomId, ui as range, Zc as readDeviceProfile, hc as readThemeToken, Sc as readableForeground, go as redirect, Wo as registerPeriodicSync, ze as registerServiceWorker, Cc as relativeLuminance, Zt as relativeTime, Ua as removeById, We as requestPersistentStorage, Ri as retry, wc as rgbToHex, Cr as roleLabel, Jc as scaleSteps, Yc as sequentialScale, Zn as setAudioOutput, Us as setSenderBitrate, Vs as setTunedLocalDescription, Ai as share, ji as shareOrDownloadBlob, Fa as shouldRetryQuery, Be as skipWaiting, Ti as sleep, oi as slugify, Yo as stopAudio, Zr as storage, wr as tailSignature, Ec as themeContrast, Ac as themeInitScript, Oc as themePresets, wi as throttle, Ni as toCsv, $s as toRadians, si as truncate, Hs as tuneOpus, Tr as turnTime, di as uniqueBy, xs as unmask, lc as unprojectMercator, Ve as unregisterAllServiceWorkers, na as uploadFingerprint, Zi as uploadWithProgress, Wa as upsertById, Mo as urlBase64ToUint8Array, wa as useAccessControl, le as useAnnounce, X as useAsync, Xo as useAudio, ts as useAudioBus, ar as useAudioRecorder, vr as useBarcodeScanner, z as useBeforeInstallPrompt, a as useBreakpoint, Ta as useCan, ke as useClickOutside, W as useClientFilter, te as useClipboard, Se as useCountdown, xe as useCounter, Qa as useCreate, Va as useCursorQuery, Ya as useDataProvider, H as useDebounce, de as useDeepMemo, $a as useDelete, Yi as useDescribeApiError, ye as useDisclosure, we as useDocumentTitle, Q as useDocumentVisibility, ms as useErrorHandler, q as useEventListener, Ao as useEventStream, Te as useFavicon, zc as useFeatureFlag, Ms as useFieldArray, Bc as useFlagValue, oe as useFocusTrap, Ns as useForm, Ps as useFormContext, Fs as useFormState, ie as useGeolocation, _e as useHover, Ki as useI18n, re as useIdle, Re as useInstallPrompt, $ as useIntersectionObserver, pe as useInterval, Ae as useIsFirstRender, ne as useKeyboardShortcut, K as useLatestRef, eo as useList, be as useListState, J as useLocalStorage, _o as useLocation, ve as useLongPress, Me as useLongPressHandlers, Ee as useMap, vo as useMatch, Xe as useMediaDevices, Ye as useMediaPermission, G as useMediaQuery, sr as useMicrophone, st as useModals, yo as useNavigate, $t as useNotificationInbox, Aa as useOAuthCallback, je as useObjectUrl, Ha as useOfflineMutation, Qe as useOfflineSync, to as useOne, Z as useOnline, qi as useOptionalI18n, Ba as usePaginatedQuery, U as usePagination, bo as useParams, ya as usePasskeyCapabilities, ba as usePasskeyRegistration, xa as usePasskeySignIn, ra as usePoll, fc as usePositionTracker, fe as usePrevious, Io as usePushSubscription, Oe as useQueue, ee as useResizeObserver, xo as useRouteError, ss as useScreenCapture, ae as useScrollLock, Je as useScrollOverflow, So as useSearchParams, He as useServiceWorkerUpdate, De as useSet, Qo as useSfxPool, qe as useSortable, ls as useSpeechRecognition, ue as useStableCallback, Ge as useStorageEstimate, $e as useSyncStatus, Pc as useTelemetry, Eo as useTheme, he as useThrottle, me as useTimeout, qt as useToast, Y as useToggle, _r as useTorch, Ji as useTranslate, Ce as useTypewriter, no as useUpdate, ks as useViaCEP, as as useVideoRecorder, Is as useWatch, Bs as useWebSocket, ge as useWindowSize, vs as useZodForm, Ss as validateCNPJ, Cs as validateCPF, gs as validateForm, Er as visibleTurns, Ei as withTimeout, Oi as writeXlsx, _s as zodResolver };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e="Two copies of a package that carries React context cannot see each other's providers. Run `npm dedupe`, or `npx tempest doctor` to list every duplicated dependency, then align the versions so a single copy satisfies both the app and the SDK.";exports.DUPLICATE_COPY_REMEDY=e;
|
|
2
|
+
//# sourceMappingURL=duplicate-instance.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-instance.cjs","names":[],"sources":["../../src/utils/duplicate-instance.ts"],"sourcesContent":["/**\n * Internal, and imported by path rather than through the `utils` barrel: it is\n * diagnostic copy, not API, and re-exporting it would give a sentence the\n * semver weight of a public export. The call sites are `<QueryProvider>` and\n * `<FormField>` — the two places where a second copy of a context-carrying\n * dependency surfaces as a failure the app cannot read.\n */\n\n/**\n * What to do about a second copy, worded once so the two call sites cannot\n * drift apart.\n *\n * `npm dedupe` comes first because it fixes the common case outright: npm\n * nested the copy only because the app's range and the SDK's range did not\n * overlap at install time, and deduping collapses them whenever a single\n * version satisfies both. `tempest doctor` comes second as the way to *see*\n * the problem — it walks `node_modules/tempest-react-sdk` for nested copies of\n * every context-carrying dependency and names the ones it finds, which is the\n * confirmation the app needs before touching versions by hand.\n */\nexport const DUPLICATE_COPY_REMEDY =\n \"Two copies of a package that carries React context cannot see each other's \" +\n \"providers. Run `npm dedupe`, or `npx tempest doctor` to list every duplicated \" +\n \"dependency, then align the versions so a single copy satisfies both the app \" +\n \"and the SDK.\";\n"],"mappings":"AAoBA,IAAa,EACT"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
//#region src/utils/duplicate-instance.ts
|
|
2
|
+
var e = "Two copies of a package that carries React context cannot see each other's providers. Run `npm dedupe`, or `npx tempest doctor` to list every duplicated dependency, then align the versions so a single copy satisfies both the app and the SDK.";
|
|
3
|
+
//#endregion
|
|
4
|
+
export { e as DUPLICATE_COPY_REMEDY };
|
|
5
|
+
|
|
6
|
+
//# sourceMappingURL=duplicate-instance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duplicate-instance.js","names":[],"sources":["../../src/utils/duplicate-instance.ts"],"sourcesContent":["/**\n * Internal, and imported by path rather than through the `utils` barrel: it is\n * diagnostic copy, not API, and re-exporting it would give a sentence the\n * semver weight of a public export. The call sites are `<QueryProvider>` and\n * `<FormField>` — the two places where a second copy of a context-carrying\n * dependency surfaces as a failure the app cannot read.\n */\n\n/**\n * What to do about a second copy, worded once so the two call sites cannot\n * drift apart.\n *\n * `npm dedupe` comes first because it fixes the common case outright: npm\n * nested the copy only because the app's range and the SDK's range did not\n * overlap at install time, and deduping collapses them whenever a single\n * version satisfies both. `tempest doctor` comes second as the way to *see*\n * the problem — it walks `node_modules/tempest-react-sdk` for nested copies of\n * every context-carrying dependency and names the ones it finds, which is the\n * confirmation the app needs before touching versions by hand.\n */\nexport const DUPLICATE_COPY_REMEDY =\n \"Two copies of a package that carries React context cannot see each other's \" +\n \"providers. Run `npm dedupe`, or `npx tempest doctor` to list every duplicated \" +\n \"dependency, then align the versions so a single copy satisfies both the app \" +\n \"and the SDK.\";\n"],"mappings":";AAoBA,IAAa,IACT"}
|
package/dist/utils/format.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function e(e){return new Intl.NumberFormat(`pt-BR`,{style:`currency`,currency:`BRL`}).format(e)}function t(e){let t=typeof e==`string`?new Date(e):e;return Number.isNaN(t.getTime())?``:new Intl.DateTimeFormat(`pt-BR`).format(t)}function n(e){if(typeof e==`string`&&/^\d{4}-\d{2}-\d{2}$/.test(e))return e;let t=typeof e==`string`?new Date(e):e;if(Number.isNaN(t.getTime()))return``;let n=`${t.getMonth()+1}`.padStart(2,`0`),r=`${t.getDate()}`.padStart(2,`0`);return`${t.getFullYear()}-${n}-${r}`}function r(e){let t=typeof e==`string`?new Date(e):e;return Number.isNaN(t.getTime())?``:new Intl.DateTimeFormat(`pt-BR`,{dateStyle:`short`,timeStyle:`short`}).format(t)}function i(e){let
|
|
1
|
+
function e(e){return new Intl.NumberFormat(`pt-BR`,{style:`currency`,currency:`BRL`}).format(e)}function t(e){let t=typeof e==`string`?new Date(e):e;return Number.isNaN(t.getTime())?``:new Intl.DateTimeFormat(`pt-BR`).format(t)}function n(e){if(typeof e==`string`&&/^\d{4}-\d{2}-\d{2}$/.test(e))return e;let t=typeof e==`string`?new Date(e):e;if(Number.isNaN(t.getTime()))return``;let n=`${t.getMonth()+1}`.padStart(2,`0`),r=`${t.getDate()}`.padStart(2,`0`);return`${t.getFullYear()}-${n}-${r}`}function r(e){let t=typeof e==`string`?new Date(e):e;return Number.isNaN(t.getTime())?``:new Intl.DateTimeFormat(`pt-BR`,{dateStyle:`short`,timeStyle:`short`}).format(t)}function i(e,t={}){let n=e.replace(/\D/g,``).slice(0,11);if(!t.mobile)return n.length<=10?n.replace(/(\d{2})(\d)/,`($1) $2`).replace(/(\d{4})(\d)/,`$1-$2`):n.replace(/(\d{2})(\d)/,`($1) $2`).replace(/(\d{5})(\d)/,`$1-$2`);if(n.length<=2)return n;let r=n.slice(0,2),i=n.slice(2);i[0]!==`9`&&(i=`9${i}`),i=i.slice(0,9);let a=i.slice(0,5),o=i.slice(5);return o?`(${r}) ${a}-${o}`:`(${r}) ${a}`}function a(e){return e.replace(/\D/g,``).slice(0,11).replace(/(\d{3})(\d)/,`$1.$2`).replace(/(\d{3})(\d)/,`$1.$2`).replace(/(\d{3})(\d{1,2})$/,`$1-$2`)}function o(e){return new Intl.NumberFormat(`pt-BR`,{style:`percent`,minimumFractionDigits:1,maximumFractionDigits:1}).format(e)}exports.formatCPF=a,exports.formatCurrency=e,exports.formatDate=t,exports.formatDateForInput=n,exports.formatDateTime=r,exports.formatPercent=o,exports.formatPhone=i;
|
|
2
2
|
//# sourceMappingURL=format.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.cjs","names":[],"sources":["../../src/utils/format.ts"],"sourcesContent":["/**\n * Format a number as Brazilian Real currency.\n *\n * @param value - The amount in BRL.\n * @returns A locale-formatted string, e.g. \"R$ 1.234,56\".\n */\nexport function formatCurrency(value: number): string {\n return new Intl.NumberFormat(\"pt-BR\", {\n style: \"currency\",\n currency: \"BRL\",\n }).format(value);\n}\n\n/**\n * Format an ISO date or Date instance as `dd/MM/yyyy`.\n *\n * @param value - ISO string or Date.\n * @returns Formatted date string, or empty string when input is invalid.\n */\nexport function formatDate(value: string | Date): string {\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\n return new Intl.DateTimeFormat(\"pt-BR\").format(date);\n}\n\n/**\n * Format an ISO date or Date instance as `yyyy-MM-dd`, the value an\n * `<input type=\"date\">` accepts.\n *\n * Built from the **local** calendar parts rather than `toISOString().slice(0, 10)`,\n * which is the reflex and which is wrong: `toISOString` converts to UTC first, so\n * anything after 21:00 in UTC-3 reports the next day and the form opens on the\n * wrong date. `formatDate` cannot fill this role because a date input rejects\n * `dd/MM/yyyy` outright.\n *\n * A value that is already `yyyy-MM-dd` is returned untouched, and that shortcut\n * is load-bearing rather than an optimisation: `new Date(\"2026-05-16\")` is parsed\n * as **UTC** midnight, which in UTC-3 is the 15th at 21:00, so round-tripping the\n * exact value a backend sent would move it back a day.\n *\n * @example\n * <input type=\"date\" defaultValue={formatDateForInput(order.createdAt)} />\n *\n * @param value - ISO string or Date.\n * @returns The `yyyy-MM-dd` value, or an empty string when the input is invalid —\n * which is what a date input reads as \"no value\", unlike `\"Invalid Date\"`.\n */\nexport function formatDateForInput(value: string | Date): string {\n if (typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return value;\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\n const month = `${date.getMonth() + 1}`.padStart(2, \"0\");\n const day = `${date.getDate()}`.padStart(2, \"0\");\n return `${date.getFullYear()}-${month}-${day}`;\n}\n\n/**\n * Format an ISO date or Date instance as `dd/MM/yyyy HH:mm`.\n *\n * @param value - ISO string or Date.\n * @returns Formatted datetime string, or empty string when input is invalid.\n */\nexport function formatDateTime(value: string | Date): string {\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\n return new Intl.DateTimeFormat(\"pt-BR\", {\n dateStyle: \"short\",\n timeStyle: \"short\",\n }).format(date);\n}\n\n/**\n * Apply the Brazilian phone mask `(XX) XXXXX-XXXX` or `(XX) XXXX-XXXX`.\n *\n * @param value - Raw digits or partially masked string.\n * @returns Masked phone string.\n */\nexport function formatPhone(value: string): string {\n const digits = value.replace(/\\D/g, \"\").slice(0, 11);\n if (digits.length <= 10) {\n
|
|
1
|
+
{"version":3,"file":"format.cjs","names":[],"sources":["../../src/utils/format.ts"],"sourcesContent":["/**\n * Format a number as Brazilian Real currency.\n *\n * @param value - The amount in BRL.\n * @returns A locale-formatted string, e.g. \"R$ 1.234,56\".\n */\nexport function formatCurrency(value: number): string {\n return new Intl.NumberFormat(\"pt-BR\", {\n style: \"currency\",\n currency: \"BRL\",\n }).format(value);\n}\n\n/**\n * Format an ISO date or Date instance as `dd/MM/yyyy`.\n *\n * @param value - ISO string or Date.\n * @returns Formatted date string, or empty string when input is invalid.\n */\nexport function formatDate(value: string | Date): string {\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\n return new Intl.DateTimeFormat(\"pt-BR\").format(date);\n}\n\n/**\n * Format an ISO date or Date instance as `yyyy-MM-dd`, the value an\n * `<input type=\"date\">` accepts.\n *\n * Built from the **local** calendar parts rather than `toISOString().slice(0, 10)`,\n * which is the reflex and which is wrong: `toISOString` converts to UTC first, so\n * anything after 21:00 in UTC-3 reports the next day and the form opens on the\n * wrong date. `formatDate` cannot fill this role because a date input rejects\n * `dd/MM/yyyy` outright.\n *\n * A value that is already `yyyy-MM-dd` is returned untouched, and that shortcut\n * is load-bearing rather than an optimisation: `new Date(\"2026-05-16\")` is parsed\n * as **UTC** midnight, which in UTC-3 is the 15th at 21:00, so round-tripping the\n * exact value a backend sent would move it back a day.\n *\n * @example\n * <input type=\"date\" defaultValue={formatDateForInput(order.createdAt)} />\n *\n * @param value - ISO string or Date.\n * @returns The `yyyy-MM-dd` value, or an empty string when the input is invalid —\n * which is what a date input reads as \"no value\", unlike `\"Invalid Date\"`.\n */\nexport function formatDateForInput(value: string | Date): string {\n if (typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return value;\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\n const month = `${date.getMonth() + 1}`.padStart(2, \"0\");\n const day = `${date.getDate()}`.padStart(2, \"0\");\n return `${date.getFullYear()}-${month}-${day}`;\n}\n\n/**\n * Format an ISO date or Date instance as `dd/MM/yyyy HH:mm`.\n *\n * @param value - ISO string or Date.\n * @returns Formatted datetime string, or empty string when input is invalid.\n */\nexport function formatDateTime(value: string | Date): string {\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\n return new Intl.DateTimeFormat(\"pt-BR\", {\n dateStyle: \"short\",\n timeStyle: \"short\",\n }).format(date);\n}\n\nexport interface FormatPhoneOptions {\n /**\n * Treat the number as a mobile line: insert the mandatory `9` after the area\n * code when it is missing, and group the subscriber part `5+4` from the\n * first digit typed instead of waiting for the eleventh.\n *\n * Default `false`, which keeps the length-based behaviour: `4+4` up to ten\n * digits, `5+4` at eleven.\n */\n mobile?: boolean;\n}\n\n/**\n * Apply the Brazilian phone mask `(XX) XXXXX-XXXX` or `(XX) XXXX-XXXX`.\n *\n * By default the grouping is decided by **length**, which is what a field\n * accepting both landlines and mobiles needs.\n *\n * `mobile: true` is for a field that only accepts mobile numbers, and it exists\n * because the default is wrong as an as-you-type mask there. Reading anything up\n * to ten digits as a landline puts the hyphen after the fourth subscriber digit,\n * so a half-typed mobile renders `(11) 9123-4`; it only becomes `(11) 91234-5`\n * once the eleventh digit lands. The separator visibly jumps backwards while the\n * user is still typing. With `mobile`, the same input reads `(11) 91234` and the\n * hyphen never moves. It also inserts the leading `9` every Brazilian mobile\n * carries, so a ten-digit number gets corrected rather than masked as a landline.\n *\n * @param value - Raw digits or partially masked string.\n * @param options - Masking options.\n * @returns Masked phone string.\n *\n * @example\n * formatPhone(\"1191234\"); // \"(11) 9123-4\"\n * formatPhone(\"1191234\", { mobile: true }); // \"(11) 91234\"\n * formatPhone(\"1112345678\", { mobile: true }); // \"(11) 91234-5678\" — 9 inserted\n */\nexport function formatPhone(value: string, options: FormatPhoneOptions = {}): string {\n const digits = value.replace(/\\D/g, \"\").slice(0, 11);\n\n if (!options.mobile) {\n if (digits.length <= 10) {\n return digits.replace(/(\\d{2})(\\d)/, \"($1) $2\").replace(/(\\d{4})(\\d)/, \"$1-$2\");\n }\n return digits.replace(/(\\d{2})(\\d)/, \"($1) $2\").replace(/(\\d{5})(\\d)/, \"$1-$2\");\n }\n\n if (digits.length <= 2) return digits;\n\n const area = digits.slice(0, 2);\n let subscriber = digits.slice(2);\n if (subscriber[0] !== \"9\") subscriber = `9${subscriber}`;\n subscriber = subscriber.slice(0, 9);\n\n const prefix = subscriber.slice(0, 5);\n const suffix = subscriber.slice(5);\n return suffix ? `(${area}) ${prefix}-${suffix}` : `(${area}) ${prefix}`;\n}\n\n/**\n * Apply the Brazilian CPF mask `XXX.XXX.XXX-XX`.\n *\n * @param value - Raw digits or partially masked string.\n * @returns Masked CPF string.\n */\nexport function formatCPF(value: string): string {\n return value\n .replace(/\\D/g, \"\")\n .slice(0, 11)\n .replace(/(\\d{3})(\\d)/, \"$1.$2\")\n .replace(/(\\d{3})(\\d)/, \"$1.$2\")\n .replace(/(\\d{3})(\\d{1,2})$/, \"$1-$2\");\n}\n\n/**\n * Format a fraction (0-1) as a percentage with one decimal.\n *\n * @param value - Fraction between 0 and 1.\n * @returns Formatted percent string, e.g. \"12,5%\".\n */\nexport function formatPercent(value: number): string {\n return new Intl.NumberFormat(\"pt-BR\", {\n style: \"percent\",\n minimumFractionDigits: 1,\n maximumFractionDigits: 1,\n }).format(value);\n}\n"],"mappings":"AAMA,SAAgB,EAAe,EAAuB,CAClD,OAAO,IAAI,KAAK,aAAa,QAAS,CAClC,MAAO,WACP,SAAU,KACd,CAAC,CAAC,CAAC,OAAO,CAAK,CACnB,CAQA,SAAgB,EAAW,EAA8B,CACrD,IAAM,EAAO,OAAO,GAAU,SAAW,IAAI,KAAK,CAAK,EAAI,EAE3D,OADI,OAAO,MAAM,EAAK,QAAQ,CAAC,EAAU,GAClC,IAAI,KAAK,eAAe,OAAO,CAAC,CAAC,OAAO,CAAI,CACvD,CAwBA,SAAgB,EAAmB,EAA8B,CAC7D,GAAI,OAAO,GAAU,UAAY,sBAAsB,KAAK,CAAK,EAAG,OAAO,EAC3E,IAAM,EAAO,OAAO,GAAU,SAAW,IAAI,KAAK,CAAK,EAAI,EAC3D,GAAI,OAAO,MAAM,EAAK,QAAQ,CAAC,EAAG,MAAO,GACzC,IAAM,EAAQ,GAAG,EAAK,SAAS,EAAI,IAAI,SAAS,EAAG,GAAG,EAChD,EAAM,GAAG,EAAK,QAAQ,IAAI,SAAS,EAAG,GAAG,EAC/C,MAAO,GAAG,EAAK,YAAY,EAAE,GAAG,EAAM,GAAG,GAC7C,CAQA,SAAgB,EAAe,EAA8B,CACzD,IAAM,EAAO,OAAO,GAAU,SAAW,IAAI,KAAK,CAAK,EAAI,EAE3D,OADI,OAAO,MAAM,EAAK,QAAQ,CAAC,EAAU,GAClC,IAAI,KAAK,eAAe,QAAS,CACpC,UAAW,QACX,UAAW,OACf,CAAC,CAAC,CAAC,OAAO,CAAI,CAClB,CAsCA,SAAgB,EAAY,EAAe,EAA8B,CAAC,EAAW,CACjF,IAAM,EAAS,EAAM,QAAQ,MAAO,EAAE,CAAC,CAAC,MAAM,EAAG,EAAE,EAEnD,GAAI,CAAC,EAAQ,OAIT,OAHI,EAAO,QAAU,GACV,EAAO,QAAQ,cAAe,SAAS,CAAC,CAAC,QAAQ,cAAe,OAAO,EAE3E,EAAO,QAAQ,cAAe,SAAS,CAAC,CAAC,QAAQ,cAAe,OAAO,EAGlF,GAAI,EAAO,QAAU,EAAG,OAAO,EAE/B,IAAM,EAAO,EAAO,MAAM,EAAG,CAAC,EAC1B,EAAa,EAAO,MAAM,CAAC,EAC3B,EAAW,KAAO,MAAK,EAAa,IAAI,KAC5C,EAAa,EAAW,MAAM,EAAG,CAAC,EAElC,IAAM,EAAS,EAAW,MAAM,EAAG,CAAC,EAC9B,EAAS,EAAW,MAAM,CAAC,EACjC,OAAO,EAAS,IAAI,EAAK,IAAI,EAAO,GAAG,IAAW,IAAI,EAAK,IAAI,GACnE,CAQA,SAAgB,EAAU,EAAuB,CAC7C,OAAO,EACF,QAAQ,MAAO,EAAE,CAAC,CAClB,MAAM,EAAG,EAAE,CAAC,CACZ,QAAQ,cAAe,OAAO,CAAC,CAC/B,QAAQ,cAAe,OAAO,CAAC,CAC/B,QAAQ,oBAAqB,OAAO,CAC7C,CAQA,SAAgB,EAAc,EAAuB,CACjD,OAAO,IAAI,KAAK,aAAa,QAAS,CAClC,MAAO,UACP,sBAAuB,EACvB,sBAAuB,CAC3B,CAAC,CAAC,CAAC,OAAO,CAAK,CACnB"}
|
package/dist/utils/format.js
CHANGED
|
@@ -23,9 +23,14 @@ function r(e) {
|
|
|
23
23
|
timeStyle: "short"
|
|
24
24
|
}).format(t);
|
|
25
25
|
}
|
|
26
|
-
function i(e) {
|
|
27
|
-
let
|
|
28
|
-
|
|
26
|
+
function i(e, t = {}) {
|
|
27
|
+
let n = e.replace(/\D/g, "").slice(0, 11);
|
|
28
|
+
if (!t.mobile) return n.length <= 10 ? n.replace(/(\d{2})(\d)/, "($1) $2").replace(/(\d{4})(\d)/, "$1-$2") : n.replace(/(\d{2})(\d)/, "($1) $2").replace(/(\d{5})(\d)/, "$1-$2");
|
|
29
|
+
if (n.length <= 2) return n;
|
|
30
|
+
let r = n.slice(0, 2), i = n.slice(2);
|
|
31
|
+
i[0] !== "9" && (i = `9${i}`), i = i.slice(0, 9);
|
|
32
|
+
let a = i.slice(0, 5), o = i.slice(5);
|
|
33
|
+
return o ? `(${r}) ${a}-${o}` : `(${r}) ${a}`;
|
|
29
34
|
}
|
|
30
35
|
function a(e) {
|
|
31
36
|
return e.replace(/\D/g, "").slice(0, 11).replace(/(\d{3})(\d)/, "$1.$2").replace(/(\d{3})(\d)/, "$1.$2").replace(/(\d{3})(\d{1,2})$/, "$1-$2");
|
package/dist/utils/format.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.js","names":[],"sources":["../../src/utils/format.ts"],"sourcesContent":["/**\n * Format a number as Brazilian Real currency.\n *\n * @param value - The amount in BRL.\n * @returns A locale-formatted string, e.g. \"R$ 1.234,56\".\n */\nexport function formatCurrency(value: number): string {\n return new Intl.NumberFormat(\"pt-BR\", {\n style: \"currency\",\n currency: \"BRL\",\n }).format(value);\n}\n\n/**\n * Format an ISO date or Date instance as `dd/MM/yyyy`.\n *\n * @param value - ISO string or Date.\n * @returns Formatted date string, or empty string when input is invalid.\n */\nexport function formatDate(value: string | Date): string {\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\n return new Intl.DateTimeFormat(\"pt-BR\").format(date);\n}\n\n/**\n * Format an ISO date or Date instance as `yyyy-MM-dd`, the value an\n * `<input type=\"date\">` accepts.\n *\n * Built from the **local** calendar parts rather than `toISOString().slice(0, 10)`,\n * which is the reflex and which is wrong: `toISOString` converts to UTC first, so\n * anything after 21:00 in UTC-3 reports the next day and the form opens on the\n * wrong date. `formatDate` cannot fill this role because a date input rejects\n * `dd/MM/yyyy` outright.\n *\n * A value that is already `yyyy-MM-dd` is returned untouched, and that shortcut\n * is load-bearing rather than an optimisation: `new Date(\"2026-05-16\")` is parsed\n * as **UTC** midnight, which in UTC-3 is the 15th at 21:00, so round-tripping the\n * exact value a backend sent would move it back a day.\n *\n * @example\n * <input type=\"date\" defaultValue={formatDateForInput(order.createdAt)} />\n *\n * @param value - ISO string or Date.\n * @returns The `yyyy-MM-dd` value, or an empty string when the input is invalid —\n * which is what a date input reads as \"no value\", unlike `\"Invalid Date\"`.\n */\nexport function formatDateForInput(value: string | Date): string {\n if (typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return value;\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\n const month = `${date.getMonth() + 1}`.padStart(2, \"0\");\n const day = `${date.getDate()}`.padStart(2, \"0\");\n return `${date.getFullYear()}-${month}-${day}`;\n}\n\n/**\n * Format an ISO date or Date instance as `dd/MM/yyyy HH:mm`.\n *\n * @param value - ISO string or Date.\n * @returns Formatted datetime string, or empty string when input is invalid.\n */\nexport function formatDateTime(value: string | Date): string {\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\n return new Intl.DateTimeFormat(\"pt-BR\", {\n dateStyle: \"short\",\n timeStyle: \"short\",\n }).format(date);\n}\n\n/**\n * Apply the Brazilian phone mask `(XX) XXXXX-XXXX` or `(XX) XXXX-XXXX`.\n *\n * @param value - Raw digits or partially masked string.\n * @returns Masked phone string.\n */\nexport function formatPhone(value: string): string {\n const digits = value.replace(/\\D/g, \"\").slice(0, 11);\n if (digits.length <= 10) {\n
|
|
1
|
+
{"version":3,"file":"format.js","names":[],"sources":["../../src/utils/format.ts"],"sourcesContent":["/**\n * Format a number as Brazilian Real currency.\n *\n * @param value - The amount in BRL.\n * @returns A locale-formatted string, e.g. \"R$ 1.234,56\".\n */\nexport function formatCurrency(value: number): string {\n return new Intl.NumberFormat(\"pt-BR\", {\n style: \"currency\",\n currency: \"BRL\",\n }).format(value);\n}\n\n/**\n * Format an ISO date or Date instance as `dd/MM/yyyy`.\n *\n * @param value - ISO string or Date.\n * @returns Formatted date string, or empty string when input is invalid.\n */\nexport function formatDate(value: string | Date): string {\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\n return new Intl.DateTimeFormat(\"pt-BR\").format(date);\n}\n\n/**\n * Format an ISO date or Date instance as `yyyy-MM-dd`, the value an\n * `<input type=\"date\">` accepts.\n *\n * Built from the **local** calendar parts rather than `toISOString().slice(0, 10)`,\n * which is the reflex and which is wrong: `toISOString` converts to UTC first, so\n * anything after 21:00 in UTC-3 reports the next day and the form opens on the\n * wrong date. `formatDate` cannot fill this role because a date input rejects\n * `dd/MM/yyyy` outright.\n *\n * A value that is already `yyyy-MM-dd` is returned untouched, and that shortcut\n * is load-bearing rather than an optimisation: `new Date(\"2026-05-16\")` is parsed\n * as **UTC** midnight, which in UTC-3 is the 15th at 21:00, so round-tripping the\n * exact value a backend sent would move it back a day.\n *\n * @example\n * <input type=\"date\" defaultValue={formatDateForInput(order.createdAt)} />\n *\n * @param value - ISO string or Date.\n * @returns The `yyyy-MM-dd` value, or an empty string when the input is invalid —\n * which is what a date input reads as \"no value\", unlike `\"Invalid Date\"`.\n */\nexport function formatDateForInput(value: string | Date): string {\n if (typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return value;\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\n const month = `${date.getMonth() + 1}`.padStart(2, \"0\");\n const day = `${date.getDate()}`.padStart(2, \"0\");\n return `${date.getFullYear()}-${month}-${day}`;\n}\n\n/**\n * Format an ISO date or Date instance as `dd/MM/yyyy HH:mm`.\n *\n * @param value - ISO string or Date.\n * @returns Formatted datetime string, or empty string when input is invalid.\n */\nexport function formatDateTime(value: string | Date): string {\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return \"\";\n return new Intl.DateTimeFormat(\"pt-BR\", {\n dateStyle: \"short\",\n timeStyle: \"short\",\n }).format(date);\n}\n\nexport interface FormatPhoneOptions {\n /**\n * Treat the number as a mobile line: insert the mandatory `9` after the area\n * code when it is missing, and group the subscriber part `5+4` from the\n * first digit typed instead of waiting for the eleventh.\n *\n * Default `false`, which keeps the length-based behaviour: `4+4` up to ten\n * digits, `5+4` at eleven.\n */\n mobile?: boolean;\n}\n\n/**\n * Apply the Brazilian phone mask `(XX) XXXXX-XXXX` or `(XX) XXXX-XXXX`.\n *\n * By default the grouping is decided by **length**, which is what a field\n * accepting both landlines and mobiles needs.\n *\n * `mobile: true` is for a field that only accepts mobile numbers, and it exists\n * because the default is wrong as an as-you-type mask there. Reading anything up\n * to ten digits as a landline puts the hyphen after the fourth subscriber digit,\n * so a half-typed mobile renders `(11) 9123-4`; it only becomes `(11) 91234-5`\n * once the eleventh digit lands. The separator visibly jumps backwards while the\n * user is still typing. With `mobile`, the same input reads `(11) 91234` and the\n * hyphen never moves. It also inserts the leading `9` every Brazilian mobile\n * carries, so a ten-digit number gets corrected rather than masked as a landline.\n *\n * @param value - Raw digits or partially masked string.\n * @param options - Masking options.\n * @returns Masked phone string.\n *\n * @example\n * formatPhone(\"1191234\"); // \"(11) 9123-4\"\n * formatPhone(\"1191234\", { mobile: true }); // \"(11) 91234\"\n * formatPhone(\"1112345678\", { mobile: true }); // \"(11) 91234-5678\" — 9 inserted\n */\nexport function formatPhone(value: string, options: FormatPhoneOptions = {}): string {\n const digits = value.replace(/\\D/g, \"\").slice(0, 11);\n\n if (!options.mobile) {\n if (digits.length <= 10) {\n return digits.replace(/(\\d{2})(\\d)/, \"($1) $2\").replace(/(\\d{4})(\\d)/, \"$1-$2\");\n }\n return digits.replace(/(\\d{2})(\\d)/, \"($1) $2\").replace(/(\\d{5})(\\d)/, \"$1-$2\");\n }\n\n if (digits.length <= 2) return digits;\n\n const area = digits.slice(0, 2);\n let subscriber = digits.slice(2);\n if (subscriber[0] !== \"9\") subscriber = `9${subscriber}`;\n subscriber = subscriber.slice(0, 9);\n\n const prefix = subscriber.slice(0, 5);\n const suffix = subscriber.slice(5);\n return suffix ? `(${area}) ${prefix}-${suffix}` : `(${area}) ${prefix}`;\n}\n\n/**\n * Apply the Brazilian CPF mask `XXX.XXX.XXX-XX`.\n *\n * @param value - Raw digits or partially masked string.\n * @returns Masked CPF string.\n */\nexport function formatCPF(value: string): string {\n return value\n .replace(/\\D/g, \"\")\n .slice(0, 11)\n .replace(/(\\d{3})(\\d)/, \"$1.$2\")\n .replace(/(\\d{3})(\\d)/, \"$1.$2\")\n .replace(/(\\d{3})(\\d{1,2})$/, \"$1-$2\");\n}\n\n/**\n * Format a fraction (0-1) as a percentage with one decimal.\n *\n * @param value - Fraction between 0 and 1.\n * @returns Formatted percent string, e.g. \"12,5%\".\n */\nexport function formatPercent(value: number): string {\n return new Intl.NumberFormat(\"pt-BR\", {\n style: \"percent\",\n minimumFractionDigits: 1,\n maximumFractionDigits: 1,\n }).format(value);\n}\n"],"mappings":";AAMA,SAAgB,EAAe,GAAuB;CAClD,OAAO,IAAI,KAAK,aAAa,SAAS;EAClC,OAAO;EACP,UAAU;CACd,CAAC,CAAC,CAAC,OAAO,CAAK;AACnB;AAQA,SAAgB,EAAW,GAA8B;CACrD,IAAM,IAAO,OAAO,KAAU,WAAW,IAAI,KAAK,CAAK,IAAI;CAE3D,OADI,OAAO,MAAM,EAAK,QAAQ,CAAC,IAAU,KAClC,IAAI,KAAK,eAAe,OAAO,CAAC,CAAC,OAAO,CAAI;AACvD;AAwBA,SAAgB,EAAmB,GAA8B;CAC7D,IAAI,OAAO,KAAU,YAAY,sBAAsB,KAAK,CAAK,GAAG,OAAO;CAC3E,IAAM,IAAO,OAAO,KAAU,WAAW,IAAI,KAAK,CAAK,IAAI;CAC3D,IAAI,OAAO,MAAM,EAAK,QAAQ,CAAC,GAAG,OAAO;CACzC,IAAM,IAAQ,GAAG,EAAK,SAAS,IAAI,IAAI,SAAS,GAAG,GAAG,GAChD,IAAM,GAAG,EAAK,QAAQ,IAAI,SAAS,GAAG,GAAG;CAC/C,OAAO,GAAG,EAAK,YAAY,EAAE,GAAG,EAAM,GAAG;AAC7C;AAQA,SAAgB,EAAe,GAA8B;CACzD,IAAM,IAAO,OAAO,KAAU,WAAW,IAAI,KAAK,CAAK,IAAI;CAE3D,OADI,OAAO,MAAM,EAAK,QAAQ,CAAC,IAAU,KAClC,IAAI,KAAK,eAAe,SAAS;EACpC,WAAW;EACX,WAAW;CACf,CAAC,CAAC,CAAC,OAAO,CAAI;AAClB;AAsCA,SAAgB,EAAY,GAAe,IAA8B,CAAC,GAAW;CACjF,IAAM,IAAS,EAAM,QAAQ,OAAO,EAAE,CAAC,CAAC,MAAM,GAAG,EAAE;CAEnD,IAAI,CAAC,EAAQ,QAIT,OAHI,EAAO,UAAU,KACV,EAAO,QAAQ,eAAe,SAAS,CAAC,CAAC,QAAQ,eAAe,OAAO,IAE3E,EAAO,QAAQ,eAAe,SAAS,CAAC,CAAC,QAAQ,eAAe,OAAO;CAGlF,IAAI,EAAO,UAAU,GAAG,OAAO;CAE/B,IAAM,IAAO,EAAO,MAAM,GAAG,CAAC,GAC1B,IAAa,EAAO,MAAM,CAAC;CAE/B,AADI,EAAW,OAAO,QAAK,IAAa,IAAI,MAC5C,IAAa,EAAW,MAAM,GAAG,CAAC;CAElC,IAAM,IAAS,EAAW,MAAM,GAAG,CAAC,GAC9B,IAAS,EAAW,MAAM,CAAC;CACjC,OAAO,IAAS,IAAI,EAAK,IAAI,EAAO,GAAG,MAAW,IAAI,EAAK,IAAI;AACnE;AAQA,SAAgB,EAAU,GAAuB;CAC7C,OAAO,EACF,QAAQ,OAAO,EAAE,CAAC,CAClB,MAAM,GAAG,EAAE,CAAC,CACZ,QAAQ,eAAe,OAAO,CAAC,CAC/B,QAAQ,eAAe,OAAO,CAAC,CAC/B,QAAQ,qBAAqB,OAAO;AAC7C;AAQA,SAAgB,EAAc,GAAuB;CACjD,OAAO,IAAI,KAAK,aAAa,SAAS;EAClC,OAAO;EACP,uBAAuB;EACvB,uBAAuB;CAC3B,CAAC,CAAC,CAAC,OAAO,CAAK;AACnB"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
var e=new Set([`maxAverageBitrate`,`maxPlaybackRate`,`stereo`,`fec`,`dtx`,`cbr`,`extra`]);function t(t){let n=Object.keys(t);return n.length===0||n.some(t=>e.has(t))}function n(e){let t={};return e.maxAverageBitrate!==void 0&&(t.maxaveragebitrate=String(e.maxAverageBitrate)),e.maxPlaybackRate!==void 0&&(t.maxplaybackrate=String(e.maxPlaybackRate)),e.stereo!==void 0&&(t.stereo=e.stereo?`1`:`0`,t[`sprop-stereo`]=e.stereo?`1`:`0`),e.fec!==void 0&&(t.useinbandfec=e.fec?`1`:`0`),e.dtx!==void 0&&(t.usedtx=e.dtx?`1`:`0`),e.cbr!==void 0&&(t.cbr=e.cbr?`1`:`0`),{...t,...e.extra}}function r(e,t){let n=new Map;for(let t of e.split(`;`)){let e=t.trim();if(!e)continue;let r=e.indexOf(`=`);r<0?n.set(e,``):n.set(e.slice(0,r),e.slice(r+1))}for(let[e,r]of Object.entries(t))n.set(e,r);return[...n.entries()].map(([e,t])=>t===``?e:`${e}=${t}`).join(`;`)}function i(e){let t=[];for(let n of e){let e=/^a=rtpmap:(\d+)\s+opus\/48000/i.exec(n);e&&t.push(e[1])}return t}function a(e){for(let t of e){let e=/^a=mid:(.+)$/.exec(t);if(e)return e[1].trim()}return null}function o(e,t){let a=n(t),o=i(e);if(o.length===0||Object.keys(a).length===0)return[...e];let s=[];for(let t of e){let n=/^a=fmtp:(\d+)\s+(.*)$/.exec(t);if(n&&o.includes(n[1])){s.push(`a=fmtp:${n[1]} ${r(n[2],a)}`);continue}s.push(t);let i=/^a=rtpmap:(\d+)\s+opus\/48000/i.exec(t);i&&!e.some(e=>e.startsWith(`a=fmtp:${i[1]} `))&&s.push(`a=fmtp:${i[1]} ${r(``,a)}`)}return s}function s(e){let t=[],n=[];for(let r of e.split(/\r\n|\n/))r.startsWith(`m=`)&&n.length>0&&(t.push(n),n=[]),n.push(r);return n.length>0&&t.push(n),t}function c(e,n){let r=t(n)?n:null,i=r?null:n,c=0;return s(e).map(e=>{if(!e[0]?.startsWith(`m=audio`))return e;let t=c;if(c+=1,r)return o(e,r);let n=a(e),s=i?.[t]??(n===null?void 0:i?.[n]);return s?o(e,s):e}).flat().join(`\r
|
|
2
|
+
`)}async function l(e,t,n){if(t.sdp){let r=c(t.sdp,n);if(r!==t.sdp)try{return await e.setLocalDescription({...t,sdp:r}),`tuned`}catch{}}return await e.setLocalDescription(t),`original`}exports.setTunedLocalDescription=l,exports.tuneOpus=c;
|
|
3
|
+
//# sourceMappingURL=opus-sdp.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opus-sdp.cjs","names":[],"sources":["../../src/webrtc/opus-sdp.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines — parsing a session description, merging one\n * `fmtp` line and inserting a missing one are the same pass over the same\n * grammar; split apart, each half is a parser of half an SDP.\n */\n\n/** What an Opus `fmtp` line can be asked to carry. Every field is optional. */\nexport interface OpusProfile {\n /**\n * Ceiling the encoder is asked to respect, in bits per second.\n *\n * This describes what we want **to receive**. To cap what we *send*, use\n * `setSenderBitrate` — in a mesh that is the one that matters, because the\n * uplink carries one copy per participant.\n */\n maxAverageBitrate?: number;\n /** Highest sample rate worth decoding, in Hz. `48000` for full band. */\n maxPlaybackRate?: number;\n /**\n * Two channels instead of one.\n *\n * Sets `stereo` **and** `sprop-stereo`, which point in opposite directions:\n * `stereo=1` asks the *remote* to send two channels, `sprop-stereo=1`\n * announces that *we* will. Setting only one leaves the link asymmetric,\n * which is the recurring reason \"I asked for stereo and got mono\".\n */\n stereo?: boolean;\n /**\n * In-band forward error correction (`useinbandfec`).\n *\n * Rebuilds a lost packet from the next one, which keeps speech intelligible\n * on a lossy link — and smears music, since it spends bitrate on redundancy\n * instead of detail.\n */\n fec?: boolean;\n /**\n * Discontinuous transmission (`usedtx`): stop sending during silence.\n *\n * Saves uplink in a mesh, at the cost of clipping the first instant after a\n * pause. Wrong for music and for a shared screen, where the quiet passages\n * are content.\n */\n dtx?: boolean;\n /** Constant bitrate (`cbr`). Off by default, as Opus intends. */\n cbr?: boolean;\n /**\n * Any other `fmtp` key, merged verbatim.\n *\n * The escape hatch for a parameter this type does not model. Values are\n * written as given; a key with an empty string is emitted as a bare flag.\n */\n extra?: Record<string, string>;\n}\n\n/**\n * Profiles to apply, keyed by audio m-line index (`0`, `1`, …) or by `mid`.\n *\n * Position and `mid` can be mixed in one object. A key that matches nothing is\n * ignored rather than an error — an SDP is negotiated, and a slot that was not\n * offered this time is normal.\n */\nexport type OpusProfileMap = Record<string | number, OpusProfile>;\n\n/** Keys that identify a bare {@link OpusProfile} rather than a map of them. */\nconst PROFILE_KEYS: ReadonlySet<string> = new Set([\n \"maxAverageBitrate\",\n \"maxPlaybackRate\",\n \"stereo\",\n \"fec\",\n \"dtx\",\n \"cbr\",\n \"extra\",\n]);\n\n/**\n * Whether the caller passed one profile for every audio m-line, or a map.\n *\n * An empty object is read as a profile, which makes `tuneOpus(sdp, {})` a no-op\n * instead of a silent surprise.\n */\nfunction isSingleProfile(value: OpusProfile | OpusProfileMap): value is OpusProfile {\n const keys = Object.keys(value);\n return keys.length === 0 || keys.some((key) => PROFILE_KEYS.has(key));\n}\n\n/** The `fmtp` parameters a profile asks for, in the order Opus documents them. */\nfunction fmtpParams(profile: OpusProfile): Record<string, string> {\n const params: Record<string, string> = {};\n if (profile.maxAverageBitrate !== undefined) {\n params.maxaveragebitrate = String(profile.maxAverageBitrate);\n }\n if (profile.maxPlaybackRate !== undefined) {\n params.maxplaybackrate = String(profile.maxPlaybackRate);\n }\n if (profile.stereo !== undefined) {\n params.stereo = profile.stereo ? \"1\" : \"0\";\n params[\"sprop-stereo\"] = profile.stereo ? \"1\" : \"0\";\n }\n if (profile.fec !== undefined) params.useinbandfec = profile.fec ? \"1\" : \"0\";\n if (profile.dtx !== undefined) params.usedtx = profile.dtx ? \"1\" : \"0\";\n if (profile.cbr !== undefined) params.cbr = profile.cbr ? \"1\" : \"0\";\n return { ...params, ...profile.extra };\n}\n\n/**\n * Merge parameters into an existing `fmtp` payload, key by key.\n *\n * Replacing the whole line is the obvious move and it is wrong: the browser\n * emits `minptime=10;useinbandfec=1` on its own, and overwriting drops the\n * `minptime` — a packetization decision nobody meant to change.\n */\nfunction mergeFmtp(existing: string, params: Record<string, string>): string {\n const merged = new Map<string, string>();\n for (const entry of existing.split(\";\")) {\n const trimmed = entry.trim();\n if (!trimmed) continue;\n const eq = trimmed.indexOf(\"=\");\n if (eq < 0) merged.set(trimmed, \"\");\n else merged.set(trimmed.slice(0, eq), trimmed.slice(eq + 1));\n }\n for (const [key, value] of Object.entries(params)) merged.set(key, value);\n return [...merged.entries()]\n .map(([key, value]) => (value === \"\" ? key : `${key}=${value}`))\n .join(\";\");\n}\n\n/** Payload types this media block maps to Opus. There can be more than one. */\nfunction opusPayloads(block: readonly string[]): string[] {\n const payloads: string[] = [];\n for (const line of block) {\n const match = /^a=rtpmap:(\\d+)\\s+opus\\/48000/i.exec(line);\n if (match) payloads.push(match[1]);\n }\n return payloads;\n}\n\n/** The block's `mid`, when it declares one. */\nfunction blockMid(block: readonly string[]): string | null {\n for (const line of block) {\n const match = /^a=mid:(.+)$/.exec(line);\n if (match) return match[1].trim();\n }\n return null;\n}\n\n/**\n * Rewrite one media block's Opus `fmtp` lines to carry a profile.\n *\n * A payload type with no `fmtp` of its own gets one inserted right after its\n * `rtpmap`, because \"the parameter is missing\" and \"the parameter is empty\" are\n * the same request from the caller's side.\n */\nfunction tuneBlock(block: readonly string[], profile: OpusProfile): string[] {\n const params = fmtpParams(profile);\n const payloads = opusPayloads(block);\n if (payloads.length === 0 || Object.keys(params).length === 0) return [...block];\n\n const out: string[] = [];\n for (const line of block) {\n const fmtp = /^a=fmtp:(\\d+)\\s+(.*)$/.exec(line);\n if (fmtp && payloads.includes(fmtp[1])) {\n out.push(`a=fmtp:${fmtp[1]} ${mergeFmtp(fmtp[2], params)}`);\n continue;\n }\n out.push(line);\n\n const rtpmap = /^a=rtpmap:(\\d+)\\s+opus\\/48000/i.exec(line);\n if (rtpmap && !block.some((other) => other.startsWith(`a=fmtp:${rtpmap[1]} `))) {\n out.push(`a=fmtp:${rtpmap[1]} ${mergeFmtp(\"\", params)}`);\n }\n }\n return out;\n}\n\n/** Split a session description into its session part plus one block per m-line. */\nfunction mediaBlocks(sdp: string): string[][] {\n const blocks: string[][] = [];\n let current: string[] = [];\n for (const line of sdp.split(/\\r\\n|\\n/)) {\n if (line.startsWith(\"m=\") && current.length > 0) {\n blocks.push(current);\n current = [];\n }\n current.push(line);\n }\n if (current.length > 0) blocks.push(current);\n return blocks;\n}\n\n/**\n * Apply Opus profiles to the audio m-lines of a session description.\n *\n * Audio over WebRTC is mono and narrow by default, and the only place that is\n * corrected is the SDP — which is why shared-screen audio famously sounds like a\n * telephone: music and video inherit the speech profile (mono, ~32 kbps, FEC on,\n * DTX gating the quiet passages) and no high-level API lets you change it.\n *\n * Deliberately **without** built-in presets: which values to use is the\n * consumer's call — voice in a mesh does not want what system audio wants — and\n * a preset table is the kind of thing that has no business inside a dependency.\n * What lives here is the parsing, merging and insertion, which is where the long\n * tail is. Getting any of it wrong degrades silently: nobody sees an exception,\n * the audio is just worse.\n *\n * @param sdp - The description from `createOffer` or `createAnswer`.\n * @param profiles - One {@link OpusProfile} for every audio m-line, or an\n * {@link OpusProfileMap} keyed by audio m-line index or by `mid`.\n * @returns The rewritten SDP, with CRLF line endings as RFC 4566 requires.\n * Untouched when there is no Opus, when no profile matches, or when a profile\n * asks for nothing.\n *\n * @example\n * const tuned = tuneOpus(offer.sdp, {\n * 0: { maxAverageBitrate: 48_000, stereo: false, fec: true, dtx: true },\n * 1: { maxAverageBitrate: 192_000, stereo: true, fec: false, dtx: false },\n * });\n *\n * @example\n * const tuned = tuneOpus(offer.sdp, { stereo: true, dtx: false });\n */\nexport function tuneOpus(sdp: string, profiles: OpusProfile | OpusProfileMap): string {\n const single = isSingleProfile(profiles) ? profiles : null;\n const map = single ? null : (profiles as OpusProfileMap);\n\n let audioIndex = 0;\n const tuned = mediaBlocks(sdp).map((block) => {\n if (!block[0]?.startsWith(\"m=audio\")) return block;\n\n const index = audioIndex;\n audioIndex += 1;\n if (single) return tuneBlock(block, single);\n\n const mid = blockMid(block);\n const profile = map?.[index] ?? (mid !== null ? map?.[mid] : undefined);\n return profile ? tuneBlock(block, profile) : block;\n });\n\n return tuned.flat().join(\"\\r\\n\");\n}\n\n/** Which description a peer connection actually accepted. */\nexport type TunedDescriptionResult = \"tuned\" | \"original\";\n\n/**\n * Set a local description, falling back to the untouched one if it is refused.\n *\n * Chrome has been tightening what `setLocalDescription` accepts from edited SDP,\n * and there is no way to know in advance. Without a fallback the call dies\n * instead of merely losing the profile — which is the wrong trade by a wide\n * margin: worse audio beats no audio.\n *\n * @param connection - The peer connection.\n * @param description - The description from `createOffer` / `createAnswer`.\n * @param profiles - Passed straight to {@link tuneOpus}.\n * @returns `\"tuned\"` when the rewritten SDP was accepted, `\"original\"` when the\n * fallback was used — worth reporting, because it means the profile silently\n * did not apply.\n * @throws Whatever `setLocalDescription` throws for the original description: at\n * that point the failure is not about the rewrite and the caller has to know.\n *\n * @example\n * const applied = await setTunedLocalDescription(pc, await pc.createOffer(), profiles);\n * if (applied === \"original\") logger.warn(\"opus profile refused by the browser\");\n */\nexport async function setTunedLocalDescription(\n connection: RTCPeerConnection,\n description: RTCSessionDescriptionInit,\n profiles: OpusProfile | OpusProfileMap,\n): Promise<TunedDescriptionResult> {\n if (description.sdp) {\n const sdp = tuneOpus(description.sdp, profiles);\n if (sdp !== description.sdp) {\n try {\n await connection.setLocalDescription({ ...description, sdp });\n return \"tuned\";\n } catch {\n /* the browser refused the edit; the untouched offer still works */\n }\n }\n }\n await connection.setLocalDescription(description);\n return \"original\";\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//#region src/webrtc/opus-sdp.ts
|
|
2
|
+
var e = /* @__PURE__ */ new Set([
|
|
3
|
+
"maxAverageBitrate",
|
|
4
|
+
"maxPlaybackRate",
|
|
5
|
+
"stereo",
|
|
6
|
+
"fec",
|
|
7
|
+
"dtx",
|
|
8
|
+
"cbr",
|
|
9
|
+
"extra"
|
|
10
|
+
]);
|
|
11
|
+
function t(t) {
|
|
12
|
+
let n = Object.keys(t);
|
|
13
|
+
return n.length === 0 || n.some((t) => e.has(t));
|
|
14
|
+
}
|
|
15
|
+
function n(e) {
|
|
16
|
+
let t = {};
|
|
17
|
+
return e.maxAverageBitrate !== void 0 && (t.maxaveragebitrate = String(e.maxAverageBitrate)), e.maxPlaybackRate !== void 0 && (t.maxplaybackrate = String(e.maxPlaybackRate)), e.stereo !== void 0 && (t.stereo = e.stereo ? "1" : "0", t["sprop-stereo"] = e.stereo ? "1" : "0"), e.fec !== void 0 && (t.useinbandfec = e.fec ? "1" : "0"), e.dtx !== void 0 && (t.usedtx = e.dtx ? "1" : "0"), e.cbr !== void 0 && (t.cbr = e.cbr ? "1" : "0"), {
|
|
18
|
+
...t,
|
|
19
|
+
...e.extra
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function r(e, t) {
|
|
23
|
+
let n = /* @__PURE__ */ new Map();
|
|
24
|
+
for (let t of e.split(";")) {
|
|
25
|
+
let e = t.trim();
|
|
26
|
+
if (!e) continue;
|
|
27
|
+
let r = e.indexOf("=");
|
|
28
|
+
r < 0 ? n.set(e, "") : n.set(e.slice(0, r), e.slice(r + 1));
|
|
29
|
+
}
|
|
30
|
+
for (let [e, r] of Object.entries(t)) n.set(e, r);
|
|
31
|
+
return [...n.entries()].map(([e, t]) => t === "" ? e : `${e}=${t}`).join(";");
|
|
32
|
+
}
|
|
33
|
+
function i(e) {
|
|
34
|
+
let t = [];
|
|
35
|
+
for (let n of e) {
|
|
36
|
+
let e = /^a=rtpmap:(\d+)\s+opus\/48000/i.exec(n);
|
|
37
|
+
e && t.push(e[1]);
|
|
38
|
+
}
|
|
39
|
+
return t;
|
|
40
|
+
}
|
|
41
|
+
function a(e) {
|
|
42
|
+
for (let t of e) {
|
|
43
|
+
let e = /^a=mid:(.+)$/.exec(t);
|
|
44
|
+
if (e) return e[1].trim();
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
function o(e, t) {
|
|
49
|
+
let a = n(t), o = i(e);
|
|
50
|
+
if (o.length === 0 || Object.keys(a).length === 0) return [...e];
|
|
51
|
+
let s = [];
|
|
52
|
+
for (let t of e) {
|
|
53
|
+
let n = /^a=fmtp:(\d+)\s+(.*)$/.exec(t);
|
|
54
|
+
if (n && o.includes(n[1])) {
|
|
55
|
+
s.push(`a=fmtp:${n[1]} ${r(n[2], a)}`);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
s.push(t);
|
|
59
|
+
let i = /^a=rtpmap:(\d+)\s+opus\/48000/i.exec(t);
|
|
60
|
+
i && !e.some((e) => e.startsWith(`a=fmtp:${i[1]} `)) && s.push(`a=fmtp:${i[1]} ${r("", a)}`);
|
|
61
|
+
}
|
|
62
|
+
return s;
|
|
63
|
+
}
|
|
64
|
+
function s(e) {
|
|
65
|
+
let t = [], n = [];
|
|
66
|
+
for (let r of e.split(/\r\n|\n/)) r.startsWith("m=") && n.length > 0 && (t.push(n), n = []), n.push(r);
|
|
67
|
+
return n.length > 0 && t.push(n), t;
|
|
68
|
+
}
|
|
69
|
+
function c(e, n) {
|
|
70
|
+
let r = t(n) ? n : null, i = r ? null : n, c = 0;
|
|
71
|
+
return s(e).map((e) => {
|
|
72
|
+
if (!e[0]?.startsWith("m=audio")) return e;
|
|
73
|
+
let t = c;
|
|
74
|
+
if (c += 1, r) return o(e, r);
|
|
75
|
+
let n = a(e), s = i?.[t] ?? (n === null ? void 0 : i?.[n]);
|
|
76
|
+
return s ? o(e, s) : e;
|
|
77
|
+
}).flat().join("\r\n");
|
|
78
|
+
}
|
|
79
|
+
async function l(e, t, n) {
|
|
80
|
+
if (t.sdp) {
|
|
81
|
+
let r = c(t.sdp, n);
|
|
82
|
+
if (r !== t.sdp) try {
|
|
83
|
+
return await e.setLocalDescription({
|
|
84
|
+
...t,
|
|
85
|
+
sdp: r
|
|
86
|
+
}), "tuned";
|
|
87
|
+
} catch {}
|
|
88
|
+
}
|
|
89
|
+
return await e.setLocalDescription(t), "original";
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { l as setTunedLocalDescription, c as tuneOpus };
|
|
93
|
+
|
|
94
|
+
//# sourceMappingURL=opus-sdp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opus-sdp.js","names":[],"sources":["../../src/webrtc/opus-sdp.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines — parsing a session description, merging one\n * `fmtp` line and inserting a missing one are the same pass over the same\n * grammar; split apart, each half is a parser of half an SDP.\n */\n\n/** What an Opus `fmtp` line can be asked to carry. Every field is optional. */\nexport interface OpusProfile {\n /**\n * Ceiling the encoder is asked to respect, in bits per second.\n *\n * This describes what we want **to receive**. To cap what we *send*, use\n * `setSenderBitrate` — in a mesh that is the one that matters, because the\n * uplink carries one copy per participant.\n */\n maxAverageBitrate?: number;\n /** Highest sample rate worth decoding, in Hz. `48000` for full band. */\n maxPlaybackRate?: number;\n /**\n * Two channels instead of one.\n *\n * Sets `stereo` **and** `sprop-stereo`, which point in opposite directions:\n * `stereo=1` asks the *remote* to send two channels, `sprop-stereo=1`\n * announces that *we* will. Setting only one leaves the link asymmetric,\n * which is the recurring reason \"I asked for stereo and got mono\".\n */\n stereo?: boolean;\n /**\n * In-band forward error correction (`useinbandfec`).\n *\n * Rebuilds a lost packet from the next one, which keeps speech intelligible\n * on a lossy link — and smears music, since it spends bitrate on redundancy\n * instead of detail.\n */\n fec?: boolean;\n /**\n * Discontinuous transmission (`usedtx`): stop sending during silence.\n *\n * Saves uplink in a mesh, at the cost of clipping the first instant after a\n * pause. Wrong for music and for a shared screen, where the quiet passages\n * are content.\n */\n dtx?: boolean;\n /** Constant bitrate (`cbr`). Off by default, as Opus intends. */\n cbr?: boolean;\n /**\n * Any other `fmtp` key, merged verbatim.\n *\n * The escape hatch for a parameter this type does not model. Values are\n * written as given; a key with an empty string is emitted as a bare flag.\n */\n extra?: Record<string, string>;\n}\n\n/**\n * Profiles to apply, keyed by audio m-line index (`0`, `1`, …) or by `mid`.\n *\n * Position and `mid` can be mixed in one object. A key that matches nothing is\n * ignored rather than an error — an SDP is negotiated, and a slot that was not\n * offered this time is normal.\n */\nexport type OpusProfileMap = Record<string | number, OpusProfile>;\n\n/** Keys that identify a bare {@link OpusProfile} rather than a map of them. */\nconst PROFILE_KEYS: ReadonlySet<string> = new Set([\n \"maxAverageBitrate\",\n \"maxPlaybackRate\",\n \"stereo\",\n \"fec\",\n \"dtx\",\n \"cbr\",\n \"extra\",\n]);\n\n/**\n * Whether the caller passed one profile for every audio m-line, or a map.\n *\n * An empty object is read as a profile, which makes `tuneOpus(sdp, {})` a no-op\n * instead of a silent surprise.\n */\nfunction isSingleProfile(value: OpusProfile | OpusProfileMap): value is OpusProfile {\n const keys = Object.keys(value);\n return keys.length === 0 || keys.some((key) => PROFILE_KEYS.has(key));\n}\n\n/** The `fmtp` parameters a profile asks for, in the order Opus documents them. */\nfunction fmtpParams(profile: OpusProfile): Record<string, string> {\n const params: Record<string, string> = {};\n if (profile.maxAverageBitrate !== undefined) {\n params.maxaveragebitrate = String(profile.maxAverageBitrate);\n }\n if (profile.maxPlaybackRate !== undefined) {\n params.maxplaybackrate = String(profile.maxPlaybackRate);\n }\n if (profile.stereo !== undefined) {\n params.stereo = profile.stereo ? \"1\" : \"0\";\n params[\"sprop-stereo\"] = profile.stereo ? \"1\" : \"0\";\n }\n if (profile.fec !== undefined) params.useinbandfec = profile.fec ? \"1\" : \"0\";\n if (profile.dtx !== undefined) params.usedtx = profile.dtx ? \"1\" : \"0\";\n if (profile.cbr !== undefined) params.cbr = profile.cbr ? \"1\" : \"0\";\n return { ...params, ...profile.extra };\n}\n\n/**\n * Merge parameters into an existing `fmtp` payload, key by key.\n *\n * Replacing the whole line is the obvious move and it is wrong: the browser\n * emits `minptime=10;useinbandfec=1` on its own, and overwriting drops the\n * `minptime` — a packetization decision nobody meant to change.\n */\nfunction mergeFmtp(existing: string, params: Record<string, string>): string {\n const merged = new Map<string, string>();\n for (const entry of existing.split(\";\")) {\n const trimmed = entry.trim();\n if (!trimmed) continue;\n const eq = trimmed.indexOf(\"=\");\n if (eq < 0) merged.set(trimmed, \"\");\n else merged.set(trimmed.slice(0, eq), trimmed.slice(eq + 1));\n }\n for (const [key, value] of Object.entries(params)) merged.set(key, value);\n return [...merged.entries()]\n .map(([key, value]) => (value === \"\" ? key : `${key}=${value}`))\n .join(\";\");\n}\n\n/** Payload types this media block maps to Opus. There can be more than one. */\nfunction opusPayloads(block: readonly string[]): string[] {\n const payloads: string[] = [];\n for (const line of block) {\n const match = /^a=rtpmap:(\\d+)\\s+opus\\/48000/i.exec(line);\n if (match) payloads.push(match[1]);\n }\n return payloads;\n}\n\n/** The block's `mid`, when it declares one. */\nfunction blockMid(block: readonly string[]): string | null {\n for (const line of block) {\n const match = /^a=mid:(.+)$/.exec(line);\n if (match) return match[1].trim();\n }\n return null;\n}\n\n/**\n * Rewrite one media block's Opus `fmtp` lines to carry a profile.\n *\n * A payload type with no `fmtp` of its own gets one inserted right after its\n * `rtpmap`, because \"the parameter is missing\" and \"the parameter is empty\" are\n * the same request from the caller's side.\n */\nfunction tuneBlock(block: readonly string[], profile: OpusProfile): string[] {\n const params = fmtpParams(profile);\n const payloads = opusPayloads(block);\n if (payloads.length === 0 || Object.keys(params).length === 0) return [...block];\n\n const out: string[] = [];\n for (const line of block) {\n const fmtp = /^a=fmtp:(\\d+)\\s+(.*)$/.exec(line);\n if (fmtp && payloads.includes(fmtp[1])) {\n out.push(`a=fmtp:${fmtp[1]} ${mergeFmtp(fmtp[2], params)}`);\n continue;\n }\n out.push(line);\n\n const rtpmap = /^a=rtpmap:(\\d+)\\s+opus\\/48000/i.exec(line);\n if (rtpmap && !block.some((other) => other.startsWith(`a=fmtp:${rtpmap[1]} `))) {\n out.push(`a=fmtp:${rtpmap[1]} ${mergeFmtp(\"\", params)}`);\n }\n }\n return out;\n}\n\n/** Split a session description into its session part plus one block per m-line. */\nfunction mediaBlocks(sdp: string): string[][] {\n const blocks: string[][] = [];\n let current: string[] = [];\n for (const line of sdp.split(/\\r\\n|\\n/)) {\n if (line.startsWith(\"m=\") && current.length > 0) {\n blocks.push(current);\n current = [];\n }\n current.push(line);\n }\n if (current.length > 0) blocks.push(current);\n return blocks;\n}\n\n/**\n * Apply Opus profiles to the audio m-lines of a session description.\n *\n * Audio over WebRTC is mono and narrow by default, and the only place that is\n * corrected is the SDP — which is why shared-screen audio famously sounds like a\n * telephone: music and video inherit the speech profile (mono, ~32 kbps, FEC on,\n * DTX gating the quiet passages) and no high-level API lets you change it.\n *\n * Deliberately **without** built-in presets: which values to use is the\n * consumer's call — voice in a mesh does not want what system audio wants — and\n * a preset table is the kind of thing that has no business inside a dependency.\n * What lives here is the parsing, merging and insertion, which is where the long\n * tail is. Getting any of it wrong degrades silently: nobody sees an exception,\n * the audio is just worse.\n *\n * @param sdp - The description from `createOffer` or `createAnswer`.\n * @param profiles - One {@link OpusProfile} for every audio m-line, or an\n * {@link OpusProfileMap} keyed by audio m-line index or by `mid`.\n * @returns The rewritten SDP, with CRLF line endings as RFC 4566 requires.\n * Untouched when there is no Opus, when no profile matches, or when a profile\n * asks for nothing.\n *\n * @example\n * const tuned = tuneOpus(offer.sdp, {\n * 0: { maxAverageBitrate: 48_000, stereo: false, fec: true, dtx: true },\n * 1: { maxAverageBitrate: 192_000, stereo: true, fec: false, dtx: false },\n * });\n *\n * @example\n * const tuned = tuneOpus(offer.sdp, { stereo: true, dtx: false });\n */\nexport function tuneOpus(sdp: string, profiles: OpusProfile | OpusProfileMap): string {\n const single = isSingleProfile(profiles) ? profiles : null;\n const map = single ? null : (profiles as OpusProfileMap);\n\n let audioIndex = 0;\n const tuned = mediaBlocks(sdp).map((block) => {\n if (!block[0]?.startsWith(\"m=audio\")) return block;\n\n const index = audioIndex;\n audioIndex += 1;\n if (single) return tuneBlock(block, single);\n\n const mid = blockMid(block);\n const profile = map?.[index] ?? (mid !== null ? map?.[mid] : undefined);\n return profile ? tuneBlock(block, profile) : block;\n });\n\n return tuned.flat().join(\"\\r\\n\");\n}\n\n/** Which description a peer connection actually accepted. */\nexport type TunedDescriptionResult = \"tuned\" | \"original\";\n\n/**\n * Set a local description, falling back to the untouched one if it is refused.\n *\n * Chrome has been tightening what `setLocalDescription` accepts from edited SDP,\n * and there is no way to know in advance. Without a fallback the call dies\n * instead of merely losing the profile — which is the wrong trade by a wide\n * margin: worse audio beats no audio.\n *\n * @param connection - The peer connection.\n * @param description - The description from `createOffer` / `createAnswer`.\n * @param profiles - Passed straight to {@link tuneOpus}.\n * @returns `\"tuned\"` when the rewritten SDP was accepted, `\"original\"` when the\n * fallback was used — worth reporting, because it means the profile silently\n * did not apply.\n * @throws Whatever `setLocalDescription` throws for the original description: at\n * that point the failure is not about the rewrite and the caller has to know.\n *\n * @example\n * const applied = await setTunedLocalDescription(pc, await pc.createOffer(), profiles);\n * if (applied === \"original\") logger.warn(\"opus profile refused by the browser\");\n */\nexport async function setTunedLocalDescription(\n connection: RTCPeerConnection,\n description: RTCSessionDescriptionInit,\n profiles: OpusProfile | OpusProfileMap,\n): Promise<TunedDescriptionResult> {\n if (description.sdp) {\n const sdp = tuneOpus(description.sdp, profiles);\n if (sdp !== description.sdp) {\n try {\n await connection.setLocalDescription({ ...description, sdp });\n return \"tuned\";\n } catch {\n /* the browser refused the edit; the untouched offer still works */\n }\n }\n }\n await connection.setLocalDescription(description);\n return \"original\";\n}\n"],"mappings":";AAgEA,IAAM,oBAAoC,IAAI,IAAI;CAC9C;CACA;CACA;CACA;CACA;CACA;CACA;AACJ,CAAC;AAQD,SAAS,EAAgB,GAA2D;CAChF,IAAM,IAAO,OAAO,KAAK,CAAK;CAC9B,OAAO,EAAK,WAAW,KAAK,EAAK,MAAM,MAAQ,EAAa,IAAI,CAAG,CAAC;AACxE;AAGA,SAAS,EAAW,GAA8C;CAC9D,IAAM,IAAiC,CAAC;CAcxC,OAbI,EAAQ,sBAAsB,KAAA,MAC9B,EAAO,oBAAoB,OAAO,EAAQ,iBAAiB,IAE3D,EAAQ,oBAAoB,KAAA,MAC5B,EAAO,kBAAkB,OAAO,EAAQ,eAAe,IAEvD,EAAQ,WAAW,KAAA,MACnB,EAAO,SAAS,EAAQ,SAAS,MAAM,KACvC,EAAO,kBAAkB,EAAQ,SAAS,MAAM,MAEhD,EAAQ,QAAQ,KAAA,MAAW,EAAO,eAAe,EAAQ,MAAM,MAAM,MACrE,EAAQ,QAAQ,KAAA,MAAW,EAAO,SAAS,EAAQ,MAAM,MAAM,MAC/D,EAAQ,QAAQ,KAAA,MAAW,EAAO,MAAM,EAAQ,MAAM,MAAM,MACzD;EAAE,GAAG;EAAQ,GAAG,EAAQ;CAAM;AACzC;AASA,SAAS,EAAU,GAAkB,GAAwC;CACzE,IAAM,oBAAS,IAAI,IAAoB;CACvC,KAAK,IAAM,KAAS,EAAS,MAAM,GAAG,GAAG;EACrC,IAAM,IAAU,EAAM,KAAK;EAC3B,IAAI,CAAC,GAAS;EACd,IAAM,IAAK,EAAQ,QAAQ,GAAG;EAC9B,AAAI,IAAK,IAAG,EAAO,IAAI,GAAS,EAAE,IAC7B,EAAO,IAAI,EAAQ,MAAM,GAAG,CAAE,GAAG,EAAQ,MAAM,IAAK,CAAC,CAAC;CAC/D;CACA,KAAK,IAAM,CAAC,GAAK,MAAU,OAAO,QAAQ,CAAM,GAAG,EAAO,IAAI,GAAK,CAAK;CACxE,OAAO,CAAC,GAAG,EAAO,QAAQ,CAAC,CAAC,CACvB,KAAK,CAAC,GAAK,OAAY,MAAU,KAAK,IAAM,GAAG,EAAI,GAAG,GAAQ,CAAC,CAC/D,KAAK,GAAG;AACjB;AAGA,SAAS,EAAa,GAAoC;CACtD,IAAM,IAAqB,CAAC;CAC5B,KAAK,IAAM,KAAQ,GAAO;EACtB,IAAM,IAAQ,iCAAiC,KAAK,CAAI;EACxD,AAAI,KAAO,EAAS,KAAK,EAAM,EAAE;CACrC;CACA,OAAO;AACX;AAGA,SAAS,EAAS,GAAyC;CACvD,KAAK,IAAM,KAAQ,GAAO;EACtB,IAAM,IAAQ,eAAe,KAAK,CAAI;EACtC,IAAI,GAAO,OAAO,EAAM,EAAE,CAAC,KAAK;CACpC;CACA,OAAO;AACX;AASA,SAAS,EAAU,GAA0B,GAAgC;CACzE,IAAM,IAAS,EAAW,CAAO,GAC3B,IAAW,EAAa,CAAK;CACnC,IAAI,EAAS,WAAW,KAAK,OAAO,KAAK,CAAM,CAAC,CAAC,WAAW,GAAG,OAAO,CAAC,GAAG,CAAK;CAE/E,IAAM,IAAgB,CAAC;CACvB,KAAK,IAAM,KAAQ,GAAO;EACtB,IAAM,IAAO,wBAAwB,KAAK,CAAI;EAC9C,IAAI,KAAQ,EAAS,SAAS,EAAK,EAAE,GAAG;GACpC,EAAI,KAAK,UAAU,EAAK,GAAG,GAAG,EAAU,EAAK,IAAI,CAAM,GAAG;GAC1D;EACJ;EACA,EAAI,KAAK,CAAI;EAEb,IAAM,IAAS,iCAAiC,KAAK,CAAI;EACzD,AAAI,KAAU,CAAC,EAAM,MAAM,MAAU,EAAM,WAAW,UAAU,EAAO,GAAG,EAAE,CAAC,KACzE,EAAI,KAAK,UAAU,EAAO,GAAG,GAAG,EAAU,IAAI,CAAM,GAAG;CAE/D;CACA,OAAO;AACX;AAGA,SAAS,EAAY,GAAyB;CAC1C,IAAM,IAAqB,CAAC,GACxB,IAAoB,CAAC;CACzB,KAAK,IAAM,KAAQ,EAAI,MAAM,SAAS,GAKlC,AAJI,EAAK,WAAW,IAAI,KAAK,EAAQ,SAAS,MAC1C,EAAO,KAAK,CAAO,GACnB,IAAU,CAAC,IAEf,EAAQ,KAAK,CAAI;CAGrB,OADI,EAAQ,SAAS,KAAG,EAAO,KAAK,CAAO,GACpC;AACX;AAiCA,SAAgB,EAAS,GAAa,GAAgD;CAClF,IAAM,IAAS,EAAgB,CAAQ,IAAI,IAAW,MAChD,IAAM,IAAS,OAAQ,GAEzB,IAAa;CAajB,OAZc,EAAY,CAAG,CAAC,CAAC,KAAK,MAAU;EAC1C,IAAI,CAAC,EAAM,EAAE,EAAE,WAAW,SAAS,GAAG,OAAO;EAE7C,IAAM,IAAQ;EAEd,IADA,KAAc,GACV,GAAQ,OAAO,EAAU,GAAO,CAAM;EAE1C,IAAM,IAAM,EAAS,CAAK,GACpB,IAAU,IAAM,OAAW,MAAQ,OAAoB,KAAA,IAAb,IAAM;EACtD,OAAO,IAAU,EAAU,GAAO,CAAO,IAAI;CACjD,CAEO,CAAA,CAAM,KAAK,CAAC,CAAC,KAAK,MAAM;AACnC;AA0BA,eAAsB,EAClB,GACA,GACA,GAC+B;CAC/B,IAAI,EAAY,KAAK;EACjB,IAAM,IAAM,EAAS,EAAY,KAAK,CAAQ;EAC9C,IAAI,MAAQ,EAAY,KACpB,IAAI;GAEA,OADA,MAAM,EAAW,oBAAoB;IAAE,GAAG;IAAa;GAAI,CAAC,GACrD;EACX,QAAQ,CAER;CAER;CAEA,OADA,MAAM,EAAW,oBAAoB,CAAW,GACzC;AACX"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
async function e(e,t){try{let n=e.getParameters();(!n.encodings||n.encodings.length===0)&&(n.encodings=[{}]);for(let e of n.encodings)t===null?delete e.maxBitrate:e.maxBitrate=t;return await e.setParameters(n),!0}catch{return!1}}exports.setSenderBitrate=e;
|
|
2
|
+
//# sourceMappingURL=sender-bitrate.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sender-bitrate.cjs","names":[],"sources":["../../src/webrtc/sender-bitrate.ts"],"sourcesContent":["/**\n * Cap what one sender transmits, in bits per second.\n *\n * This is the other half of the pair that confuses people: an Opus `fmtp` line\n * describes what we want **to receive**, while this is what limits what we\n * **send**. Both are needed, and in a mesh topology this one matters more,\n * because the uplink carries one copy of the stream per participant.\n *\n * The read-modify-write is not ceremony. `setParameters` only accepts the very\n * object `getParameters` handed back — a freshly built one is rejected — and a\n * sender that has not negotiated yet reports **no** encodings at all, so writing\n * to `encodings[0]` without checking throws on exactly the call that sets the\n * cap before the first offer.\n *\n * @param sender - The `RTCRtpSender` to cap, from `pc.getSenders()` or\n * `transceiver.sender`.\n * @param maxBitrate - Ceiling in bits per second, or `null` to lift the cap.\n * @returns `false` when the browser refused the change, which happens when the\n * sender has no track or the transceiver is gone. Playback continues\n * uncapped — a call that keeps running beats one that throws over a bitrate.\n *\n * @example\n * const sender = pc.getSenders().find((s) => s.track?.kind === \"audio\");\n * if (sender) await setSenderBitrate(sender, 48_000);\n */\nexport async function setSenderBitrate(\n sender: RTCRtpSender,\n maxBitrate: number | null,\n): Promise<boolean> {\n try {\n const parameters = sender.getParameters();\n if (!parameters.encodings || parameters.encodings.length === 0) {\n parameters.encodings = [{}];\n }\n for (const encoding of parameters.encodings) {\n if (maxBitrate === null) delete encoding.maxBitrate;\n else encoding.maxBitrate = maxBitrate;\n }\n await sender.setParameters(parameters);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"AAyBA,eAAsB,EAClB,EACA,EACgB,CAChB,GAAI,CACA,IAAM,EAAa,EAAO,cAAc,GACpC,CAAC,EAAW,WAAa,EAAW,UAAU,SAAW,KACzD,EAAW,UAAY,CAAC,CAAC,CAAC,GAE9B,IAAK,IAAM,KAAY,EAAW,UAC1B,IAAe,KAAM,OAAO,EAAS,WACpC,EAAS,WAAa,EAG/B,OADA,MAAM,EAAO,cAAc,CAAU,EAC9B,EACX,MAAQ,CACJ,MAAO,EACX,CACJ"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/webrtc/sender-bitrate.ts
|
|
2
|
+
async function e(e, t) {
|
|
3
|
+
try {
|
|
4
|
+
let n = e.getParameters();
|
|
5
|
+
(!n.encodings || n.encodings.length === 0) && (n.encodings = [{}]);
|
|
6
|
+
for (let e of n.encodings) t === null ? delete e.maxBitrate : e.maxBitrate = t;
|
|
7
|
+
return await e.setParameters(n), !0;
|
|
8
|
+
} catch {
|
|
9
|
+
return !1;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { e as setSenderBitrate };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=sender-bitrate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sender-bitrate.js","names":[],"sources":["../../src/webrtc/sender-bitrate.ts"],"sourcesContent":["/**\n * Cap what one sender transmits, in bits per second.\n *\n * This is the other half of the pair that confuses people: an Opus `fmtp` line\n * describes what we want **to receive**, while this is what limits what we\n * **send**. Both are needed, and in a mesh topology this one matters more,\n * because the uplink carries one copy of the stream per participant.\n *\n * The read-modify-write is not ceremony. `setParameters` only accepts the very\n * object `getParameters` handed back — a freshly built one is rejected — and a\n * sender that has not negotiated yet reports **no** encodings at all, so writing\n * to `encodings[0]` without checking throws on exactly the call that sets the\n * cap before the first offer.\n *\n * @param sender - The `RTCRtpSender` to cap, from `pc.getSenders()` or\n * `transceiver.sender`.\n * @param maxBitrate - Ceiling in bits per second, or `null` to lift the cap.\n * @returns `false` when the browser refused the change, which happens when the\n * sender has no track or the transceiver is gone. Playback continues\n * uncapped — a call that keeps running beats one that throws over a bitrate.\n *\n * @example\n * const sender = pc.getSenders().find((s) => s.track?.kind === \"audio\");\n * if (sender) await setSenderBitrate(sender, 48_000);\n */\nexport async function setSenderBitrate(\n sender: RTCRtpSender,\n maxBitrate: number | null,\n): Promise<boolean> {\n try {\n const parameters = sender.getParameters();\n if (!parameters.encodings || parameters.encodings.length === 0) {\n parameters.encodings = [{}];\n }\n for (const encoding of parameters.encodings) {\n if (maxBitrate === null) delete encoding.maxBitrate;\n else encoding.maxBitrate = maxBitrate;\n }\n await sender.setParameters(parameters);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":";AAyBA,eAAsB,EAClB,GACA,GACgB;CAChB,IAAI;EACA,IAAM,IAAa,EAAO,cAAc;EACxC,CAAI,CAAC,EAAW,aAAa,EAAW,UAAU,WAAW,OACzD,EAAW,YAAY,CAAC,CAAC,CAAC;EAE9B,KAAK,IAAM,KAAY,EAAW,WAC9B,AAAI,MAAe,OAAM,OAAO,EAAS,aACpC,EAAS,aAAa;EAG/B,OADA,MAAM,EAAO,cAAc,CAAU,GAC9B;CACX,QAAQ;EACJ,OAAO;CACX;AACJ"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
const e=require("./resilience.cjs");function t(e){try{return JSON.parse(e)}catch{return e}}function n(n,r={}){let{protocols:i,maxRetries:a=10,initialBackoff:o=1e3,maxBackoff:s=3e4,jitter:c=.3,handshakeTimeout:l=8e3,silenceTimeout:u=0,waitForOnline:d=!0,pingInterval:f=0,pingPayload:p=JSON.stringify({type:`ping`}),respondToPing:ee=!0,pongPayload:te=JSON.stringify({type:`pong`}),queueWhileClosed:ne=!1,maxQueuedMessages:re=100,parser:m=t,onOpen:h,onMessage:g,onClose:_,onError:v,onStatusChange:y,onReconnecting:b,onReconnected:x,onLost:S}=r,C=null,w=null,T=null,E=null,D=null,O=null,k=u,A=0,j=`idle`,M=!1,N=!1,P=[],F=null,I=null,L=new Promise((e,t)=>{F=e,I=t});L.catch(()=>void 0);function R(e){return typeof e==`object`&&!!e&&e.type===`ping`}function z(e){for(;P.length>0&&e.readyState===WebSocket.OPEN;)e.send(P.shift())}function B(e){j!==e&&(j=e,y?.(e))}function V(){T&&=(clearInterval(T),null)}function H(){E&&=(clearTimeout(E),null)}function U(){D&&=(clearTimeout(D),null)}function W(){!f||f<=0||(V(),T=setInterval(()=>{C?.readyState===WebSocket.OPEN&&C.send(p)},f))}function G(){U(),!(M||k<=0)&&(D=setTimeout(K,k))}function K(){U(),!(M||!C)&&(J(C),C=null,B(`closed`),X())}function q(e){H(),!(M||e.readyState!==WebSocket.CONNECTING)&&(J(e),C===e&&(C=null),B(`closed`),X())}function J(e){e.onopen=null,e.onmessage=null,e.onerror=null,e.onclose=null;try{e.close()}catch{}}function Y(e){if(U(),H(),Z(),e===`rejected`&&(M=!0),B(`error`),!N&&I){let t=I;I=null,F=null,t(Error(`websocket_${e}`))}S?.(e)}function X(){if(M)return;if(A>=a){Y(`exhausted`);return}let t=e.backoffDelay(A,{initialBackoff:o,maxBackoff:s,jitter:c});if(A+=1,b?.(A,a),d&&typeof navigator<`u`&&navigator.onLine===!1){ie();return}w=setTimeout(Q,t)}function ie(){if(O||typeof window>`u`)return;let e=()=>{Z(),M||Q()};O=e,window.addEventListener(`online`,e)}function Z(){!O||typeof window>`u`||(window.removeEventListener(`online`,O),O=null)}function Q(){if(M)return;if(w=null,H(),C){let e=C;e.onmessage=null,e.onclose=null,e.onerror=null,e.readyState!==WebSocket.CONNECTING&&(e.onopen=null),$(e)}B(`connecting`);let t=new WebSocket(n,i);C=t,l>0&&(E=setTimeout(()=>q(t),l)),t.onopen=e=>{H();let n=A>0;if(A=0,N=!0,B(`open`),W(),G(),z(t),F){let e=F;F=null,I=null,e()}h?.(e),n&&x?.()},t.onmessage=e=>{G();let n=typeof e.data==`string`?e.data:``,r=m(n);ee&&R(r)&&t.readyState===WebSocket.OPEN&&t.send(te),g?.({data:r,raw:e})},t.onerror=e=>{v?.(e)},t.onclose=t=>{if(H(),V(),U(),_?.(t),C=null,B(`closed`),!M){if(e.isRejectionCloseCode(t.code)){Y(`rejected`);return}if(e.shouldRetryClose(t.code,t.wasClean)){X();return}N||Y(`rejected`)}}}function ae(e){return C?.readyState===WebSocket.OPEN?(C.send(e),!0):!ne||M?!1:(P.length>=re&&P.shift(),P.push(e),!0)}function oe(e,t){if(M=!0,w&&=(clearTimeout(w),null),V(),H(),U(),Z(),A=0,P.length=0,I){let e=I;I=null,F=null,e(Error(`websocket_closed`))}C&&=(B(`closing`),$(C,e,t),null),B(`closed`)}function $(e,t,n){if(e.readyState===WebSocket.CONNECTING){e.onopen=()=>e.close(t,n),e.onmessage=null,e.onerror=null,e.onclose=null;return}e.close(t,n)}function se(){w&&=(clearTimeout(w),null),Z(),A=0,M=!1,Q()}function ce(e){k=Number.isFinite(e)&&e>0?e:0,C?.readyState===WebSocket.OPEN?G():U()}return Q(),{send:ae,close:oe,reconnect:se,setSilenceTimeout:ce,opened:L,get status(){return j}}}exports.createWebSocket=n;
|
|
2
2
|
//# sourceMappingURL=create-web-socket.cjs.map
|