Cannot read a public spreadsheet when signed in, but can when signed out - google-sheets-api

I am using the Google Sheets API for client-side javascript.
I cannot read a public spreadsheet when the user is signed/logged in, but can read it when the user is signed/logged out.
I adapted the quickstart guide:
https://developers.google.com/sheets/api/quickstart/js
However, I purposefully do not want my app to ask for read-only permission on all the user's spreadsheets.
Since the spreadsheet is public, I can also listMajors() (which reads the spreadsheet) when the user is not signed in by adding a line in updateSigninStatus. And this works.
However, with a different set of permissions than
var SCOPES = "https://www.googleapis.com/auth/spreadsheets.readonly"; , I am not able to read the spreadsheet when the user is signed in.
I would expect it would work, because anything that can be done when signed out should also work when signed in.
I use these permissions:
var SCOPES = "https://www.googleapis.com/auth/drive.appdata https://www.googleapis.com/auth/drive.file";
These permissions enable me to write to the AppFolder and read files created by my app.
Why is my expectation wrong? Is it a bug or work as intended? Is there a scope that doesn't require more permissions that I can use to read the spreadsheet when signed in? Alternatively, can I make a request pretending I am not signed in when signed in (I don't want to sign out the user)?
Here is the full example:
<!DOCTYPE html>
<html>
<head>
<title>Google Sheets API Quickstart</title>
<meta charset="utf-8" />
</head>
<body>
<p>Google Sheets API Quickstart</p>
<!--Add buttons to initiate auth sequence and sign out-->
<button id="authorize_button" style="display: none;">Authorize</button>
<button id="signout_button" style="display: none;">Sign Out</button>
<pre id="content" style="white-space: pre-wrap;"></pre>
<script type="text/javascript">
// Client ID and API key from the Developer Console
var CLIENT_ID = '...';
var API_KEY = '...';
// Array of API discovery doc URLs for APIs used
var DISCOVERY_DOCS = ["https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", "https://sheets.googleapis.com/$discovery/rest?version=v4"];
// in quickstart:
//var DISCOVERY_DOCS = ["https://sheets.googleapis.com/$discovery/rest?version=v4"];
// Authorization scopes required by the API; multiple scopes can be
// included, separated by spaces.
var SCOPES = "https://www.googleapis.com/auth/drive.appdata https://www.googleapis.com/auth/drive.file";
// in quickstart:
//var SCOPES = "https://www.googleapis.com/auth/spreadsheets.readonly";
var authorizeButton = document.getElementById('authorize_button');
var signoutButton = document.getElementById('signout_button');
/**
* On load, called to load the auth2 library and API client library.
*/
function handleClientLoad() {
gapi.load('client:auth2', initClient);
}
/**
* Initializes the API client library and sets up sign-in state
* listeners.
*/
function initClient() {
gapi.client.init({
apiKey: API_KEY,
clientId: CLIENT_ID,
discoveryDocs: DISCOVERY_DOCS,
scope: SCOPES
}).then(function () {
// Listen for sign-in state changes.
gapi.auth2.getAuthInstance().isSignedIn.listen(updateSigninStatus);
// Handle the initial sign-in state.
updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get());
authorizeButton.onclick = handleAuthClick;
signoutButton.onclick = handleSignoutClick;
}, function(error) {
console.log(error);
appendPre(JSON.stringify(error, null, 2));
});
}
/**
* Called when the signed in status changes, to update the UI
* appropriately. After a sign-in, the API is called.
*/
function updateSigninStatus(isSignedIn) {
if (isSignedIn) {
authorizeButton.style.display = 'none';
signoutButton.style.display = 'block';
} else {
authorizeButton.style.display = 'block';
signoutButton.style.display = 'none';
}
// get the spreadsheet regardless of login status
listMajors();
}
/**
* Sign in the user upon button click.
*/
function handleAuthClick(event) {
gapi.auth2.getAuthInstance().signIn();
}
/**
* Sign out the user upon button click.
*/
function handleSignoutClick(event) {
gapi.auth2.getAuthInstance().signOut();
}
/**
* Append a pre element to the body containing the given message
* as its text node. Used to display the results of the API call.
*
* #param {string} message Text to be placed in pre element.
*/
function appendPre(message) {
var pre = document.getElementById('content');
var textContent = document.createTextNode(message + '\n');
pre.appendChild(textContent);
}
/**
* Print the names and majors of students in a sample spreadsheet:
* https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
*/
function listMajors() {
gapi.client.sheets.spreadsheets.values.get({
spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms',
range: 'Class Data!A2:E',
}).then(function(response) {
var range = response.result;
if (range.values.length > 0) {
appendPre('Name, Major:');
for (i = 0; i < range.values.length; i++) {
var row = range.values[i];
// Print columns A and E, which correspond to indices 0 and 4.
appendPre(row[0] + ', ' + row[4]);
}
} else {
appendPre('No data found.');
}
}, function(response) {
console.log(response);
appendPre('Error: ' + response.result.error.message);
});
}
</script>
<script async defer src="https://apis.google.com/js/api.js"
onload="this.onload=function(){};handleClientLoad()"
onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>
</body>
</html>

