Skip to content

SDK-290 Add Expo Config Plugin and Fix Static Linkage Swift Header Import#813

Draft
joaodordio wants to merge 2 commits intomasterfrom
fix/SDK-290-Export-iOS-sdk-header-file
Draft

SDK-290 Add Expo Config Plugin and Fix Static Linkage Swift Header Import#813
joaodordio wants to merge 2 commits intomasterfrom
fix/SDK-290-Export-iOS-sdk-header-file

Conversation

@joaodordio
Copy link
Member

@joaodordio joaodordio commented Feb 4, 2026

🔹 JIRA Ticket(s) if any

✏️ Description

  • Add Expo config plugin for automated iOS build configuration
  • Fix Swift header import for static framework builds
  • Update iOS SDK dependency to 6.6.5
  • Add comprehensive plugin tests

Problem

Integrating the Iterable React Native SDK in Expo managed workflows requires complex manual configuration:

  1. React Native + Expo must remain statically linked
  2. Iterable pods must be dynamically linked due to Swift bridging
  3. Autolinking must be disabled to avoid duplicate Podfile entries
  4. Push notification extensions need separate target configuration

This leads to recurring build failures:

  • 'Iterable_React_Native_SDK-Swift.h' file not found
  • "transitive dependencies include statically linked binaries" errors
  • Duplicate pod declaration errors

Solution

1. Expo Config Plugin

New plugin at plugin/src/ that automates iOS build configuration during expo prebuild:

Autolinking Exclusion (withIterableAutolinking.ts):

  • Excludes @iterable/react-native-sdk from iOS autolinking to prevent duplicate entries

Podfile Transformation (withIterablePodfile.ts):

  • Ensures use_frameworks! :linkage => :static is set (Expo requirement)
  • Injects Iterable pods with :linkage => :dynamic override
  • Optionally adds IterableNotifications target for push extensions
  • Idempotent - safe to run multiple times

Usage:

{
  "expo": {
    "plugins": [
      ["@iterable/react-native-sdk", { "enableNotificationExtension": true }]
    ]
  }
}

2. Swift Header Import Fix

Updated RNIterableAPI.mm to use conditional import for static framework builds:

#if __has_include(<Iterable_React_Native_SDK/Iterable_React_Native_SDK-Swift.h>)
#import <Iterable_React_Native_SDK/Iterable_React_Native_SDK-Swift.h>
#else
#import "Iterable_React_Native_SDK-Swift.h"
#endif

3. Podspec Updates

Added to Iterable-React-Native-SDK.podspec:

  • SWIFT_OBJC_INTERFACE_HEADER_NAME setting
  • HEADER_SEARCH_PATHS for generated Swift header location

Files Changed

File Change
plugin/src/index.ts Main plugin entry with config options
plugin/src/withIterableAutolinking.ts Autolinking exclusion logic
plugin/src/withIterablePodfile.ts Podfile transformation
plugin/__tests__/plugin.test.ts Jest tests for all transformations
ios/RNIterableAPI/RNIterableAPI.mm Conditional Swift header import
Iterable-React-Native-SDK.podspec Header search paths
package.json Plugin build scripts, expo config

Testing

  1. Run yarn build - SDK and plugin compile successfully
  2. Run yarn test:plugin - All plugin tests pass
  3. Build example app with yarn ios - No Swift header errors
  4. Test Expo integration with npx expo prebuild - Correct Podfile output

Dependencies

  • Requires iterable-swift-sdk version 6.6.5+ with C linkage header fixes

contents = injectNotificationExtensionTarget(contents, targetName);
}

fs.writeFileSync(podfilePath, contents);

Check failure

Code scanning / CodeQL

Potential file system race condition High

The file may have changed since it
was checked
.
@github-actions
Copy link

github-actions bot commented Feb 4, 2026

Lines Statements Branches Functions
Coverage: 63%
63.07% (398/631) 40.07% (103/257) 61.5% (139/226)

@qltysh
Copy link

qltysh bot commented Feb 4, 2026

Qlty

Coverage Impact

This PR will not change total coverage.

🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

@qltysh
Copy link

qltysh bot commented Feb 4, 2026

❌ 9 blocking issues (13 total)

Tool Category Rule Count
eslint Lint tsdoc-characters-after-block-tag: The token "@Iterable" looks like a TSDoc tag but contains an invalid character "/"; if it is not a tag, use a backslash to escape the "@" 6
eslint Lint '@typescript-eslint/no-explicit-any' rule is disabled but never reported. 1
eslint Lint Unexpected any. Specify a different type. 1
eslint Lint 'config' is already declared in the upper scope on line 17 column 72. 1
qlty Structure Function with high complexity (count = 6): withIterablePodfile 2
qlty Structure Function with many returns (count = 4): ensureStaticLinkage 2

* This plugin handles the complex iOS build configuration required for
* Iterable SDK integration with Expo managed workflows:
*
* 1. Disables iOS autolinking for @iterable/react-native-sdk to prevent
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tsdoc-characters-after-block-tag: The token "@Iterable" looks like a TSDoc tag but contains an invalid character "/"; if it is not a tag, use a backslash to escape the "@" [eslint:tsdoc/syntax]

import { IterablePluginProps } from './index';

/**
* Disables iOS autolinking for @iterable/react-native-sdk.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tsdoc-characters-after-block-tag: The token "@Iterable" looks like a TSDoc tag but contains an invalid character "/"; if it is not a tag, use a backslash to escape the "@" [eslint:tsdoc/syntax]

};

/**
* Modifies the Expo config to exclude @iterable/react-native-sdk from
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tsdoc-characters-after-block-tag: The token "@Iterable" looks like a TSDoc tag but contains an invalid character "/"; if it is not a tag, use a backslash to escape the "@" [eslint:tsdoc/syntax]

*
* Note: autolinking is not in the base ExpoConfig type but is supported at runtime.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'@typescript-eslint/no-explicit-any' rule is disabled but never reported. [eslint:eslint-comments/no-unused-disable]

Suggested change
// eslint-disable-next-line @typescript-eslint/no-explicit-any

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function disableAutolinking<T>(config: T): T {
// Cast to any for dynamic property access (autolinking not in ExpoConfig types)
const cfg = config as any;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected any. Specify a different type. [eslint:@typescript-eslint/no-explicit-any]

Suggested change
const cfg = config as any;
const cfg = config as unknown;

Use unknown instead, this will force you to explicitly, and safely assert the type is correct.

export const withIterablePodfile: ConfigPlugin<IterablePluginProps> = (config, props) => {
return withDangerousMod(config, [
'ios',
async (config) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'config' is already declared in the upper scope on line 17 column 72. [eslint:@typescript-eslint/no-shadow]

fs.writeFileSync(podfilePath, contents);
return config;
},
]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with high complexity (count = 6): withIterablePodfile [qlty:function-complexity]

};

/**
* Ensures the Podfile has use_frameworks! :linkage => :static.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tsdoc-escape-greater-than: The ">" character should be escaped using a backslash to avoid confusion with an HTML tag [eslint:tsdoc/syntax]

return contents.replace(/use_frameworks!\s*(?!\s*:linkage)/, 'use_frameworks! :linkage => :static');
}

return contents;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with many returns (count = 4): ensureStaticLinkage [qlty:return-statements]

throw new Error(
'Could not find a suitable location to inject Iterable pods. ' +
'Please ensure your Podfile has a valid target block.'
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 2 issues:

1. Function with many returns (count = 5): injectIterablePods [qlty:return-statements]


2. Function with high complexity (count = 7): injectIterablePods [qlty:function-complexity]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant