@thanksjs/react-native-webview 0.0.1-beta.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 ADDED
@@ -0,0 +1,78 @@
1
+ Thanks JS react native adapter
2
+
3
+ ===
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ yarn add @thanksjs/react-native-webview
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```tsx
14
+ import { ThanksWidget } from '@thanksjs/react-native-webview';
15
+
16
+ /// somewhere in your code
17
+ <ThanksWidget
18
+ partnerId="{your partner id}"
19
+ // other available props, see Thanks Configuration section
20
+ />
21
+ ```
22
+
23
+ ### Advanced usage
24
+ The important parts are:
25
+ - providing a customer `email`. Strictly sha256 hash will be transferred during the widget lifecycle. No sensitive information leaves your application without your permission.
26
+ - giving permission to send Personal Information to improve efficiency of communications
27
+ - `subject` and `info` can be used to decide what information to send
28
+ - `subject` can be `notification` or `autofill` of visible UI elements
29
+ - `info.token` is a unique identifier for the request and can be used to trace PII flow further in our systems
30
+ - `keywords`, `category` and `items` are used to fine-tune ads to display
31
+
32
+ Example
33
+ ```tsx
34
+ <ThanksWidget
35
+ partnerId='{your partner id}'
36
+ // information for the first scren
37
+ statusText='Your order has been confirmed'
38
+ emailHash={{ sha256: customersEmailHash }}
39
+ // or
40
+ email={customerEmail}
41
+ onPersonalInformationRequest={(subject, info) => {
42
+ return {
43
+ email,
44
+ firstName: 'TestUser',
45
+ };
46
+ }}
47
+
48
+ onDisplay={() => {
49
+ console.log('widget displayed');
50
+ }}
51
+ onClose={() => {
52
+ console.log('widget closed');
53
+ }}
54
+
55
+ keywords={[
56
+ 'violet',
57
+ 'roses',
58
+ 'blueberry',
59
+ ]}
60
+ items={[
61
+ {
62
+ 'name': 'Flatwhite',
63
+ 'value': 4.00,
64
+ 'currency': 'AUD',
65
+ 'quantity': 2,
66
+ 'type': 'coffee',
67
+ 'category': 'drinks',
68
+ 'subcategory': 'australian-coffee',
69
+ },
70
+ ]}
71
+ categories={[
72
+ 'lifestyle',
73
+ ]}
74
+ />;
75
+ ```
76
+
77
+ # License
78
+ MIT
@@ -0,0 +1,3 @@
1
+ var W="INIT_TUNNEL",k="TUNNEL_READY",E="rocket-seed";var w=e=>typeof e=="object"&&"seed"in e&&e.seed===E&&"cmd"in e?e:{error:!0},D=(e,r)=>({cmd:e,payload:r,seed:E}),M=(e,r,t)=>{(e[r]||[]).forEach(o=>o(t))};var v=(e,r)=>{let t={},o=!1,c=n=>{let{cmd:i,payload:a,error:d}=w(n);d?console.log("error decoding event",n,d):M(t,i,a)};return{on(n,i){(t[n]=t[n]||[]).push(i)},isReady(){return o},pushMessage:n=>{let{cmd:i,payload:a,error:d}=w(n);if(o){c(n);return}!d&&i===W&&a===e&&(o=!0,M(t,k,void 0))},send(n,i){if(!o)throw new Error("trying to `send` into non initialized tunnel");r(D(n,i))}}};var U=e=>{let r=Object.entries(e).filter(([t,o])=>!!o);return Object.fromEntries(r)},_=(e,r={})=>{let t={...r};return t.__forceAd=e.__forceAd,t.impressionId=e.impressionId,new URLSearchParams(U(t)).toString()},A=(e="production")=>e==="development"?"http://localhost:8081/":e==="staging"?"https://thanksad.dev/":"https://thanks.is/";var R={["xs"]:360,["sm"]:768,["md"]:1024,["lg"]:1200,["xl"]:1350};var V=()=>typeof window>"u"||window.innerWidth>=R.sm?"desktop":"mobile";var j=e=>Array.from(new Uint8Array(e)).map(r=>r.toString(16).padStart(2,"0")).join("");var N=async e=>{if(!e)return;let r=new TextEncoder().encode(e);return j(await crypto.subtle.digest("SHA-256",r))};var B=async(e,r)=>{let t=e?await(typeof e=="function"?e({encode:N}):N(e)):"";return Object.assign({sha256:t},r)};var G=async e=>{let{partnerId:r,email:t,userId:o,traceId:c=o,keywords:p,items:n,flags:i={},style:a}=e,{switches:d,...x}=i,l=(await B(t,e.emailHash)).sha256;return _(x,{partnerId:r,emailHash:l,traceId:c,deviceMode:V(),keywords:p?p.join(","):void 0,items:JSON.stringify(n),embedTheme:a?.embed?.theme})},S=async(e,r)=>`${A(r)}?${await G(e)}`;import{useCallback as C,useEffect as I,useRef as xe,useState as b}from"react";import{Modal as Ce,Linking as be}from"react-native";import{WebView as Ee}from"react-native-webview";import{View as K,Modal as Z}from"react-native";import Te from"react-native-webview";import{Text as J,View as Y,TouchableOpacity as Q}from"react-native";var O=({onBack:e})=><Y style={{height:150,justifyContent:"center",alignItems:"flex-end",backgroundColor:"#000",width:"100%"}}><Q onPress={e}><J style={{color:"#FFF"}}>Close</J></Q></Y>;import{Component as X}from"react";var T=class extends X{state={error:null};static getDerivedStateFromError(r){return{error:r}}componentDidCatch(r,t){this.props.onError(r,t.componentStack)}render(){return this.state.error?null:this.props.children}};var L=({uri:e,onClose:r})=><T onError={r}><Z animationType="slide"onRequestClose={r}><K style={{flex:1,justifyContent:"center",alignItems:"center",display:"flex",flexDirection:"column"}}><O onBack={r}/><Te source={{uri:e}}onError={t=>{console.error("visible page error",t),r()}}onContentProcessDidTerminate={t=>{console.error("visible page terminate",t),r()}}onRenderProcessGone={t=>{console.error("visible render gone",t),r()}}style={{flex:1}}/><O onBack={r}/></K></Z></T>;var we=`
2
+ window.onerror = (e) => { console.error(e); window.ReactNativeWebView.postMessage({cmd: 'error', payload: e.message});}
3
+ `,Me=({uri:e,animationType:r="slide",thanks:t,onClose:o,onLinkClick:c,children:p})=>{let n=xe(),i=C(s=>{n.current=s},[]),[a,d]=b("idle"),x=C(()=>{d("closed")},[]),l=C(()=>{d("complete")},[]),[m]=b(()=>v("rocket",$=>{n.current?.injectJavaScript(`window.messageToThanks(${JSON.stringify($)});`)})),F=C(s=>{m.pushMessage(JSON.parse(s.nativeEvent.data))},[]),[P,H]=b();return I(()=>(m.on(k,()=>{m.send("init",{mode:"mobile",topOffset:t.offsetTop?.(),statusText:t.statusText,referral:"react-native"}),d("active"),t.onDisplay?.()}),m.on("close",()=>{x()}),m.on("terminate",()=>{l()}),m.on("click",s=>{(!c||c(s))&&be.openURL(s)}),()=>{m.isReady()&&m.send("close"),t.onClose?.()}),[]),I(()=>{a==="complete"&&o()},[a]),a==="complete"?null:<T onError={x}><Ce onRequestClose={x}animationType={r}transparent><Ee ref={i}source={{uri:e,headers:{referer:"https://react-native","x-thanksjs-integration-mode":"react-native"}}}onMessage={F}onError={s=>{console.error("page error",s),l()}}onContentProcessDidTerminate={s=>{console.error("page terminate",s),l()}}onRenderProcessGone={s=>{console.error("render gone",s),l()}}onLoad={s=>{}}style={{flex:1,backgroundColor:"transparent"}}webviewDebuggingEnabled injectedJavaScript={we}/>{P?<L uri={P}onClose={()=>H(void 0)}/>:null}{p}</Ce></T>},kr=({onLinkClick:e,onClose:r,env:t="production",animationType:o,children:c,...p})=>{let[n,i]=b();I(()=>{S(p,t).then(d=>{i(d)})},[]);let a=C(()=>{i(void 0),r?.()},[]);return n?<Me uri={n}thanks={p}onClose={a}onLinkClick={e}animationType={o}>{c}</Me>:null};export{kr as ThanksWidget};
@@ -0,0 +1,210 @@
1
+ import { FC } from 'react';
2
+ import { PropsWithChildren } from 'react';
3
+
4
+ declare type MayBePromise<T> = T | Promise<T>;
5
+
6
+ /**
7
+ * A user defined callback to retrieve PII information
8
+ */
9
+ declare type PersonalInformationRequest = (
10
+ subject: PersonalInformationRequestSubject,
11
+ info: PersonalInformationRequestDetails,
12
+ ) => MayBePromise<PersonalInformationTransfer>;
13
+
14
+ declare type PersonalInformationRequestDetails = {
15
+ token: string;
16
+ offerType: 'coupon' | 'subscription' | 'transaction';
17
+ advertiser: string;
18
+ };
19
+
20
+ declare type PersonalInformationRequestSubject = 'notification' | 'autofill';
21
+
22
+ declare type PersonalInformationTransfer =
23
+ | Partial<{
24
+ email: string;
25
+ firstName: string;
26
+ }>
27
+ | undefined;
28
+
29
+ /**
30
+ * @public widget-to-user callbacks
31
+ */
32
+ export declare type ThanksCallbacks = {
33
+ /**
34
+ * Callback for handling link clicks
35
+ * @returns true or do not set to let Thanks handle clicks itself
36
+ * @returns false to handle it manually. Pass information to display as `children` prop to display in within Thanks Modal
37
+ */
38
+ onLinkClick?: undefined | ((url: string) => boolean);
39
+ /**
40
+ * callback on widget close
41
+ */
42
+ onClose?: undefined | (() => void);
43
+ };
44
+
45
+ /**
46
+ * A configuration options for the Thanks widget
47
+ * @public
48
+ */
49
+ export declare type ThanksConfiguration =
50
+ | ThanksWidgetConfiguration
51
+ | ThanksEmbedConfiguration;
52
+
53
+ declare type ThanksEmailHashConfiguration = {
54
+ sha256: string;
55
+ };
56
+
57
+ declare type ThanksEmbedConfiguration = ThanksSharedConfiguration & {
58
+ /**
59
+ * specifies an element for embedded widget
60
+ * this option will suppress automatic widget Display
61
+ */
62
+ embeddedInto: HTMLElement;
63
+ };
64
+
65
+ declare type ThanksLocation = {
66
+ country?: 'AU' | 'US';
67
+ postcode?: string;
68
+ };
69
+
70
+ /**
71
+ * A configuration options for the Thanks widget
72
+ * @public
73
+ */
74
+ declare type ThanksSharedConfiguration = {
75
+ /**
76
+ * configures the message to be displayed on the first screen
77
+ */
78
+ statusText?: string;
79
+
80
+ /**
81
+ * email configuration. Accepts raw email, or a factory to produce hash.
82
+ * Real email will be never transfered to any other system
83
+ * @example
84
+ * ```ts
85
+ * // will be hashed on thanks side
86
+ * email: 'hey@jointhanks.com',
87
+ * // will be hashed on partner side
88
+ * email: (encode) => encode('hey@jointhanks.com'),
89
+ * ```
90
+ * @see {@link emailHash} - one can specify `emailHash` directly
91
+ */
92
+ email?:
93
+ | string
94
+ | ((factory: {
95
+ encode: (
96
+ email: string | undefined,
97
+ ) => Promise<string | undefined>;
98
+ }) => Promise<string | undefined>)
99
+ | undefined;
100
+ /**
101
+ * provides informating about user's email in a pre-hashed way
102
+ * @example
103
+ * ```ts
104
+ * emailHash: { sha256: sha256HashedEmail }
105
+ * ```
106
+ */
107
+ emailHash?: ThanksEmailHashConfiguration;
108
+
109
+ /**
110
+ * a callback to retrieve personal information
111
+ * @see how we process PII
112
+ */
113
+ onPersonalInformationRequest?: PersonalInformationRequest;
114
+ /**
115
+ * location of a customer
116
+ */
117
+ customerLocation?: ThanksLocation;
118
+ /**
119
+ * location of performed activity (if applicable)
120
+ */
121
+ purchaseLocation?: ThanksLocation;
122
+ /**
123
+ * @deprecated use traceId
124
+ */
125
+ userId?: string;
126
+ /**
127
+ * an unique id for partner to trace widget activity from their side
128
+ */
129
+ traceId?: string;
130
+
131
+ /**
132
+ * a set of keywords related to this activity
133
+ */
134
+ keywords?: string[];
135
+ /**
136
+ * a set of categories related to this activity
137
+ */
138
+ categories?: string[];
139
+ /**
140
+ * a set of items purchased during this activity
141
+ */
142
+ items?: any[];
143
+
144
+ /**
145
+ * a style to be passed to the Widget
146
+ */
147
+ style?: {
148
+ /**
149
+ * @defaultValue 100
150
+ */
151
+ zIndex?: number;
152
+ /**
153
+ * embed settings
154
+ */
155
+ embed?: {
156
+ theme?: string;
157
+ };
158
+ };
159
+ /**
160
+ * A function returning offset from page top for the widget
161
+ * @defaultValue 70
162
+ */
163
+ offsetTop?(): number;
164
+
165
+ /**
166
+ * used defined callback, called on Widget display
167
+ */
168
+ onDisplay?(): void;
169
+ /**
170
+ * used defined callback, called on Widget close.
171
+ * onClose without corresponding `onDisplay` usually means failure
172
+ */
173
+ onClose?(): void;
174
+
175
+ /**
176
+ * a command available to the user.
177
+ * force closes Widget
178
+ */
179
+ closeWidget?(): void;
180
+
181
+
182
+
183
+ /**
184
+ * overrides `partnerId` detected by the referral
185
+ * works only of "unlocked" hosts, contact Thanks for details
186
+ */
187
+ partnerId?: string;
188
+ };
189
+
190
+ /**
191
+ * @public
192
+ */
193
+ export declare type ThanksStyleProps = {
194
+ animationType?: 'slide' | 'fade' | 'none' | undefined;
195
+ };
196
+
197
+ /**
198
+ * Renders ThanksWidget
199
+ * @param onLinkClick - callback for handling link clicks
200
+ * @param onClose - callback for handling widget close
201
+ * @param animationType - animation type for modal
202
+ * @param configuration - configuration for ThanksWidget
203
+ *
204
+ * @public ThanksWidget itself
205
+ */
206
+ export declare const ThanksWidget: FC<Omit<ThanksConfiguration, 'partnerId'> & Pick<Required<ThanksConfiguration>, 'partnerId'> & ThanksCallbacks & ThanksStyleProps & PropsWithChildren>;
207
+
208
+ declare type ThanksWidgetConfiguration = ThanksSharedConfiguration;
209
+
210
+ export { }
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@thanksjs/react-native-webview",
3
+ "version": "0.0.1-beta.0",
4
+ "description": "ThanksJS React Native WebView",
5
+ "homepage": "https://thanks.co/",
6
+ "license": "MIT",
7
+ "author": "Anton Korzunov <thekashey@gmail.com> (https://github.com/theKashey)",
8
+ "main": "dist/esm/index.js",
9
+ "react-native": "dist/esm/index.js",
10
+ "types": "dist/react-native-webview-public.d.ts",
11
+ "dependencies": {
12
+ "react-native-webview": "^13.6.3"
13
+ },
14
+ "peerDependencies": {
15
+ "react": "*",
16
+ "react-native": "*"
17
+ },
18
+ "engines": {
19
+ "node": ">= 18.0.0"
20
+ },
21
+ "publishConfig": {
22
+ "registry": "https://registry.npmjs.org/"
23
+ }
24
+ }