Related

ArcGIS Online WebMap authentication timeout

I have an ArcGIS Online public account and add WebMap to my website.
My ArcGIS Online WebMap looks like this ESRI's sample: LINK
And I am trying to add my WebMap to my website like this ESRI's reference page. You will see there is a map in the center of page: LINK
My WebMap is displayed on my webpage well. When I access my webpage, my WebMap asks my ID and Password. If I entered it, then it shows my map.
However, my question is, if I moved to different page and then come back to map page, it asks again. Is it possible to set a timeout so I don't have to sign in everytime I access the page?
The reason I asked this question is that to find out if there were a way to reduce my code simple and work on code in front-end.
I've researched OAuth that ESRI provided and I ended up using esri/IdentityManager. There were references to use esri/IdentityManager package; however there were no sample code to using it with personal WebMap which used arcgisUtils.createMap
So here is sample code that I worked:
require([
"dojo/parser",
"dojo/ready",
"dijit/layout/BorderContainer",
"dijit/layout/ContentPane",
"dojo/dom",
"esri/map",
"esri/urlUtils",
"esri/arcgis/utils",
"esri/dijit/Legend",
"esri/dijit/LayerList",
"esri/graphic",
"esri/symbols/PictureMarkerSymbol",
"esri/symbols/TextSymbol",
"esri/geometry/Point",
"esri/dijit/Scalebar",
"dojo/_base/unload",
"dojo/cookie",
"dojo/json",
"esri/config",
"esri/IdentityManager",
"esri/layers/FeatureLayer",
"dojo/domReady!"
], function (
parser,
ready,
BorderContainer,
ContentPane,
dom,
Map,
urlUtils,
arcgisUtils,
Legend,
LayerList,
Graphic,
PictureMarkerSymbol,
TextSymbol,
Point,
Scalebar,
baseUnload,
cookie,
JSON,
esriConfig,
esriId,
FeatureLayer
) {
var mapOptions = {
basemap: "topo",
autoResize: true, // see http://forums.arcgis.com/threads/90825-Mobile-Sample-Fail
center: [currentPosition.lng, currentPosition.lat],
zoom: 15,
logo: false
};
// cookie/local storage name
var cred = "esri_jsapi_id_manager_data";
// store credentials/serverInfos before the page unloads
baseUnload.addOnUnload(storeCredentials);
// look for credentials in local storage
loadCredentials();
parser.parse();
esriConfig.defaults.io.proxyUrl = "/proxy/";
//Create a map based on an ArcGIS Online web map id
arcgisUtils.createMap('PUT-YOUR-ESRI-KEY', "esriMapCanvas", { mapOptions: mapOptions }).then(function (response) {
var map = response.map;
// add a blue marker
var picSymbol = new PictureMarkerSymbol(
'http://static.arcgis.com/images/Symbols/Shapes/RedPin1LargeB.png', 50, 50);
var geometryPoint = new Point('SET YOUR LAT', 'SET YOUR LONG');
map.graphics.add(new Graphic(geometryPoint, picSymbol));
//add the scalebar
var scalebar = new Scalebar({
map: map,
scalebarUnit: "english"
});
//add the map layers
var mapLayers = new LayerList({
map: map,
layers: arcgisUtils.getLayerList(response)
}, "esriLayerList");
mapLayers.startup();
//add the legend. Note that we use the utility method getLegendLayers to get
//the layers to display in the legend from the createMap response.
var legendLayers = arcgisUtils.getLegendLayers(response);
var legendDijit = new Legend({
map: map,
layerInfos: legendLayers
}, "esriLegend");
legendDijit.startup();
});
function storeCredentials() {
// make sure there are some credentials to persist
if (esriId.credentials.length === 0) {
return;
}
// serialize the ID manager state to a string
var idString = JSON.stringify(esriId.toJson());
// store it client side
if (supports_local_storage()) {
// use local storage
window.localStorage.setItem(cred, idString);
// console.log("wrote to local storage");
}
else {
// use a cookie
cookie(cred, idString, { expires: 1 });
// console.log("wrote a cookie :-/");
}
}
function supports_local_storage() {
try {
return "localStorage" in window && window["localStorage"] !== null;
} catch (e) {
return false;
}
}
function loadCredentials() {
var idJson, idObject;
if (supports_local_storage()) {
// read from local storage
idJson = window.localStorage.getItem(cred);
}
else {
// read from a cookie
idJson = cookie(cred);
}
if (idJson && idJson != "null" && idJson.length > 4) {
idObject = JSON.parse(idJson);
esriId.initialize(idObject);
}
else {
// console.log("didn't find anything to load :(");
}
}
});

Firebase make user object from auth data

So I'm using Angularfire in an ionic app and trying to figure out how to make a user object that is associated with the auth data from an Auth $createUser call. My first try had the auth call and the user got authenticated, then a user object was made and pushed into a $firebaseArray which works fine, but I don't know how to grab the current user after they are logged in to update, destory, or do anything with that users data. I have made it work with looping through the users array and matching the uid from the user array item and the auth.uid item which was set to be the same in the user array object creation. This seems really ineffecient to loop over if there is a large user array and it needs to be done on multiple pages.
My current attempt is using a different method like so:
angular.module('haulya.main')
.controller('RegisterController', ['Auth', '$scope', 'User', '$ionicPlatform', '$cordovaCamera','CurrentUserService',
function(Auth, $scope, User, $ionicPlatform, $cordovaCamera, CurrentUserService) {
//scope variable for controller
$scope.user = {};
console.log(User);
$scope.createUser = function(isValid) {
var userModel;
$scope.submitted = true;
//messages for successful or failed user creation
$scope.user.message = null;
$scope.user.error = null;
//if form is filled out valid
if(isValid) {
//Create user with email and password firebase Auth method
Auth.$createUser({
email: $scope.user.email,
password: $scope.user.password
})
.then(function(userData) {
userModel = {
uid: userData.uid,
photo: $scope.user.photo || null,
firstName: $scope.user.firstName,
lastName: $scope.user.lastName,
email: $scope.user.email,
cell: $scope.user.cell,
dob: $scope.user.dob.toString(),
city: $scope.user.city,
state: $scope.user.state,
zip: $scope.user.zip
}
// add new user to profiles array
User.create(userModel).then(function(user) {
$scope.sharedUser = User.get(user.path.o[1]);
});
$scope.user.message = "User created for email: " + $scope.user.email;
})
.catch(function(error) {
//set error messages contextually
if(error.code == 'INVALID_EMAIL') {
$scope.user.error = "Invalid Email";
}
else if(error.code == 'EMAIL_TAKEN'){
$scope.user.error = "Email already in use, if you think this is an error contact an administrator";
}
else {
$scope.user.error = "Fill in all required fields";
}
});
}
};
//Get profile pic from camera, or photo library
$scope.getPhoto = function(type) {
$ionicPlatform.ready(function() {
//options for images quality/type/size/dimensions
var options = {
quality: 65,
destinationType: Camera.DestinationType.DATA_URL,
sourceType: Camera.PictureSourceType[type.toUpperCase()],
allowEdit: true,
encodingType: Camera.EncodingType.JPEG,
targetWidth: 100,
targetHeight: 100,
popoverOptions: CameraPopoverOptions,
saveToPhotoAlbum: false
};
//get image function using cordova-plugin-camera
$cordovaCamera.getPicture(options)
.then(function(photo) {
$scope.user.photo = photo;
}, function(err) {
console.log(err);
});
});
};
}]);
And here's the service the controller is using:
angular
.module('haulya.main')
.factory('User', function($firebaseArray) {
var ref = new Firebase('https://haulya.firebaseio.com');
var users = $firebaseArray(ref.child('profiles'));
var User = {
all: users,
create: function(user) {
return users.$add(user);
},
get: function(userId) {
return $firebaseArray(ref.child('profiles').child(userId));
},
delete: function(user) {
return users.$remove(user);
}
};
return User;
});
This also works, but again I don't have a solid reference to the currently logged in users object data from the array. The objects id is only stored on the controllers scope.
I looked through other posts, but they were all using older versions of firebase with deprecated methods.
If you're storing items that have a "natural key", it is best to store them under that key. For users this would be the uid.
So instead of storing them with $add(), store them with child().set().
create: function(user) {
var userRef = users.$ref().child(user.uid);
userRef.set(user);
return $firebaseObject(userRef);
}
You'll note that I'm using non-AngularFire methods child() and set(). AngularFire is built on top of Firebase's regular JavaScript SDK, so they interoperate nicely. The advantage of this is that you can use all the power of the Firebase JavaScript SDK and only use AngularFire for what it's best at: binding things to Angular's $scope.
Storing user data is explained in Firebase's guide for JavaScript. We store them under their uid there too instead of using push(), which is what $add() calls behind the scenes.

