expo-apple-external-link 1.0.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/.eslintrc.js +5 -0
- package/LICENSE.md +21 -0
- package/README.md +56 -0
- package/app.plugin.js +1 -0
- package/build/ExpoAppleExternalLinkModule.d.ts +7 -0
- package/build/ExpoAppleExternalLinkModule.d.ts.map +1 -0
- package/build/ExpoAppleExternalLinkModule.js +4 -0
- package/build/ExpoAppleExternalLinkModule.js.map +1 -0
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +2 -0
- package/build/index.js.map +1 -0
- package/expo-module.config.json +6 -0
- package/ios/ExpoAppleExternalLink.podspec +29 -0
- package/ios/ExpoAppleExternalLinkModule.swift +44 -0
- package/package.json +41 -0
- package/plugin/build/index.d.ts +8 -0
- package/plugin/build/index.js +16 -0
- package/plugin/src/index.ts +27 -0
- package/plugin/tsconfig.json +10 -0
- package/plugin/tsconfig.tsbuildinfo +1 -0
- package/src/ExpoAppleExternalLinkModule.ts +8 -0
- package/src/index.ts +1 -0
- package/tsconfig.json +9 -0
package/.eslintrc.js
ADDED
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vox Media
|
|
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,56 @@
|
|
|
1
|
+
# Expo Apple External Link
|
|
2
|
+
|
|
3
|
+
This is a custom [Expo Module](https://docs.expo.dev/modules/overview/) + [Config Plugin](https://docs.expo.dev/config-plugins/introduction/) which enables an iOS app built with Expo to direct users to an external website for account management and creation.
|
|
4
|
+
|
|
5
|
+
This custom native module is iOS-only.
|
|
6
|
+
|
|
7
|
+
> [!IMPORTANT]
|
|
8
|
+
> The External Link API requires iOS 16+. You may also need to add `expo-build-properties`.
|
|
9
|
+
|
|
10
|
+
## External Links
|
|
11
|
+
|
|
12
|
+
Typically, Apple does not allow apps which contain links to your website on the App Store. Instead, you must implement in-app purchases.
|
|
13
|
+
|
|
14
|
+
However there is a special exception for "reader" apps. These kinds of apps can optionally apply for an additional Entitlement, and if this is granted then the app will then be allowed on the App Store.
|
|
15
|
+
|
|
16
|
+
- see https://developer.apple.com/support/reader-apps
|
|
17
|
+
- see https://developer.apple.com/documentation/storekit/external_link_account
|
|
18
|
+
|
|
19
|
+
## Module
|
|
20
|
+
|
|
21
|
+
This Expo Module exposes a single method called `openInAppModal()`. This calls [ExternalLinkAccount.open()](https://developer.apple.com/documentation/storekit/externallinkaccount/4047547-open) in native (Swift) code.
|
|
22
|
+
|
|
23
|
+
## Plugin
|
|
24
|
+
|
|
25
|
+
The Plugin is responsible for modifying your `Entitlements` and `Info.plist` files, using options passed to the plugin in your `app.json` config.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
Can be included alongside any other Expo Config Plugins in `app.json`:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
"plugins": [
|
|
33
|
+
// ...other plugins...
|
|
34
|
+
[
|
|
35
|
+
"expo-build-properties",
|
|
36
|
+
{
|
|
37
|
+
"ios": {
|
|
38
|
+
"deploymentTarget": "16.0",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
[
|
|
43
|
+
"expo-apple-external-link",
|
|
44
|
+
{
|
|
45
|
+
"externalLinks": {
|
|
46
|
+
"*": "https://mywebsite.com/subscribe"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The `externalLinks` Object corresponds to the [SKExternalLinkAccount](https://developer.apple.com/documentation/bundleresources/information_property_list/skexternallinkaccount/) dictionary, and maps country codes to region-specific URLs.
|
|
54
|
+
|
|
55
|
+
> [!TIP]
|
|
56
|
+
> Alternately, the key `"*"` can be used as a wildcard for all regions.
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require("./plugin/build");
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { NativeModule } from 'expo';
|
|
2
|
+
declare class ExpoAppleExternalLinkModule extends NativeModule {
|
|
3
|
+
openInAppModal(): Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
declare const _default: ExpoAppleExternalLinkModule;
|
|
6
|
+
export default _default;
|
|
7
|
+
//# sourceMappingURL=ExpoAppleExternalLinkModule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoAppleExternalLinkModule.d.ts","sourceRoot":"","sources":["../src/ExpoAppleExternalLinkModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,OAAO,2BAA4B,SAAQ,YAAY;IAC5D,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;CAChC;;AAGD,wBAAyF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpoAppleExternalLinkModule.js","sourceRoot":"","sources":["../src/ExpoAppleExternalLinkModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAMzD,yDAAyD;AACzD,eAAe,mBAAmB,CAA8B,uBAAuB,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from 'expo';\n\ndeclare class ExpoAppleExternalLinkModule extends NativeModule {\n openInAppModal(): Promise<void>;\n}\n\n// This call loads the native module object from the JSI.\nexport default requireNativeModule<ExpoAppleExternalLinkModule>('ExpoAppleExternalLink');\n"]}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC","sourcesContent":["export { default } from './ExpoAppleExternalLinkModule';\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'ExpoAppleExternalLink'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.description = package['description']
|
|
10
|
+
s.license = package['license']
|
|
11
|
+
s.author = package['author']
|
|
12
|
+
s.homepage = package['homepage']
|
|
13
|
+
s.platforms = {
|
|
14
|
+
:ios => '15.1',
|
|
15
|
+
:tvos => '15.1'
|
|
16
|
+
}
|
|
17
|
+
s.swift_version = '5.9'
|
|
18
|
+
s.source = { git: 'https://github.com/voxmedia/expo-apple-external-link' }
|
|
19
|
+
s.static_framework = true
|
|
20
|
+
|
|
21
|
+
s.dependency 'ExpoModulesCore'
|
|
22
|
+
|
|
23
|
+
# Swift/Objective-C compatibility
|
|
24
|
+
s.pod_target_xcconfig = {
|
|
25
|
+
'DEFINES_MODULE' => 'YES',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
|
|
29
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import StoreKit
|
|
3
|
+
|
|
4
|
+
internal class NetworkError: Exception {
|
|
5
|
+
override var reason: String {
|
|
6
|
+
"StoreKitError: A network error occurred when communicating with the App Store."
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
internal class NotEntitled: Exception {
|
|
11
|
+
override var reason: String {
|
|
12
|
+
"StoreKitError: The application is not entitled to perform the action."
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public class ExpoAppleExternalLinkModule: Module {
|
|
17
|
+
// Each module class must implement the definition function. The definition consists of components
|
|
18
|
+
// that describes the module's functionality and behavior.
|
|
19
|
+
// See https://docs.expo.dev/modules/module-api for more details about available components.
|
|
20
|
+
public func definition() -> ModuleDefinition {
|
|
21
|
+
// Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
|
|
22
|
+
// Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
|
|
23
|
+
// The module will be accessible from `requireNativeModule('ExpoAppleExternalLink')` in JavaScript.
|
|
24
|
+
Name("ExpoAppleExternalLink")
|
|
25
|
+
|
|
26
|
+
// Defines a JavaScript function that always returns a Promise and whose native code
|
|
27
|
+
// is by default dispatched on the different thread than the JavaScript runtime runs on
|
|
28
|
+
AsyncFunction("openInAppModal") { (promise: Promise) in
|
|
29
|
+
if #available(iOS 16.0, *) {
|
|
30
|
+
Task {
|
|
31
|
+
do {
|
|
32
|
+
try await ExternalLinkAccount.open()
|
|
33
|
+
} catch StoreKitError.networkError {
|
|
34
|
+
promise.reject(NetworkError())
|
|
35
|
+
} catch StoreKitError.notEntitled {
|
|
36
|
+
promise.reject(NotEntitled())
|
|
37
|
+
} catch {
|
|
38
|
+
promise.reject(error)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "expo-apple-external-link",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Expo module which allows reader apps to link to an external website for account creation or management",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"types": "build/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "expo-module build",
|
|
9
|
+
"clean": "expo-module clean",
|
|
10
|
+
"lint": "expo-module lint",
|
|
11
|
+
"test": "expo-module test",
|
|
12
|
+
"prepare": "expo-module prepare",
|
|
13
|
+
"prepublishOnly": "expo-module prepublishOnly",
|
|
14
|
+
"expo-module": "expo-module"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"react-native",
|
|
18
|
+
"expo",
|
|
19
|
+
"expo-apple-external-link",
|
|
20
|
+
"ExpoAppleExternalLink"
|
|
21
|
+
],
|
|
22
|
+
"repository": "https://github.com/voxmedia/expo-apple-external-link",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/voxmedia/expo-apple-external-link/issues"
|
|
25
|
+
},
|
|
26
|
+
"author": "jpdriver <jpdriver@users.noreply.github.com> (https://github.com/jpdriver)",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"homepage": "https://github.com/voxmedia/expo-apple-external-link#readme",
|
|
29
|
+
"dependencies": {},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/react": "~19.1.1",
|
|
32
|
+
"expo-module-scripts": "^55.0.2",
|
|
33
|
+
"expo": "^55.0.8",
|
|
34
|
+
"react-native": "0.82.1"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"expo": "*",
|
|
38
|
+
"react": "*",
|
|
39
|
+
"react-native": "*"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const config_plugins_1 = require("expo/config-plugins");
|
|
4
|
+
const withExternalLinks = (config, { externalLinks }) => {
|
|
5
|
+
config = (0, config_plugins_1.withEntitlementsPlist)(config, props => {
|
|
6
|
+
props.modResults["com.apple.developer.storekit.external-link.account"] =
|
|
7
|
+
true;
|
|
8
|
+
return props;
|
|
9
|
+
});
|
|
10
|
+
config = (0, config_plugins_1.withInfoPlist)(config, props => {
|
|
11
|
+
props.modResults.SKExternalLinkAccount = externalLinks;
|
|
12
|
+
return props;
|
|
13
|
+
});
|
|
14
|
+
return config;
|
|
15
|
+
};
|
|
16
|
+
exports.default = withExternalLinks;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
withEntitlementsPlist,
|
|
3
|
+
withInfoPlist,
|
|
4
|
+
ConfigPlugin,
|
|
5
|
+
} from "expo/config-plugins";
|
|
6
|
+
|
|
7
|
+
type ExternalLinks = { [key: string]: string };
|
|
8
|
+
|
|
9
|
+
const withExternalLinks: ConfigPlugin<{ externalLinks: ExternalLinks }> = (
|
|
10
|
+
config,
|
|
11
|
+
{ externalLinks },
|
|
12
|
+
) => {
|
|
13
|
+
config = withEntitlementsPlist(config, props => {
|
|
14
|
+
props.modResults["com.apple.developer.storekit.external-link.account"] =
|
|
15
|
+
true;
|
|
16
|
+
return props;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
config = withInfoPlist(config, props => {
|
|
20
|
+
props.modResults.SKExternalLinkAccount = externalLinks;
|
|
21
|
+
return props;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return config;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default withExternalLinks;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/index.ts"],"version":"5.9.3"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { NativeModule, requireNativeModule } from 'expo';
|
|
2
|
+
|
|
3
|
+
declare class ExpoAppleExternalLinkModule extends NativeModule {
|
|
4
|
+
openInAppModal(): Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// This call loads the native module object from the JSI.
|
|
8
|
+
export default requireNativeModule<ExpoAppleExternalLinkModule>('ExpoAppleExternalLink');
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './ExpoAppleExternalLinkModule';
|
package/tsconfig.json
ADDED