@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 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={["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=`
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>},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};
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
- * configures the message to be displayed on the first screen
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 informating about user's email in a pre-hashed way
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 to be passed to the Widget
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
- * used defined callback, called on Widget display
190
+ * user defined callback, called on Widget display
167
191
  */
168
192
  onDisplay?(): void;
169
193
  /**
170
- * used defined callback, called on Widget close.
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
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thanksjs/react-native-webview",
3
- "version": "0.0.1-beta.0",
3
+ "version": "0.0.1-beta.1",
4
4
  "description": "ThanksJS React Native WebView",
5
5
  "homepage": "https://thanks.co/",
6
6
  "license": "MIT",