Using WebRTC getStat() API

Hey I am trying to implement the getstat API in my WebRTC application. Im finding it hard to get any tutorials at all , at a beginners level.
My Application
I created a 2 person chat-room using the peer js framework. so in my application I am using what can be described a "Sneeker-net" for signaling , ie I am manually sharing a peer id with the person I want a chat with via giving them my id in a email lets say then they call that ID . it uses the stun and turn servers to make our connections its a simple peer to peer chat with Html5 and JavaScript which uses the peerjs API.
here is my HTML 5 AND Javascript code
HTML5 code
<html>
<head>
<title> PeerJS video chat with manual signalling example</title>
<link rel="stylesheet" href="style.css">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
<script type="text/javascript" src="http://cdn.peerjs.com/0.3/peer.js"></script>
<script type="text/javascript" src="ps-webrtc-peerjs-start.js ></script>
</head>
<body>
<div>
<!-- Video area -->
<div id="video-container">
Your Friend<video id="their-video" autoplay class="their-video"></video>
<video id="my-video" muted="true" autoplay class="my-video"></video> You
</div>
<!-- Steps -->
<div>
<h2> PeerJS Video Chat with Manual Signalling</h2>
<!--Get local audio/video stream-->
<div id="step1">
<p>Please click 'allow' on the top of the screen so we can access your webcam and microphone for calls</p>
<div id="step1-error">
<p>Failed to access the webcam and microphone. Make sure to run this demo on an http server and click allow when asked for permission by the browser.</p>
Try again
</div>
</div>
<!--Get local audio/video stream-->
<!--Make calls to others-->
<div id="step2">
<p>Your id: <span id="my-id">...</span></p>
<p>Share this id with others so they can call you.</p>
<p><span id="subhead">Make a call</span><br>
<input type="text" placeholder="Call user id..." id="callto-id">
Call
</p>
</div>
<!--Call in progress-->
<!--Call in progress-->
<div id="step3">
<p>Currently in call with <span id="their-id">...</span></p>
<p>End call</p>
</div>
</div>
</div>
</body>
</html>
My Javascript file
navigator.getWebcam = (navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);
// PeerJS object ** FOR PRODUCTION, GET YOUR OWN KEY at http://peerjs.com/peerserver **
var peer = new Peer({
key: 'XXXXXXXXXXXXXXXX',
debug: 3,
config: {
'iceServers': [{
url: 'stun:stun.l.google.com:19302'
}, {
url: 'stun:stun1.l.google.com:19302'
}, {
url: 'turn:numb.viagenie.ca',
username: "XXXXXXXXXXXXXXXXXXXXXXXXX",
credential: "XXXXXXXXXXXXXXXXX"
}]
}
});
// On open, set the peer id so when peer is on we display our peer id as text
peer.on('open', function() {
$('#my-id').text(peer.id);
});
peer.on('call', function(call) {
// Answer automatically for demo
call.answer(window.localStream);
step3(call);
});
// Click handlers setup
$(function() {
$('#make-call').click(function() {
//Initiate a call!
var call = peer.call($('#callto-id').val(), window.localStream);
step3(call);
});
$('end-call').click(function() {
window.existingCall.close();
step2();
});
// Retry if getUserMedia fails
$('#step1-retry').click(function() {
$('#step1-error').hide();
step();
});
// Get things started
step1();
});
function step1() {
//Get audio/video stream
navigator.getWebcam({
audio: true,
video: true
}, function(stream) {
// Display the video stream in the video object
$('#my-video').prop('src', URL.createObjectURL(stream));
// Displays error
window.localStream = stream;
step2();
}, function() {
$('#step1-error').show();
});
}
function step2() { //Adjust the UI
$('#step1', '#step3').hide();
$('#step2').show();
}
function step3(call) {
// Hang up on an existing call if present
if (window.existingCall) {
window.existingCall.close();
}
// Wait for stream on the call, then setup peer video
call.on('stream', function(stream) {
$('#their-video').prop('src', URL.createObjectURL(stream));
});
$('#step1', '#step2').hide();
$('#step3').show();
}
Many thanks to anybody who takes time out to help me I am very grateful, as Im only a beginner at WebRTC .
Cheers
Here is my code, which works in both Chrome and Firefox. It traces stats in the browser console. Because Chrome stats are very verbose, I filter them following an arbitrary criteria (statNames.indexOf("transportId") > -1):
function logStats() {
var rtcPeerConn = ...;
try {
// Chrome
rtcPeerConn.getStats(function callback(report) {
var rtcStatsReports = report.result();
for (var i=0; i<rtcStatsReports.length; i++) {
var statNames = rtcStatsReports[i].names();
// filter the ICE stats
if (statNames.indexOf("transportId") > -1) {
var logs = "";
for (var j=0; j<statNames.length; j++) {
var statName = statNames[j];
var statValue = rtcStatsReports[i].stat(statName);
logs = logs + statName + ": " + statValue + ", ";
}
console.log(logs);
}
}
});
} catch (e) {
// Firefox
if (remoteVideoStream) {
var tracks = remoteVideoStream.getTracks();
for (var h=0; h<tracks.length; h++) {
rtcPeerConn.getStats(tracks[h], function callback(report) {
console.log(report);
}, function(error) {});
}
}
}
}
You need the rtcPeerConnection, and Firefox requires the stream in addition.
for twilio SDK look at this post:
Is there an API for the chrome://webrtc-internals/ variables in javascript?
var rtcPeerConn =Twilio.Device.activeConnection();
rtcPeerConn.options.mediaStreamFactory.protocol.pc.getStats(function callback(report) {
var rtcStatsReports = report.result();
for (var i=0; i<rtcStatsReports.length; i++) {
var statNames = rtcStatsReports[i].names();
// filter the ICE stats
if (statNames.indexOf("transportId") > -1) {
var logs = "";
for (var j=0; j<statNames.length; j++) {
var statName = statNames[j];
var statValue = rtcStatsReports[i].stat(statName);
logs = logs + statName + ": " + statValue + ", ";
}
console.log(logs);
}
}
});
I advice you to read Real-Time Communication with WebRTC for O'Reilly
It is very useful book for beginners in addition the book will guide you to build your webchat application ste by step using sokcet.io for signaling
the link in the first comment

