@siyaqi/react-native 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/README.md +29 -0
- package/dist/SiyaqiAd.d.ts +14 -0
- package/dist/SiyaqiAd.js +47 -0
- package/dist/api.d.ts +13 -0
- package/dist/api.js +39 -0
- package/dist/api.test.d.ts +1 -0
- package/dist/api.test.js +17 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/useSiyaqiAd.d.ts +13 -0
- package/dist/useSiyaqiAd.js +38 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @siyaqi/react-native
|
|
2
|
+
|
|
3
|
+
Expo / React Native publisher module. Calls **Fly serve only**.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @siyaqi/react-native
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { SiyaqiAd } from "@siyaqi/react-native";
|
|
11
|
+
|
|
12
|
+
<SiyaqiAd
|
|
13
|
+
serveBase="https://siyaqi-prod.fly.dev"
|
|
14
|
+
placementId="plc_YOUR_ID"
|
|
15
|
+
/>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Hook form (same contract):
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import { useSiyaqiAd } from "@siyaqi/react-native";
|
|
22
|
+
|
|
23
|
+
const { ad, isLoading } = useSiyaqiAd({
|
|
24
|
+
placementId: "plc_YOUR_ID",
|
|
25
|
+
serveUrl: "https://siyaqi-prod.fly.dev",
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Peer deps: `react`, `react-native`. MCP for advertiser campaign ops stays in-repo (`npx tsx apps/mcp/src/index.ts`) — it is not published to npm.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type TextStyle, type ViewStyle } from "react-native";
|
|
2
|
+
export type SiyaqiAdProps = {
|
|
3
|
+
serveBase: string;
|
|
4
|
+
placementId: string;
|
|
5
|
+
context?: string;
|
|
6
|
+
style?: ViewStyle;
|
|
7
|
+
textStyle?: TextStyle;
|
|
8
|
+
ctaStyle?: TextStyle;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Lightweight text ad unit for Expo / React Native publishers.
|
|
12
|
+
* Talks only to Fly serve (`serveBase`), never Railway portal APIs.
|
|
13
|
+
*/
|
|
14
|
+
export declare function SiyaqiAd({ serveBase, placementId, context, style, textStyle, ctaStyle, }: SiyaqiAdProps): import("react").JSX.Element;
|
package/dist/SiyaqiAd.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from "react";
|
|
3
|
+
import { Pressable, StyleSheet, Text, View, } from "react-native";
|
|
4
|
+
import { fetchAd, reportClick } from "./api.js";
|
|
5
|
+
/**
|
|
6
|
+
* Lightweight text ad unit for Expo / React Native publishers.
|
|
7
|
+
* Talks only to Fly serve (`serveBase`), never Railway portal APIs.
|
|
8
|
+
*/
|
|
9
|
+
export function SiyaqiAd({ serveBase, placementId, context, style, textStyle, ctaStyle, }) {
|
|
10
|
+
const [ad, setAd] = useState(null);
|
|
11
|
+
const [error, setError] = useState(null);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
let cancelled = false;
|
|
14
|
+
void (async () => {
|
|
15
|
+
try {
|
|
16
|
+
const next = await fetchAd(serveBase, placementId, context);
|
|
17
|
+
if (!cancelled)
|
|
18
|
+
setAd(next);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
if (!cancelled)
|
|
22
|
+
setError("Failed to load ad");
|
|
23
|
+
}
|
|
24
|
+
})();
|
|
25
|
+
return () => {
|
|
26
|
+
cancelled = true;
|
|
27
|
+
};
|
|
28
|
+
}, [serveBase, placementId, context]);
|
|
29
|
+
const onPress = useCallback(() => {
|
|
30
|
+
if (!ad)
|
|
31
|
+
return;
|
|
32
|
+
void reportClick(serveBase, ad);
|
|
33
|
+
}, [ad, serveBase]);
|
|
34
|
+
if (error) {
|
|
35
|
+
return (_jsx(View, { style: [styles.box, style], children: _jsx(Text, { style: styles.muted, children: error }) }));
|
|
36
|
+
}
|
|
37
|
+
if (!ad) {
|
|
38
|
+
return (_jsx(View, { style: [styles.box, style], children: _jsx(Text, { style: styles.muted, children: "Loading\u2026" }) }));
|
|
39
|
+
}
|
|
40
|
+
return (_jsxs(Pressable, { onPress: onPress, style: [styles.box, style], children: [_jsx(Text, { style: [styles.text, textStyle], children: ad.text }), _jsx(Text, { style: [styles.cta, ctaStyle], children: ad.cta })] }));
|
|
41
|
+
}
|
|
42
|
+
const styles = StyleSheet.create({
|
|
43
|
+
box: { padding: 12 },
|
|
44
|
+
text: { fontSize: 15, color: "#111" },
|
|
45
|
+
cta: { marginTop: 8, fontSize: 14, fontWeight: "600", color: "#0B6E4F" },
|
|
46
|
+
muted: { fontSize: 13, color: "#666" },
|
|
47
|
+
});
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function serveUrl(serveBase: string, placementId: string, context?: string): string;
|
|
2
|
+
export declare function clickUrl(serveBase: string): string;
|
|
3
|
+
export declare function impressionUrl(serveBase: string): string;
|
|
4
|
+
export type ServedCreative = {
|
|
5
|
+
trackingId: string;
|
|
6
|
+
campaignId: string;
|
|
7
|
+
variantId: string;
|
|
8
|
+
placementId: string;
|
|
9
|
+
text: string;
|
|
10
|
+
cta: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function fetchAd(serveBase: string, placementId: string, context?: string, fetchImpl?: typeof fetch): Promise<ServedCreative | null>;
|
|
13
|
+
export declare function reportClick(serveBase: string, ad: ServedCreative, fetchImpl?: typeof fetch): Promise<void>;
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Vendored serve paths — keep in sync with packages/shared `API`. */
|
|
2
|
+
const SERVE = {
|
|
3
|
+
serveAd: "/v1/ads/serve",
|
|
4
|
+
impression: "/v1/ads/impression",
|
|
5
|
+
click: "/v1/ads/click",
|
|
6
|
+
};
|
|
7
|
+
export function serveUrl(serveBase, placementId, context) {
|
|
8
|
+
const base = serveBase.replace(/\/$/, "");
|
|
9
|
+
const u = new URL(`${base}${SERVE.serveAd}`);
|
|
10
|
+
u.searchParams.set("placementId", placementId);
|
|
11
|
+
if (context)
|
|
12
|
+
u.searchParams.set("context", context);
|
|
13
|
+
return u.toString();
|
|
14
|
+
}
|
|
15
|
+
export function clickUrl(serveBase) {
|
|
16
|
+
return `${serveBase.replace(/\/$/, "")}${SERVE.click}`;
|
|
17
|
+
}
|
|
18
|
+
export function impressionUrl(serveBase) {
|
|
19
|
+
return `${serveBase.replace(/\/$/, "")}${SERVE.impression}`;
|
|
20
|
+
}
|
|
21
|
+
export async function fetchAd(serveBase, placementId, context, fetchImpl = fetch) {
|
|
22
|
+
const res = await fetchImpl(serveUrl(serveBase, placementId, context));
|
|
23
|
+
if (!res.ok)
|
|
24
|
+
return null;
|
|
25
|
+
const data = (await res.json());
|
|
26
|
+
return data.ad ?? null;
|
|
27
|
+
}
|
|
28
|
+
export async function reportClick(serveBase, ad, fetchImpl = fetch) {
|
|
29
|
+
await fetchImpl(clickUrl(serveBase), {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: { "Content-Type": "application/json" },
|
|
32
|
+
body: JSON.stringify({
|
|
33
|
+
trackingId: ad.trackingId,
|
|
34
|
+
campaignId: ad.campaignId,
|
|
35
|
+
variantId: ad.variantId,
|
|
36
|
+
placementId: ad.placementId,
|
|
37
|
+
}),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/api.test.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { clickUrl, serveUrl } from "./api.js";
|
|
6
|
+
describe("siyaqi-react-native api helpers", () => {
|
|
7
|
+
it("builds serve URL", () => {
|
|
8
|
+
expect(serveUrl("https://siyaqi-prod.fly.dev", "plc_1")).toBe("https://siyaqi-prod.fly.dev/v1/ads/serve?placementId=plc_1");
|
|
9
|
+
});
|
|
10
|
+
it("builds click URL", () => {
|
|
11
|
+
expect(clickUrl("https://siyaqi-prod.fly.dev/")).toBe("https://siyaqi-prod.fly.dev/v1/ads/click");
|
|
12
|
+
});
|
|
13
|
+
it("does not import @siyaqi/shared", () => {
|
|
14
|
+
const src = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "api.ts"), "utf8");
|
|
15
|
+
expect(src).not.toContain("@siyaqi/shared");
|
|
16
|
+
});
|
|
17
|
+
});
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ServedCreative } from "./api.js";
|
|
2
|
+
export type UseSiyaqiAdOptions = {
|
|
3
|
+
placementId: string;
|
|
4
|
+
serveBase?: string;
|
|
5
|
+
/** Alias used in generated portal snippets. */
|
|
6
|
+
serveUrl?: string;
|
|
7
|
+
context?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function useSiyaqiAd(options: UseSiyaqiAdOptions): {
|
|
10
|
+
ad: ServedCreative | null;
|
|
11
|
+
isLoading: boolean;
|
|
12
|
+
error: string | null;
|
|
13
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { fetchAd } from "./api.js";
|
|
3
|
+
export function useSiyaqiAd(options) {
|
|
4
|
+
const serveBase = options.serveBase ?? options.serveUrl ?? "";
|
|
5
|
+
const { placementId, context } = options;
|
|
6
|
+
const [ad, setAd] = useState(null);
|
|
7
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
8
|
+
const [error, setError] = useState(null);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
let cancelled = false;
|
|
11
|
+
setIsLoading(true);
|
|
12
|
+
void (async () => {
|
|
13
|
+
try {
|
|
14
|
+
const next = serveBase
|
|
15
|
+
? await fetchAd(serveBase, placementId, context)
|
|
16
|
+
: null;
|
|
17
|
+
if (!cancelled) {
|
|
18
|
+
setAd(next);
|
|
19
|
+
setError(null);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
if (!cancelled) {
|
|
24
|
+
setAd(null);
|
|
25
|
+
setError("Failed to load ad");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
if (!cancelled)
|
|
30
|
+
setIsLoading(false);
|
|
31
|
+
}
|
|
32
|
+
})();
|
|
33
|
+
return () => {
|
|
34
|
+
cancelled = true;
|
|
35
|
+
};
|
|
36
|
+
}, [serveBase, placementId, context]);
|
|
37
|
+
return { ad, isLoading, error };
|
|
38
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@siyaqi/react-native",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "Expo / React Native publisher module for Siyaqi text ads (Fly serve /v1).",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/Siyaqi-AI/Siyaqi.git",
|
|
22
|
+
"directory": "packages/siyaqi-react-native"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/Siyaqi-AI/Siyaqi/tree/main/packages/siyaqi-react-native",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/Siyaqi-AI/Siyaqi/issues"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -p tsconfig.json",
|
|
33
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"prepublishOnly": "npm run build"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"react": ">=18",
|
|
39
|
+
"react-native": ">=0.73"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/react": "^19",
|
|
43
|
+
"typescript": "^5",
|
|
44
|
+
"vitest": "^3.2.4"
|
|
45
|
+
}
|
|
46
|
+
}
|