@ateam-ai-mobile/plugin-runtime 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/dist/index.d.mts +1129 -0
- package/dist/index.d.ts +1129 -0
- package/dist/index.js +2388 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2369 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +31 -0
- package/src/api/plugins.ts +257 -0
- package/src/bridge/native.ts +266 -0
- package/src/bridge/types.ts +268 -0
- package/src/bundles/bundleManager.ts +396 -0
- package/src/bundles/storage.ts +68 -0
- package/src/hooks/usePlugin.ts +185 -0
- package/src/hooks/useRemotePlugins.ts +97 -0
- package/src/host.ts +117 -0
- package/src/index.ts +79 -0
- package/src/registry/SurfaceDispatcher.tsx +66 -0
- package/src/registry/store.ts +131 -0
- package/src/registry/types.ts +60 -0
- package/src/sdk/components.ts +370 -0
- package/src/sdk/index.ts +76 -0
- package/src/sdk/primitives.ts +22 -0
- package/src/sdk/registration.ts +52 -0
- package/src/sdk/types.ts +315 -0
- package/src/sdk/useApi.ts +78 -0
- package/src/sdk/usePluginTheme.ts +86 -0
- package/src/surfaces/AdaOrb.tsx +318 -0
- package/src/surfaces/SurfaceCard.tsx +137 -0
- package/src/surfaces/SurfaceDrawer.tsx +216 -0
- package/src/surfaces/SurfaceFullscreen.tsx +207 -0
- package/src/utils/sha256.ts +85 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SurfaceFullscreen — edge-to-edge takeover container for plugins
|
|
3
|
+
* (voice mode, focus modes, photo capture, etc.).
|
|
4
|
+
*
|
|
5
|
+
* Plugins render their content; SurfaceFullscreen provides:
|
|
6
|
+
* - Paper background, fade-in motion
|
|
7
|
+
* - Top status line ("ada" wordmark + status text)
|
|
8
|
+
* - Optional hero element above body (e.g. AdaOrb)
|
|
9
|
+
* - Bottom close button with label
|
|
10
|
+
* - Spring entry on the hero (scale 0.6 → 1)
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* <SurfaceFullscreen
|
|
14
|
+
* visible={open}
|
|
15
|
+
* onClose={hangUp}
|
|
16
|
+
* status="Ada is speaking"
|
|
17
|
+
* hero={<AdaOrb size={180} state="speaking" />}
|
|
18
|
+
* closeIcon="hangup"
|
|
19
|
+
* closeLabel="End"
|
|
20
|
+
* >
|
|
21
|
+
* <Transcript ... />
|
|
22
|
+
* </SurfaceFullscreen>
|
|
23
|
+
*/
|
|
24
|
+
import { useEffect, useRef, type ReactNode } from 'react';
|
|
25
|
+
import {
|
|
26
|
+
View,
|
|
27
|
+
Text,
|
|
28
|
+
Pressable,
|
|
29
|
+
StyleSheet,
|
|
30
|
+
Animated,
|
|
31
|
+
Platform,
|
|
32
|
+
} from 'react-native';
|
|
33
|
+
import Svg, { Path } from 'react-native-svg';
|
|
34
|
+
import { ada } from '@ateam-ai-mobile/core';
|
|
35
|
+
|
|
36
|
+
const fontFam = Platform.OS === 'ios' ? 'SF Pro Rounded' : undefined;
|
|
37
|
+
|
|
38
|
+
export interface SurfaceFullscreenProps {
|
|
39
|
+
visible: boolean;
|
|
40
|
+
onClose: () => void;
|
|
41
|
+
/** Status text shown under the wordmark */
|
|
42
|
+
status?: string;
|
|
43
|
+
/** Optional hero element above body (typically an AdaOrb) */
|
|
44
|
+
hero?: ReactNode;
|
|
45
|
+
/** Body content */
|
|
46
|
+
children?: ReactNode;
|
|
47
|
+
/** Whether to show the small "ada" wordmark (default: true) */
|
|
48
|
+
showWordmark?: boolean;
|
|
49
|
+
/** Built-in close icon flavor or a custom node. Default: 'x' */
|
|
50
|
+
closeIcon?: 'x' | 'hangup' | ReactNode;
|
|
51
|
+
/** Label under the close button (default: "Close") */
|
|
52
|
+
closeLabel?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default function SurfaceFullscreen({
|
|
56
|
+
visible,
|
|
57
|
+
onClose,
|
|
58
|
+
status,
|
|
59
|
+
hero,
|
|
60
|
+
children,
|
|
61
|
+
showWordmark = true,
|
|
62
|
+
closeIcon = 'x',
|
|
63
|
+
closeLabel = 'Close',
|
|
64
|
+
}: SurfaceFullscreenProps) {
|
|
65
|
+
const fadeAnim = useRef(new Animated.Value(0)).current;
|
|
66
|
+
const heroScale = useRef(new Animated.Value(0.6)).current;
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (visible) {
|
|
70
|
+
Animated.parallel([
|
|
71
|
+
Animated.timing(fadeAnim, { toValue: 1, duration: 280, useNativeDriver: true }),
|
|
72
|
+
Animated.spring(heroScale, { toValue: 1, useNativeDriver: true, tension: 60, friction: 11 }),
|
|
73
|
+
]).start();
|
|
74
|
+
} else {
|
|
75
|
+
fadeAnim.setValue(0);
|
|
76
|
+
heroScale.setValue(0.6);
|
|
77
|
+
}
|
|
78
|
+
}, [visible]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
79
|
+
|
|
80
|
+
const close = () => {
|
|
81
|
+
Animated.parallel([
|
|
82
|
+
Animated.timing(fadeAnim, { toValue: 0, duration: 180, useNativeDriver: true }),
|
|
83
|
+
Animated.timing(heroScale, { toValue: 0.6, duration: 180, useNativeDriver: true }),
|
|
84
|
+
]).start(() => onClose());
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
if (!visible) return null;
|
|
88
|
+
|
|
89
|
+
const renderCloseIcon = () => {
|
|
90
|
+
if (closeIcon === 'hangup') {
|
|
91
|
+
return (
|
|
92
|
+
<Svg width={22} height={22} viewBox="0 0 24 24" fill={ada.paper}>
|
|
93
|
+
<Path d="M3.68 16.07l3.92-3.11c.28-.23.68-.28 1.01-.14l2.77 1.15c.36.15.61.5.61.89v2.82c4.34-.76 7.93-4.35 8.69-8.69h-2.82c-.39 0-.74-.25-.89-.61L16.12 5.6c-.13-.33-.09-.73.14-1.01l3.11-3.92c.29-.37.81-.41 1.15-.08l3.24 3.24c.2.2.31.47.31.76C24 14.44 14.44 24 4.59 24c-.29 0-.56-.11-.76-.31L.59 20.45c-.33-.34-.29-.86.09-1.15z" />
|
|
94
|
+
</Svg>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
if (closeIcon === 'x') {
|
|
98
|
+
return (
|
|
99
|
+
<Svg width={20} height={20} viewBox="0 0 24 24" fill="none" stroke={ada.paper} strokeWidth={2.5} strokeLinecap="round">
|
|
100
|
+
<Path d="M18 6L6 18M6 6l12 12" />
|
|
101
|
+
</Svg>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return closeIcon;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<Animated.View style={[styles.overlay, { opacity: fadeAnim }]} pointerEvents="auto">
|
|
109
|
+
{/* Top status */}
|
|
110
|
+
<View style={styles.topBar}>
|
|
111
|
+
{showWordmark ? <Text style={styles.wordmark}>ada</Text> : null}
|
|
112
|
+
{status ? <Text style={styles.status}>{status}</Text> : null}
|
|
113
|
+
</View>
|
|
114
|
+
|
|
115
|
+
{/* Optional hero (e.g. AdaOrb) — springs in */}
|
|
116
|
+
{hero ? (
|
|
117
|
+
<View style={styles.heroWrap}>
|
|
118
|
+
<Animated.View style={{ transform: [{ scale: heroScale }] }}>
|
|
119
|
+
{hero}
|
|
120
|
+
</Animated.View>
|
|
121
|
+
</View>
|
|
122
|
+
) : null}
|
|
123
|
+
|
|
124
|
+
{/* Body — caller content */}
|
|
125
|
+
<View style={styles.body}>{children}</View>
|
|
126
|
+
|
|
127
|
+
{/* Bottom close */}
|
|
128
|
+
<View style={styles.bottomBar}>
|
|
129
|
+
<Pressable
|
|
130
|
+
accessibilityRole="button"
|
|
131
|
+
accessibilityLabel={closeLabel}
|
|
132
|
+
style={styles.closeBtn}
|
|
133
|
+
onPress={close}
|
|
134
|
+
hitSlop={12}
|
|
135
|
+
>
|
|
136
|
+
{renderCloseIcon()}
|
|
137
|
+
</Pressable>
|
|
138
|
+
{closeLabel ? <Text style={styles.closeLabel}>{closeLabel}</Text> : null}
|
|
139
|
+
</View>
|
|
140
|
+
</Animated.View>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const styles = StyleSheet.create({
|
|
145
|
+
overlay: {
|
|
146
|
+
position: 'absolute',
|
|
147
|
+
top: 0, left: 0, right: 0, bottom: 0,
|
|
148
|
+
backgroundColor: ada.paper,
|
|
149
|
+
zIndex: 1000,
|
|
150
|
+
elevation: 1000,
|
|
151
|
+
},
|
|
152
|
+
topBar: {
|
|
153
|
+
paddingTop: Platform.OS === 'ios' ? 64 : 36,
|
|
154
|
+
paddingHorizontal: 24,
|
|
155
|
+
alignItems: 'center',
|
|
156
|
+
gap: 6,
|
|
157
|
+
},
|
|
158
|
+
wordmark: {
|
|
159
|
+
fontSize: 14,
|
|
160
|
+
fontFamily: fontFam,
|
|
161
|
+
fontWeight: '500',
|
|
162
|
+
color: ada.inkMuted,
|
|
163
|
+
letterSpacing: 4,
|
|
164
|
+
},
|
|
165
|
+
status: {
|
|
166
|
+
fontSize: 17,
|
|
167
|
+
fontFamily: fontFam,
|
|
168
|
+
fontWeight: '500',
|
|
169
|
+
color: ada.ink,
|
|
170
|
+
letterSpacing: -0.2,
|
|
171
|
+
},
|
|
172
|
+
heroWrap: {
|
|
173
|
+
paddingTop: 24,
|
|
174
|
+
paddingBottom: 12,
|
|
175
|
+
alignItems: 'center',
|
|
176
|
+
justifyContent: 'center',
|
|
177
|
+
},
|
|
178
|
+
body: {
|
|
179
|
+
flex: 1,
|
|
180
|
+
},
|
|
181
|
+
bottomBar: {
|
|
182
|
+
paddingBottom: Platform.OS === 'ios' ? 48 : 32,
|
|
183
|
+
paddingTop: 12,
|
|
184
|
+
alignItems: 'center',
|
|
185
|
+
gap: 8,
|
|
186
|
+
},
|
|
187
|
+
closeBtn: {
|
|
188
|
+
width: 64,
|
|
189
|
+
height: 64,
|
|
190
|
+
borderRadius: 32,
|
|
191
|
+
backgroundColor: ada.ink,
|
|
192
|
+
justifyContent: 'center',
|
|
193
|
+
alignItems: 'center',
|
|
194
|
+
shadowColor: ada.ink,
|
|
195
|
+
shadowOffset: { width: 0, height: 8 },
|
|
196
|
+
shadowOpacity: 0.18,
|
|
197
|
+
shadowRadius: 14,
|
|
198
|
+
elevation: 8,
|
|
199
|
+
},
|
|
200
|
+
closeLabel: {
|
|
201
|
+
fontSize: 13,
|
|
202
|
+
fontFamily: fontFam,
|
|
203
|
+
fontWeight: '500',
|
|
204
|
+
color: ada.inkMuted,
|
|
205
|
+
letterSpacing: 0.3,
|
|
206
|
+
},
|
|
207
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure-JS SHA-256 — used to verify integrity of downloaded plugin bundles.
|
|
3
|
+
* Avoids pulling expo-crypto / a native module into the dependency graph for
|
|
4
|
+
* what amounts to a one-shot post-download check.
|
|
5
|
+
*
|
|
6
|
+
* Hot path: hash a ~100KB-1MB bundle once after download. Performance is
|
|
7
|
+
* acceptable on every supported device (sub-50ms).
|
|
8
|
+
*
|
|
9
|
+
* Algorithm reference: FIPS 180-4. Implementation derived from the public-
|
|
10
|
+
* domain reference code; see https://en.wikipedia.org/wiki/SHA-2#Pseudocode.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const K = new Uint32Array([
|
|
14
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
15
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
16
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
17
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
18
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
19
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
20
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
21
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const rotr = (n: number, x: number) => (x >>> n) | (x << (32 - n));
|
|
25
|
+
|
|
26
|
+
/** SHA-256 of a UTF-8 string. Returns the lowercase hex digest. */
|
|
27
|
+
export function sha256Hex(message: string): string {
|
|
28
|
+
// 1. Encode to UTF-8 bytes
|
|
29
|
+
const utf8 = unescape(encodeURIComponent(message));
|
|
30
|
+
const bytes = new Uint8Array(utf8.length);
|
|
31
|
+
for (let i = 0; i < utf8.length; i++) bytes[i] = utf8.charCodeAt(i);
|
|
32
|
+
|
|
33
|
+
// 2. Pre-processing: append 0x80, pad with zeros, then append 64-bit big-endian length
|
|
34
|
+
const bitLen = bytes.length * 8;
|
|
35
|
+
const padLen = (56 - (bytes.length + 1) % 64 + 64) % 64;
|
|
36
|
+
const buf = new Uint8Array(bytes.length + 1 + padLen + 8);
|
|
37
|
+
buf.set(bytes);
|
|
38
|
+
buf[bytes.length] = 0x80;
|
|
39
|
+
// 64-bit big-endian length (high 32 bits zeroed — bundles are well under 2^32 bits)
|
|
40
|
+
const len = buf.length;
|
|
41
|
+
buf[len - 4] = (bitLen >>> 24) & 0xff;
|
|
42
|
+
buf[len - 3] = (bitLen >>> 16) & 0xff;
|
|
43
|
+
buf[len - 2] = (bitLen >>> 8) & 0xff;
|
|
44
|
+
buf[len - 1] = bitLen & 0xff;
|
|
45
|
+
|
|
46
|
+
const h = new Uint32Array([
|
|
47
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
|
48
|
+
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
49
|
+
]);
|
|
50
|
+
const w = new Uint32Array(64);
|
|
51
|
+
|
|
52
|
+
// 3. Process each 512-bit chunk
|
|
53
|
+
for (let i = 0; i < buf.length; i += 64) {
|
|
54
|
+
for (let t = 0; t < 16; t++) {
|
|
55
|
+
const j = i + t * 4;
|
|
56
|
+
w[t] = (buf[j] << 24) | (buf[j + 1] << 16) | (buf[j + 2] << 8) | buf[j + 3];
|
|
57
|
+
}
|
|
58
|
+
for (let t = 16; t < 64; t++) {
|
|
59
|
+
const s0 = rotr(7, w[t - 15]) ^ rotr(18, w[t - 15]) ^ (w[t - 15] >>> 3);
|
|
60
|
+
const s1 = rotr(17, w[t - 2]) ^ rotr(19, w[t - 2]) ^ (w[t - 2] >>> 10);
|
|
61
|
+
w[t] = (w[t - 16] + s0 + w[t - 7] + s1) | 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let a = h[0], b = h[1], c = h[2], d = h[3], e = h[4], f = h[5], g = h[6], hh = h[7];
|
|
65
|
+
for (let t = 0; t < 64; t++) {
|
|
66
|
+
const S1 = rotr(6, e) ^ rotr(11, e) ^ rotr(25, e);
|
|
67
|
+
const ch = (e & f) ^ (~e & g);
|
|
68
|
+
const temp1 = (hh + S1 + ch + K[t] + w[t]) | 0;
|
|
69
|
+
const S0 = rotr(2, a) ^ rotr(13, a) ^ rotr(22, a);
|
|
70
|
+
const maj = (a & b) ^ (a & c) ^ (b & c);
|
|
71
|
+
const temp2 = (S0 + maj) | 0;
|
|
72
|
+
hh = g; g = f; f = e; e = (d + temp1) | 0; d = c; c = b; b = a; a = (temp1 + temp2) | 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
h[0] = (h[0] + a) | 0; h[1] = (h[1] + b) | 0; h[2] = (h[2] + c) | 0; h[3] = (h[3] + d) | 0;
|
|
76
|
+
h[4] = (h[4] + e) | 0; h[5] = (h[5] + f) | 0; h[6] = (h[6] + g) | 0; h[7] = (h[7] + hh) | 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 4. Produce hex
|
|
80
|
+
let out = '';
|
|
81
|
+
for (let i = 0; i < 8; i++) {
|
|
82
|
+
out += h[i].toString(16).padStart(8, '0');
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|