YouTube Analytics API: How to enter key | Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup

I am using this sample code for my YouTube analytics API:
(function() {
// Retrieve your client ID from the {{ Google Cloud Console }} at
// {{ https://cloud.google.com/console }}.
var OAUTH2_CLIENT_ID = 'YOUR_CLIENT_ID';
var OAUTH2_SCOPES = [
'https://www.googleapis.com/auth/yt-analytics.readonly',
'https://www.googleapis.com/auth/youtube.readonly'
];
var ONE_MONTH_IN_MILLISECONDS = 1000 * 60 * 60 * 24 * 30;
// Keep track of the currently authenticated user's YouTube channel ID.
var channelId;
// For information about the Google Chart Tools API, see:
// https://developers.google.com/chart/interactive/docs/quick_start
google.load('visualization', '1.0', {'packages': ['corechart']});
// Upon loading, the Google APIs JS client automatically invokes this callback.
// See http://code.google.com/p/google-api-javascript-client/wiki/Authentication
window.onJSClientLoad = function() {
gapi.auth.init(function() {
window.setTimeout(checkAuth, 1);
});
};
// Attempt the immediate OAuth 2.0 client flow as soon as the page loads.
// If the currently logged-in Google Account has previously authorized
// the client specified as the OAUTH2_CLIENT_ID, then the authorization
// succeeds with no user intervention. Otherwise, it fails and the
// user interface that prompts for authorization needs to display.
function checkAuth() {
gapi.auth.authorize({
client_id: OAUTH2_CLIENT_ID,
scope: OAUTH2_SCOPES,
immediate: true
}, handleAuthResult);
}
// Handle the result of a gapi.auth.authorize() call.
function handleAuthResult(authResult) {
if (authResult) {
// Authorization was successful. Hide authorization prompts and show
// content that should be visible after authorization succeeds.
$('.pre-auth').hide();
$('.post-auth').show();
loadAPIClientInterfaces();
} else {
// Authorization was unsuccessful. Show content related to prompting for
// authorization and hide content that should be visible if authorization
// succeeds.
$('.post-auth').hide();
$('.pre-auth').show();
// Make the #login-link clickable. Attempt a non-immediate OAuth 2.0
// client flow. The current function is called when that flow completes.
$('#login-link').click(function() {
gapi.auth.authorize({
client_id: OAUTH2_CLIENT_ID,
scope: OAUTH2_SCOPES,
immediate: false
}, handleAuthResult);
});
}
}
// Load the client interfaces for the YouTube Analytics and Data APIs, which
// are required to use the Google APIs JS client. More info is available at
// http://code.google.com/p/google-api-javascript-client/wiki/GettingStarted#Loading_the_Client
function loadAPIClientInterfaces() {
gapi.client.load('youtube', 'v3', function() {
gapi.client.load('youtubeAnalytics', 'v1', function() {
// After both client interfaces load, use the Data API to request
// information about the authenticated user's channel.
getUserChannel();
});
});
}
// Call the Data API to retrieve information about the currently
// authenticated user's YouTube channel.
function getUserChannel() {
// Also see: https://developers.google.com/youtube/v3/docs/channels/list
var request = gapi.client.youtube.channels.list({
// Setting the "mine" request parameter's value to "true" indicates that
// you want to retrieve the currently authenticated user's channel.
mine: true,
part: 'id,contentDetails'
});
request.execute(function(response) {
if ('error' in response) {
displayMessage(response.error.message);
} else {
// We need the channel's channel ID to make calls to the Analytics API.
// The channel ID value has the form "UCdLFeWKpkLhkguiMZUp8lWA".
channelId = response.items[0].id;
// Retrieve the playlist ID that uniquely identifies the playlist of
// videos uploaded to the authenticated user's channel. This value has
// the form "UUdLFeWKpkLhkguiMZUp8lWA".
var uploadsListId = response.items[0].contentDetails.relatedPlaylists.uploads;
// Use the playlist ID to retrieve the list of uploaded videos.
getPlaylistItems(uploadsListId);
}
});
}
// Call the Data API to retrieve the items in a particular playlist. In this
// example, we are retrieving a playlist of the currently authenticated user's
// uploaded videos. By default, the list returns the most recent videos first.
function getPlaylistItems(listId) {
// See https://developers.google.com/youtube/v3/docs/playlistitems/list
var request = gapi.client.youtube.playlistItems.list({
playlistId: listId,
part: 'snippet'
});
request.execute(function(response) {
if ('error' in response) {
displayMessage(response.error.message);
} else {
if ('items' in response) {
// The jQuery.map() function iterates through all of the items in
// the response and creates a new array that only contains the
// specific property we're looking for: videoId.
var videoIds = $.map(response.items, function(item) {
return item.snippet.resourceId.videoId;
});
// Now that we know the IDs of all the videos in the uploads list,
// we can retrieve information about each video.
getVideoMetadata(videoIds);
} else {
displayMessage('There are no videos in your channel.');
}
}
});
}
// Given an array of video IDs, this function obtains metadata about each
// video and then uses that metadata to display a list of videos.
function getVideoMetadata(videoIds) {
// https://developers.google.com/youtube/v3/docs/videos/list
var request = gapi.client.youtube.videos.list({
// The 'id' property's value is a comma-separated string of video IDs.
id: videoIds.join(','),
part: 'id,snippet,statistics'
});
request.execute(function(response) {
if ('error' in response) {
displayMessage(response.error.message);
} else {
// Get the jQuery wrapper for the #video-list element before starting
// the loop.
var videoList = $('#video-list');
$.each(response.items, function() {
// Exclude videos that do not have any views, since those videos
// will not have any interesting viewcount Analytics data.
if (this.statistics.viewCount == 0) {
return;
}
var title = this.snippet.title;
var videoId = this.id;
// Create a new <li> element that contains an <a> element.
// Set the <a> element's text content to the video's title, and
// add a click handler that will display Analytics data when invoked.
var liElement = $('<li>');
var aElement = $('<a>');
// Setting the href value to '#' ensures that the browser renders the
// <a> element as a clickable link.
aElement.attr('href', '#');
aElement.text(title);
aElement.click(function() {
displayVideoAnalytics(videoId);
});
// Call the jQuery.append() method to add the new <a> element to
// the <li> element, and the <li> element to the parent
// list, which is identified by the 'videoList' variable.
liElement.append(aElement);
videoList.append(liElement);
});
if (videoList.children().length == 0) {
// Display a message if the channel does not have any viewed videos.
displayMessage('Your channel does not have any videos that have been viewed.');
}
}
});
}
// This function requests YouTube Analytics data for a video and displays
// the results in a chart.
function displayVideoAnalytics(videoId) {
if (channelId) {
// To use a different date range, modify the ONE_MONTH_IN_MILLISECONDS
// variable to a different millisecond delta as desired.
var today = new Date();
var lastMonth = new Date(today.getTime() - ONE_MONTH_IN_MILLISECONDS);
var request = gapi.client.youtubeAnalytics.reports.query({
// The start-date and end-date parameters must be YYYY-MM-DD strings.
'start-date': formatDateString(lastMonth),
'end-date': formatDateString(today),
// At this time, you need to explicitly specify channel==channelId.
// See https://developers.google.com/youtube/analytics/v1/#ids
ids: 'channel==' + channelId,
dimensions: 'day',
sort: 'day',
// See https://developers.google.com/youtube/analytics/v1/available_reports
// for details about the different filters and metrics you can request
// if the "dimensions" parameter value is "day".
metrics: 'views',
filters: 'video==' + videoId
});
request.execute(function(response) {
// This function is called regardless of whether the request succeeds.
// The response contains YouTube Analytics data or an error message.
if ('error' in response) {
displayMessage(response.error.message);
} else {
displayChart(videoId, response);
}
});
} else {
// The currently authenticated user's channel ID is not available.
displayMessage('The YouTube channel ID for the current user is not available.');
}
}
// This boilerplate code takes a Date object and returns a YYYY-MM-DD string.
function formatDateString(date) {
var yyyy = date.getFullYear().toString();
var mm = padToTwoCharacters(date.getMonth() + 1);
var dd = padToTwoCharacters(date.getDate());
return yyyy + '-' + mm + '-' + dd;
}
// If number is a single digit, prepend a '0'. Otherwise, return the number
// as a string.
function padToTwoCharacters(number) {
if (number < 10) {
return '0' + number;
} else {
return number.toString();
}
}
// Call the Google Chart Tools API to generate a chart of Analytics data.
function displayChart(videoId, response) {
if ('rows' in response) {
hideMessage();
// The columnHeaders property contains an array of objects representing
// each column's title -- e.g.: [{name:"day"},{name:"views"}]
// We need these column titles as a simple array, so we call jQuery.map()
// to get each element's "name" property and create a new array that only
// contains those values.
var columns = $.map(response.columnHeaders, function(item) {
return item.name;
});
// The google.visualization.arrayToDataTable() function wants an array
// of arrays. The first element is an array of column titles, calculated
// above as "columns". The remaining elements are arrays that each
// represent a row of data. Fortunately, response.rows is already in
// this format, so it can just be concatenated.
// See https://developers.google.com/chart/interactive/docs/datatables_dataviews#arraytodatatable
var chartDataArray = [columns].concat(response.rows);
var chartDataTable = google.visualization.arrayToDataTable(chartDataArray);
var chart = new google.visualization.LineChart(document.getElementById('chart'));
chart.draw(chartDataTable, {
// Additional options can be set if desired as described at:
// https://developers.google.com/chart/interactive/docs/reference#visdraw
title: 'Views per Day of Video ' + videoId
});
} else {
displayMessage('No data available for video ' + videoId);
}
}
// This helper method displays a message on the page.
function displayMessage(message) {
$('#message').text(message).show();
}
// This helper method hides a previously displayed message on the page.
function hideMessage() {
$('#message').hide();
}
})();
When I insert in the client ID, it says this:
Daily Limit for Unauthenticated Use Exceeded. Continued use requires
signup.
I researched this, and it means I have to also put in the API Key. Where do I do this?
Thanks,
Ben
Before
gapi.auth.authorize
put
gapi.client.setApiKey(API_KEY);

SignalR cannot read property 'chatterHub' undefined

My little chat project is barking at me "Uncaught TypeError: Cannot read property 'chatterHub' of undefined"
I must have something out of order. I checked in Chrome inspector the signalr/hubs defines 'chatterHub'. Below is signalr/hubs. (is it generated? if yes, by what and when?)
/*!
* ASP.NET SignalR JavaScript Library v2.0.1
* http://signalr.net/
*
* Copyright Microsoft Open Technologies, Inc. All rights reserved.
* Licensed under the Apache 2.0
* https://github.com/SignalR/SignalR/blob/master/LICENSE.md
*
*/
/// <reference path="..\..\SignalR.Client.JS\Scripts\jquery-1.6.4.js" />
/// <reference path="jquery.signalR.js" />
(function ($, window, undefined) {
/// <param name="$" type="jQuery" />
"use strict";
if (typeof ($.signalR) !== "function") {
throw new Error("SignalR: SignalR is not loaded. Please ensure jquery.signalR-x.js is referenced before ~/signalr/js.");
}
var signalR = $.signalR;
function makeProxyCallback(hub, callback) {
return function () {
// Call the client hub method
callback.apply(hub, $.makeArray(arguments));
};
}
function registerHubProxies(instance, shouldSubscribe) {
var key, hub, memberKey, memberValue, subscriptionMethod;
for (key in instance) {
if (instance.hasOwnProperty(key)) {
hub = instance[key];
if (!(hub.hubName)) {
// Not a client hub
continue;
}
if (shouldSubscribe) {
// We want to subscribe to the hub events
subscriptionMethod = hub.on;
} else {
// We want to unsubscribe from the hub events
subscriptionMethod = hub.off;
}
// Loop through all members on the hub and find client hub functions to subscribe/unsubscribe
for (memberKey in hub.client) {
if (hub.client.hasOwnProperty(memberKey)) {
memberValue = hub.client[memberKey];
if (!$.isFunction(memberValue)) {
// Not a client hub function
continue;
}
subscriptionMethod.call(hub, memberKey, makeProxyCallback(hub, memberValue));
}
}
}
}
}
$.hubConnection.prototype.createHubProxies = function () {
var proxies = {};
this.starting(function () {
// Register the hub proxies as subscribed
// (instance, shouldSubscribe)
registerHubProxies(proxies, true);
this._registerSubscribedHubs();
}).disconnected(function () {
// Unsubscribe all hub proxies when we "disconnect". This is to ensure that we do not re-add functional call backs.
// (instance, shouldSubscribe)
registerHubProxies(proxies, false);
});
proxies.chatterHub = this.createHubProxy('chatterHub');
proxies.chatterHub.client = { };
proxies.chatterHub.server = {
send: function (name, message) {
return proxies.chatterHub.invoke.apply(proxies.chatterHub, $.merge(["Send"], $.makeArray(arguments)));
}
};
proxies.products = this.createHubProxy('products');
proxies.products.client = { };
proxies.products.server = {
add: function (newProduct) {
return proxies.products.invoke.apply(proxies.products, $.merge(["Add"], $.makeArray(arguments)));
},
getAll: function () {
return proxies.products.invoke.apply(proxies.products, $.merge(["GetAll"], $.makeArray(arguments)));
},
remove: function (ProductId) {
return proxies.products.invoke.apply(proxies.products, $.merge(["Remove"], $.makeArray(arguments)));
},
update: function (updatedProduct) {
return proxies.products.invoke.apply(proxies.products, $.merge(["Update"], $.makeArray(arguments)));
}
};
return proxies;
};
signalR.hub = $.hubConnection("/signalr", { useDefaultPath: false });
$.extend(signalR, signalR.hub.createHubProxies());
}(window.jQuery, window));
And here is Chatter.cshtml
#{
ViewBag.Title = "Chatter";
}
<h2>Welcome #ViewBag.UserName!</h2>
<div id="container">
<input type="text" id="message" />
<input type="button" id="sendMessage" value="Send " />
<input type="hidden" id="displayName" />
<ul id="discussion"> </ul>
</div>
#section Scripts {
#Scripts.Render("~/bundles/signalr")
<script src="/Scripts/jquery.signalR-2.0.1.min.js" type="text/javascript"></script>
<script src="~/signalr/hubs" type="text/javascript"></script>
<script src="/Scripts/jquery-2.0.3.min.js"></script>
<script>
$(function () {
// the client proxy object chat takes all the work communicating to the server
var chat = $.connection.chatterHub; // client proxy object starts lowercase. The server Class starts uppercase
// dynamically defines function addNewMessageToPage that corresponding to the one defined in the ChatterHub on server side
// this is what the server is to call to send messages to each clients
chat.client.addNewMessageToPage = function (name,message) {
$("#discussion").append("<li>" + (new Date).toString("HH:mm:ss") + "<strong>" + htmlEncode(name) + "</strong>" + htmlEncode(message) + "</li>");
};
$("#displayName").val('#ViewBag.UserName');
$("#message").focus();
// a ready function for SignalR
$.connection.hub.start().done(function () {
// click event to send username and message to the server and all clients
$("#sendMessage").click(function () {
chat.server.send($("#displayName").val(), $("#message").val());
$("#message").focus();
});
});
$("#message").keypress(function (event) {
if (event.which === 13) $("#sendMessage").click();
});
});
function htmlEncode(value) {
var encodedValue = $("<div />").text(value).html();
return encodedValue;
}
</script>
}
Can anyone tell what is missing or out of order?
Thanks,
I found it. the page source shown in Chrome inspector like this
<script src="/Scripts/jquery-2.0.3.js"></script>
<script src="/Scripts/jquery-ui-1.10.3.js"></script>
<script src="/Scripts/jquery.signalR-2.0.1.min.js" type="text/javascript"></script>
<script src="/signalr/hubs" type="text/javascript"></script>
<script src="/Scripts/jquery-2.0.3.min.js"></script>
<script>
$(function () {
// the client proxy object chat takes all the work communicating to the server
var chat = $.hubConnection.chatterHub; // client proxy object starts lowercase. The server Class starts uppercase
Obviously jquery-2.0.3 was loaded twice. I removed
<script src="/Scripts/jquery-2.0.3.min.js"></script>
and it worked.