@vibecodeapp/sdk 0.1.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/LICENSE +21 -0
- package/README.md +110 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/polyfills/alert.web.d.ts +6 -0
- package/dist/polyfills/alert.web.d.ts.map +1 -0
- package/dist/polyfills/alert.web.js +6 -0
- package/dist/polyfills/alert.web.js.map +1 -0
- package/dist/polyfills/haptics.web.d.ts +21 -0
- package/dist/polyfills/haptics.web.d.ts.map +1 -0
- package/dist/polyfills/haptics.web.js +54 -0
- package/dist/polyfills/haptics.web.js.map +1 -0
- package/dist/polyfills/maps.web.d.ts +10 -0
- package/dist/polyfills/maps.web.d.ts.map +1 -0
- package/dist/polyfills/maps.web.js +27 -0
- package/dist/polyfills/maps.web.js.map +1 -0
- package/dist/polyfills/refresh-control-component.d.ts +19 -0
- package/dist/polyfills/refresh-control-component.d.ts.map +1 -0
- package/dist/polyfills/refresh-control-component.js +120 -0
- package/dist/polyfills/refresh-control-component.js.map +1 -0
- package/dist/polyfills/secure-store.web.d.ts +21 -0
- package/dist/polyfills/secure-store.web.d.ts.map +1 -0
- package/dist/polyfills/secure-store.web.js +67 -0
- package/dist/polyfills/secure-store.web.js.map +1 -0
- package/metro/index.cjs +151 -0
- package/metro/index.d.ts +26 -0
- package/metro/metro-http-store.cjs +274 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 vibecode
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# @vibecodeapp/sdk
|
|
2
|
+
|
|
3
|
+
SDK for Vibecodeapp.com - Web polyfills and Metro configuration for React Native.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @vibecodeapp/sdk
|
|
9
|
+
# or
|
|
10
|
+
yarn add @vibecodeapp/sdk
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @vibecodeapp/sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Metro Configuration
|
|
16
|
+
|
|
17
|
+
The SDK provides a Metro wrapper that adds:
|
|
18
|
+
- Web polyfills for native modules
|
|
19
|
+
- HTTP/2 remote cache support
|
|
20
|
+
- Configurable local file caching
|
|
21
|
+
|
|
22
|
+
### Basic Usage
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
// metro.config.js
|
|
26
|
+
const { getDefaultConfig } = require("expo/metro-config");
|
|
27
|
+
const { withVibecodeMetro } = require("@vibecodeapp/sdk/metro");
|
|
28
|
+
|
|
29
|
+
const config = getDefaultConfig(__dirname);
|
|
30
|
+
|
|
31
|
+
module.exports = withVibecodeMetro(config);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### With NativeWind
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
// metro.config.js
|
|
38
|
+
const { getDefaultConfig } = require("expo/metro-config");
|
|
39
|
+
const { withNativeWind } = require("nativewind/metro");
|
|
40
|
+
const { withVibecodeMetro } = require("@vibecodeapp/sdk/metro");
|
|
41
|
+
|
|
42
|
+
const config = getDefaultConfig(__dirname);
|
|
43
|
+
|
|
44
|
+
// Apply Vibecode config first, then NativeWind
|
|
45
|
+
module.exports = withNativeWind(
|
|
46
|
+
withVibecodeMetro(config),
|
|
47
|
+
{ input: "./global.css" }
|
|
48
|
+
);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### With Remote HTTP Cache
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
// metro.config.js
|
|
55
|
+
const { getDefaultConfig } = require("expo/metro-config");
|
|
56
|
+
const { withVibecodeMetro } = require("@vibecodeapp/sdk/metro");
|
|
57
|
+
|
|
58
|
+
const config = getDefaultConfig(__dirname);
|
|
59
|
+
|
|
60
|
+
module.exports = withVibecodeMetro(config, {
|
|
61
|
+
httpEndpoint: process.env.METRO_CACHE_HTTP_ENDPOINT,
|
|
62
|
+
httpTimeout: 30000,
|
|
63
|
+
httpMaxConnections: 64,
|
|
64
|
+
httpMaxConcurrent: 256,
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Configuration Options
|
|
69
|
+
|
|
70
|
+
| Option | Type | Default | Description |
|
|
71
|
+
|--------|------|---------|-------------|
|
|
72
|
+
| `enableCache` | boolean | `true` | Enable cache configuration |
|
|
73
|
+
| `cacheDir` | string | `~/.metro-cache` | Local cache directory |
|
|
74
|
+
| `cacheVersion` | string | `"1"` | Cache version (increment to invalidate) |
|
|
75
|
+
| `httpEndpoint` | string | - | HTTP/2 cache server URL |
|
|
76
|
+
| `httpTimeout` | number | `30000` | HTTP request timeout (ms) |
|
|
77
|
+
| `httpMaxConnections` | number | `64` | HTTP/2 connection pool size |
|
|
78
|
+
| `httpMaxConcurrent` | number | `256` | Max concurrent HTTP requests |
|
|
79
|
+
|
|
80
|
+
### Environment Variables
|
|
81
|
+
|
|
82
|
+
The SDK also respects these environment variables:
|
|
83
|
+
|
|
84
|
+
| Variable | Description |
|
|
85
|
+
|----------|-------------|
|
|
86
|
+
| `METRO_CACHE_DIR` | Local cache directory |
|
|
87
|
+
| `METRO_CACHE_VERSION` | Cache version string |
|
|
88
|
+
| `METRO_CACHE_HTTP_ENDPOINT` | HTTP/2 cache server URL |
|
|
89
|
+
|
|
90
|
+
## Web Polyfills
|
|
91
|
+
|
|
92
|
+
The SDK automatically provides web polyfills for:
|
|
93
|
+
|
|
94
|
+
| Native Module | Web Implementation |
|
|
95
|
+
|---------------|-------------------|
|
|
96
|
+
| `expo-haptics` | Vibration API |
|
|
97
|
+
| `expo-secure-store` | localStorage |
|
|
98
|
+
| `react-native-maps` | Google Maps (`@teovilla/react-native-web-maps`) |
|
|
99
|
+
| `RefreshControl` | Custom PanResponder implementation |
|
|
100
|
+
| `Alert` | Web-compatible alert dialog |
|
|
101
|
+
|
|
102
|
+
### Environment Variables for Polyfills
|
|
103
|
+
|
|
104
|
+
| Variable | Purpose |
|
|
105
|
+
|----------|---------|
|
|
106
|
+
| `EXPO_PUBLIC_GOOGLE_MAPS_API_KEY` | Google Maps API key (for web maps) |
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alert.web.d.ts","sourceRoot":"","sources":["../../src/polyfills/alert.web.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,QAAQ,MAAM,kCAAkC,CAAC;AAExD,eAAe,QAAQ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alert.web.js","sourceRoot":"","sources":["../../src/polyfills/alert.web.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,QAAQ,MAAM,kCAAkC,CAAC;AAExD,eAAe,QAAQ,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web polyfill for expo-haptics using the Vibration API
|
|
3
|
+
*
|
|
4
|
+
* This provides haptic-like feedback on web browsers that support vibration.
|
|
5
|
+
*/
|
|
6
|
+
export declare enum NotificationFeedbackType {
|
|
7
|
+
Success = "success",
|
|
8
|
+
Warning = "warning",
|
|
9
|
+
Error = "error"
|
|
10
|
+
}
|
|
11
|
+
export declare enum ImpactFeedbackStyle {
|
|
12
|
+
Light = "light",
|
|
13
|
+
Medium = "medium",
|
|
14
|
+
Heavy = "heavy",
|
|
15
|
+
Soft = "soft",
|
|
16
|
+
Rigid = "rigid"
|
|
17
|
+
}
|
|
18
|
+
export declare function selectionAsync(): Promise<void>;
|
|
19
|
+
export declare function notificationAsync(type?: NotificationFeedbackType): Promise<void>;
|
|
20
|
+
export declare function impactAsync(style?: ImpactFeedbackStyle): Promise<void>;
|
|
21
|
+
//# sourceMappingURL=haptics.web.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"haptics.web.d.ts","sourceRoot":"","sources":["../../src/polyfills/haptics.web.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,oBAAY,wBAAwB;IAClC,OAAO,YAAY;IACnB,OAAO,YAAY;IACnB,KAAK,UAAU;CAChB;AAED,oBAAY,mBAAmB;IAC7B,KAAK,UAAU;IACf,MAAM,WAAW;IACjB,KAAK,UAAU;IACf,IAAI,SAAS;IACb,KAAK,UAAU;CAChB;AAsBD,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAKpD;AAED,wBAAsB,iBAAiB,CACrC,IAAI,GAAE,wBAA2D,GAChE,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,wBAAsB,WAAW,CAC/B,KAAK,GAAE,mBAAgD,GACtD,OAAO,CAAC,IAAI,CAAC,CAKf"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web polyfill for expo-haptics using the Vibration API
|
|
3
|
+
*
|
|
4
|
+
* This provides haptic-like feedback on web browsers that support vibration.
|
|
5
|
+
*/
|
|
6
|
+
export var NotificationFeedbackType;
|
|
7
|
+
(function (NotificationFeedbackType) {
|
|
8
|
+
NotificationFeedbackType["Success"] = "success";
|
|
9
|
+
NotificationFeedbackType["Warning"] = "warning";
|
|
10
|
+
NotificationFeedbackType["Error"] = "error";
|
|
11
|
+
})(NotificationFeedbackType || (NotificationFeedbackType = {}));
|
|
12
|
+
export var ImpactFeedbackStyle;
|
|
13
|
+
(function (ImpactFeedbackStyle) {
|
|
14
|
+
ImpactFeedbackStyle["Light"] = "light";
|
|
15
|
+
ImpactFeedbackStyle["Medium"] = "medium";
|
|
16
|
+
ImpactFeedbackStyle["Heavy"] = "heavy";
|
|
17
|
+
ImpactFeedbackStyle["Soft"] = "soft";
|
|
18
|
+
ImpactFeedbackStyle["Rigid"] = "rigid";
|
|
19
|
+
})(ImpactFeedbackStyle || (ImpactFeedbackStyle = {}));
|
|
20
|
+
const vibrationPatterns = {
|
|
21
|
+
[NotificationFeedbackType.Success]: [40, 100, 40],
|
|
22
|
+
[NotificationFeedbackType.Warning]: [50, 100, 50],
|
|
23
|
+
[NotificationFeedbackType.Error]: [60, 100, 60, 100, 60],
|
|
24
|
+
[ImpactFeedbackStyle.Light]: [40],
|
|
25
|
+
[ImpactFeedbackStyle.Medium]: [50],
|
|
26
|
+
[ImpactFeedbackStyle.Heavy]: [60],
|
|
27
|
+
[ImpactFeedbackStyle.Soft]: [35],
|
|
28
|
+
[ImpactFeedbackStyle.Rigid]: [45],
|
|
29
|
+
selection: [50],
|
|
30
|
+
};
|
|
31
|
+
function isVibrationAvailable() {
|
|
32
|
+
return (typeof window !== "undefined" &&
|
|
33
|
+
"navigator" in window &&
|
|
34
|
+
"vibrate" in navigator);
|
|
35
|
+
}
|
|
36
|
+
export async function selectionAsync() {
|
|
37
|
+
if (!isVibrationAvailable()) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
navigator.vibrate(vibrationPatterns.selection);
|
|
41
|
+
}
|
|
42
|
+
export async function notificationAsync(type = NotificationFeedbackType.Success) {
|
|
43
|
+
if (!isVibrationAvailable()) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
navigator.vibrate(vibrationPatterns[type]);
|
|
47
|
+
}
|
|
48
|
+
export async function impactAsync(style = ImpactFeedbackStyle.Medium) {
|
|
49
|
+
if (!isVibrationAvailable()) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
navigator.vibrate(vibrationPatterns[style]);
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=haptics.web.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"haptics.web.js","sourceRoot":"","sources":["../../src/polyfills/haptics.web.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAN,IAAY,wBAIX;AAJD,WAAY,wBAAwB;IAClC,+CAAmB,CAAA;IACnB,+CAAmB,CAAA;IACnB,2CAAe,CAAA;AACjB,CAAC,EAJW,wBAAwB,KAAxB,wBAAwB,QAInC;AAED,MAAM,CAAN,IAAY,mBAMX;AAND,WAAY,mBAAmB;IAC7B,sCAAe,CAAA;IACf,wCAAiB,CAAA;IACjB,sCAAe,CAAA;IACf,oCAAa,CAAA;IACb,sCAAe,CAAA;AACjB,CAAC,EANW,mBAAmB,KAAnB,mBAAmB,QAM9B;AAED,MAAM,iBAAiB,GAAsC;IAC3D,CAAC,wBAAwB,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC;IACjD,CAAC,wBAAwB,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC;IACjD,CAAC,wBAAwB,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC;IACxD,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;IACjC,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;IAClC,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;IACjC,CAAC,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IAChC,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;IACjC,SAAS,EAAE,CAAC,EAAE,CAAC;CAChB,CAAC;AAEF,SAAS,oBAAoB;IAC3B,OAAO,CACL,OAAO,MAAM,KAAK,WAAW;QAC7B,WAAW,IAAI,MAAM;QACrB,SAAS,IAAI,SAAS,CACvB,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,IAAI,CAAC,oBAAoB,EAAE,EAAE,CAAC;QAC5B,OAAO;IACT,CAAC;IACD,SAAS,CAAC,OAAO,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAiC,wBAAwB,CAAC,OAAO;IAEjE,IAAI,CAAC,oBAAoB,EAAE,EAAE,CAAC;QAC5B,OAAO;IACT,CAAC;IACD,SAAS,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,QAA6B,mBAAmB,CAAC,MAAM;IAEvD,IAAI,CAAC,oBAAoB,EAAE,EAAE,CAAC;QAC5B,OAAO;IACT,CAAC;IACD,SAAS,CAAC,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web polyfill for react-native-maps using Google Maps
|
|
3
|
+
*/
|
|
4
|
+
import React from "react";
|
|
5
|
+
export declare const PROVIDER_GOOGLE = "google";
|
|
6
|
+
export declare const PROVIDER_DEFAULT: undefined;
|
|
7
|
+
declare const MapView: React.ForwardRefExoticComponent<Omit<any, "ref"> & React.RefAttributes<unknown>>;
|
|
8
|
+
export declare const Marker: any, Callout: any, Polyline: any, Polygon: any, Circle: any, Overlay: any, Heatmap: any, UrlTile: any, WMSTile: any, LocalTile: any;
|
|
9
|
+
export default MapView;
|
|
10
|
+
//# sourceMappingURL=maps.web.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"maps.web.d.ts","sourceRoot":"","sources":["../../src/polyfills/maps.web.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,eAAO,MAAM,eAAe,WAAW,CAAC;AACxC,eAAO,MAAM,gBAAgB,WAAY,CAAC;AAK1C,QAAA,MAAM,OAAO,kFAOX,CAAC;AAUH,eAAO,MACL,MAAM,OACN,OAAO,OACP,QAAQ,OACR,OAAO,OACP,MAAM,OACN,OAAO,OACP,OAAO,OACP,OAAO,OACP,OAAO,OACP,SAAS,KACA,CAAC;AAEZ,eAAe,OAAO,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web polyfill for react-native-maps using Google Maps
|
|
3
|
+
*/
|
|
4
|
+
// @ts-nocheck
|
|
5
|
+
import WebMapView, * as WebMaps from "@teovilla/react-native-web-maps";
|
|
6
|
+
import React from "react";
|
|
7
|
+
export const PROVIDER_GOOGLE = "google";
|
|
8
|
+
export const PROVIDER_DEFAULT = undefined;
|
|
9
|
+
const GOOGLE_MAPS_API_KEY = process.env
|
|
10
|
+
.EXPO_PUBLIC_GOOGLE_MAPS_API_KEY;
|
|
11
|
+
const MapView = React.forwardRef((props, ref) => {
|
|
12
|
+
return React.createElement(WebMapView, {
|
|
13
|
+
ref,
|
|
14
|
+
googleMapsApiKey: GOOGLE_MAPS_API_KEY,
|
|
15
|
+
...props,
|
|
16
|
+
provider: PROVIDER_GOOGLE,
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
MapView.displayName = "MapView";
|
|
20
|
+
Object.assign(MapView, {
|
|
21
|
+
...WebMaps,
|
|
22
|
+
PROVIDER_GOOGLE,
|
|
23
|
+
PROVIDER_DEFAULT,
|
|
24
|
+
});
|
|
25
|
+
export const { Marker, Callout, Polyline, Polygon, Circle, Overlay, Heatmap, UrlTile, WMSTile, LocalTile, } = WebMaps;
|
|
26
|
+
export default MapView;
|
|
27
|
+
//# sourceMappingURL=maps.web.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"maps.web.js","sourceRoot":"","sources":["../../src/polyfills/maps.web.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc;AACd,OAAO,UAAU,EAAE,KAAK,OAAO,MAAM,iCAAiC,CAAC;AACvE,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,CAAC,MAAM,eAAe,GAAG,QAAQ,CAAC;AACxC,MAAM,CAAC,MAAM,gBAAgB,GAAG,SAAS,CAAC;AAE1C,MAAM,mBAAmB,GAAI,OAAO,CAAC,GAAW;KAC7C,+BAAqD,CAAC;AAEzD,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,KAAU,EAAE,GAAG,EAAE,EAAE;IACnD,OAAO,KAAK,CAAC,aAAa,CAAC,UAAU,EAAE;QACrC,GAAG;QACH,gBAAgB,EAAE,mBAAmB;QACrC,GAAG,KAAK;QACR,QAAQ,EAAE,eAAe;KAC1B,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;AAEhC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE;IACrB,GAAG,OAAO;IACV,eAAe;IACf,gBAAgB;CACjB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,EACX,MAAM,EACN,OAAO,EACP,QAAQ,EACR,OAAO,EACP,MAAM,EACN,OAAO,EACP,OAAO,EACP,OAAO,EACP,OAAO,EACP,SAAS,GACV,GAAG,OAAO,CAAC;AAEZ,eAAe,OAAO,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web polyfill for React Native RefreshControl
|
|
3
|
+
*
|
|
4
|
+
* Implements pull-to-refresh functionality using PanResponder on web.
|
|
5
|
+
*/
|
|
6
|
+
import React from "react";
|
|
7
|
+
interface WebRefreshControlProps {
|
|
8
|
+
refreshing: boolean;
|
|
9
|
+
tintColor?: string;
|
|
10
|
+
colors?: string[];
|
|
11
|
+
style?: any;
|
|
12
|
+
progressViewOffset?: number;
|
|
13
|
+
children?: React.ReactNode;
|
|
14
|
+
onRefresh?: () => void;
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export default function WebRefreshControl({ refreshing, tintColor, colors, style, progressViewOffset, children, onRefresh, enabled, }: WebRefreshControlProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=refresh-control-component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh-control-component.d.ts","sourceRoot":"","sources":["../../src/polyfills/refresh-control-component.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAkD,MAAM,OAAO,CAAC;AAUvE,UAAU,sBAAsB;IAC9B,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,EACxC,UAAU,EACV,SAAS,EACT,MAAM,EACN,KAAK,EACL,kBAAkB,EAClB,QAAQ,EACR,SAAS,EACT,OAAO,GACR,EAAE,sBAAsB,2CAoJxB"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Web polyfill for React Native RefreshControl
|
|
4
|
+
*
|
|
5
|
+
* Implements pull-to-refresh functionality using PanResponder on web.
|
|
6
|
+
*/
|
|
7
|
+
// @ts-nocheck
|
|
8
|
+
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
|
9
|
+
// Import from react-native-web directly to avoid infinite loop
|
|
10
|
+
// (metro redirects "react-native" to our polyfill on web)
|
|
11
|
+
import { ActivityIndicator, Animated, PanResponder, View, } from "react-native-web";
|
|
12
|
+
export default function WebRefreshControl({ refreshing, tintColor, colors, style, progressViewOffset, children, onRefresh, enabled, }) {
|
|
13
|
+
const onRefreshRef = useRef(onRefresh);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
onRefreshRef.current = onRefresh;
|
|
16
|
+
}, [onRefresh]);
|
|
17
|
+
const enabledRef = useRef(enabled);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
enabledRef.current = enabled;
|
|
20
|
+
}, [enabled]);
|
|
21
|
+
const containerRef = useRef(null);
|
|
22
|
+
const pullPosReachedState = useRef(0);
|
|
23
|
+
const pullDownSwipeMargin = useRef(new Animated.Value(0));
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
Animated.timing(pullDownSwipeMargin.current, {
|
|
26
|
+
toValue: refreshing ? 50 : 0,
|
|
27
|
+
duration: 350,
|
|
28
|
+
useNativeDriver: false,
|
|
29
|
+
}).start();
|
|
30
|
+
if (refreshing) {
|
|
31
|
+
pullPosReachedState.current = 0;
|
|
32
|
+
}
|
|
33
|
+
}, [refreshing]);
|
|
34
|
+
const onPanResponderFinish = useCallback(() => {
|
|
35
|
+
if (pullPosReachedState.current && onRefreshRef.current) {
|
|
36
|
+
onRefreshRef.current();
|
|
37
|
+
}
|
|
38
|
+
if (!pullPosReachedState.current) {
|
|
39
|
+
Animated.timing(pullDownSwipeMargin.current, {
|
|
40
|
+
toValue: 0,
|
|
41
|
+
duration: 350,
|
|
42
|
+
useNativeDriver: false,
|
|
43
|
+
}).start();
|
|
44
|
+
}
|
|
45
|
+
}, []);
|
|
46
|
+
const panResponder = useRef(PanResponder.create({
|
|
47
|
+
onStartShouldSetPanResponder: () => false,
|
|
48
|
+
onStartShouldSetPanResponderCapture: () => false,
|
|
49
|
+
onMoveShouldSetPanResponder: (_, gestureState) => {
|
|
50
|
+
if (!containerRef.current)
|
|
51
|
+
return false;
|
|
52
|
+
// React 19 fix: use direct DOM traversal instead of findNodeHandle
|
|
53
|
+
const scrollContainer = containerRef.current?.firstChild;
|
|
54
|
+
if (!scrollContainer)
|
|
55
|
+
return false;
|
|
56
|
+
return (scrollContainer.scrollTop === 0 &&
|
|
57
|
+
Math.abs(gestureState.dy) > Math.abs(gestureState.dx) * 2 &&
|
|
58
|
+
Math.abs(gestureState.vy) > Math.abs(gestureState.vx) * 2.5);
|
|
59
|
+
},
|
|
60
|
+
onMoveShouldSetPanResponderCapture: () => false,
|
|
61
|
+
onPanResponderMove: (_, gestureState) => {
|
|
62
|
+
if (enabledRef.current !== undefined && !enabledRef.current)
|
|
63
|
+
return;
|
|
64
|
+
const adjustedDy = gestureState.dy <= 0
|
|
65
|
+
? 0
|
|
66
|
+
: (gestureState.dy * 150) / (gestureState.dy + 120); // Diminishing returns function
|
|
67
|
+
pullDownSwipeMargin.current.setValue(adjustedDy);
|
|
68
|
+
const newValue = adjustedDy > 45 ? 1 : 0;
|
|
69
|
+
if (newValue !== pullPosReachedState.current) {
|
|
70
|
+
pullPosReachedState.current = newValue;
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
onPanResponderTerminationRequest: () => true,
|
|
74
|
+
onPanResponderRelease: onPanResponderFinish,
|
|
75
|
+
onPanResponderTerminate: onPanResponderFinish,
|
|
76
|
+
}));
|
|
77
|
+
const refreshIndicatorColor = useMemo(() => (tintColor ? tintColor : colors && colors.length ? colors[0] : null), [colors, tintColor]);
|
|
78
|
+
const containerStyle = useMemo(() => [
|
|
79
|
+
style,
|
|
80
|
+
// @ts-ignore - overflowY is valid on web
|
|
81
|
+
{ overflowY: "hidden", overflow: "hidden", paddingTop: progressViewOffset },
|
|
82
|
+
], [progressViewOffset, style]);
|
|
83
|
+
const indicatorTransformStyle = useMemo(() => ({
|
|
84
|
+
alignSelf: "center",
|
|
85
|
+
justifyContent: "center",
|
|
86
|
+
alignItems: "center",
|
|
87
|
+
marginTop: -40,
|
|
88
|
+
height: 40,
|
|
89
|
+
transform: [{ translateY: pullDownSwipeMargin.current }],
|
|
90
|
+
}), []);
|
|
91
|
+
// This is messing with react-native-web's internal implementation
|
|
92
|
+
// Will probably break if anything changes on their end
|
|
93
|
+
const AnimatedContentContainer = useMemo(() => {
|
|
94
|
+
const childElement = children;
|
|
95
|
+
if (!childElement?.props?.children?.type)
|
|
96
|
+
return null;
|
|
97
|
+
return withAnimated((childProps) => React.createElement(childElement.props.children.type, childProps));
|
|
98
|
+
}, [children]);
|
|
99
|
+
const childElement = children;
|
|
100
|
+
const newContentContainerStyle = useMemo(() => [
|
|
101
|
+
childElement?.props?.children?.props?.style,
|
|
102
|
+
{ transform: [{ translateY: pullDownSwipeMargin.current }] },
|
|
103
|
+
], [childElement?.props?.children?.props?.style]);
|
|
104
|
+
if (!childElement || !AnimatedContentContainer) {
|
|
105
|
+
return _jsx(_Fragment, { children: children });
|
|
106
|
+
}
|
|
107
|
+
const newChildren = React.cloneElement(childElement, {}, _jsxs(_Fragment, { children: [_jsx(Animated.View, { style: indicatorTransformStyle, children: _jsx(ActivityIndicator, { color: refreshIndicatorColor || "#999999", size: "small" }) }), _jsx(AnimatedContentContainer, { ...childElement.props.children.props, style: newContentContainerStyle })] }));
|
|
108
|
+
return (_jsx(View, { ref: containerRef, style: containerStyle, ...panResponder.current.panHandlers, children: newChildren }));
|
|
109
|
+
}
|
|
110
|
+
function withAnimated(WrappedComponent) {
|
|
111
|
+
const displayName = WrappedComponent.displayName || WrappedComponent.name || "Component";
|
|
112
|
+
class WithAnimated extends React.Component {
|
|
113
|
+
render() {
|
|
114
|
+
return _jsx(WrappedComponent, { ...this.props });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
WithAnimated.displayName = `WithAnimated(${displayName})`;
|
|
118
|
+
return Animated.createAnimatedComponent(WithAnimated);
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=refresh-control-component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh-control-component.js","sourceRoot":"","sources":["../../src/polyfills/refresh-control-component.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web polyfill for expo-secure-store using localStorage
|
|
3
|
+
*
|
|
4
|
+
* Note: localStorage is not truly "secure" on web, but provides similar API.
|
|
5
|
+
*/
|
|
6
|
+
export type KeychainAccessibilityConstant = number;
|
|
7
|
+
export declare const AFTER_FIRST_UNLOCK: number, AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: number, ALWAYS: number, WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: number, ALWAYS_THIS_DEVICE_ONLY: number, WHEN_UNLOCKED: number, WHEN_UNLOCKED_THIS_DEVICE_ONLY: number;
|
|
8
|
+
export type SecureStoreOptions = {
|
|
9
|
+
keychainService?: string;
|
|
10
|
+
requireAuthentication?: boolean;
|
|
11
|
+
authenticationPrompt?: string;
|
|
12
|
+
keychainAccessible?: KeychainAccessibilityConstant;
|
|
13
|
+
};
|
|
14
|
+
export declare function isAvailableAsync(): Promise<boolean>;
|
|
15
|
+
export declare function deleteItemAsync(key: string, _options?: SecureStoreOptions): Promise<void>;
|
|
16
|
+
export declare function getItemAsync(key: string, _options?: SecureStoreOptions): Promise<string | null>;
|
|
17
|
+
export declare function setItemAsync(key: string, value: string, _options?: SecureStoreOptions): Promise<void>;
|
|
18
|
+
export declare function setItem(key: string, value: string, _options?: SecureStoreOptions): void;
|
|
19
|
+
export declare function getItem(key: string, _options?: SecureStoreOptions): string | null;
|
|
20
|
+
export declare function canUseBiometricAuthentication(): boolean;
|
|
21
|
+
//# sourceMappingURL=secure-store.web.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secure-store.web.d.ts","sourceRoot":"","sources":["../../src/polyfills/secure-store.web.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH,MAAM,MAAM,6BAA6B,GAAG,MAAM,CAAC;AAEnD,eAAO,MACL,kBAAkB,UAClB,mCAAmC,UACnC,MAAM,UACN,kCAAkC,UAClC,uBAAuB,UACvB,aAAa,UACb,8BAA8B,QACV,CAAC;AAEvB,MAAM,MAAM,kBAAkB,GAAG;IAC/B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,6BAA6B,CAAC;CACpD,CAAC;AAkBF,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,CAYzD;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,kBAAuB,GAChC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,kBAAuB,GAChC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAExB;AAED,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,QAAQ,GAAE,kBAAuB,GAChC,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAgB,OAAO,CACrB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,QAAQ,GAAE,kBAAuB,GAChC,IAAI,CAON;AAED,wBAAgB,OAAO,CACrB,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,kBAAuB,GAChC,MAAM,GAAG,IAAI,CAEf;AAED,wBAAgB,6BAA6B,IAAI,OAAO,CAEvD"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web polyfill for expo-secure-store using localStorage
|
|
3
|
+
*
|
|
4
|
+
* Note: localStorage is not truly "secure" on web, but provides similar API.
|
|
5
|
+
*/
|
|
6
|
+
const VALUE_BYTES_LIMIT = 2048;
|
|
7
|
+
const KEYCHAIN_CONSTANTS = {
|
|
8
|
+
AFTER_FIRST_UNLOCK: 0,
|
|
9
|
+
AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: 1,
|
|
10
|
+
ALWAYS: 2,
|
|
11
|
+
WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: 3,
|
|
12
|
+
ALWAYS_THIS_DEVICE_ONLY: 4,
|
|
13
|
+
WHEN_UNLOCKED: 5,
|
|
14
|
+
WHEN_UNLOCKED_THIS_DEVICE_ONLY: 6,
|
|
15
|
+
};
|
|
16
|
+
export const { AFTER_FIRST_UNLOCK, AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY, ALWAYS, WHEN_PASSCODE_SET_THIS_DEVICE_ONLY, ALWAYS_THIS_DEVICE_ONLY, WHEN_UNLOCKED, WHEN_UNLOCKED_THIS_DEVICE_ONLY, } = KEYCHAIN_CONSTANTS;
|
|
17
|
+
function isValidValue(value) {
|
|
18
|
+
if (typeof value !== "string") {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (new Blob([value]).size > VALUE_BYTES_LIMIT) {
|
|
22
|
+
console.warn(`Value being stored in SecureStore is larger than ${VALUE_BYTES_LIMIT} bytes and it may not be stored successfully.`);
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
function getStorageKey(key) {
|
|
27
|
+
return `_vibecode_secure_store_${key}`;
|
|
28
|
+
}
|
|
29
|
+
export async function isAvailableAsync() {
|
|
30
|
+
const testKey = "__SECURE_STORE_AVAILABILITY_TEST_KEY__";
|
|
31
|
+
try {
|
|
32
|
+
localStorage.setItem(testKey, "test");
|
|
33
|
+
if (localStorage.getItem(testKey) !== "test") {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
localStorage.removeItem(testKey);
|
|
37
|
+
return localStorage.getItem(testKey) === null;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function deleteItemAsync(key, _options = {}) {
|
|
44
|
+
localStorage.removeItem(getStorageKey(key));
|
|
45
|
+
}
|
|
46
|
+
export async function getItemAsync(key, _options = {}) {
|
|
47
|
+
return localStorage.getItem(getStorageKey(key));
|
|
48
|
+
}
|
|
49
|
+
export async function setItemAsync(key, value, _options = {}) {
|
|
50
|
+
if (!isValidValue(value)) {
|
|
51
|
+
throw new Error("Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.");
|
|
52
|
+
}
|
|
53
|
+
localStorage.setItem(getStorageKey(key), value);
|
|
54
|
+
}
|
|
55
|
+
export function setItem(key, value, _options = {}) {
|
|
56
|
+
if (!isValidValue(value)) {
|
|
57
|
+
throw new Error("Invalid value provided to SecureStore. Values must be strings; consider JSON-encoding your values if they are serializable.");
|
|
58
|
+
}
|
|
59
|
+
localStorage.setItem(getStorageKey(key), value);
|
|
60
|
+
}
|
|
61
|
+
export function getItem(key, _options = {}) {
|
|
62
|
+
return localStorage.getItem(getStorageKey(key));
|
|
63
|
+
}
|
|
64
|
+
export function canUseBiometricAuthentication() {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=secure-store.web.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secure-store.web.js","sourceRoot":"","sources":["../../src/polyfills/secure-store.web.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,iBAAiB,GAAG,IAAI,CAAC;AAE/B,MAAM,kBAAkB,GAAG;IACzB,kBAAkB,EAAE,CAAC;IACrB,mCAAmC,EAAE,CAAC;IACtC,MAAM,EAAE,CAAC;IACT,kCAAkC,EAAE,CAAC;IACrC,uBAAuB,EAAE,CAAC;IAC1B,aAAa,EAAE,CAAC;IAChB,8BAA8B,EAAE,CAAC;CAClC,CAAC;AAIF,MAAM,CAAC,MAAM,EACX,kBAAkB,EAClB,mCAAmC,EACnC,MAAM,EACN,kCAAkC,EAClC,uBAAuB,EACvB,aAAa,EACb,8BAA8B,GAC/B,GAAG,kBAAkB,CAAC;AASvB,SAAS,YAAY,CAAC,KAAa;IACjC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,iBAAiB,EAAE,CAAC;QAC/C,OAAO,CAAC,IAAI,CACV,oDAAoD,iBAAiB,+CAA+C,CACrH,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,aAAa,CAAC,GAAW;IAChC,OAAO,0BAA0B,GAAG,EAAE,CAAC;AACzC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,OAAO,GAAG,wCAAwC,CAAC;IACzD,IAAI,CAAC;QACH,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACtC,IAAI,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,MAAM,EAAE,CAAC;YAC7C,OAAO,KAAK,CAAC;QACf,CAAC;QACD,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QACjC,OAAO,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAW,EACX,WAA+B,EAAE;IAEjC,YAAY,CAAC,UAAU,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,GAAW,EACX,WAA+B,EAAE;IAEjC,OAAO,YAAY,CAAC,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,GAAW,EACX,KAAa,EACb,WAA+B,EAAE;IAEjC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,6HAA6H,CAC9H,CAAC;IACJ,CAAC;IACD,YAAY,CAAC,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,OAAO,CACrB,GAAW,EACX,KAAa,EACb,WAA+B,EAAE;IAEjC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,6HAA6H,CAC9H,CAAC;IACJ,CAAC;IACD,YAAY,CAAC,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,OAAO,CACrB,GAAW,EACX,WAA+B,EAAE;IAEjC,OAAO,YAAY,CAAC,OAAO,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,6BAA6B;IAC3C,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/metro/index.cjs
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
const path = require("node:path");
|
|
2
|
+
const os = require("node:os");
|
|
3
|
+
const { FileStore } = require("metro-cache");
|
|
4
|
+
const { HttpStore } = require("./metro-http-store.cjs");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Metro configuration helper for Vibecode
|
|
8
|
+
* Provides web polyfills and HTTP cache store for remote caching.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const distPath = path.resolve(__dirname, "../dist/");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create cache stores array for Metro config.
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} options
|
|
17
|
+
* @param {string} [options.cacheDir] - Local cache directory (default: ~/.metro-cache)
|
|
18
|
+
* @param {string} [options.httpEndpoint] - HTTP/2 cache server endpoint (optional)
|
|
19
|
+
* @param {number} [options.httpTimeout=30000] - HTTP cache timeout in ms
|
|
20
|
+
* @param {number} [options.httpMaxConnections=64] - Max HTTP/2 connections
|
|
21
|
+
* @param {number} [options.httpMaxConcurrent=256] - Max concurrent HTTP requests
|
|
22
|
+
* @returns {Array} Array of cache store instances
|
|
23
|
+
*/
|
|
24
|
+
function createCacheStores(options = {}) {
|
|
25
|
+
const cacheDir =
|
|
26
|
+
options.cacheDir ||
|
|
27
|
+
process.env.METRO_CACHE_DIR ||
|
|
28
|
+
path.join(os.homedir(), ".metro-cache");
|
|
29
|
+
|
|
30
|
+
const httpEndpoint = options.httpEndpoint || process.env.METRO_CACHE_HTTP_ENDPOINT;
|
|
31
|
+
|
|
32
|
+
const stores = [new FileStore({ root: cacheDir })];
|
|
33
|
+
|
|
34
|
+
if (httpEndpoint) {
|
|
35
|
+
stores.push(
|
|
36
|
+
new HttpStore({
|
|
37
|
+
endpoint: httpEndpoint,
|
|
38
|
+
timeout: options.httpTimeout ?? 30000,
|
|
39
|
+
maxConnections: options.httpMaxConnections ?? 64,
|
|
40
|
+
maxConcurrent: options.httpMaxConcurrent ?? 256,
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return stores;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Wrap a Metro config with Vibecode enhancements.
|
|
50
|
+
*
|
|
51
|
+
* Features:
|
|
52
|
+
* - Web polyfills for expo-haptics, expo-secure-store, react-native-maps, etc.
|
|
53
|
+
* - Optional HTTP/2 remote cache store
|
|
54
|
+
* - Configurable cache settings
|
|
55
|
+
*
|
|
56
|
+
* @param {Object} config - Base Metro config
|
|
57
|
+
* @param {Object} [options]
|
|
58
|
+
* @param {boolean} [options.enableCache=true] - Enable cache configuration
|
|
59
|
+
* @param {string} [options.cacheDir] - Local cache directory
|
|
60
|
+
* @param {string} [options.cacheVersion] - Cache version string
|
|
61
|
+
* @param {string} [options.httpEndpoint] - HTTP/2 cache server endpoint
|
|
62
|
+
* @param {number} [options.httpTimeout] - HTTP cache timeout
|
|
63
|
+
* @param {number} [options.httpMaxConnections] - Max HTTP/2 connections
|
|
64
|
+
* @param {number} [options.httpMaxConcurrent] - Max concurrent requests
|
|
65
|
+
* @returns {Object} Enhanced Metro config
|
|
66
|
+
*/
|
|
67
|
+
function withVibecodeMetro(config, options = {}) {
|
|
68
|
+
const originalResolveRequest = config.resolver?.resolveRequest;
|
|
69
|
+
const enableCache = options.enableCache !== false;
|
|
70
|
+
|
|
71
|
+
const result = {
|
|
72
|
+
...config,
|
|
73
|
+
resolver: {
|
|
74
|
+
...config.resolver,
|
|
75
|
+
extraNodeModules: {
|
|
76
|
+
assert: require.resolve("assert"),
|
|
77
|
+
...config.resolver?.extraNodeModules,
|
|
78
|
+
},
|
|
79
|
+
resolveRequest: (context, moduleName, platform) => {
|
|
80
|
+
// Redirect expo-haptics to our web polyfill on web platform
|
|
81
|
+
if (moduleName === "expo-haptics" && platform === "web") {
|
|
82
|
+
return {
|
|
83
|
+
filePath: path.resolve(distPath, "polyfills/haptics.web.js"),
|
|
84
|
+
type: "sourceFile",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (moduleName === "expo-secure-store" && platform === "web") {
|
|
89
|
+
return {
|
|
90
|
+
filePath: path.resolve(distPath, "polyfills/secure-store.web.js"),
|
|
91
|
+
type: "sourceFile",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (moduleName === "react-native-maps" && platform === "web") {
|
|
96
|
+
return {
|
|
97
|
+
filePath: path.resolve(distPath, "polyfills/maps.web.js"),
|
|
98
|
+
type: "sourceFile",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (
|
|
103
|
+
moduleName === "react-native-web/dist/exports/RefreshControl" &&
|
|
104
|
+
platform === "web"
|
|
105
|
+
) {
|
|
106
|
+
return {
|
|
107
|
+
filePath: path.resolve(
|
|
108
|
+
distPath,
|
|
109
|
+
"polyfills/refresh-control-component.js"
|
|
110
|
+
),
|
|
111
|
+
type: "sourceFile",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (
|
|
116
|
+
moduleName === "react-native-web/dist/exports/Alert" &&
|
|
117
|
+
platform === "web"
|
|
118
|
+
) {
|
|
119
|
+
return {
|
|
120
|
+
filePath: path.resolve(distPath, "polyfills/alert.web.js"),
|
|
121
|
+
type: "sourceFile",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (originalResolveRequest) {
|
|
126
|
+
return originalResolveRequest(context, moduleName, platform);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return context.resolveRequest(context, moduleName, platform);
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Configure caching if enabled
|
|
135
|
+
if (enableCache) {
|
|
136
|
+
result.cacheStores = createCacheStores({
|
|
137
|
+
cacheDir: options.cacheDir,
|
|
138
|
+
httpEndpoint: options.httpEndpoint,
|
|
139
|
+
httpTimeout: options.httpTimeout,
|
|
140
|
+
httpMaxConnections: options.httpMaxConnections,
|
|
141
|
+
httpMaxConcurrent: options.httpMaxConcurrent,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
result.cacheVersion =
|
|
145
|
+
options.cacheVersion || process.env.METRO_CACHE_VERSION || "1";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = { withVibecodeMetro };
|
package/metro/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { MetroConfig } from "metro-config";
|
|
2
|
+
|
|
3
|
+
export interface VibecodeMetroOptions {
|
|
4
|
+
/** Enable cache configuration (default: true) */
|
|
5
|
+
enableCache?: boolean;
|
|
6
|
+
/** Local cache directory (default: ~/.metro-cache) */
|
|
7
|
+
cacheDir?: string;
|
|
8
|
+
/** Cache version string (default: "1") */
|
|
9
|
+
cacheVersion?: string;
|
|
10
|
+
/** HTTP/2 cache server endpoint */
|
|
11
|
+
httpEndpoint?: string;
|
|
12
|
+
/** HTTP request timeout in ms (default: 30000) */
|
|
13
|
+
httpTimeout?: number;
|
|
14
|
+
/** HTTP/2 connection pool size (default: 64) */
|
|
15
|
+
httpMaxConnections?: number;
|
|
16
|
+
/** Max concurrent HTTP requests (default: 256) */
|
|
17
|
+
httpMaxConcurrent?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Wrap a Metro config with Vibecode enhancements.
|
|
22
|
+
*/
|
|
23
|
+
export function withVibecodeMetro(
|
|
24
|
+
config: MetroConfig,
|
|
25
|
+
options?: VibecodeMetroOptions
|
|
26
|
+
): MetroConfig;
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP/2 Metro Cache Store
|
|
3
|
+
*
|
|
4
|
+
* A Metro cache store implementation using HTTP/2 with:
|
|
5
|
+
* - Connection pool (default 8 connections) for higher throughput
|
|
6
|
+
* - Multiplexed requests over each connection
|
|
7
|
+
* - Concurrency limiting (configurable max concurrent requests)
|
|
8
|
+
* - Non-blocking writes (fire-and-forget)
|
|
9
|
+
* - Blocking reads
|
|
10
|
+
*
|
|
11
|
+
* Requires an HTTP/2 server - will fail on HTTP/1.1.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const http2 = require("node:http2");
|
|
15
|
+
const zlib = require("node:zlib");
|
|
16
|
+
const { Buffer } = require("node:buffer");
|
|
17
|
+
const pLimit = require("p-limit");
|
|
18
|
+
|
|
19
|
+
const NULL_BYTE = 0x00;
|
|
20
|
+
const NULL_BYTE_BUFFER = Buffer.from([NULL_BYTE]);
|
|
21
|
+
|
|
22
|
+
const ZLIB_OPTIONS = {
|
|
23
|
+
level: 9,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
class HttpStore {
|
|
27
|
+
#origin;
|
|
28
|
+
#path;
|
|
29
|
+
#timeout;
|
|
30
|
+
#sessions = [];
|
|
31
|
+
#maxConnections;
|
|
32
|
+
#nextSession = 0;
|
|
33
|
+
#limit;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {Object} options
|
|
37
|
+
* @param {string} options.endpoint - Full URL to the cache server (e.g., "https://cache.example.com/metro")
|
|
38
|
+
* @param {number} [options.timeout=10000] - Request timeout in milliseconds
|
|
39
|
+
* @param {number} [options.maxConnections=8] - Number of HTTP/2 connections in pool
|
|
40
|
+
* @param {number} [options.maxConcurrent=64] - Max concurrent requests in flight
|
|
41
|
+
*/
|
|
42
|
+
constructor(options) {
|
|
43
|
+
const url = new URL(options.endpoint);
|
|
44
|
+
this.#origin = url.origin;
|
|
45
|
+
this.#path = url.pathname.replace(/\/$/, ""); // Remove trailing slash
|
|
46
|
+
this.#timeout = options.timeout ?? 10000;
|
|
47
|
+
this.#maxConnections = options.maxConnections ?? 8;
|
|
48
|
+
this.#limit = pLimit(options.maxConcurrent ?? 64);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get an HTTP/2 session from the pool using round-robin.
|
|
53
|
+
* Lazily creates connections on first use.
|
|
54
|
+
* Never throws - returns null if connection fails.
|
|
55
|
+
*/
|
|
56
|
+
#getSession() {
|
|
57
|
+
// Round-robin through connections
|
|
58
|
+
const index = this.#nextSession++ % this.#maxConnections;
|
|
59
|
+
|
|
60
|
+
// Check if we have a valid session at this index
|
|
61
|
+
const existing = this.#sessions[index];
|
|
62
|
+
if (existing && !existing.closed && !existing.destroyed) {
|
|
63
|
+
return existing;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Create new session for this slot
|
|
67
|
+
try {
|
|
68
|
+
const session = http2.connect(this.#origin);
|
|
69
|
+
|
|
70
|
+
session.on("error", (err) => {
|
|
71
|
+
console.warn("[HttpStore] Session error:", err.message);
|
|
72
|
+
if (this.#sessions[index] === session) {
|
|
73
|
+
this.#sessions[index] = null;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
session.on("close", () => {
|
|
78
|
+
if (this.#sessions[index] === session) {
|
|
79
|
+
this.#sessions[index] = null;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
this.#sessions[index] = session;
|
|
84
|
+
return session;
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.warn("[HttpStore] Failed to connect:", err.message);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* GET - Blocking read from cache.
|
|
93
|
+
* Returns null on any error (network, timeout, parse) - never throws.
|
|
94
|
+
* @param {Buffer} key
|
|
95
|
+
* @returns {Promise<any|null>}
|
|
96
|
+
*/
|
|
97
|
+
async get(key) {
|
|
98
|
+
try {
|
|
99
|
+
return await this.#limit(() => this.#doGet(key));
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.warn("[HttpStore] Get failed:", err.message);
|
|
102
|
+
return null; // Fall back to next cache store (FileStore)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#doGet(key) {
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
const session = this.#getSession();
|
|
109
|
+
if (!session) {
|
|
110
|
+
reject(new Error("No session available"));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const path = `${this.#path}/${key.toString("hex")}`;
|
|
115
|
+
const req = session.request({
|
|
116
|
+
":method": "GET",
|
|
117
|
+
":path": path,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const timeoutId = setTimeout(() => {
|
|
121
|
+
req.close(http2.constants.NGHTTP2_CANCEL);
|
|
122
|
+
reject(new Error("Request timed out"));
|
|
123
|
+
}, this.#timeout);
|
|
124
|
+
|
|
125
|
+
const chunks = [];
|
|
126
|
+
|
|
127
|
+
req.on("response", (headers) => {
|
|
128
|
+
const status = headers[":status"];
|
|
129
|
+
|
|
130
|
+
// 404 = cache miss
|
|
131
|
+
if (status === 404) {
|
|
132
|
+
clearTimeout(timeoutId);
|
|
133
|
+
req.close();
|
|
134
|
+
resolve(null);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Non-2xx = error
|
|
139
|
+
if (status < 200 || status >= 300) {
|
|
140
|
+
clearTimeout(timeoutId);
|
|
141
|
+
req.close();
|
|
142
|
+
reject(new Error(`HTTP error: ${status}`));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
req.on("data", (chunk) => {
|
|
148
|
+
chunks.push(chunk);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
req.on("end", () => {
|
|
152
|
+
clearTimeout(timeoutId);
|
|
153
|
+
|
|
154
|
+
const compressed = Buffer.concat(chunks);
|
|
155
|
+
if (compressed.length === 0) {
|
|
156
|
+
resolve(null);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Decompress the response
|
|
161
|
+
zlib.gunzip(compressed, (err, buffer) => {
|
|
162
|
+
if (err) {
|
|
163
|
+
reject(err);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
// If first byte is NULL_BYTE, it's a raw Buffer
|
|
169
|
+
if (buffer.length > 0 && buffer[0] === NULL_BYTE) {
|
|
170
|
+
resolve(buffer.subarray(1));
|
|
171
|
+
} else {
|
|
172
|
+
resolve(JSON.parse(buffer.toString("utf8")));
|
|
173
|
+
}
|
|
174
|
+
} catch (parseErr) {
|
|
175
|
+
reject(parseErr);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
req.on("error", (err) => {
|
|
181
|
+
clearTimeout(timeoutId);
|
|
182
|
+
reject(err);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
req.end();
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* SET - Non-blocking write to cache.
|
|
191
|
+
* Returns immediately, HTTP request happens in background.
|
|
192
|
+
* @param {Buffer} key
|
|
193
|
+
* @param {any} value
|
|
194
|
+
* @returns {Promise<void>}
|
|
195
|
+
*/
|
|
196
|
+
set(key, value) {
|
|
197
|
+
// Fire and forget - queue the work but return immediately
|
|
198
|
+
this.#limit(() => this.#doSet(key, value)).catch((err) => {
|
|
199
|
+
console.warn("[HttpStore] Background write failed:", err.message);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
return Promise.resolve();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
#doSet(key, value) {
|
|
206
|
+
return new Promise((resolve, reject) => {
|
|
207
|
+
const session = this.#getSession();
|
|
208
|
+
if (!session) {
|
|
209
|
+
reject(new Error("No session available"));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const path = `${this.#path}/${key.toString("hex")}`;
|
|
214
|
+
|
|
215
|
+
// Prepare data to compress
|
|
216
|
+
let data;
|
|
217
|
+
if (Buffer.isBuffer(value)) {
|
|
218
|
+
// Prepend NULL_BYTE marker for Buffer values
|
|
219
|
+
data = Buffer.concat([NULL_BYTE_BUFFER, value]);
|
|
220
|
+
} else {
|
|
221
|
+
data = Buffer.from(JSON.stringify(value) ?? "null", "utf8");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Compress the data
|
|
225
|
+
zlib.gzip(data, ZLIB_OPTIONS, (err, compressed) => {
|
|
226
|
+
if (err) {
|
|
227
|
+
reject(err);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const req = session.request({
|
|
232
|
+
":method": "PUT",
|
|
233
|
+
":path": path,
|
|
234
|
+
"content-type": "application/octet-stream",
|
|
235
|
+
"content-length": compressed.length,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const timeoutId = setTimeout(() => {
|
|
239
|
+
req.close(http2.constants.NGHTTP2_CANCEL);
|
|
240
|
+
reject(new Error("Request timed out"));
|
|
241
|
+
}, this.#timeout);
|
|
242
|
+
|
|
243
|
+
req.on("response", (headers) => {
|
|
244
|
+
const status = headers[":status"];
|
|
245
|
+
|
|
246
|
+
clearTimeout(timeoutId);
|
|
247
|
+
|
|
248
|
+
if (status >= 200 && status < 300) {
|
|
249
|
+
resolve();
|
|
250
|
+
} else {
|
|
251
|
+
reject(new Error(`HTTP error: ${status}`));
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
req.on("error", (err) => {
|
|
256
|
+
clearTimeout(timeoutId);
|
|
257
|
+
reject(err);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
req.write(compressed);
|
|
261
|
+
req.end();
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* CLEAR - Not implemented.
|
|
268
|
+
*/
|
|
269
|
+
clear() {
|
|
270
|
+
// No-op - Metro's HTTP cache protocol doesn't define a clear operation
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
module.exports = { HttpStore };
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vibecodeapp/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "SDK for Vibecodeapp.com - Web polyfills and Metro configuration for React Native",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./package.json": "./package.json",
|
|
13
|
+
"./metro": {
|
|
14
|
+
"default": "./metro/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"metro"
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/vibecode/vibecode-sdk.git"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/vibecode/vibecode-sdk#readme",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/vibecode/vibecode-sdk/issues"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc",
|
|
32
|
+
"lint": "biome check .",
|
|
33
|
+
"lint:fix": "biome check --write .",
|
|
34
|
+
"type-check": "tsc --noEmit",
|
|
35
|
+
"clean": "rm -rf dist",
|
|
36
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@blazejkustra/react-native-alert": "^1.0.0",
|
|
40
|
+
"@teovilla/react-native-web-maps": "^0.9.5",
|
|
41
|
+
"@stardazed/streams-text-encoding": "^1.0.2",
|
|
42
|
+
"assert": "^2.1.0",
|
|
43
|
+
"p-limit": "^3.1.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@biomejs/biome": "^2.3.8",
|
|
47
|
+
"@types/bun": "^1.3.4",
|
|
48
|
+
"@types/react": "19.1.11",
|
|
49
|
+
"react": "19.1.0",
|
|
50
|
+
"react-native": "0.81.4",
|
|
51
|
+
"react-native-web": "^0.21.2",
|
|
52
|
+
"typescript": "^5.9.3"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"react": "*",
|
|
56
|
+
"react-native": "*",
|
|
57
|
+
"react-native-web": "*",
|
|
58
|
+
"metro-cache": "*"
|
|
59
|
+
},
|
|
60
|
+
"type": "module"
|
|
61
|
+
}
|