1 /**!
2 * 微信内置浏览器的Javascript API,功能包括:
3 *
4 * 1、分享到微信朋友圈
5 * 2、分享给微信好友
6 * 3、分享到腾讯微博
7 * 4、新的分享接口,包含朋友圈、好友、微博的分享(for iOS)
8 * 5、隐藏/显示右上角的菜单入口
9 * 6、隐藏/显示底部浏览器工具栏
10 * 7、获取当前的网络状态
11 * 8、调起微信客户端的图片播放组件
12 * 9、关闭公众平台Web页面
13 * 10、判断当前网页是否在微信内置浏览器中打开
14 * 11、增加打开扫描二维码
15 * 12、支持WeixinApi的错误监控
16 * 13、检测应用程序是否已经安装(需要官方开通权限)
17 * 14、发送电子邮件
18 * 15、禁止用户分享
19 *
20 * @author guanguoxiang(http://www.ggxapp.com)
21 */
22 (function (window) {
23
24 "use strict";
25
26 /**
27 * 定义WeixinApi
28 */
29 var WeixinApi = {
30 version: 4.1
31 };
32
33 // 将WeixinApi暴露到window下:全局可使用,对旧版本向下兼容
34 window.WeixinApi = WeixinApi;
35
36 /////////////////////////// CommonJS /////////////////////////////////
37 if (typeof define === 'function' && (define.amd || define.cmd)) {
38 if (define.amd) {
39 // AMD 规范,for:requirejs
40 define(function () {
41 return WeixinApi;
42 });
43 } else if (define.cmd) {
44 // CMD 规范,for:seajs
45 define(function (require, exports, module) {
46 module.exports = WeixinApi;
47 });
48 }
49 }
50
51 /**
52 * 对象简单继承,后面的覆盖前面的,继承深度:deep=1
53 * @private
54 */
55 var _extend = function () {
56 var result = {}, obj, k;
57 for (var i = 0, len = arguments.length; i < len; i++) {
58 obj = arguments[i];
59 if (typeof obj === 'object') {
60 for (k in obj) {
61 obj[k] && (result[k] = obj[k]);
62 }
63 }
64 }
65 return result;
66 };
67
68 /**
69 * 内部私有方法,分享用
70 * @private
71 */
72 var _share = function (cmd, data, callbacks) {
73 callbacks = callbacks || {};
74
75 // 分享过程中的一些回调
76 var progress = function (resp) {
77 switch (true) {
78 // 用户取消
79 case /\:cancel$/i.test(resp.err_msg) :
80 callbacks.cancel && callbacks.cancel(resp);
81 break;
82 // 发送成功
83 case /\:(confirm|ok)$/i.test(resp.err_msg):
84 callbacks.confirm && callbacks.confirm(resp);
85 break;
86 // fail 发送失败
87 case /\:fail$/i.test(resp.err_msg) :
88 default:
89 callbacks.fail && callbacks.fail(resp);
90 break;
91 }
92 // 无论成功失败都会执行的回调
93 callbacks.all && callbacks.all(resp);
94 };
95
96 // 执行分享,并处理结果
97 var handler = function (theData, argv) {
98
99 // 加工一下数据
100 if (cmd.menu == 'menu:share:timeline' ||
101 (cmd.menu == 'general:share' && argv.shareTo == 'timeline')) {
102
103 var title = theData.title;
104 theData.title = theData.desc || title;
105 theData.desc = title || theData.desc;
106 }
107
108 // 新的分享接口,单独处理
109 if (cmd.menu === 'general:share') {
110 // 如果是收藏操作,并且在wxCallbacks中配置了favorite为false,则不执行回调
111 if (argv.shareTo == 'favorite' || argv.scene == 'favorite') {
112 if (callbacks.favorite === false) {
113 return argv.generalShare(theData, function () {
114 });
115 }
116 }
117 if (argv.shareTo === 'timeline') {
118 WeixinJSBridge.invoke('shareTimeline', theData, progress);
119 } else if (argv.shareTo === 'friend') {
120 WeixinJSBridge.invoke('sendAppMessage', theData, progress);
121 } else if (argv.shareTo === 'QQ') {
122 WeixinJSBridge.invoke('shareQQ', theData, progress);
123 }else if (argv.shareTo === 'weibo') {
124 WeixinJSBridge.invoke('shareWeibo', theData, progress);
125 }
126 } else {
127 WeixinJSBridge.invoke(cmd.action, theData, progress);
128 }
129 };
130
131 // 监听分享操作
132 WeixinJSBridge.on(cmd.menu, function (argv) {
133 callbacks.dataLoaded = callbacks.dataLoaded || new Function();
134 if (callbacks.async && callbacks.ready) {
135 WeixinApi["_wx_loadedCb_"] = callbacks.dataLoaded;
136 if (WeixinApi["_wx_loadedCb_"].toString().indexOf("_wx_loadedCb_") > 0) {
137 WeixinApi["_wx_loadedCb_"] = new Function();
138 }
139 callbacks.dataLoaded = function (newData) {
140 callbacks.__cbkCalled = true;
141 var theData = _extend(data, newData);
142 theData.img_url = theData.imgUrl || theData.img_url;
143 delete theData.imgUrl;
144 WeixinApi["_wx_loadedCb_"](theData);
145 handler(theData, argv);
146 };
147 // 然后就绪
148 if (!(argv && (argv.shareTo == 'favorite' || argv.scene == 'favorite') && callbacks.favorite === false)) {
149 callbacks.ready && callbacks.ready(argv, data);
150 // 如果设置了async为true,但是在ready方法中并没有手动调用dataLoaded方法,则自动触发一次
151 if (!callbacks.__cbkCalled) {
152 callbacks.dataLoaded({});
153 callbacks.__cbkCalled = false;
154 }
155 }
156 } else {
157 // 就绪状态
158 var theData = _extend(data);
159 if (!(argv && (argv.shareTo == 'favorite' || argv.scene == 'favorite') && callbacks.favorite === false)) {
160 callbacks.ready && callbacks.ready(argv, theData);
161 }
162 handler(theData, argv);
163 }
164 });
165 };
166
167 /**
168 * 分享到微信朋友圈
169 * @param {Object} data 待分享的信息
170 * @p-config {String} appId 公众平台的appId(服务号可用)
171 * @p-config {String} imgUrl 图片地址
172 * @p-config {String} link 链接地址
173 * @p-config {String} desc 描述
174 * @p-config {String} title 分享的标题
175 *
176 * @param {Object} callbacks 相关回调方法
177 * @p-config {Boolean} async ready方法是否需要异步执行,默认false
178 * @p-config {Function} ready(argv, data) 就绪状态
179 * @p-config {Function} dataLoaded(data) 数据加载完成后调用,async为true时有用,也可以为空
180 * @p-config {Function} cancel(resp) 取消
181 * @p-config {Function} fail(resp) 失败
182 * @p-config {Function} confirm(resp) 成功
183 * @p-config {Function} all(resp) 无论成功失败都会执行的回调
184 */
185 WeixinApi.shareToTimeline = function (data, callbacks) {
186 _share({
187 menu: 'menu:share:timeline',
188 action: 'shareTimeline'
189 }, {
190 "appid": data.appId ? data.appId : '',
191 "img_url": data.imgUrl,
192 "link": data.link,
193 "desc": data.desc,
194 "title": data.title,
195 "img_width": "640",
196 "img_height": "640"
197 }, callbacks);
198 };
199
200 /**
201 * 发送给微信上的好友
202 * @param {Object} data 待分享的信息
203 * @p-config {String} appId 公众平台的appId(服务号可用)
204 * @p-config {String} imgUrl 图片地址
205 * @p-config {String} link 链接地址
206 * @p-config {String} desc 描述
207 * @p-config {String} title 分享的标题
208 *
209 * @param {Object} callbacks 相关回调方法
210 * @p-config {Boolean} async ready方法是否需要异步执行,默认false
211 * @p-config {Function} ready(argv, data) 就绪状态
212 * @p-config {Function} dataLoaded(data) 数据加载完成后调用,async为true时有用,也可以为空
213 * @p-config {Function} cancel(resp) 取消
214 * @p-config {Function} fail(resp) 失败
215 * @p-config {Function} confirm(resp) 成功
216 * @p-config {Function} all(resp) 无论成功失败都会执行的回调
217 */
218 WeixinApi.shareToFriend = function (data, callbacks) {
219 _share({
220 menu: 'menu:share:appmessage',
221 action: 'sendAppMessage'
222 }, {
223 "appid": data.appId ? data.appId : '',
224 "img_url": data.imgUrl,
225 "link": data.link,
226 "desc": data.desc,
227 "title": data.title,
228 "img_width": "640",
229 "img_height": "640"
230 }, callbacks);
231 };
232
233
234 /**
235 * 分享到腾讯微博
236 * @param {Object} data 待分享的信息
237 * @p-config {String} link 链接地址
238 * @p-config {String} desc 描述
239 *
240 * @param {Object} callbacks 相关回调方法
241 * @p-config {Boolean} async ready方法是否需要异步执行,默认false
242 * @p-config {Function} ready(argv, data) 就绪状态
243 * @p-config {Function} dataLoaded(data) 数据加载完成后调用,async为true时有用,也可以为空
244 * @p-config {Function} cancel(resp) 取消
245 * @p-config {Function} fail(resp) 失败
246 * @p-config {Function} confirm(resp) 成功
247 * @p-config {Function} all(resp) 无论成功失败都会执行的回调
248 */
249 WeixinApi.shareToWeibo = function (data, callbacks) {
250 _share({
251 menu: 'menu:share:weibo',
252 action: 'shareWeibo'
253 }, {
254 "content": data.desc,
255 "url": data.link
256 }, callbacks);
257 };
258
259 /**
260 * 新的分享接口
261 * @param {Object} data 待分享的信息
262 * @p-config {String} appId 公众平台的appId(服务号可用)
263 * @p-config {String} imgUrl 图片地址
264 * @p-config {String} link 链接地址
265 * @p-config {String} desc 描述
266 * @p-config {String} title 分享的标题
267 *
268 * @param {Object} callbacks 相关回调方法
269 * @p-config {Boolean} async ready方法是否需要异步执行,默认false
270 * @p-config {Function} ready(argv, data) 就绪状态
271 * @p-config {Function} dataLoaded(data) 数据加载完成后调用,async为true时有用,也可以为空
272 * @p-config {Function} cancel(resp) 取消
273 * @p-config {Function} fail(resp) 失败
274 * @p-config {Function} confirm(resp) 成功
275 * @p-config {Function} all(resp) 无论成功失败都会执行的回调
276 */
277 WeixinApi.generalShare = function (data, callbacks) {
278 _share({
279 menu: 'general:share'
280 }, {
281 "appid": data.appId ? data.appId : '',
282 "img_url": data.imgUrl,
283 "link": data.link,
284 "desc": data.desc,
285 "title": data.title,
286 "img_width": "640",
287 "img_height": "640"
288 }, callbacks);
289 };
290
291 /**
292 * 设置页面禁止分享:包括朋友圈、好友、腾讯微博、qq
293 * @param callback
294 */
295 WeixinApi.disabledShare = function (callback) {
296 callback = callback || function () {
297 alert('当前页面禁止分享!');
298 };
299 ['menu:share:timeline', 'menu:share:appmessage', 'menu:share:qq',
300 'menu:share:weibo', 'general:share'].forEach(function (menu) {
301 WeixinJSBridge.on(menu, function () {
302 callback();
303 return false;
304 });
305 });
306 };
307
308 /**
309 * 加关注(此功能只是暂时先加上,不过因为权限限制问题,不能用,如果你的站点是部署在*.qq.com下,也许可行)
310 * @param {String} appWeixinId 微信公众号ID
311 * @param {Object} callbacks 回调方法
312 * @p-config {Function} fail(resp) 失败
313 * @p-config {Function} confirm(resp) 成功
314 */
315 WeixinApi.addContact = function (appWeixinId, callbacks) {
316 callbacks = callbacks || {};
317 WeixinJSBridge.invoke("addContact", {
318 webtype: "1",
319 username: appWeixinId
320 }, function (resp) {
321 var success = !resp.err_msg || "add_contact:ok" == resp.err_msg
322 || "add_contact:added" == resp.err_msg;
323 if (success) {
324 callbacks.success && callbacks.success(resp);
325 } else {
326 callbacks.fail && callbacks.fail(resp);
327 }
328 })
329 };
330
331 /**
332 * 调起微信Native的图片播放组件。
333 * 这里必须对参数进行强检测,如果参数不合法,直接会导致微信客户端crash
334 *
335 * @param {String} curSrc 当前播放的图片地址
336 * @param {Array} srcList 图片地址列表
337 */
338 WeixinApi.imagePreview = function (curSrc, srcList) {
339 if (!curSrc || !srcList || srcList.length == 0) {
340 return;
341 }
342 WeixinJSBridge.invoke('imagePreview', {
343 'current': curSrc,
344 'urls': srcList
345 });
346 };
347
348 /**
349 * 显示网页右上角的按钮
350 */
351 WeixinApi.showOptionMenu = function () {
352 WeixinJSBridge.call('showOptionMenu');
353 };
354
355
356 /**
357 * 隐藏网页右上角的按钮
358 */
359 WeixinApi.hideOptionMenu = function () {
360 WeixinJSBridge.call('hideOptionMenu');
361 };
362
363 /**
364 * 显示底部工具栏
365 */
366 WeixinApi.showToolbar = function () {
367 WeixinJSBridge.call('showToolbar');
368 };
369
370 /**
371 * 隐藏底部工具栏
372 */
373 WeixinApi.hideToolbar = function () {
374 WeixinJSBridge.call('hideToolbar');
375 };
376
377 /**
378 * 返回如下几种类型:
379 *
380 * network_type:wifi wifi网络
381 * network_type:edge 非wifi,包含3G/2G
382 * network_type:fail 网络断开连接
383 * network_type:wwan 2g或者3g
384 *
385 * 使用方法:
386 * WeixinApi.getNetworkType(function(networkType){
387 *
388 * });
389 *
390 * @param callback
391 */
392 WeixinApi.getNetworkType = function (callback) {
393 if (callback && typeof callback == 'function') {
394 WeixinJSBridge.invoke('getNetworkType', {}, function (e) {
395 // 在这里拿到e.err_msg,这里面就包含了所有的网络类型
396 callback(e.err_msg);
397 });
398 }
399 };
400
401 /**
402 * 关闭当前微信公众平台页面
403 * @param {Object} callbacks 回调方法
404 * @p-config {Function} fail(resp) 失败
405 * @p-config {Function} success(resp) 成功
406 */
407 WeixinApi.closeWindow = function (callbacks) {
408 callbacks = callbacks || {};
409 WeixinJSBridge.invoke("closeWindow", {}, function (resp) {
410 switch (resp.err_msg) {
411 // 关闭成功
412 case 'close_window:ok':
413 callbacks.success && callbacks.success(resp);
414 break;
415
416 // 关闭失败
417 default :
418 callbacks.fail && callbacks.fail(resp);
419 break;
420 }
421 });
422 };
423
424 /**
425 * 当页面加载完毕后执行,使用方法:
426 * WeixinApi.ready(function(Api){
427 * // 从这里只用Api即是WeixinApi
428 * });
429 * @param readyCallback
430 */
431 WeixinApi.ready = function (readyCallback) {
432
433 /**
434 * 加一个钩子,同时解决Android和iOS下的分享问题
435 * @private
436 */
437 var _hook = function () {
438 var _WeixinJSBridge = {};
439 Object.keys(WeixinJSBridge).forEach(function (key) {
440 _WeixinJSBridge[key] = WeixinJSBridge[key];
441 });
442 Object.keys(WeixinJSBridge).forEach(function (key) {
443 if (typeof WeixinJSBridge[key] === 'function') {
444 WeixinJSBridge[key] = function () {
445 try {
446 var args = arguments.length > 0 ? arguments[0] : {},
447 runOn3rd_apis = args.__params ? args.__params.__runOn3rd_apis || [] : [];
448 ['menu:share:timeline', 'menu:share:appmessage', 'menu:share:weibo',
449 'menu:share:qq', 'general:share'].forEach(function (menu) {
450 runOn3rd_apis.indexOf(menu) === -1 && runOn3rd_apis.push(menu);
451 });
452 } catch (e) {
453 }
454 return _WeixinJSBridge[key].apply(WeixinJSBridge, arguments);
455 };
456 }
457 });
458 };
459
460 if (readyCallback && typeof readyCallback == 'function') {
461 var Api = this;
462 var wxReadyFunc = function () {
463 _hook();
464 readyCallback(Api);
465 };
466 if (typeof window.WeixinJSBridge == "undefined") {
467 if (document.addEventListener) {
468 document.addEventListener('WeixinJSBridgeReady', wxReadyFunc, false);
469 } else if (document.attachEvent) {
470 document.attachEvent('WeixinJSBridgeReady', wxReadyFunc);
471 document.attachEvent('onWeixinJSBridgeReady', wxReadyFunc);
472 }
473 } else {
474 wxReadyFunc();
475 }
476 }
477 };
478
479 /**
480 * 判断当前网页是否在微信内置浏览器中打开
481 */
482 WeixinApi.openInWeixin = function () {
483 return /MicroMessenger/i.test(navigator.userAgent);
484 };
485
486 /*
487 * 打开扫描二维码
488 * @param {Object} callbacks 回调方法
489 * @p-config {Boolean} needResult 是否直接获取分享后的内容
490 * @p-config {String} desc 扫描二维码时的描述
491 * @p-config {Function} fail(resp) 失败
492 * @p-config {Function} success(resp) 成功
493 */
494 WeixinApi.scanQRCode = function (callbacks) {
495 callbacks = callbacks || {};
496 WeixinJSBridge.invoke("scanQRCode", {
497 needResult: callbacks.needResult ? 1 : 0,
498 desc: callbacks.desc || 'WeixinApi Desc'
499 }, function (resp) {
500 switch (resp.err_msg) {
501 // 打开扫描器成功
502 case 'scanQRCode:ok':
503 case 'scan_qrcode:ok':
504 callbacks.success && callbacks.success(resp);
505 break;
506
507 // 打开扫描器失败
508 default :
509 callbacks.fail && callbacks.fail(resp);
510 break;
511 }
512 });
513 };
514
515 /**
516 * 检测应用程序是否已安装
517 * by mingcheng 2014-10-17
518 *
519 * @param {Object} data 应用程序的信息
520 * @p-config {String} packageUrl 应用注册的自定义前缀,如 xxx:// 就取 xxx
521 * @p-config {String} packageName 应用的包名
522 *
523 * @param {Object} callbacks 相关回调方法
524 * @p-config {Function} fail(resp) 失败
525 * @p-config {Function} success(resp) 成功,如果有对应的版本信息,则写入到 resp.version 中
526 * @p-config {Function} all(resp) 无论成功失败都会执行的回调
527 */
528 WeixinApi.getInstallState = function (data, callbacks) {
529 callbacks = callbacks || {};
530
531 WeixinJSBridge.invoke("getInstallState", {
532 "packageUrl": data.packageUrl || "",
533 "packageName": data.packageName || ""
534 }, function (resp) {
535 var msg = resp.err_msg, match = msg.match(/state:yes_?(.*)$/);
536 if (match) {
537 resp.version = match[1] || "";
538 callbacks.success && callbacks.success(resp);
539 } else {
540 callbacks.fail && callbacks.fail(resp);
541 }
542
543 callbacks.all && callbacks.all(resp);
544 });
545 };
546
547 /**
548 * 发送邮件
549 * @param {Object} data 邮件初始内容
550 * @p-config {String} subject 邮件标题
551 * @p-config {String} body 邮件正文
552 *
553 * @param {Object} callbacks 相关回调方法
554 * @p-config {Function} fail(resp) 失败
555 * @p-config {Function} success(resp) 成功
556 * @p-config {Function} all(resp) 无论成功失败都会执行的回调
557 */
558 WeixinApi.sendEmail = function (data, callbacks) {
559 callbacks = callbacks || {};
560 WeixinJSBridge.invoke("sendEmail", {
561 "title": data.subject,
562 "content": data.body
563 }, function (resp) {
564 if (resp.err_msg === 'send_email:sent') {
565 callbacks.success && callbacks.success(resp);
566 } else {
567 callbacks.fail && callbacks.fail(resp);
568 }
569 callbacks.all && callbacks.all(resp);
570 })
571 };
572
573 /**
574 * 开启Api的debug模式,比如出了个什么错误,能alert告诉你,而不是一直很苦逼的在想哪儿出问题了
575 * @param {Function} callback(error) 出错后的回调,默认是alert
576 */
577 WeixinApi.enableDebugMode = function (callback) {
578 /**
579 * @param {String} errorMessage 错误信息
580 * @param {String} scriptURI 出错的文件
581 * @param {Long} lineNumber 出错代码的行号
582 * @param {Long} columnNumber 出错代码的列号
583 */
584 window.onerror = function (errorMessage, scriptURI, lineNumber, columnNumber) {
585
586 // 有callback的情况下,将错误信息传递到options.callback中
587 if (typeof callback === 'function') {
588 callback({
589 message: errorMessage,
590 script: scriptURI,
591 line: lineNumber,
592 column: columnNumber
593 });
594 } else {
595 // 其他情况,都以alert方式直接提示错误信息
596 var msgs = [];
597 msgs.push("额,代码有错。。。");
598 msgs.push("\n错误信息:", errorMessage);
599 msgs.push("\n出错文件:", scriptURI);
600 msgs.push("\n出错位置:", lineNumber + '行,' + columnNumber + '列');
601 alert(msgs.join(''));
602 }
603 }
604 };
605
606 })(window);