@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 +78 -0
- package/dist/esm/index.js +3 -0
- package/dist/react-native-webview-public.d.ts +210 -0
- package/package.json +24 -0
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
|
+
}
|