@thanksjs/react-native-webview 0.0.1-beta.0 β 0.0.1-beta.1
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 +40 -2
- package/dist/esm/index.js +2 -2
- package/dist/react-native-webview-public.d.ts +37 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
Thanks JS react native adapter
|
|
2
|
-
|
|
3
2
|
===
|
|
4
|
-
|
|
3
|
+
[ThanksJS React-Native](https://www.npmjs.com/package/@thanksjs/react-native-webview)
|
|
4
|
+
| [ThanksJS React](https://www.npmjs.com/package/@thanksjs/react)
|
|
5
|
+
| [ThanksJS Web](https://www.npmjs.com/package/@thanksjs/web)
|
|
6
|
+
| [Integration Examples](https://github.com/thanksad/Thanks-examples)
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```bash
|
|
@@ -19,6 +21,7 @@ import { ThanksWidget } from '@thanksjs/react-native-webview';
|
|
|
19
21
|
// other available props, see Thanks Configuration section
|
|
20
22
|
/>
|
|
21
23
|
```
|
|
24
|
+
It's all comes with TypeScript support, dont worry.
|
|
22
25
|
|
|
23
26
|
### Advanced usage
|
|
24
27
|
The important parts are:
|
|
@@ -74,5 +77,40 @@ Example
|
|
|
74
77
|
/>;
|
|
75
78
|
```
|
|
76
79
|
|
|
80
|
+
## Managing Personal Information
|
|
81
|
+
By default, Thanks Widget does not send any personal information.
|
|
82
|
+
The `email` specified in configuration is always converted into sha256 `emailHash` before being sent to server.
|
|
83
|
+
All other extended information provided is used only to improve the efficiency of the widget and is not stored unless user performs an action.
|
|
84
|
+
In such case the information will kept until the action is settled, but no longer than 60 days.
|
|
85
|
+
|
|
86
|
+
However, there are situations when we need Partner to provide Personal Information:
|
|
87
|
+
- to improve our communications and send email notification to the user following their actions
|
|
88
|
+
- prefill a form with user's data to ease their interactions
|
|
89
|
+
|
|
90
|
+
In both case we are going to call `onPersonalInformationRequest` function with `subject` and `info` arguments.
|
|
91
|
+
Depending on request you may decide to return some information, or return nothing.
|
|
92
|
+
```tsx
|
|
93
|
+
onPersonalInformationRequest: (subject:'notification' | 'autofill', info) => {
|
|
94
|
+
return {
|
|
95
|
+
email,
|
|
96
|
+
firstName: 'TestUser',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
- subject of type `notification` will be used to send email notification to the user about the action just taken, for example coupon code they just claimed
|
|
101
|
+
- subject of type `autofill` will be used to prefill a form with user's data, making the subscription process friction less
|
|
102
|
+
|
|
103
|
+
In case of `onPersonalInformationRequest` is not defined, but `email` is provided as a part of configuraiton - nothing will happen.
|
|
104
|
+
The process of capturing PII information is always in the Partners hands.
|
|
105
|
+
|
|
106
|
+
π‘It's not always known upfront if PII is required, so `onPersonalInformationRequest` will be called more often than PII information is being "consumed".
|
|
107
|
+
In case it was not required, the information will be discarded.
|
|
108
|
+
|
|
109
|
+
### Using PII information for Promotions
|
|
110
|
+
While not every Ad requires PII, some Ads can have a "Promotion" attached to them,
|
|
111
|
+
for example "Subscribe to XYZ newsletter to enter the draw".
|
|
112
|
+
In case of user action PII information might not be used by a particular Ad, but will be used by Promotion to keep track of participation.
|
|
113
|
+
The information will be redacted after the Promotion ends.
|
|
114
|
+
|
|
77
115
|
# License
|
|
78
116
|
MIT
|
package/dist/esm/index.js
CHANGED
|
@@ -1,3 +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={
|
|
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"react";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"react";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
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>},
|
|
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>},br=({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{br as ThanksWidget};
|
|
@@ -12,11 +12,27 @@ declare type PersonalInformationRequest = (
|
|
|
12
12
|
) => MayBePromise<PersonalInformationTransfer>;
|
|
13
13
|
|
|
14
14
|
declare type PersonalInformationRequestDetails = {
|
|
15
|
+
/**
|
|
16
|
+
* A unique token for this operation.
|
|
17
|
+
* (not implemented) could be used to track PII information flow
|
|
18
|
+
*/
|
|
15
19
|
token: string;
|
|
20
|
+
/**
|
|
21
|
+
* A type of offer, which is requesting information
|
|
22
|
+
*/
|
|
16
23
|
offerType: 'coupon' | 'subscription' | 'transaction';
|
|
24
|
+
/**
|
|
25
|
+
* The advertiser, who is requesting information.
|
|
26
|
+
* Information will be NOT shared with them, unless this the essence of an action (subscription)
|
|
27
|
+
*/
|
|
17
28
|
advertiser: string;
|
|
18
29
|
};
|
|
19
30
|
|
|
31
|
+
/**
|
|
32
|
+
* a type of request for personal information
|
|
33
|
+
* - `notification` - a request to notify user about action just taken, for example send them a coupon code
|
|
34
|
+
* - `autofill` - a request to autofill a form
|
|
35
|
+
*/
|
|
20
36
|
declare type PersonalInformationRequestSubject = 'notification' | 'autofill';
|
|
21
37
|
|
|
22
38
|
declare type PersonalInformationTransfer =
|
|
@@ -73,7 +89,13 @@ declare type ThanksLocation = {
|
|
|
73
89
|
*/
|
|
74
90
|
declare type ThanksSharedConfiguration = {
|
|
75
91
|
/**
|
|
76
|
-
*
|
|
92
|
+
* overrides `partnerId` detected by the referral
|
|
93
|
+
* works only of "unlocked" hosts, contact Thanks for details
|
|
94
|
+
* @recommended
|
|
95
|
+
*/
|
|
96
|
+
partnerId?: string;
|
|
97
|
+
/**
|
|
98
|
+
* configures the message to be displayed at the first screen
|
|
77
99
|
*/
|
|
78
100
|
statusText?: string;
|
|
79
101
|
|
|
@@ -87,7 +109,7 @@ declare type ThanksSharedConfiguration = {
|
|
|
87
109
|
* // will be hashed on partner side
|
|
88
110
|
* email: (encode) => encode('hey@jointhanks.com'),
|
|
89
111
|
* ```
|
|
90
|
-
* @see {@link emailHash} - one can specify `emailHash` directly
|
|
112
|
+
* @see {@link ThanksSharedConfiguration.emailHash} - one can specify `emailHash` directly
|
|
91
113
|
*/
|
|
92
114
|
email?:
|
|
93
115
|
| string
|
|
@@ -98,7 +120,7 @@ declare type ThanksSharedConfiguration = {
|
|
|
98
120
|
}) => Promise<string | undefined>)
|
|
99
121
|
| undefined;
|
|
100
122
|
/**
|
|
101
|
-
* provides
|
|
123
|
+
* provides information about user's email in a pre-hashed way
|
|
102
124
|
* @example
|
|
103
125
|
* ```ts
|
|
104
126
|
* emailHash: { sha256: sha256HashedEmail }
|
|
@@ -113,10 +135,12 @@ declare type ThanksSharedConfiguration = {
|
|
|
113
135
|
onPersonalInformationRequest?: PersonalInformationRequest;
|
|
114
136
|
/**
|
|
115
137
|
* location of a customer
|
|
138
|
+
* @optional
|
|
116
139
|
*/
|
|
117
140
|
customerLocation?: ThanksLocation;
|
|
118
141
|
/**
|
|
119
142
|
* location of performed activity (if applicable)
|
|
143
|
+
* @optional
|
|
120
144
|
*/
|
|
121
145
|
purchaseLocation?: ThanksLocation;
|
|
122
146
|
/**
|
|
@@ -125,36 +149,36 @@ declare type ThanksSharedConfiguration = {
|
|
|
125
149
|
userId?: string;
|
|
126
150
|
/**
|
|
127
151
|
* an unique id for partner to trace widget activity from their side
|
|
152
|
+
* @optional
|
|
128
153
|
*/
|
|
129
154
|
traceId?: string;
|
|
130
155
|
|
|
131
156
|
/**
|
|
132
157
|
* a set of keywords related to this activity
|
|
158
|
+
* @recommended
|
|
133
159
|
*/
|
|
134
160
|
keywords?: string[];
|
|
135
161
|
/**
|
|
136
162
|
* a set of categories related to this activity
|
|
163
|
+
* @recommended
|
|
137
164
|
*/
|
|
138
165
|
categories?: string[];
|
|
139
166
|
/**
|
|
140
167
|
* a set of items purchased during this activity
|
|
168
|
+
* @recommended
|
|
141
169
|
*/
|
|
142
170
|
items?: any[];
|
|
143
171
|
|
|
144
172
|
/**
|
|
145
|
-
* a style
|
|
173
|
+
* a style overrides for the Widget
|
|
146
174
|
*/
|
|
147
175
|
style?: {
|
|
148
176
|
/**
|
|
177
|
+
* z-index for the widget iframe, which is expected to be top-most
|
|
149
178
|
* @defaultValue 100
|
|
150
179
|
*/
|
|
151
180
|
zIndex?: number;
|
|
152
|
-
|
|
153
|
-
* embed settings
|
|
154
|
-
*/
|
|
155
|
-
embed?: {
|
|
156
|
-
theme?: string;
|
|
157
|
-
};
|
|
181
|
+
|
|
158
182
|
};
|
|
159
183
|
/**
|
|
160
184
|
* A function returning offset from page top for the widget
|
|
@@ -163,12 +187,12 @@ declare type ThanksSharedConfiguration = {
|
|
|
163
187
|
offsetTop?(): number;
|
|
164
188
|
|
|
165
189
|
/**
|
|
166
|
-
*
|
|
190
|
+
* user defined callback, called on Widget display
|
|
167
191
|
*/
|
|
168
192
|
onDisplay?(): void;
|
|
169
193
|
/**
|
|
170
|
-
*
|
|
171
|
-
* onClose without corresponding `onDisplay` usually means failure
|
|
194
|
+
* user defined callback, called on Widget close.
|
|
195
|
+
* hint: onClose without corresponding `onDisplay` usually means failure
|
|
172
196
|
*/
|
|
173
197
|
onClose?(): void;
|
|
174
198
|
|
|
@@ -179,12 +203,6 @@ declare type ThanksSharedConfiguration = {
|
|
|
179
203
|
closeWidget?(): void;
|
|
180
204
|
|
|
181
205
|
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* overrides `partnerId` detected by the referral
|
|
185
|
-
* works only of "unlocked" hosts, contact Thanks for details
|
|
186
|
-
*/
|
|
187
|
-
partnerId?: string;
|
|
188
206
|
};
|
|
189
207
|
|
|
190
208
|
/**
|