Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

react-google-contacts

Package Overview
Dependencies
Maintainers
2
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-google-contacts - npm Package Compare versions

Comparing version 1.0.1 to 2.0.0

4

CHANGELOG.md

@@ -0,1 +1,5 @@

## 2.0.0
- Contact API deprecation solved and People API implementation added
## 1.0.1

@@ -2,0 +6,0 @@

12

package.json
{
"name": "react-google-contacts",
"version": "1.0.1",
"version": "2.0.0",
"description": "A Google Button to import user's gmail contacts",

@@ -43,8 +43,4 @@ "main": "dist/google-contacts.js",

"dependencies": {
"@types/react": "17.0.0",
"buffer": "6.0.3",
"prop-types": "^15.6.0",
"stream": "0.0.2",
"terser-webpack-plugin": "5.1.1",
"xml-js": "1.6.11"
"@types/react": "*",
"prop-types": "^15.6.0"
},

@@ -75,3 +71,3 @@ "devDependencies": {

"webpack": "5.17.0",
"webpack-cli": "4.4.0",
"webpack-cli": "4.10.0",
"webpack-dev-server": "3.11.2"

@@ -78,0 +74,0 @@ },

/* eslint-disable no-underscore-dangle */
/* eslint-disable better-mutation/no-mutating-methods */
import React, { Component } from "react";
import PropTypes from "prop-types";
import xml from "xml-js";
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Icon from "./icon";
import ButtonContent from "./button-content";
import Icon from './icon'
import ButtonContent from './button-content'
import {
extractTitleFromEntry,
extractGivenNameFromEntry,
extractFamilyNameFromEntry,
extractEmailFromEntry,
extractPhoneNumberFromEntry,
} from "./utils";
const SCOPE = 'https://www.googleapis.com/auth/contacts.other.readonly'
const SCOPE = "https://www.googleapis.com/auth/contacts.readonly";
// const MAX_RESULTS = '999' // TODO Make this parametable or paginate
class GoogleContacts extends Component {
constructor(props) {
super(props);
this.signIn = this.signIn.bind(this);
this.handleImportContacts = this.handleImportContacts.bind(this);
this.handleParseContacts = this.handleParseContacts.bind(this);
super(props)
this.signIn = this.signIn.bind(this)
this.handleImportContacts = this.handleImportContacts.bind(this)
this.handleParseContacts = this.handleParseContacts.bind(this)
this.state = {
hovered: false,
active: false,
};
active: false
}
this.allData = []
}
componentDidMount() {
const { jsSrc } = this.props;
((d, s, id, cb) => {
const element = d.getElementsByTagName(s)[0];
const fjs = element;
let js = element;
js = d.createElement(s);
js.id = id;
js.src = jsSrc;
this.allData = []
const { jsSrc } = this.props
;((d, s, id, cb) => {
const element = d.getElementsByTagName(s)[0]
const fjs = element
let js = element
js = d.createElement(s)
js.id = id
js.src = jsSrc
if (fjs && fjs.parentNode) {
fjs.parentNode.insertBefore(js, fjs);
fjs.parentNode.insertBefore(js, fjs)
} else {
d.head.appendChild(js);
d.head.appendChild(js)
}
js.onload = cb;
})(document, "script", "google-contacts");
js.onload = cb
})(document, 'script', 'google-contacts')
}
handleImportContacts(res) {
const { onFailure } = this.props;
handleImportContacts(res, pageToken = null) {
const { onFailure, maxResults } = this.props
if (res) {
const authResponse = res.getAuthResponse();
window.gapi.load("client", () => {
const authResponse = res.getAuthResponse()
window.gapi.load('client', () => {
window.gapi.client
.request({
path: "/m8/feeds/contacts/default/full",
params: { "max-results": this.props.maxResults },
path: 'https://people.googleapis.com/v1/otherContacts',
params: {
readMask: 'names,emailAddresses',
pageSize: maxResults > 1000 ? 1000 : maxResults,
...(pageToken && { pageToken })
},
headers: {
"GData-Version": "3.0",
Authorization: `Bearer ${authResponse.access_token}`,
},
'GData-Version': '3.0',
Authorization: `Bearer ${authResponse.access_token}`
}
})
.then(
(response) => this.handleParseContacts(response),
(err) => onFailure(err)
);
});
response => this.handleNextDataFetch(response, res),
err => onFailure(err)
)
})
}
}
handleParseContacts(response) {
const { onSuccess } = this.props;
handleNextDataFetch(response, authResponse) {
const { maxResults } = this.props
// Parse the response body
const parsedData = JSON.parse(response.body)
// Now let's parse the XML...
const options = {
ignoreDeclaration: true,
ignoreComment: true,
compact: true,
};
const parsed = xml.xml2js(response.body, options);
// Store the fetched data so that we can use it later
this.allData = [...this.allData, ...parsedData.otherContacts]
// Iterate over each contact.
const results = [];
// If we have more data and the number of data we fethced is less than maxResults then fetch again using the nextPageToken
if ('nextPageToken' in parsedData && maxResults < this.allData.length) {
this.handleImportContacts(authResponse, parsedData.nextPageToken)
} else {
this.handleParseContacts()
}
}
Object.keys(parsed.feed.entry).forEach((key) => {
if (
parsed.feed.entry[key] &&
parsed.feed.entry[key]["gd:email"] &&
parsed.feed.entry[key]["gd:email"]._attributes &&
parsed.feed.entry[key]["gd:email"]._attributes.address
) {
handleParseContacts() {
const { onSuccess, onFailure } = this.props
const results = []
try {
for (let index = 0; index < this.allData.length; index += 1) {
const element = this.allData[index]
results.push({
title: extractTitleFromEntry(parsed.feed.entry[key]),
givenName: extractGivenNameFromEntry(parsed.feed.entry[key]),
familyName: extractFamilyNameFromEntry(parsed.feed.entry[key]),
email: extractEmailFromEntry(parsed.feed.entry[key]),
phoneNumber: extractPhoneNumberFromEntry(parsed.feed.entry[key]),
});
email: element.emailAddresses[0].value,
title: 'names' in element ? element.names[0].displayName : element.emailAddresses[0].value
})
}
});
onSuccess(results);
onSuccess(results)
} catch (error) {
onFailure('Error to fetch contact')
}
}
signIn(e) {
this.allData = []
const {

@@ -123,5 +119,7 @@ clientId,

prompt,
onSuccess,
} = this.props;
onSuccess
} = this.props
const { disable } = this.state
const params = {

@@ -136,38 +134,37 @@ client_id: clientId,

scope: SCOPE,
access_type: accessType,
};
access_type: accessType
}
if (responseType === "code") {
params.access_type = "offline";
if (responseType === 'code') {
params.access_type = 'offline'
}
if (e) {
e.preventDefault(); // to prevent submit if used within form
e.preventDefault() // to prevent submit if used within form
}
if (!this.state.disabled) {
if (!disable) {
const _signIn = () => {
const auth2 = window.gapi.auth2.getAuthInstance();
const options = { prompt };
onRequest();
if (responseType === "code") {
auth2
.grantOfflineAccess(options)
.then((res) => onSuccess(res), (err) => onFailure(err));
const auth2 = window.gapi.auth2.getAuthInstance()
const options = { prompt }
onRequest()
if (responseType === 'code') {
auth2.grantOfflineAccess(options).then(
res => onSuccess(res),
err => onFailure(err)
)
} else {
auth2
.signIn(options)
.then(
(res) => this.handleImportContacts(res),
(err) => onFailure(err)
);
auth2.signIn(options).then(
res => this.handleImportContacts(res),
err => onFailure(err)
)
}
};
}
window.gapi.load("auth2", () => {
window.gapi.load('auth2', () => {
if (!window.gapi.auth2.getAuthInstance()) {
window.gapi.auth2.init(params).then(_signIn);
window.gapi.auth2.init(params).then(_signIn)
} else {
_signIn();
_signIn()
}
});
})
}

@@ -177,64 +174,55 @@ }

render() {
const {
tag,
type,
className,
disabledStyle,
buttonText,
children,
render,
theme,
icon,
} = this.props;
const disabled = this.state.disabled || this.props.disabled;
const { tag, type, className, disabledStyle, buttonText, children, render, theme, icon, disabled: disabledProps } = this.props
const { active, hovered, disabled: disabledState } = this.state
const disabled = disabledState || disabledProps
if (render) {
return render({ onClick: this.signIn });
return render({ onClick: this.signIn })
}
const initialStyle = {
backgroundColor: theme === "dark" ? "rgb(66, 133, 244)" : "#fff",
display: "inline-flex",
alignItems: "center",
color: theme === "dark" ? "#fff" : "rgba(0, 0, 0, .54)",
boxShadow: "0 2px 2px 0 rgba(0, 0, 0, .24), 0 0 1px 0 rgba(0, 0, 0, .24)",
backgroundColor: theme === 'dark' ? 'rgb(66, 133, 244)' : '#fff',
display: 'inline-flex',
alignItems: 'center',
color: theme === 'dark' ? '#fff' : 'rgba(0, 0, 0, .54)',
boxShadow: '0 2px 2px 0 rgba(0, 0, 0, .24), 0 0 1px 0 rgba(0, 0, 0, .24)',
padding: 0,
borderRadius: 2,
border: "1px solid transparent",
border: '1px solid transparent',
fontSize: 14,
fontWeight: "500",
fontFamily: "Roboto, sans-serif",
};
fontWeight: '500',
fontFamily: 'Roboto, sans-serif'
}
const hoveredStyle = {
cursor: "pointer",
opacity: 0.9,
};
cursor: 'pointer',
opacity: 0.9
}
const activeStyle = {
cursor: "pointer",
backgroundColor: theme === "dark" ? "#3367D6" : "#eee",
color: theme === "dark" ? "#fff" : "rgba(0, 0, 0, .54)",
opacity: 1,
};
cursor: 'pointer',
backgroundColor: theme === 'dark' ? '#3367D6' : '#eee',
color: theme === 'dark' ? '#fff' : 'rgba(0, 0, 0, .54)',
opacity: 1
}
const defaultStyle = (() => {
if (disabled) {
return Object.assign({}, initialStyle, disabledStyle);
return Object.assign({}, initialStyle, disabledStyle)
}
if (this.state.active) {
if (theme === "dark") {
return Object.assign({}, initialStyle, activeStyle);
if (active) {
if (theme === 'dark') {
return Object.assign({}, initialStyle, activeStyle)
}
return Object.assign({}, initialStyle, activeStyle);
return Object.assign({}, initialStyle, activeStyle)
}
if (this.state.hovered) {
return Object.assign({}, initialStyle, hoveredStyle);
if (hovered) {
return Object.assign({}, initialStyle, hoveredStyle)
}
return initialStyle;
})();
return initialStyle
})()
const googleLoginButton = React.createElement(

@@ -251,13 +239,13 @@ tag,

disabled,
className,
className
},
[
icon && <Icon key={1} active={this.state.active} />,
icon && <Icon key={1} active={active} />,
<ButtonContent key={2} icon={icon}>
{children || buttonText}
</ButtonContent>,
</ButtonContent>
]
);
)
return googleLoginButton;
return googleLoginButton
}

@@ -267,48 +255,48 @@ }

GoogleContacts.propTypes = {
onSuccess: PropTypes.func.isRequired,
onFailure: PropTypes.func.isRequired,
clientId: PropTypes.string.isRequired,
jsSrc: PropTypes.string,
onRequest: PropTypes.func,
accessType: PropTypes.string,
buttonText: PropTypes.node,
children: PropTypes.node,
className: PropTypes.string,
redirectUri: PropTypes.string,
clientId: PropTypes.string.isRequired,
cookiePolicy: PropTypes.string,
disabled: PropTypes.bool,
disabledStyle: PropTypes.object,
discoveryDocs: PropTypes.array,
hostedDomain: PropTypes.string,
icon: PropTypes.bool,
jsSrc: PropTypes.string,
loginHint: PropTypes.string,
hostedDomain: PropTypes.string,
children: PropTypes.node,
disabledStyle: PropTypes.object,
maxResults: PropTypes.number,
onFailure: PropTypes.func.isRequired,
onRequest: PropTypes.func,
onSuccess: PropTypes.func.isRequired,
prompt: PropTypes.string,
redirectUri: PropTypes.string,
render: PropTypes.func,
responseType: PropTypes.string,
tag: PropTypes.string,
disabled: PropTypes.bool,
discoveryDocs: PropTypes.array,
uxMode: PropTypes.string,
responseType: PropTypes.string,
theme: PropTypes.string,
type: PropTypes.string,
accessType: PropTypes.string,
render: PropTypes.func,
theme: PropTypes.string,
icon: PropTypes.bool,
maxResults: PropTypes.number,
};
uxMode: PropTypes.string
}
GoogleContacts.defaultProps = {
type: "button",
tag: "button",
buttonText: "Import from Gmail",
accessType: "online",
prompt: "consent",
cookiePolicy: "single_host_origin",
uxMode: "popup",
accessType: 'online',
buttonText: 'Import from Gmail',
cookiePolicy: 'single_host_origin',
disabled: false,
maxResults: 999,
disabledStyle: {
opacity: 0.6,
opacity: 0.6
},
icon: true,
theme: "light",
jsSrc: 'https://apis.google.com/js/api.js',
maxResults: 999,
onRequest: () => {},
jsSrc: "https://apis.google.com/js/api.js",
};
prompt: 'consent',
tag: 'button',
theme: 'light',
type: 'button',
uxMode: 'popup'
}
export default GoogleContacts;
export default GoogleContacts
import React from 'react'
export default ({ active }) => (
<div style={{ marginRight: 10, background: active ? '#eee' : '#fff', padding: 10, borderRadius: 2 }}>
<svg width="18" height="18" xmlns="http://www.w3.org/2000/svg">
<g fill="#000" fillRule="evenodd">
<path
d="M9 3.48c1.69 0 2.83.73 3.48 1.34l2.54-2.48C13.46.89 11.43 0 9 0 5.48 0 2.44 2.02.96 4.96l2.91 2.26C4.6 5.05 6.62 3.48 9 3.48z"
fill="#EA4335"
/>
<path d="M17.64 9.2c0-.74-.06-1.28-.19-1.84H9v3.34h4.96c-.1.83-.64 2.08-1.84 2.92l2.84 2.2c1.7-1.57 2.68-3.88 2.68-6.62z" fill="#4285F4" />
<path
d="M3.88 10.78A5.54 5.54 0 0 1 3.58 9c0-.62.11-1.22.29-1.78L.96 4.96A9.008 9.008 0 0 0 0 9c0 1.45.35 2.82.96 4.04l2.92-2.26z"
fill="#FBBC05"
/>
<path
d="M9 18c2.43 0 4.47-.8 5.96-2.18l-2.84-2.2c-.76.53-1.78.9-3.12.9-2.38 0-4.4-1.57-5.12-3.74L.97 13.04C2.45 15.98 5.48 18 9 18z"
fill="#34A853"
/>
<path fill="none" d="M0 0h18v18H0z" />
</g>
</svg>
</div>
)
export default function Icon({ active }) {
return (
<div
style={{
background: active ? '#eee' : '#fff',
borderRadius: 2,
marginRight: 10,
padding: 10
}}
>
<svg height="18" width="18" xmlns="http://www.w3.org/2000/svg">
<g fill="#000" fillRule="evenodd">
<path
d="M9 3.48c1.69 0 2.83.73 3.48 1.34l2.54-2.48C13.46.89 11.43 0 9 0 5.48 0 2.44 2.02.96 4.96l2.91 2.26C4.6 5.05 6.62 3.48 9 3.48z"
fill="#EA4335"
/>
<path
d="M17.64 9.2c0-.74-.06-1.28-.19-1.84H9v3.34h4.96c-.1.83-.64 2.08-1.84 2.92l2.84 2.2c1.7-1.57 2.68-3.88 2.68-6.62z"
fill="#4285F4"
/>
<path
d="M3.88 10.78A5.54 5.54 0 0 1 3.58 9c0-.62.11-1.22.29-1.78L.96 4.96A9.008 9.008 0 0 0 0 9c0 1.45.35 2.82.96 4.04l2.92-2.26z"
fill="#FBBC05"
/>
<path
d="M9 18c2.43 0 4.47-.8 5.96-2.18l-2.84-2.2c-.76.53-1.78.9-3.12.9-2.38 0-4.4-1.57-5.12-3.74L.97 13.04C2.45 15.98 5.48 18 9 18z"
fill="#34A853"
/>
<path d="M0 0h18v18H0z" fill="none" />
</g>
</svg>
</div>
)
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc