Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/bare-expo/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3854,7 +3854,7 @@ SPEC CHECKSUMS:
EXUpdates: de8bb2659b9861c6ee4e39b9c90d15d72601e1f4
EXUpdatesInterface: 48272cb8995e613f0843fe531347e2f783e1df5f
FBLazyVector: 3a7ea85f6009224ad89f7daeda516f189e6b5759
hermes-engine: 46fce5491dbe4962025d06f9bb2f860c231839f8
hermes-engine: 6bb3000824be2770010ae038914fa26721255c8e
libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7
libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
Expand All @@ -3872,7 +3872,7 @@ SPEC CHECKSUMS:
React: 4bc1f928568ad4bcfd147260f907b4ea5873a03b
React-callinvoker: 8dd44c31888a314694efd0ef5f19ab7e0e855ef8
React-Core: 0c1f3042e1204c0512b2e4263062c66550d7e6a3
React-Core-prebuilt: 083f4a23208b7c4789739103c467d04ced7cbdcc
React-Core-prebuilt: 7894b037a2f0fa699a44de6c88d20acd4235b255
React-CoreModules: f6a626221d52f21b5eb8df2d79780b96f20240e5
React-cxxreact: 2e3990595049d43dd1d59ccd6cb35545f0dc6f03
React-debug: 60be0767f5672afc81bfd6a50d996507430f7906
Expand Down Expand Up @@ -3942,7 +3942,7 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: bfb12ead469222b022a2024f32aba47ce50de512
ReactCodegen: b9b0ea5c72e425fa5a89a031ada3e64dfd839771
ReactCommon: 0084791d25c4deae3d8b77efd4440fb2d5c38746
ReactNativeDependencies: fbc8678e9f0e4f553245e4669a3a8d19ba03bb13
ReactNativeDependencies: 17a617edb4d5883a4c48339514ccb8b765f8af4f
RNCAsyncStorage: e85a99325df9eb0191a6ee2b2a842644c7eb29f4
RNCMaskedView: 3c9d7586e2b9bbab573591dcb823918bc4668005
RNCPicker: e0149590451d5eae242cf686014a6f6d808f93c7
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,13 @@ class WorkletsTesterModule : Module() {
Function("scheduleWorklet") { worklet: Worklet ->
worklet.schedule(appContext.uiRuntime)
}

Function("executeWorkletWithArgs") { worklet: Worklet ->
worklet.execute(appContext.uiRuntime, 2026, "worklet", true)
}

Function("scheduleWorkletWithArgs") { worklet: Worklet ->
worklet.schedule(appContext.uiRuntime, 2026, "worklet", true)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ type SerializableRef<TValue = unknown> = {
declare class WorkletsTesterModule extends NativeModule {
executeWorklet(worklet: SerializableRef<() => void>): void;
scheduleWorklet(worklet: SerializableRef<() => void>): void;

executeWorkletWithArgs(
worklet: SerializableRef<(num: number, str: string, bool: boolean) => void>
): void;
scheduleWorkletWithArgs(
worklet: SerializableRef<(num: number, str: string, bool: boolean) => void>
): void;
}

export default requireOptionalNativeModule<WorkletsTesterModule>('WorkletsTesterModule');
8 changes: 8 additions & 0 deletions apps/bare-expo/modules/worklets-tester/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ export const WorkletsTester = {
WorkletsTesterModule?.executeWorklet?.(createSerializable(worklet));
},

executeWorkletWithArgs: (worklet: (num: number, str: string, bool: boolean) => void) => {
WorkletsTesterModule?.executeWorkletWithArgs?.(createSerializable(worklet));
},

scheduleWorkletWithArgs: (worklet: (num: number, str: string, bool: boolean) => void) => {
WorkletsTesterModule?.scheduleWorkletWithArgs?.(createSerializable(worklet));
},

isAvailable() {
return WorkletsTesterModule != null;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,44 @@ export default function WorkletsTesterScreen() {
}
};

const testExecuteWorkletWithArgs = () => {
try {
WorkletsTester.executeWorkletWithArgs((num, str, bool) => {
'worklet';
runOnJS(addResult)({
name: 'executeWorkletWithArgs',
success: true,
message: `Received args - Number: ${num}, String: ${str}, Boolean: ${bool}`,
});
});
} catch (error) {
addResult({
name: 'executeWorkletWithArgs',
success: false,
message: `Error: ${error instanceof Error ? error.message : String(error)}`,
});
}
};

const testScheduleWorkletWithArgs = () => {
try {
WorkletsTester.scheduleWorkletWithArgs((num, str, bool) => {
'worklet';
runOnJS(addResult)({
name: 'scheduleWorkletWithArgs',
success: true,
message: `Received args - Number: ${num}, String: ${str}, Boolean: ${bool}`,
});
});
} catch (error) {
addResult({
name: 'scheduleWorkletWithArgs',
success: false,
message: `Error: ${error instanceof Error ? error.message : String(error)}`,
});
}
};

return (
<ScrollView style={styles.container}>
<View style={styles.buttonContainer}>
Expand All @@ -73,6 +111,14 @@ export default function WorkletsTesterScreen() {
<Text style={styles.buttonText}>Test scheduleWorklet</Text>
</TouchableOpacity>

<TouchableOpacity style={styles.button} onPress={testExecuteWorkletWithArgs}>
<Text style={styles.buttonText}>Test executeWorkletWithArgs</Text>
</TouchableOpacity>

<TouchableOpacity style={styles.button} onPress={testScheduleWorkletWithArgs}>
<Text style={styles.buttonText}>Test scheduleWorkletWithArgs</Text>
</TouchableOpacity>

<TouchableOpacity style={[styles.button, styles.clearButton]} onPress={clearResults}>
<Text style={styles.buttonText}>Clear Results</Text>
</TouchableOpacity>
Expand Down
44 changes: 40 additions & 4 deletions docs/components/plugins/api/APISectionProps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from './APIDataTypes';
import { APISectionDeprecationNote } from './APISectionDeprecationNote';
import {
LITERAL_UNION_COLLAPSE_THRESHOLD,
defineLiteralType,
extractDefaultPropValue,
getAllTagData,
Expand All @@ -41,6 +42,25 @@ export type RenderPropOptions = {
};

const UNKNOWN_VALUE = '...';
const LITERAL_KINDS = new Set(['literal', 'templateLiteral']);

const isSFSymbolLiteral = (t: TypeDefinitionData) =>
(t.type === 'literal' && typeof t.value === 'string' && t.value.startsWith('sf:')) ||
(t.type === 'templateLiteral' &&
typeof (t as any).head === 'string' &&
(t as any).head.startsWith('sf:'));

const getSFCollapseInfo = (type?: TypeDefinitionData) => {
if (type?.type !== 'union') {
return { collapse: false, nonLiteral: [] as TypeDefinitionData[] };
}
const allTypes = type.types ?? [];
const literalTypes = allTypes.filter(t => LITERAL_KINDS.has(t.type));
const collapse =
literalTypes.length > LITERAL_UNION_COLLAPSE_THRESHOLD && literalTypes.some(isSFSymbolLiteral);
const nonLiteral = collapse ? allTypes.filter(t => !LITERAL_KINDS.has(t.type)) : [];
return { collapse, nonLiteral };
};
const renderInheritedProp = (ip: TypeDefinitionData, sdkVersion: string) => {
return (
<LI key={`inherited-prop-${ip.name}-${ip.type}`}>
Expand Down Expand Up @@ -127,10 +147,13 @@ export const renderProp = (
const extractedComment = getCommentOrSignatureComment(comment, extractedSignatures);
const platforms = getAllTagData('platform', extractedComment);

const isLiteralType =
const { collapse: shouldCollapseLiteralUnion, nonLiteral: nonLiteralTypes } =
getSFCollapseInfo(type);

const isLiteralLike =
type?.type && ['literal', 'templateLiteral', 'union', 'tuple'].includes(type.type);
const definedLiteralGeneric =
isLiteralType && type?.types ? defineLiteralType(type.types) : undefined;
isLiteralLike && type?.types ? defineLiteralType(type.types) : undefined;

return (
<div
Expand All @@ -148,7 +171,7 @@ export const renderProp = (
{flags?.isOptional && <>Optional&emsp;&bull;&emsp;</>}
{flags?.isReadonly && <>Read Only&emsp;&bull;&emsp;</>}
{definedLiteralGeneric && <>Literal type: {definedLiteralGeneric}</>}
{!isLiteralType && (
{!isLiteralLike && (
<>
Type:{' '}
<APITypeOrSignatureType
Expand All @@ -174,7 +197,7 @@ export const renderProp = (
className={mergeClasses(VERTICAL_SPACING, ELEMENT_SPACING)}
/>
))}
{type?.types && isLiteralType && (
{type?.types && isLiteralLike && !shouldCollapseLiteralUnion && (
<CALLOUT className={mergeClasses(STYLES_SECONDARY, VERTICAL_SPACING, ELEMENT_SPACING)}>
Acceptable values are:{' '}
{type.types.map((lt, index, arr) => (
Expand All @@ -185,6 +208,19 @@ export const renderProp = (
))}
</CALLOUT>
)}
{type?.types && shouldCollapseLiteralUnion && (
<CALLOUT className={mergeClasses(STYLES_SECONDARY, VERTICAL_SPACING, ELEMENT_SPACING)}>
Acceptable values are:{' '}
{nonLiteralTypes.map((lt, index) => (
<Fragment key={`${name}-non-literal-type-${index}`}>
<CODE className="mb-px">{resolveTypeName(lt, sdkVersion)}</CODE>
{index + 1 !== nonLiteralTypes.length && <span className="text-quaternary"> | </span>}
</Fragment>
))}
{nonLiteralTypes.length > 0 && <span className="text-quaternary"> | </span>}
<CODE className="mb-px">sf:&lt;symbol&gt;</CODE>
</CALLOUT>
)}
</div>
);
};
Expand Down
20 changes: 19 additions & 1 deletion docs/pages/eas-update/override.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,24 @@ This guide explains how you can change the update URL and request headers at run

> **warning** The feature described in this section is available in Expo SDK 54 with the `expo-updates` version 0.29.0 and later.

The primary use case for this feature is to **enable switching between update channels in a production build**. This is useful to enable non-technical stakeholders to test and validate updates of work-in-progress (such as from a pull request or different feature branches) without having to use a development build or create a separate preview build for each change.
The primary use case for this feature is [channel surfing](#what-is-channel-surfing), which allows switching between update channels in a production build at runtime. This enables non-technical stakeholders to test and validate updates of work-in-progress (such as from a pull request or different feature branches) without having to use a development build or create a separate preview build for each change.

For example, if you have a `default` channel for production updates and a `preview` channel for preview updates, you can override the `expo-channel-name` request header to point to the `preview` channel. This enables you to test preview updates in your current production builds.

Another potential use case is to provide different updates to different users, for example, so that a group of internal users (such as employees) receive updates before end-users.

### What is channel surfing?

Channel surfing is the practice of switching the update channel an installed app pulls updates from at runtime. Instead of being permanently tied to the channel configured at build time, a running app can be redirected to another channel (such as a `preview` channel) and load updates from there.

You can use channel surfing to:

- Allow a single installed app to move between channels on demand. The app can switch channels at runtime instead of being permanently locked to the channel defined at build time.
- Enable preview and testing workflows on real builds so that developers, QA, or other stakeholders can try in-progress updates using the same production build that users have installed.
- Speed up iteration and validation updates can be tested, reviewed, or validated immediately by redirecting the app to another channel, without waiting for a new build.

For more information about problem channel surfing solves and how to apply it, read the [blog post on channel surfing](https://expo.dev/blog/channel-surfing-for-expo-updates-how-to-switch-update-channels-at-runtime).

### How it works

You can override the `expo-channel-name` request header by calling [`Updates.setUpdateRequestHeadersOverride`](/versions/latest/sdk/updates/#updatessetupdaterequestheadersoverriderequestheaders). This will override update requests to fetch updates from the specified channel.
Expand All @@ -39,6 +51,12 @@ await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
```

### Risks and considerations when switching channels

Switching channels changes the JavaScript bundle the app runs. If your app depends on migrations or data shapes that are not compatible across channels, switching back and forth may cause issues.

For example, if a beta update applies a database migration, the production version might not understand the new schema. Developers should ensure their updates remain safe to switch between or restrict switching to one direction when needed.

## Override both update URL and request headers

> **warning** The feature described in this section is available in Expo SDK 52 with the `expo-updates` version 0.27.0 and later. Using the `disableAntiBrickingMeasures` option is not recommended for production apps, it is currently primarily intended for preview environments.
Expand Down
2 changes: 1 addition & 1 deletion docs/public/static/data/unversioned/expo-image.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/public/static/data/v55.0.0/expo-image.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions packages/@expo/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

### 🐛 Bug fixes

- Reallow connections on `/expo-dev-plugins/broadcast` broadcast socket to local connections ([#42538](https://github.com/expo/expo/pull/42538) by [@kitten](https://github.com/kitten))

### 💡 Others

## 55.0.3 — 2026-01-26
Expand All @@ -21,6 +23,7 @@
- Add allowedValues validation for string-or-boolean args ([#42523](https://github.com/expo/expo/pull/42523) by [@brentvatne](https://github.com/brentvatne))
- Drop `freeport-async` dependency ([#42478](https://github.com/expo/expo/pull/42478) by [@kitten](https://github.com/kitten))
- Drop `pretty-bytes` for `Intl.NumberFormat` call with fallback ([#42482](https://github.com/expo/expo/pull/42482) by [@kitten](https://github.com/kitten))
- Warn when `@expo/fingerprint` is missing during build cache check ([#41910](https://github.com/expo/expo/pull/41910) by [@gustavoharff](https://github.com/gustavoharff))

## 55.0.2 — 2026-01-23

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { WebSocket, WebSocketServer } from 'ws';

import { isLocalSocket, isMatchingOrigin } from '../../../utils/net';
import { isMatchingOrigin } from '../../../utils/net';

interface DevToolsPluginWebsocketEndpointParams {
serverBaseUrl: string;
Expand All @@ -13,7 +13,7 @@ export function createDevToolsPluginWebsocketEndpoint({

wss.on('connection', (ws, request) => {
// Explicitly limit devtools websocket to loopback requests
if (!isLocalSocket(request.socket) || !isMatchingOrigin(request, serverBaseUrl)) {
if (request.headers.origin && !isMatchingOrigin(request, serverBaseUrl)) {
// NOTE: `socket.close` nicely closes the websocket, which will still allow incoming messages
// `socket.terminate` instead forcefully closes down the socket
ws.terminate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ async function calculateFingerprintHashAsync({

const Fingerprint = importFingerprintForDev(projectRoot);
if (!Fingerprint) {
debug('@expo/fingerprint is not installed in the project, unable to calculate fingerprint');
Log.warn('@expo/fingerprint is not installed in the project, skipping build cache.');
return null;
}
const fingerprint = await Fingerprint.createFingerprintAsync(projectRoot);
Expand Down
1 change: 1 addition & 0 deletions packages/expo-clipboard/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ _This version does not introduce any user-facing changes._
### 💡 Others

- Remove tests related files from the published package content. ([#39551](https://github.com/expo/expo/pull/39551) by [@Simek](https://github.com/Simek))
- mark `removeClipboardListener` call as deprecated ([#42098](https://github.com/expo/expo/pull/42098) by [@vonovak](https://github.com/vonovak))
- [iOS] Update documentation regarding the edge case when the user denies paste permission ([#40259](https://github.com/expo/expo/pull/40259) by [@hryhoriiK97](https://github.com/hryhoriiK97))
- Update internal TypeScript types. ([#41740](https://github.com/expo/expo/pull/41740) by [@barthap](https://github.com/barthap))

Expand Down
11 changes: 1 addition & 10 deletions packages/expo-clipboard/build/Clipboard.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/expo-clipboard/build/Clipboard.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 1 addition & 10 deletions packages/expo-clipboard/build/Clipboard.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading