Skip to content

Commit 4662282

Browse files
committed
Merge pull request #17 from georgeOsdDev/develop
Release v0.2.2
2 parents 3968d34 + 6ee4677 commit 4662282

File tree

10 files changed

+154
-36
lines changed

10 files changed

+154
-36
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
## Change Log
22

3-
### Ver 0.2.2 (Next Release)
3+
### Ver 0.2.3 (Next Release)
4+
5+
### Ver 0.2.2
6+
7+
* [#15 Permission being repeatedly asked for in Safari](https://github.com/georgeOsdDev/react-web-notification/issues/11)
48

59
### Ver 0.2.1
610

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ Notification.propTypes = {
4343

4444
* `disableActiveWindow` : if true, nothing will be happen when window is active
4545

46+
* `askAgain` : if true, `window.Notification.requestPermission` will be called on `componentDidMount`, even if it was denied before,
47+
4648
* `notSupported()` : Called when [HTML5 Web Notification API](https://developer.mozilla.org/en/docs/Web/API/notification) is not supported.
4749

4850
* `onPermissionGranted()` : Called when permission granted.
@@ -78,4 +80,12 @@ npm run start:example
7880

7981
```bash
8082
npm test
81-
```
83+
```
84+
85+
86+
### Known Issues
87+
88+
* [Notification.sound](https://github.com/georgeOsdDev/react-web-notification/issues/13)
89+
`Notification.sound` is [not supported in any browser](https://developer.mozilla.org/en/docs/Web/API/notification/sound#Browser_compatibility).
90+
You can emulate it with `onShow` callback. see [example](https://github.com/georgeOsdDev/react-web-notification/tree/develop/example).
91+

example/app.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import React from 'react';
44
import ReactDom from 'react-dom';
5-
import Notification from '../src/components/Notification';
5+
import Notification from '../lib/components/Notification';
66

77
//allow react dev tools work
88
window.React = React;
@@ -48,9 +48,14 @@ class App extends React.Component {
4848
}
4949

5050
handleNotificationOnShow(e, tag){
51+
this.playSound();
5152
console.log(e, 'Notification shown tag:' + tag);
5253
}
5354

55+
playSound(filename){
56+
document.getElementById('sound').play();
57+
}
58+
5459
handleButtonClick() {
5560

5661
if(this.state.ignore) {
@@ -72,7 +77,8 @@ class App extends React.Component {
7277
body: body,
7378
icon: icon,
7479
lang: 'en',
75-
dir: 'ltr'
80+
dir: 'ltr',
81+
sound: './sound.mp3' // no browsers supported https://developer.mozilla.org/en/docs/Web/API/notification/sound#Browser_compatibility
7682
}
7783
this.setState({
7884
title: title,
@@ -98,6 +104,11 @@ class App extends React.Component {
98104
title={this.state.title}
99105
options={this.state.options}
100106
/>
107+
<audio id='sound' preload='auto'>
108+
<source src='./sound.mp3' type='audio/mpeg' />
109+
<source src='./sound.ogg' type='audio/ogg' />
110+
<embed hidden='true' autostart='false' loop='false' src='./sound.mp3' />
111+
</audio>
101112
</div>
102113
)
103114
}

example/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<link href="https://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.3/normalize.min.css" rel="stylesheet">
77
</head>
88
<body>
9-
<a href="https://github.com/georgeosdev/react-web-notification"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"></a>
9+
<a href="https://github.com/georgeosddev/react-web-notification"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"></a>
1010
<div id="out" style="margin: 40px;"></div>
1111
<small>Icons made by <a href="http://www.flaticon.com/authors/google" title="Google">Google</a> from <a href="http://www.flaticon.com" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0">CC BY 3.0</a></small>
1212
<!--[if lt IE 9]>

example/sound.mp3

30.8 KB
Binary file not shown.

example/sound.ogg

32.2 KB
Binary file not shown.

lib/components/Notification.js

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
1919
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
2020

2121
var PERMISSION_GRANTED = 'granted';
22+
var PERMISSION_DENIED = 'denied';
2223

2324
var seqGen = function seqGen() {
2425
var i = 0;
@@ -70,10 +71,26 @@ var Notification = (function (_React$Component) {
7071
this.windowFocus = false;
7172
}
7273
}, {
73-
key: 'componentDidMount',
74-
value: function componentDidMount() {
74+
key: '_askPermission',
75+
value: function _askPermission() {
7576
var _this2 = this;
7677

78+
window.Notification.requestPermission(function (permission) {
79+
var result = permission === PERMISSION_GRANTED;
80+
_this2.setState({
81+
granted: result
82+
}, function () {
83+
if (result) {
84+
_this2.props.onPermissionGranted();
85+
} else {
86+
_this2.props.onPermissionDenied();
87+
}
88+
});
89+
});
90+
}
91+
}, {
92+
key: 'componentDidMount',
93+
value: function componentDidMount() {
7794
if (this.props.disableActiveWindow) {
7895
if (window.addEventListener) {
7996
window.addEventListener('focus', this.onWindowFocus);
@@ -86,22 +103,17 @@ var Notification = (function (_React$Component) {
86103

87104
if (!this.state.supported) {
88105
this.props.notSupported();
106+
} else if (this.state.granted) {
107+
this.props.onPermissionGranted();
89108
} else {
90-
if (this.state.granted) {
91-
this.props.onPermissionGranted();
109+
if (window.Notification.permission === PERMISSION_DENIED) {
110+
if (this.props.askAgain) {
111+
this._askPermission();
112+
} else {
113+
this.props.onPermissionDenied();
114+
}
92115
} else {
93-
window.Notification.requestPermission(function (permission) {
94-
var result = permission === PERMISSION_GRANTED;
95-
_this2.setState({
96-
granted: result
97-
}, function () {
98-
if (result) {
99-
_this2.props.onPermissionGranted();
100-
} else {
101-
_this2.props.onPermissionDenied();
102-
}
103-
});
104-
});
116+
this._askPermission();
105117
}
106118
}
107119
}
@@ -182,6 +194,7 @@ var Notification = (function (_React$Component) {
182194
Notification.propTypes = {
183195
ignore: _react2.default.PropTypes.bool,
184196
disableActiveWindow: _react2.default.PropTypes.bool,
197+
askAgain: _react2.default.PropTypes.bool,
185198
notSupported: _react2.default.PropTypes.func,
186199
onPermissionGranted: _react2.default.PropTypes.func,
187200
onPermissionDenied: _react2.default.PropTypes.func,
@@ -197,6 +210,7 @@ Notification.propTypes = {
197210
Notification.defaultProps = {
198211
ignore: false,
199212
disableActiveWindow: false,
213+
askAgain: false,
200214
notSupported: function notSupported() {},
201215
onPermissionGranted: function onPermissionGranted() {},
202216
onPermissionDenied: function onPermissionDenied() {},

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-web-notification",
3-
"version": "0.2.1",
3+
"version": "0.2.2",
44
"description": "React component with HTML5 Web Notification API",
55
"main": "./lib/components/Notification.js",
66
"scripts": {

src/components/Notification.js

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import React from 'react';
44

55
const PERMISSION_GRANTED = 'granted';
6+
const PERMISSION_DENIED = 'denied';
67

78
const seqGen = () => {
89
let i = 0;
@@ -46,6 +47,21 @@ class Notification extends React.Component {
4647
this.windowFocus = false;
4748
}
4849

50+
_askPermission(){
51+
window.Notification.requestPermission((permission) => {
52+
let result = permission === PERMISSION_GRANTED;
53+
this.setState({
54+
granted: result
55+
}, () => {
56+
if (result) {
57+
this.props.onPermissionGranted();
58+
} else {
59+
this.props.onPermissionDenied();
60+
}
61+
});
62+
});
63+
}
64+
4965
componentDidMount(){
5066
if (this.props.disableActiveWindow) {
5167
if (window.addEventListener){
@@ -59,22 +75,17 @@ class Notification extends React.Component {
5975

6076
if (!this.state.supported) {
6177
this.props.notSupported();
62-
} else {
63-
if(this.state.granted) {
78+
} else if (this.state.granted) {
6479
this.props.onPermissionGranted();
80+
} else {
81+
if (window.Notification.permission === PERMISSION_DENIED){
82+
if (this.props.askAgain){
83+
this._askPermission();
84+
} else {
85+
this.props.onPermissionDenied();
86+
}
6587
} else {
66-
window.Notification.requestPermission( (permission) => {
67-
let result = permission === PERMISSION_GRANTED;
68-
this.setState({
69-
granted: result
70-
}, () => {
71-
if (result) {
72-
this.props.onPermissionGranted();
73-
} else {
74-
this.props.onPermissionDenied();
75-
}
76-
});
77-
});
88+
this._askPermission();
7889
}
7990
}
8091
}
@@ -91,6 +102,8 @@ class Notification extends React.Component {
91102
}
92103
}
93104

105+
106+
94107
render() {
95108
let doNotShowOnActiveWindow = this.props.disableActiveWindow && this.windowFocus;
96109
if (!this.props.ignore && this.props.title && this.state.supported && this.state.granted && !doNotShowOnActiveWindow) {
@@ -138,6 +151,7 @@ class Notification extends React.Component {
138151
Notification.propTypes = {
139152
ignore: React.PropTypes.bool,
140153
disableActiveWindow: React.PropTypes.bool,
154+
askAgain: React.PropTypes.bool,
141155
notSupported: React.PropTypes.func,
142156
onPermissionGranted: React.PropTypes.func,
143157
onPermissionDenied: React.PropTypes.func,
@@ -153,6 +167,7 @@ Notification.propTypes = {
153167
Notification.defaultProps = {
154168
ignore: false,
155169
disableActiveWindow: false,
170+
askAgain: false,
156171
notSupported: () => {},
157172
onPermissionGranted: () => {},
158173
onPermissionDenied: () => {},

test/components/Notification_spec.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Notification from '../../src/components/Notification';
1313

1414
const PERMISSION_GRANTED = 'granted';
1515
const PERMISSION_DENIED = 'denied';
16+
const PERMISSION_DEFAULT = 'default';
1617

1718
describe('Test of Notification', () => {
1819

@@ -26,6 +27,7 @@ describe('Test of Notification', () => {
2627
component = TestUtils.renderIntoDocument(<Notification title='test'/>);
2728
expect(component.props.ignore).to.be.eql(false);
2829
expect(component.props.disableActiveWindow).to.be.eql(false);
30+
expect(component.props.askAgain).to.be.eql(false);
2931
expect(typeof component.props.notSupported).to.be.eql('function');
3032
expect(typeof component.props.onPermissionGranted).to.be.eql('function');
3133
expect(typeof component.props.onPermissionDenied).to.be.eql('function');
@@ -98,6 +100,68 @@ describe('Test of Notification', () => {
98100
});
99101
});
100102

103+
describe('When Notification is already denied', () => {
104+
describe('Check callbacks', ()=> {
105+
106+
let stub1, stub2, spy1, spy2;
107+
before(() => {
108+
spy1 = sinon.spy();
109+
spy2 = sinon.spy();
110+
stub1 = sinon.stub(window.Notification, 'permission', { get: function () { return PERMISSION_DENIED; } });
111+
stub2 = sinon.stub(window.Notification, 'requestPermission', function(cb){
112+
return cb(PERMISSION_DENIED);
113+
});
114+
component = TestUtils.renderIntoDocument(<Notification title='test' notSupported={spy1} onPermissionDenied={spy2}/>);
115+
});
116+
117+
after(() => {
118+
stub1.restore();
119+
stub2.restore();
120+
});
121+
122+
it('should not call window.Notification.requestPermission', () => {
123+
expect(stub2.calledOnce).to.be.eql(false);
124+
});
125+
126+
it('should call onPermissionDenied prop', () => {
127+
expect(spy1.called).to.be.eql(false);
128+
expect(spy2.calledOnce).to.be.eql(true);
129+
});
130+
});
131+
});
132+
133+
describe('When Notification is already denied, but `askAgain` prop is true', () => {
134+
describe('Check callbacks', ()=> {
135+
136+
let stub1, stub2, spy1, spy2, spy3;
137+
before(() => {
138+
spy1 = sinon.spy();
139+
spy2 = sinon.spy();
140+
spy3 = sinon.spy();
141+
stub1 = sinon.stub(window.Notification, 'permission', { get: function () { return PERMISSION_DENIED; } });
142+
stub2 = sinon.stub(window.Notification, 'requestPermission', function(cb){
143+
return cb(PERMISSION_GRANTED);
144+
});
145+
component = TestUtils.renderIntoDocument(<Notification title='test' notSupported={spy1} onPermissionDenied={spy2} onPermissionGranted={spy3} askAgain={true}/>);
146+
});
147+
148+
after(() => {
149+
stub1.restore();
150+
stub2.restore();
151+
});
152+
153+
it('should call window.Notification.requestPermission', () => {
154+
expect(stub2.calledOnce).to.be.eql(true);
155+
});
156+
157+
it('should call onPermissionGranted prop', () => {
158+
expect(spy1.called).to.be.eql(false);
159+
expect(spy2.called).to.be.eql(false);
160+
expect(spy3.calledOnce).to.be.eql(true);
161+
});
162+
});
163+
});
164+
101165
describe('When Notification is granted', () => {
102166
let stub;
103167
before(() => {

0 commit comments

Comments
 (0)