diff --git a/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/service/messaging/MessagingConstants.java b/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/service/messaging/MessagingConstants.java index 3dfb3a9e69..5d5e57aa1f 100644 --- a/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/service/messaging/MessagingConstants.java +++ b/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/service/messaging/MessagingConstants.java @@ -37,8 +37,8 @@ public class MessagingConstants { public static final String TO_PRESENTATION_CHANNEL = TO_BBB_APPS_CHANNEL + ":presentation"; public static final String TO_POLLING_CHANNEL = TO_BBB_APPS_CHANNEL + ":polling"; public static final String TO_USERS_CHANNEL = TO_BBB_APPS_CHANNEL + ":users"; - public static final String TO_CHAT_CHANNEL = TO_BBB_APPS_CHANNEL + ":chat"; - + public static final String TO_CHAT_CHANNEL = TO_BBB_APPS_CHANNEL + ":chat"; + public static final String TO_WHITEBOARD_CHANNEL = TO_BBB_APPS_CHANNEL + ":whiteboard"; public static final String DESTROY_MEETING_REQUEST_EVENT = "DestroyMeetingRequestEvent"; public static final String CREATE_MEETING_REQUEST_EVENT = "CreateMeetingRequestEvent"; diff --git a/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/service/whiteboard/WhiteboardListener.java b/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/service/whiteboard/WhiteboardListener.java new file mode 100644 index 0000000000..47bc7b68c6 --- /dev/null +++ b/bigbluebutton-apps/src/main/java/org/bigbluebutton/conference/service/whiteboard/WhiteboardListener.java @@ -0,0 +1,52 @@ + +package org.bigbluebutton.conference.service.whiteboard; + + +import org.bigbluebutton.conference.service.messaging.MessagingConstants; +import org.bigbluebutton.conference.service.messaging.redis.MessageHandler; + +import org.bigbluebutton.core.api.IBigBlueButtonInGW; +import com.google.gson.JsonParser; +import com.google.gson.JsonObject; + +public class WhiteboardListener implements MessageHandler{ + + private IBigBlueButtonInGW bbbInGW; + + public void setBigBlueButtonInGW(IBigBlueButtonInGW bbbInGW) { + this.bbbInGW = bbbInGW; + } + + @Override + public void handleMessage(String pattern, String channel, String message) { + if (channel.equalsIgnoreCase(MessagingConstants.TO_WHITEBOARD_CHANNEL)) { + System.out.println("AntonChannel=(whiteboard)" + channel); + + JsonParser parser = new JsonParser(); + JsonObject obj = (JsonObject) parser.parse(message); + JsonObject headerObject = (JsonObject) obj.get("header"); + JsonObject payloadObject = (JsonObject) obj.get("payload"); + + String eventName = headerObject.get("name").toString().replace("\"", ""); + + if(eventName.equalsIgnoreCase("get_whiteboard_shapes_request")){ + //more cases to follow + + String roomName = payloadObject.get("meeting_id").toString().replace("\"", ""); + + if(eventName.equalsIgnoreCase("get_whiteboard_shapes_request")){ + String requesterID = payloadObject.get("requester_id").toString().replace("\"", ""); + if(payloadObject.get("whiteboard_id") != null){ + String whiteboardID = payloadObject.get("whiteboard_id").toString().replace("\"", ""); + System.out.println("\n FOUND A whiteboardID:" + whiteboardID + "\n"); + bbbInGW.requestWhiteboardAnnotationHistory(roomName, requesterID, whiteboardID, requesterID); + } + else { + System.out.println("\n DID NOT FIND A whiteboardID \n"); + } + System.out.println("\n\n\n user<" + requesterID + "> requested the shapes.\n\n"); + } + } + } + } +} diff --git a/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/MeetingActor.scala b/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/MeetingActor.scala index 29390b0bac..151d490524 100644 --- a/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/MeetingActor.scala +++ b/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/MeetingActor.scala @@ -36,60 +36,59 @@ class MeetingActor(val meetingID: String, meetingName: String, val recorded: Boo } def act() = { - loop { - react { - case "StartTimer" => handleStartTimer - case "Hello" => handleHello - case msg: ValidateAuthToken => handleValidateAuthToken(msg) - case msg: RegisterUser => handleRegisterUser(msg) - case msg: VoiceUserJoined => handleVoiceUserJoined(msg) - case msg: VoiceUserLeft => handleVoiceUserLeft(msg) - case msg: VoiceUserMuted => handleVoiceUserMuted(msg) - case msg: VoiceUserTalking => handleVoiceUserTalking(msg) - case msg: UserJoining => handleUserJoin(msg) - case msg: UserLeaving => handleUserLeft(msg) - case msg: AssignPresenter => handleAssignPresenter(msg) - case msg: GetUsers => handleGetUsers(msg) - case msg: ChangeUserStatus => handleChangeUserStatus(msg) - case msg: UserRaiseHand => handleUserRaiseHand(msg) - case msg: UserLowerHand => handleUserLowerHand(msg) - case msg: UserShareWebcam => handleUserShareWebcam(msg) - case msg: UserUnshareWebcam => handleUserunshareWebcam(msg) - case msg: MuteMeetingRequest => handleMuteMeetingRequest(msg) - case msg: IsMeetingMutedRequest => handleIsMeetingMutedRequest(msg) - case msg: MuteUserRequest => handleMuteUserRequest(msg) - case msg: LockUserRequest => handleLockUserRequest(msg) - case msg: EjectUserRequest => handleEjectUserRequest(msg) - case msg: SetLockSettings => handleSetLockSettings(msg) - case msg: InitLockSettings => handleInitLockSettings(msg) - case msg: LockUser => handleLockUser(msg) - case msg: LockAllUsers => handleLockAllUsers(msg) - case msg: GetLockSettings => handleGetLockSettings(msg) - case msg: IsMeetingLocked => handleIsMeetingLocked(msg) - case msg: GetChatHistoryRequest => handleGetChatHistoryRequest(msg) - case msg: SendPublicMessageRequest => handleSendPublicMessageRequest(msg) - case msg: SendPrivateMessageRequest => handleSendPrivateMessageRequest(msg) - case msg: UserConnectedToGlobalAudio => handleUserConnectedToGlobalAudio(msg) - case msg: UserDisconnectedFromGlobalAudio => handleUserDisconnectedFromGlobalAudio(msg) - case msg: GetCurrentLayoutRequest => handleGetCurrentLayoutRequest(msg) - case msg: LayoutLockSettings => handleLayoutLockSettings(msg) - case msg: SetLayoutRequest => handleSetLayoutRequest(msg) - case msg: LockLayoutRequest => handleLockLayoutRequest(msg) - case msg: UnlockLayoutRequest => handleUnlockLayoutRequest(msg) - case msg: InitializeMeeting => handleInitializeMeeting(msg) - case msg: ClearPresentation => handleClearPresentation(msg) - case msg: PresentationConversionUpdate => handlePresentationConversionUpdate(msg) - case msg: PresentationPageCountError => handlePresentationPageCountError(msg) - case msg: PresentationSlideGenerated => handlePresentationSlideGenerated(msg) - case msg: PresentationConversionCompleted => handlePresentationConversionCompleted(msg) - case msg: RemovePresentation => handleRemovePresentation(msg) - case msg: GetPresentationInfo => handleGetPresentationInfo(msg) - case msg: SendCursorUpdate => handleSendCursorUpdate(msg) - case msg: ResizeAndMoveSlide => handleResizeAndMoveSlide(msg) - case msg: GotoSlide => handleGotoSlide(msg) - case msg: SharePresentation => handleSharePresentation(msg) - case msg: GetSlideInfo => handleGetSlideInfo(msg) - case msg: PreuploadedPresentations => handlePreuploadedPresentations(msg) + loop { + react { + case "StartTimer" => handleStartTimer + case "Hello" => handleHello + case msg: ValidateAuthToken => handleValidateAuthToken(msg) + case msg: RegisterUser => handleRegisterUser(msg) + case msg: VoiceUserJoined => handleVoiceUserJoined(msg) + case msg: VoiceUserLeft => handleVoiceUserLeft(msg) + case msg: VoiceUserMuted => handleVoiceUserMuted(msg) + case msg: VoiceUserTalking => handleVoiceUserTalking(msg) + case msg: UserJoining => handleUserJoin(msg) + case msg: UserLeaving => handleUserLeft(msg) + case msg: AssignPresenter => handleAssignPresenter(msg) + case msg: GetUsers => handleGetUsers(msg) + case msg: ChangeUserStatus => handleChangeUserStatus(msg) + case msg: UserRaiseHand => handleUserRaiseHand(msg) + case msg: UserLowerHand => handleUserLowerHand(msg) + case msg: UserShareWebcam => handleUserShareWebcam(msg) + case msg: UserUnshareWebcam => handleUserunshareWebcam(msg) + case msg: MuteMeetingRequest => handleMuteMeetingRequest(msg) + case msg: IsMeetingMutedRequest => handleIsMeetingMutedRequest(msg) + case msg: MuteUserRequest => handleMuteUserRequest(msg) + case msg: LockUserRequest => handleLockUserRequest(msg) + case msg: EjectUserRequest => handleEjectUserRequest(msg) + case msg: SetLockSettings => handleSetLockSettings(msg) + case msg: InitLockSettings => handleInitLockSettings(msg) + case msg: LockUser => handleLockUser(msg) + case msg: LockAllUsers => handleLockAllUsers(msg) + case msg: GetLockSettings => handleGetLockSettings(msg) + case msg: IsMeetingLocked => handleIsMeetingLocked(msg) + case msg: GetChatHistoryRequest => handleGetChatHistoryRequest(msg) + case msg: SendPublicMessageRequest => handleSendPublicMessageRequest(msg) + case msg: SendPrivateMessageRequest => handleSendPrivateMessageRequest(msg) + case msg: UserConnectedToGlobalAudio => handleUserConnectedToGlobalAudio(msg) + case msg: UserDisconnectedFromGlobalAudio => handleUserDisconnectedFromGlobalAudio(msg) + case msg: GetCurrentLayoutRequest => handleGetCurrentLayoutRequest(msg) + case msg: SetLayoutRequest => handleSetLayoutRequest(msg) + case msg: LockLayoutRequest => handleLockLayoutRequest(msg) + case msg: UnlockLayoutRequest => handleUnlockLayoutRequest(msg) + case msg: InitializeMeeting => handleInitializeMeeting(msg) + case msg: ClearPresentation => handleClearPresentation(msg) + case msg: PresentationConversionUpdate => handlePresentationConversionUpdate(msg) + case msg: PresentationPageCountError => handlePresentationPageCountError(msg) + case msg: PresentationSlideGenerated => handlePresentationSlideGenerated(msg) + case msg: PresentationConversionCompleted => handlePresentationConversionCompleted(msg) + case msg: RemovePresentation => handleRemovePresentation(msg) + case msg: GetPresentationInfo => handleGetPresentationInfo(msg) + case msg: SendCursorUpdate => handleSendCursorUpdate(msg) + case msg: ResizeAndMoveSlide => handleResizeAndMoveSlide(msg) + case msg: GotoSlide => handleGotoSlide(msg) + case msg: SharePresentation => handleSharePresentation(msg) + case msg: GetSlideInfo => handleGetSlideInfo(msg) + case msg: PreuploadedPresentations => handlePreuploadedPresentations(msg) case msg: PreCreatedPoll => handlePreCreatedPoll(msg) case msg: CreatePoll => handleCreatePoll(msg) case msg: UpdatePoll => handleUpdatePoll(msg) @@ -103,22 +102,22 @@ class MeetingActor(val meetingID: String, meetingName: String, val recorded: Boo case msg: RespondToPoll => handleRespondToPoll(msg) case msg: HidePollResult => handleHidePollResult(msg) case msg: ShowPollResult => handleShowPollResult(msg) - case msg: SendWhiteboardAnnotationRequest => handleSendWhiteboardAnnotationRequest(msg) - case msg: GetWhiteboardShapesRequest => handleGetWhiteboardShapesRequest(msg) - case msg: ClearWhiteboardRequest => handleClearWhiteboardRequest(msg) - case msg: UndoWhiteboardRequest => handleUndoWhiteboardRequest(msg) - case msg: EnableWhiteboardRequest => handleEnableWhiteboardRequest(msg) - case msg: IsWhiteboardEnabledRequest => handleIsWhiteboardEnabledRequest(msg) - case msg: SetRecordingStatus => handleSetRecordingStatus(msg) - case msg: GetRecordingStatus => handleGetRecordingStatus(msg) - case msg: VoiceRecording => handleVoiceRecording(msg) - - case msg: EndMeeting => handleEndMeeting(msg) - case StopMeetingActor => exit - case _ => // do nothing - } - } - } + case msg: SendWhiteboardAnnotationRequest => handleSendWhiteboardAnnotationRequest(msg) + case msg: GetWhiteboardShapesRequest => handleGetWhiteboardShapesRequest(msg) + case msg: ClearWhiteboardRequest => handleClearWhiteboardRequest(msg) + case msg: UndoWhiteboardRequest => handleUndoWhiteboardRequest(msg) + case msg: EnableWhiteboardRequest => handleEnableWhiteboardRequest(msg) + case msg: IsWhiteboardEnabledRequest => handleIsWhiteboardEnabledRequest(msg) + case msg: SetRecordingStatus => handleSetRecordingStatus(msg) + case msg: GetRecordingStatus => handleGetRecordingStatus(msg) + case msg: VoiceRecording => handleVoiceRecording(msg) + + case msg: EndMeeting => handleEndMeeting(msg) + case StopMeetingActor => exit + case _ => // do nothing + } + } + } def hasMeetingEnded():Boolean = { meetingEnded diff --git a/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala b/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala index 8fc46abe55..d2dd418892 100644 --- a/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala +++ b/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala @@ -80,9 +80,6 @@ trait UsersApp { //send the presentation this ! (new GetPresentationInfo(meetingID, msg.userId, replyTo)) - - //send the whiteboard - //this ! (new GetWhiteboardShapesNoIdRequest(meetingID, msg.userId, replyTo)) } case None => outGW.send(new ValidateAuthTokenReply(meetingID, msg.userId, msg.token, false, msg.correlationId)) } diff --git a/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/whiteboard/WhiteboardApp.scala b/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/whiteboard/WhiteboardApp.scala index 7de004116e..30f461e8f9 100755 --- a/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/whiteboard/WhiteboardApp.scala +++ b/bigbluebutton-apps/src/main/scala/org/bigbluebutton/core/apps/whiteboard/WhiteboardApp.scala @@ -28,20 +28,20 @@ trait WhiteboardApp { } else if ((WhiteboardKeyUtil.PENCIL_TYPE == shapeType) && (WhiteboardKeyUtil.DRAW_START_STATUS == status)) { println("Received pencil draw start status") - wbModel.addAnnotation(wbId, shape) + wbModel.addAnnotation(wbId, shape) } else if ((WhiteboardKeyUtil.DRAW_END_STATUS == status) && ((WhiteboardKeyUtil.RECTANGLE_TYPE == shapeType) || (WhiteboardKeyUtil.ELLIPSE_TYPE == shapeType) - || (WhiteboardKeyUtil.TRIANGLE_TYPE == shapeType) - || (WhiteboardKeyUtil.LINE_TYPE == shapeType))) { + || (WhiteboardKeyUtil.TRIANGLE_TYPE == shapeType) + || (WhiteboardKeyUtil.LINE_TYPE == shapeType))) { println("Received [" + shapeType +"] draw end status") - wbModel.addAnnotation(wbId, shape) + wbModel.addAnnotation(wbId, shape) } else if (WhiteboardKeyUtil.TEXT_TYPE == shapeType) { - println("Received [" + shapeType +"] modify text status") - wbModel.modifyText(wbId, shape) - } else { - println("Received UNKNOWN whiteboard shape!!!!. status=[" + status + "], shapeType=[" + shapeType + "]") - } + println("Received [" + shapeType +"] modify text status") + wbModel.modifyText(wbId, shape) + } else { + println("Received UNKNOWN whiteboard shape!!!!. status=[" + status + "], shapeType=[" + shapeType + "]") + } wbModel.getWhiteboard(wbId) foreach {wb => println("WhiteboardApp::handleSendWhiteboardAnnotationRequest - num shapes [" + wb.shapes.length + "]") @@ -52,20 +52,20 @@ trait WhiteboardApp { } def handleGetWhiteboardShapesRequest(msg: GetWhiteboardShapesRequest) { - println("WB: Received page history [" + msg.whiteboardId + "]") + println("WB: Received page history [" + msg.whiteboardId + "]") wbModel.history(msg.whiteboardId) foreach {wb => outGW.send(new GetWhiteboardShapesReply(meetingID, recorded, - msg.requesterID, wb.id, wb.shapes.toArray, msg.replyTo)) + msg.requesterID, wb.id, wb.shapes.toArray, msg.replyTo)) } - } - + } + def handleClearWhiteboardRequest(msg: ClearWhiteboardRequest) { - println("WB: Received clear whiteboard") + println("WB: Received clear whiteboard") wbModel.clearWhiteboard(msg.whiteboardId) wbModel.getWhiteboard(msg.whiteboardId) foreach {wb => outGW.send(new ClearWhiteboardEvent(meetingID, recorded, - msg.requesterID, wb.id)) - } + msg.requesterID, wb.id)) + } } def handleUndoWhiteboardRequest(msg: UndoWhiteboardRequest) { diff --git a/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-app-chat.xml b/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-app-chat.xml index f0658c476e..ef1d82c2d6 100755 --- a/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-app-chat.xml +++ b/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-app-chat.xml @@ -39,5 +39,8 @@ with BigBlueButton; if not, see . - + + + + diff --git a/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-app-presentation.xml b/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-app-presentation.xml index aad204ed66..112a4a2da5 100755 --- a/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-app-presentation.xml +++ b/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-app-presentation.xml @@ -3,7 +3,7 @@ BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ -Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). +Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below). This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software @@ -51,12 +51,4 @@ with BigBlueButton; if not, see . - - - - - - - - diff --git a/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-app-users.xml b/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-app-users.xml index 0e8e62847c..a3eb650373 100755 --- a/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-app-users.xml +++ b/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-app-users.xml @@ -3,7 +3,7 @@ BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ -Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). +Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below). This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software @@ -26,7 +26,7 @@ with BigBlueButton; if not, see . http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd "> - + @@ -38,5 +38,8 @@ with BigBlueButton; if not, see . - + + + + diff --git a/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-app-whiteboard.xml b/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-app-whiteboard.xml index 8c426036f8..adcfb71c3a 100755 --- a/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-app-whiteboard.xml +++ b/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-app-whiteboard.xml @@ -3,7 +3,7 @@ BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ -Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). +Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below). This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software @@ -26,13 +26,17 @@ with BigBlueButton; if not, see . http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd "> - + - + - + - + + + + + diff --git a/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-redis-messaging.xml b/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-redis-messaging.xml index 47504b0d9f..36432b3644 100755 --- a/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-redis-messaging.xml +++ b/bigbluebutton-apps/src/main/webapp/WEB-INF/bbb-redis-messaging.xml @@ -3,7 +3,7 @@ BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ -Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). +Copyright (c) 2014 BigBlueButton Inc. and by respective authors (see below). This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software @@ -26,35 +26,35 @@ with BigBlueButton; if not, see . http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd "> - - + - - + + - - - + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/client/bbb-html5-client/config.coffee b/client/bbb-html5-client/config.coffee index 416d181cfc..210cdfbec6 100755 --- a/client/bbb-html5-client/config.coffee +++ b/client/bbb-html5-client/config.coffee @@ -33,6 +33,7 @@ config.redis.channels.toBBBApps.pattern = "bigbluebutton:to-bbb-apps:*" config.redis.channels.toBBBApps.chat = "bigbluebutton:to-bbb-apps:chat" config.redis.channels.toBBBApps.meeting = "bigbluebutton:to-bbb-apps:meeting" config.redis.channels.toBBBApps.users = "bigbluebutton:to-bbb-apps:users" +config.redis.channels.toBBBApps.whiteboard = "bigbluebutton:to-bbb-apps:whiteboard" config.redis.internalChannels = {} config.redis.internalChannels.receive = "html5-receive" config.redis.internalChannels.reply = "html5-reply" diff --git a/client/bbb-html5-client/lib/redispubsub.coffee b/client/bbb-html5-client/lib/redispubsub.coffee index 1c969805b8..c8e007f391 100644 --- a/client/bbb-html5-client/lib/redispubsub.coffee +++ b/client/bbb-html5-client/lib/redispubsub.coffee @@ -97,6 +97,7 @@ module.exports = class RedisPubSub if message.header?.name is 'get_presentation_info_reply' #filter for the current=true page on the server-side currentPage = null + numCurrentPage = null presentations = message.payload?.presentations for presentation in presentations @@ -105,6 +106,23 @@ module.exports = class RedisPubSub for page in pages if page.current is true currentPage = page + numCurrentPage = page.num + + console.log "\n\n\n\n the message is: " + JSON.stringify message + console.log "\n" + message.payload?.presentations[0]?.id + "/" + numCurrentPage + "\n\n" + #request the whiteboard information + requestMessage = { + "payload": { + "meeting_id": message.payload?.meeting_id + "requester_id": message.payload?.requester_id + "whiteboard_id": message.payload?.presentations[0]?.id + "/" + numCurrentPage #not sure if always [0] + }, + "header": { + "timestamp": new Date().getTime() + "name": "get_whiteboard_shapes_request" + } + } + @publishing(config.redis.channels.toBBBApps.whiteboard, requestMessage) #strip off excess data, leaving only the current slide information message.payload.currentPage = currentPage diff --git a/client/bbb-html5-client/public/js/collections/users.coffee b/client/bbb-html5-client/public/js/collections/users.coffee index e7f9a22016..9716ade8bd 100755 --- a/client/bbb-html5-client/public/js/collections/users.coffee +++ b/client/bbb-html5-client/public/js/collections/users.coffee @@ -5,7 +5,6 @@ define [ 'cs!models/user' ], (_, Backbone, globals, UserModel) -> - # TODO: this class should actually store UserModel's, for now it is only trigerring events UsersCollection = Backbone.Collection.extend model: UserModel diff --git a/client/bbb-html5-client/public/js/models/connection.coffee b/client/bbb-html5-client/public/js/models/connection.coffee index e6279d276e..165deecbb2 100755 --- a/client/bbb-html5-client/public/js/models/connection.coffee +++ b/client/bbb-html5-client/public/js/models/connection.coffee @@ -19,12 +19,8 @@ define [ @username = @getUrlVars()["username"] globals.meetingName = decodeURI(@getUrlVars()["meetingName"]) - disconnect: => - alert( " i go through disconnect") - if @socket? - #@socket.disconnect() - else - console.log "tried to disconnect but it's not connected" + disconnect: -> + alert( " i go through disconnect") # not used right now connect: -> console.log("user_id=" + @userId + " auth_token=" + @authToken + " meeting_id=" + @meetingId) @@ -58,12 +54,10 @@ define [ console.log "socket.io received: data" globals.events.trigger("message", data) - # Immediately say we are connected @socket.on "connect", => console.log "socket on: connect" globals.events.trigger("connection:connected") - #@socket.emit "user connect" # tell the server we have a new user message = { "payload": { @@ -81,9 +75,11 @@ define [ if @authToken? and @userId? and @meetingId? @socket.emit "message", message + # Received a list of users from bbb-apps + # param {object} message object @socket.on "get_users_reply", (message) => requesterId = message.payload?.requester_id - + if(requesterId is @userId) users = [] for user in message.payload?.users @@ -91,6 +87,8 @@ define [ globals.events.trigger("connection:load_users", users) + # Received a the chat history for a meeting + # @param {object} message object @socket.on "get_chat_history_reply", (message) => requesterId = message.payload?.requester_id if(requesterId is @userId) @@ -111,83 +109,91 @@ define [ globals.events.trigger("connection:disconnected") @socket = null - @socket.on "reconnect", -> - console.log "socket on: reconnect" - globals.events.trigger("connection:reconnect") + #@socket.on "reconnect", -> + # console.log "socket on: reconnect" + # globals.events.trigger("connection:reconnect") - @socket.on "reconnecting", -> - console.log "socket on: reconnecting" - globals.events.trigger("connection:reconnecting") + #@socket.on "reconnecting", -> + # console.log "socket on: reconnecting" + # globals.events.trigger("connection:reconnecting") - @socket.on "reconnect_failed", -> - console.log "socket on: reconnect_failed" - globals.events.trigger("connection:reconnect_failed") + #@socket.on "reconnect_failed", -> + # console.log "socket on: reconnect_failed" + # globals.events.trigger("connection:reconnect_failed") # If an error occurs while not connected # @param {string} reason Reason for the error. - @socket.on "error", (reason) -> - console.error "unable to connect socket.io", reason + #@socket.on "error", (reason) -> #TODO + # console.error "unable to connect socket.io", reason # Received event to update all the slide images # @param {Array} urls list of URLs to be added to the paper (after old images are removed) - @socket.on "all_slides", (allSlidesEventObject) => - console.log "socket on: all_slides" - console.log "allSlidesEventObject: " + allSlidesEventObject - globals.events.trigger("connection:all_slides", allSlidesEventObject); + #@socket.on "all_slides", (allSlidesEventObject) => + # console.log "socket on: all_slides" + # console.log "allSlidesEventObject: " + allSlidesEventObject + # globals.events.trigger("connection:all_slides", allSlidesEventObject); # Received event to clear the whiteboard shapes - @socket.on "clrPaper",=> - console.log "socket on: clrPaper" - globals.events.trigger("connection:clrPaper") + #@socket.on "clrPaper",=> + # console.log "socket on: clrPaper" + # globals.events.trigger("connection:clrPaper") # Received event to update all the shapes in the whiteboard # @param {Array} shapes Array of shapes to be drawn - @socket.on "allShapes", (allShapesEventObject) => - console.log "socket on: all_shapes" + allShapesEventObject - globals.events.trigger("connection:all_shapes", allShapesEventObject) + #@socket.on "allShapes", (allShapesEventObject) => + # # check for the requester_id + # console.log "socket on: all_shapes" + allShapesEventObject + # globals.events.trigger("connection:all_shapes", allShapesEventObject) + + # Received event to update all the shapes in the whiteboard + # @param {Array} shapes Array of shapes to be drawn + #@socket.on "get_whiteboard_shapes_reply", (object) => + # if @userId is object.payload?.requester_id + # #alert("I am getting some shapes reply" + JSON.stringify object) + # for shape in object.payload?.shapes + # #alert("for a shape:") + # shape_type = shape.shape_type + # globals.events.trigger("connection:whiteboard_draw_event", shape_type, shape.shape) # TODO to change the name + + # globals.events.trigger("connection:updShape", shape_type, shape.shape) # Received event to update a shape being created # @param {string} shape type of shape being updated # @param {Array} data all information to update the shape - @socket.on "whiteboard_update_event", (data) => - console.log "socket on: whiteboard_update_event" - shape = data.payload.shape_type + @socket.on "send_whiteboard_shape_message", (data) => + alert "send_whiteboard_shape_message" + JSON.stringify data + shape = data.payload.shape.shape_type + for point in data.payload.shape.shape.points + point = point/100 #early attempt to scale down + globals.events.trigger("connection:whiteboard_draw_event", shape, data) globals.events.trigger("connection:updShape", shape, data) - # Received event to create a shape on the whiteboard - # @param {string} shape type of shape being made - # @param {Array} data all information to make the shape - @socket.on "whiteboard_draw_event", (data) => - console.log "socket on: whiteboard_draw_event" - shape = data.payload.shape_type - globals.events.trigger("connection:whiteboard_draw_event", shape, data) - # Pencil drawings are received as points from the server and painted as lines. - @socket.on "whiteboardDrawPen", (data) => - console.log "socket on: whiteboardDrawPen"+ data - globals.events.trigger("connection:whiteboardDrawPen", data) + #@socket.on "whiteboardDrawPen", (data) => + # console.log "socket on: whiteboardDrawPen"+ data + # globals.events.trigger("connection:whiteboardDrawPen", data) # Received event to update the cursor coordinates # @param {number} x x-coord of the cursor as a percentage of page width # @param {number} y y-coord of the cursor as a percentage of page height - @socket.on "mvCur", (data) => - x = data.cursor.x #TODO change to new json structure - y = data.cursor.y #TODO change to new json structure - console.log "socket on: mvCur" - globals.events.trigger("connection:mvCur", x, y) + #@socket.on "mvCur", (data) => + # x = data.cursor.x #TODO change to new json structure + # y = data.cursor.y #TODO change to new json structure + # console.log "socket on: mvCur" + # globals.events.trigger("connection:mvCur", x, y) # Received event to update the zoom or move the slide # @param {number} x x-coord of the cursor as a percentage of page width # @param {number} y y-coord of the cursor as a percentage of page height - @socket.on "move_and_zoom", (xOffset, yOffset, widthRatio, heightRatio) => - console.log "socket on: move_and_zoom" - globals.events.trigger("connection:move_and_zoom", xOffset, yOffset, widthRatio, heightRatio) + #@socket.on "move_and_zoom", (xOffset, yOffset, widthRatio, heightRatio) => + # console.log "socket on: move_and_zoom" + # globals.events.trigger("connection:move_and_zoom", xOffset, yOffset, widthRatio, heightRatio) # Received event to update the slide image # @param {string} url URL of image to show - @socket.on "changeslide", (url) => - console.log "socket on: changeslide" - globals.events.trigger("connection:changeslide", url) + #@socket.on "changeslide", (url) => + # console.log "socket on: changeslide" + # globals.events.trigger("connection:changeslide", url) # Received event to update the viewBox value # @param {string} xperc Percentage of x-offset from top left corner @@ -195,50 +201,50 @@ define [ # @param {string} wperc Percentage of full width of image to be displayed # @param {string} hperc Percentage of full height of image to be displayed # TODO: not tested yet - @socket.on "viewBox", (xperc, yperc, wperc, hperc) => - console.log "socket on: viewBox" - globals.events.trigger("connection:viewBox", xperc, yperc, wperc, hperc) + #@socket.on "viewBox", (xperc, yperc, wperc, hperc) => + # console.log "socket on: viewBox" + # globals.events.trigger("connection:viewBox", xperc, yperc, wperc, hperc) # Received event to update the zoom level of the whiteboard. # @param {number} delta amount of change in scroll wheel - @socket.on "zoom", (delta) -> - console.log "socket on: zoom" - globals.events.trigger("connection:zoom", delta) + #@socket.on "zoom", (delta) -> + # console.log "socket on: zoom" + # globals.events.trigger("connection:zoom", delta) # Received event to update the whiteboard size and position # @param {number} cx x-offset from top left corner as percentage of original width of paper # @param {number} cy y-offset from top left corner as percentage of original height of paper # @param {number} sw slide width as percentage of original width of paper # @param {number} sh slide height as a percentage of original height of paper - @socket.on "paper", (cx, cy, sw, sh) -> - console.log "socket on: paper" - globals.events.trigger("connection:paper", cx, cy, sw, sh) + #@socket.on "paper", (cx, cy, sw, sh) -> + # console.log "socket on: paper" + # globals.events.trigger("connection:paper", cx, cy, sw, sh) # Received event when the panning action finishes - @socket.on "panStop", -> - console.log "socket on: panStop" - globals.events.trigger("connection:panStop") + #@socket.on "panStop", -> + # console.log "socket on: panStop" + # globals.events.trigger("connection:panStop") # Received event to denote when the text has been created - @socket.on "textDone", -> - console.log "socket on: textDone" - globals.events.trigger("connection:textDone") + #@socket.on "textDone", -> + # console.log "socket on: textDone" + # globals.events.trigger("connection:textDone") # Received event to update the status of the upload progress # @param {string} message update message of status of upload progress # @param {boolean} fade true if you wish the message to automatically disappear after 3 seconds - @socket.on "uploadStatus", (message, fade) => - console.log "socket on: uploadStatus" - globals.events.trigger("connection:uploadStatus", message, fade) + #@socket.on "uploadStatus", (message, fade) => + # console.log "socket on: uploadStatus" + # globals.events.trigger("connection:uploadStatus", message, fade) # Received event for a user list change # @param {Array} users Array of names and publicIDs of connected users # TODO: event name with spaces is bad - @socket.on "user list change", (users) => - console.log "socket on: user list change" - globals.events.trigger("connection:user_list_change", users) + #@socket.on "user list change", (users) => + # console.log "socket on: user list change" + # globals.events.trigger("connection:user_list_change", users) # Received event for a new user @socket.on "user_joined_message", (message) => @@ -253,9 +259,9 @@ define [ # Received event to set the presenter to a user # @param {string} userID publicID of the user that is being set as the current presenter - @socket.on "setPresenter", (userid) => - console.log "socket on: setPresenter" - globals.events.trigger("connection:setPresenter", userid) + #@socket.on "setPresenter", (userid) => + # console.log "socket on: setPresenter" + # globals.events.trigger("connection:setPresenter", userid) # Received event to update all the messages in the chat box # @param {Array} messages Array of messages in public chat box @@ -276,16 +282,12 @@ define [ @socket.emit "mvCur", x, y # Requests the shapes from the server. - emitAllShapes: -> - @socket.emit "all_shapes" + #emitAllShapes: -> + # @socket.emit "all_shapes" - - # Emit a message to the server - # @param {string} the message + # Emit a chat message to the server + # @param {string} the chat message emitMsg: (msg) -> - - console.log "emitting message: " + msg - object = { "header": { "name": "send_public_chat_message" @@ -312,28 +314,28 @@ define [ @socket.emit "message", object # Emit the finish of a text shape - emitTextDone: -> - @socket.emit "textDone" + #emitTextDone: -> + # @socket.emit "textDone" # Emit the creation of a shape # @param {string} shape type of shape # @param {Array} data all the data required to draw the shape on the client whiteboard - emitMakeShape: (shape, data) -> - @socket.emit "makeShape", shape, data + #emitMakeShape: (shape, data) -> + # @socket.emit "makeShape", shape, data # Emit the update of a shape # @param {string} shape type of shape # @param {Array} data all the data required to update the shape on the client whiteboard - emitUpdateShape: (shape, data) -> - @socket.emit "updShape", shape, data + #emitUpdateShape: (shape, data) -> + # @socket.emit "updShape", shape, data # Emit an update in the whiteboard position/size values # @param {number} cx x-offset from top left corner as percentage of original width of paper # @param {number} cy y-offset from top left corner as percentage of original height of paper # @param {number} sw slide width as percentage of original width of paper # @param {number} sh slide height as a percentage of original height of paper - emitPaperUpdate: (cx, cy, sw, sh) -> - @socket.emit "paper", cx, cy, sw, sh + #emitPaperUpdate: (cx, cy, sw, sh) -> + # @socket.emit "paper", cx, cy, sw, sh # Update the zoom level for the clients # @param {number} delta amount of change in scroll wheel @@ -341,12 +343,12 @@ define [ @socket.emit "zoom", delta # Request the next slide - emitNextSlide: -> - @socket.emit "nextslide" + #emitNextSlide: -> + # @socket.emit "nextslide" # Request the previous slide - emitPreviousSlide: -> - @socket.emit "prevslide" + #emitPreviousSlide: -> + # @socket.emit "prevslide" # Logout of the meeting emitLogout: -> @@ -363,37 +365,36 @@ define [ } @socket.emit "message", message @socket.disconnect() - #@disconnect() - + #Utils.postToUrl "logout" #window.location.replace "./" # Emit panning has stopped - emitPanStop: -> - @socket.emit "panStop" + #emitPanStop: -> + # @socket.emit "panStop" # Publish a shape to the server to be saved # @param {string} shape type of shape to be saved # @param {Array} data information about shape so that it can be recreated later - emitPublishShape: (shape, data) -> - @socket.emit "saveShape", shape, JSON.stringify(data) + #emitPublishShape: (shape, data) -> + # @socket.emit "saveShape", shape, JSON.stringify(data) # Emit a change in the current tool # @param {string} tool [description] - emitChangeTool: (tool) -> - @socket.emit "changeTool", tool + #emitChangeTool: (tool) -> + # @socket.emit "changeTool", tool # Tell the server to undo the last shape - emitUndo: -> - @socket.emit "undo" + #emitUndo: -> + # @socket.emit "undo" # Emit a change in the presenter - emitSetPresenter: (id) -> - @socket.emit "setPresenter", id + #emitSetPresenter: (id) -> + # @socket.emit "setPresenter", id # Emit signal to clear the canvas - emitClearCanvas: (id) -> - @socket.emit "clrPaper", id + #emitClearCanvas: (id) -> + # @socket.emit "clrPaper", id # Helper method to get the meeting_id, user_id and auth_token from the url getUrlVars: -> diff --git a/client/bbb-html5-client/public/js/models/whiteboard_paper.coffee b/client/bbb-html5-client/public/js/models/whiteboard_paper.coffee index 16a1c36f76..ecbf2255e6 100755 --- a/client/bbb-html5-client/public/js/models/whiteboard_paper.coffee +++ b/client/bbb-html5-client/public/js/models/whiteboard_paper.coffee @@ -23,6 +23,7 @@ define [ # Container must be a DOM element initialize: (@container) -> + alert("initializing the paper model") # a WhiteboardCursorModel @cursor = null @@ -278,7 +279,7 @@ define [ @cursor.undrag() @currentLine = @_createTool(tool) @cursor.drag(@currentLine.dragOnMove, @currentLine.dragOnStart, @currentLine.dragOnEnd) - when "rect" + when "rectangle" @cursor.undrag() @currentRect = @_createTool(tool) @cursor.drag(@currentRect.dragOnMove, @currentRect.dragOnStart, @currentRect.dragOnEnd) @@ -351,6 +352,7 @@ define [ # Draws an array of shapes to the paper. # @param {array} shapes the array of shapes to draw drawListOfShapes: (shapes) -> + alert("drawListOfShapes" + shapes.length) @currentShapesDefinitions = shapes @currentShapes = @raphaelObj.set() for shape in shapes @@ -392,6 +394,7 @@ define [ # Make a shape `shape` with the data in `data`. makeShape: (shape, data) -> + console.log("shape=" + shape + " data=" + JSON.stringify data) tool = null switch shape when "path", "line" @@ -409,6 +412,7 @@ define [ when "triangle" @currentTriangle = @_createTool(shape) toolModel = @currentTriangle + toolModel.draw(tool, data) tool = @currentTriangle.make(data) when "text" @currentText = @_createTool(shape) @@ -417,7 +421,12 @@ define [ else console.log "shape not recognized at makeShape", shape if tool? - @currentShapes.push(tool) + alert("in currentShapes") + if @currentShapes? #rewrite TODO + @currentShapes.push(tool) + else + @currentShapes = [] + @currentShapes.push(tool) @currentShapesDefinitions.push(toolModel.getDefinition()) # Update the cursor position on screen @@ -515,6 +524,7 @@ define [ globals.events.on "connection:allShapes", (allShapesEventObject) => # TODO: a hackish trick for making compatible the shapes from redis with the node.js + alert("on connection:allShapes:" + JSON.stringify allShapesEventObject) shapes = allShapesEventObject.shapes for shape in shapes properties = JSON.parse(shape.data) diff --git a/client/bbb-html5-client/public/js/models/whiteboard_rect.coffee b/client/bbb-html5-client/public/js/models/whiteboard_rect.coffee index bc27f797da..d66cc99ea6 100644 --- a/client/bbb-html5-client/public/js/models/whiteboard_rect.coffee +++ b/client/bbb-html5-client/public/js/models/whiteboard_rect.coffee @@ -24,16 +24,16 @@ define [ # @param {string} colour the colour of the object # @param {number} thickness the thickness of the object's line(s) make: (startingData) -> - console.log "make startingData"+ startingData - x = startingData.payload.data.coordinate.first_x - y = startingData.payload.data.coordinate.first_y - color = startingData.payload.data.line.color - thickness = startingData.payload.data.line.weight + console.log "make startingData"#+ JSON.stringify startingData + x = startingData.payload.shape.shape.points[0] + y = startingData.payload.shape.shape.points[1] + color = startingData.payload.shape.shape.color + thickness = startingData.payload.shape.shape.thickness @obj = @paper.rect(x * @gw + @xOffset, y * @gh + @yOffset, 0, 0, 1) @obj.attr Utils.strokeAndThickness(color, thickness) @definition = - shape: "rect" + shape: "rectangle" data: [x, y, 0, 0, @obj.attrs["stroke"], @obj.attrs["stroke-width"]] @obj @@ -44,11 +44,11 @@ define [ # @param {number} y2 the y value of the bottom right corner # @param {boolean} square (draw a square or not) update: (startingData) -> - x1 = startingData.payload.data.coordinate.first_x - y1 = startingData.payload.data.coordinate.first_y - x2 = startingData.payload.data.coordinate.last_x - y2 = startingData.payload.data.coordinate.last_y - square = startingData.payload.data.square + x1 = startingData.payload.shape.shape.points[0] + y1 = startingData.payload.shape.shape.points[1] + x2 = startingData.payload.shape.shape.points[2] + y2 = startingData.payload.shape.shape.points[3] + square = startingData.payload.shape.shape.square if @obj? [x1, x2] = [x2, x1] if x2 < x1 [x1, x2] = [x2, x1] if x2 < x1 diff --git a/client/bbb-html5-client/public/js/views/session_users.coffee b/client/bbb-html5-client/public/js/views/session_users.coffee index f6b245e194..7209b2993d 100755 --- a/client/bbb-html5-client/public/js/views/session_users.coffee +++ b/client/bbb-html5-client/public/js/views/session_users.coffee @@ -72,8 +72,6 @@ define [ # Removes all a user from the list #TODO - for now it does not remove but moves to the left hand side _removeUserByID: (userID)-> @$("#user-"+userID).remove() - #@$("#user-"+userID).parent().context.hidden = "true" - #console.log @$el.children("ul") # Marks a user as selected when clicked. _userClicked: (e) -> diff --git a/client/bbb-html5-client/public/templates/user.html b/client/bbb-html5-client/public/templates/user.html index 796c1bf1c6..816a12d461 100644 --- a/client/bbb-html5-client/public/templates/user.html +++ b/client/bbb-html5-client/public/templates/user.html @@ -1,10 +1,10 @@ -
  • -
    -
    -
    <%= username %>
    -
    -
    -
    -
    -
    -
  • +
  • +
    +
    +
    <%= username %>
    +
    +
    +
    +
    +
    +
  • \ No newline at end of file diff --git a/labs/bbb-api-php/includes/bbb-api.php b/labs/bbb-api-php/includes/bbb-api.php index 53b791ec23..311a7e7201 100644 --- a/labs/bbb-api-php/includes/bbb-api.php +++ b/labs/bbb-api-php/includes/bbb-api.php @@ -36,6 +36,8 @@ Versions: - Now using Zend coding, naming and style conventions - Refactored methods to accept standardized parameters & match BBB API structure -- See included samples for usage examples + 1.4 -- Updated by xaker1 + (email : admin [a t ] xaker1 DOT ru) */ /* _______________________________________________________________________*/ @@ -60,7 +62,7 @@ class BigBlueButton { $this->_bbbServerBaseUrl = CONFIG_SERVER_BASE_URL; } - private function _processXmlResponse($url){ + private function _processXmlResponse($url, $xml = ''){ /* A private utility method used by other public methods to process XML responses. */ @@ -71,6 +73,16 @@ class BigBlueButton { curl_setopt( $ch, CURLOPT_URL, $url ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, $timeout); + if(!empty($xml)){ + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + 'Content-type: application/xml', + 'Content-length: ' . strlen($xml) + )); + } $data = curl_exec( $ch ); curl_close( $ch ); @@ -79,10 +91,13 @@ class BigBlueButton { else return false; } + if(!empty($xml)) + throw new Exception('Set xml, but curl does not installed.'); + return (simplexml_load_file($url)); } - private function _requiredParam($param) { + private function _requiredParam($param, $name = '') { /* Process required params and throw errors if we don't get values */ if ((isset($param)) && ($param != '')) { return $param; @@ -91,7 +106,7 @@ class BigBlueButton { throw new Exception('Missing parameter.'); } else { - throw new Exception(''.$param.' is required.'); + throw new Exception(''.$name.' is required.'); } } @@ -119,8 +134,8 @@ class BigBlueButton { USAGE: (see $creationParams array in createMeetingArray method.) */ - $this->_meetingId = $this->_requiredParam($creationParams['meetingId']); - $this->_meetingName = $this->_requiredParam($creationParams['meetingName']); + $this->_meetingId = $this->_requiredParam($creationParams['meetingId'], 'meetingId'); + $this->_meetingName = $this->_requiredParam($creationParams['meetingName'], 'meetingName'); // Set up the basic creation URL: $creationUrl = $this->_bbbServerBaseUrl."api/create?"; // Add params: @@ -144,7 +159,7 @@ class BigBlueButton { return ( $creationUrl.$params.'&checksum='.sha1("create".$params.$this->_securitySalt) ); } - public function createMeetingWithXmlResponseArray($creationParams) { + public function createMeetingWithXmlResponseArray($creationParams, $xml = '') { /* USAGE: $creationParams = array( @@ -162,8 +177,9 @@ class BigBlueButton { 'duration' => '0', -- Default = 0 which means no set duration in minutes. [number] 'meta_category' => '', -- Use to pass additional info to BBB server. See API docs to enable. ); + $xml = ''; -- Use to pass additional xml to BBB server. Example, use to Preupload Slides. See API docs. */ - $xml = $this->_processXmlResponse($this->getCreateMeetingURL($creationParams)); + $xml = $this->_processXmlResponse($this->getCreateMeetingURL($creationParams), $xml); if($xml) { if($xml->meetingID) @@ -204,9 +220,9 @@ class BigBlueButton { 'webVoiceConf' => '' -- OPTIONAL - string ); */ - $this->_meetingId = $this->_requiredParam($joinParams['meetingId']); - $this->_username = $this->_requiredParam($joinParams['username']); - $this->_password = $this->_requiredParam($joinParams['password']); + $this->_meetingId = $this->_requiredParam($joinParams['meetingId'], 'meetingId'); + $this->_username = $this->_requiredParam($joinParams['username'], 'username'); + $this->_password = $this->_requiredParam($joinParams['password'], 'password'); // Establish the basic join URL: $joinUrl = $this->_bbbServerBaseUrl."api/join?"; // Add parameters to the URL: @@ -231,8 +247,8 @@ class BigBlueButton { 'password' => 'mp' -- REQUIRED - The moderator password for the meeting ); */ - $this->_meetingId = $this->_requiredParam($endParams['meetingId']); - $this->_password = $this->_requiredParam($endParams['password']); + $this->_meetingId = $this->_requiredParam($endParams['meetingId'], 'meetingId'); + $this->_password = $this->_requiredParam($endParams['password'], 'password'); $endUrl = $this->_bbbServerBaseUrl."api/end?"; $params = 'meetingID='.urlencode($this->_meetingId). @@ -272,7 +288,7 @@ class BigBlueButton { /* USAGE: $meetingId = '1234' -- REQUIRED - The unique id for the meeting */ - $this->_meetingId = $this->_requiredParam($meetingId); + $this->_meetingId = $this->_requiredParam($meetingId, 'meetingId'); $runningUrl = $this->_bbbServerBaseUrl."api/isMeetingRunning?"; $params = 'meetingID='.urlencode($this->_meetingId); @@ -363,8 +379,8 @@ class BigBlueButton { 'password' => 'mp' -- REQUIRED - The moderator password for the meeting ); */ - $this->_meetingId = $this->_requiredParam($infoParams['meetingId']); - $this->_password = $this->_requiredParam($infoParams['password']); + $this->_meetingId = $this->_requiredParam($infoParams['meetingId'], 'meetingId'); + $this->_password = $this->_requiredParam($infoParams['password'], 'password'); $infoUrl = $this->_bbbServerBaseUrl."api/getMeetingInfo?"; $params = 'meetingID='.urlencode($this->_meetingId). diff --git a/labs/demos/.gitignore b/labs/demos/.gitignore new file mode 100644 index 0000000000..3c3629e647 --- /dev/null +++ b/labs/demos/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/labs/demos/config.json b/labs/demos/config.json index 1ffa20f1fa..fc20efda85 100644 --- a/labs/demos/config.json +++ b/labs/demos/config.json @@ -1,7 +1,7 @@ { "settings": { - "IP": "http://192.168.0.203", + "IP": "http://192.168.0.232", "PORT": "4000", - "salt": "74a91f30f165423067bf3039722e33e0" + "salt": "c7faeb82a786bd71134b61833b0ec4af" } -} \ No newline at end of file +} \ No newline at end of file diff --git a/labs/demos/lib/handlers.coffee b/labs/demos/lib/handlers.coffee index 7616127ab4..11c204d3d9 100755 --- a/labs/demos/lib/handlers.coffee +++ b/labs/demos/lib/handlers.coffee @@ -20,6 +20,8 @@ login = (req, resp) -> #calling createapi bbbapi.create(createParams, serverAndSecret, {}, (errorOuter, responseOuter, bodyOuter) -> #console.log JSON.stringify(response) + console.log "\n\nouterXML=" + responseOuter.body + console.log "\nerrorOuter=" + JSON.stringify errorOuter bbbapi.join(joinParams, serverAndSecret, {}, (error, response, body) -> if error console.log error @@ -39,7 +41,7 @@ login = (req, resp) -> "\nuser_id = " + user_id + "\nauth_token = " + auth_token - url = "#{configJson.settings.IP}:3000/html5.client?meeting_id=" + meeting_id + "&user_id=" + + url = "#{configJson.settings.IP}:3000/meeting_id=" + meeting_id + "&user_id=" + user_id + "&auth_token=" + auth_token + "&username=" + joinParams.fullName + "&meetingName=" + joinParams.meetingID json = diff --git a/labs/demos/lib/testapi.coffee b/labs/demos/lib/testapi.coffee index 664f9223df..7e5b0cff1a 100644 --- a/labs/demos/lib/testapi.coffee +++ b/labs/demos/lib/testapi.coffee @@ -17,13 +17,13 @@ createParams.attendeePW = "ap" createParams.moderatorPW = "mp" createParams.record = false createParams.voiceBridge = 70827 -createParams.name = "Demo Meeting" -createParams.meetingID = "Demo Meeting" +createParams.name = "Demo Meeting9" +createParams.meetingID = "Demo Meeting9" joinParams = {} joinParams.password = "mp" joinParams.fullName = "Richard" -joinParams.meetingID = "Demo Meeting" +joinParams.meetingID = "Demo Meeting9" joinParams.redirect = false serverAndSecret = {server: bbbServer, secret: sharedSecret} diff --git a/labs/meteor-client/.gitignore b/labs/meteor-client/.gitignore new file mode 100644 index 0000000000..f9ced93c2f --- /dev/null +++ b/labs/meteor-client/.gitignore @@ -0,0 +1 @@ +packages diff --git a/labs/meteor-client/.meteor/packages b/labs/meteor-client/.meteor/packages index 6026737d9e..5a8dc1445f 100644 --- a/labs/meteor-client/.meteor/packages +++ b/labs/meteor-client/.meteor/packages @@ -6,8 +6,11 @@ standard-app-packages autopublish insecure -bootstrap -less coffeescript +redis +npm +less bootstrap-3 iron-router +router +bootstrap diff --git a/labs/meteor-client/client/routing.coffee b/labs/meteor-client/client/routing.coffee deleted file mode 100755 index 842ef9a0ef..0000000000 --- a/labs/meteor-client/client/routing.coffee +++ /dev/null @@ -1,8 +0,0 @@ -Meteor.navigateTo = (path) -> - Router.go path - -Router.configure layoutTemplate: 'layout' - -Router.map -> - @route 'main', - path: '/' \ No newline at end of file diff --git a/labs/meteor-client/collections/users.js b/labs/meteor-client/collections/users.js index 5ce37294d9..02af2f7c5f 100755 --- a/labs/meteor-client/collections/users.js +++ b/labs/meteor-client/collections/users.js @@ -23,5 +23,10 @@ Meteor.methods({ }, showUserId: function() { throw new Meteor.Error(422, this.userId); + }, + addToCollection: function(userid, meeting_id) { + var user = {userId: userid, meetingId: meeting_id}; + var userId = Meteor.users.insert(user); + console.log("added user id=[" + userId + "] :" + JSON.stringify(user)); } }); \ No newline at end of file diff --git a/labs/meteor-client/config.coffee b/labs/meteor-client/config.coffee new file mode 100644 index 0000000000..314e8d88b5 --- /dev/null +++ b/labs/meteor-client/config.coffee @@ -0,0 +1,54 @@ +# # Global configurations file + +config = {} + +# Default global variables +config.appName = 'BigBlueButton HTML5 Client' +config.maxUsernameLength = 30 +config.maxChatLength = 140 + +# the path in which an image of a presentation is stored +config.presentationImagePath = (meetingID, presentationID, filename) -> + "bigbluebutton/presentation/#{meetingID}/#{meetingID}/#{presentationID}/png/#{filename}" + +## Application configurations +config.app = {} + +# Generate a new secret with: +# $ npm install crypto +# $ coffee +# coffee> crypto = require 'crypto' +# coffee> crypto.randomBytes(32).toString('base64') +config.app.sessionSecret = "J7XSu96KC/B/UPyeGub3J6w6QFXWoUNABVgi9Q1LskE=" + +# Configs for redis +config.redis = {} +config.redis.host = "127.0.0.1" +config.redis.post = "6379" +config.redis.timeout = 5000 +config.redis.channels = {} +config.redis.channels.fromBBBApps = "bigbluebutton:from-bbb-apps:*" +config.redis.channels.toBBBApps = {} +config.redis.channels.toBBBApps.pattern = "bigbluebutton:to-bbb-apps:*" +config.redis.channels.toBBBApps.chat = "bigbluebutton:to-bbb-apps:chat" +config.redis.channels.toBBBApps.meeting = "bigbluebutton:to-bbb-apps:meeting" +config.redis.channels.toBBBApps.users = "bigbluebutton:to-bbb-apps:users" +config.redis.channels.toBBBApps.whiteboard = "bigbluebutton:to-bbb-apps:whiteboard" +config.redis.internalChannels = {} +config.redis.internalChannels.receive = "html5-receive" +config.redis.internalChannels.reply = "html5-reply" +config.redis.internalChannels.publish = "html5-publish" + +# Logging +config.log = {} + +config.log.path = if process.env.NODE_ENV == "production" + "/var/log/bigbluebutton/bbbnode.log" +else + "./log/development.log" + +# Global instance of Modules, created by `app.coffee` +config.modules = null + + +Meteor.config = config \ No newline at end of file diff --git a/labs/meteor-client/lib/router.coffee b/labs/meteor-client/lib/router.coffee new file mode 100644 index 0000000000..9c0b82a8a9 --- /dev/null +++ b/labs/meteor-client/lib/router.coffee @@ -0,0 +1,34 @@ +Meteor.Router.configure layoutTemplate: 'layout' + +Meteor.Router.add { + '/': 'main', + + '*': (url)-> + + # Here we want to extract the meeting_id, user_id, auth_token, etc + # from the uri + + if url.indexOf("meeting_id") > -1 # if the URL is /meeting_id=...&... + urlParts = url.split("&"); + + meetingId = urlParts[0]?.split("=")[1]; + console.log "meetingId=" + meetingId + + userId = urlParts[1]?.split("=")[1]; + console.log "userId=" + userId + + authToken = urlParts[2]?.split("=")[1]; + console.log "authToken=" + authToken + + userName = urlParts[3]?.split("=")[1]; + console.log "userName=" + userName + if meetingId? and userId? and authToken? and userName? + redisPubSub = new Meteor.RedisPubSub + redisPubSub.sendValidateToken(meetingId, userId, authToken) + 'main' + + else + console.log "unable to extract from the URL some of {meetingId, userName, userId, authToken}" + else + console.log "unable to extract the required information for the meeting from the URL" +} diff --git a/labs/meteor-client/packages.json b/labs/meteor-client/packages.json new file mode 100644 index 0000000000..10cfd8126c --- /dev/null +++ b/labs/meteor-client/packages.json @@ -0,0 +1,11 @@ +{ + "request": "2.34.0", + "require": "0.5.0", + "bunyan": "0.22.2", + "socket.io": "1.0.4", + "crypto-js": "3.1.2-3", + "lodash": "1.3.1", + "postal": "0.10.0", + "events": "1.0.1", + "redis": "0.10.3" +} \ No newline at end of file diff --git a/labs/meteor-client/server/app.coffee b/labs/meteor-client/server/app.coffee new file mode 100644 index 0000000000..eca0751db2 --- /dev/null +++ b/labs/meteor-client/server/app.coffee @@ -0,0 +1,30 @@ +### +if Meteor.isServer + console.log " I am in the server" + Meteor.startup -> + console.log "On startup in the server" + console.log Meteor.config.appName + + + + #a = new Meteor.ClientProxy() + + #b = new Meteor.RedisPubSub() + + + + # Module to store the modules registered in the application + Meteor.config.modules = modules = new Meteor.Modules() + # Router + #config.modules.register "MainRouter", new MainRouter() + + # Application modules + Meteor.config.modules.register "RedisPubSub", new Meteor.RedisPubSub() + #Meteor.config.modules.register "MessageBus", new Meteor.MessageBus() + #Meteor.config.modules.register "Controller", new Controller() + + clientProxy = new Meteor.ClientProxy() + Meteor.config.modules.register "ClientProxy", clientProxy + ###############clientProxy.listen(app) + +### \ No newline at end of file diff --git a/labs/meteor-client/server/bbblogger.coffee b/labs/meteor-client/server/bbblogger.coffee new file mode 100755 index 0000000000..cf1b793aca --- /dev/null +++ b/labs/meteor-client/server/bbblogger.coffee @@ -0,0 +1,17 @@ +### +bunyan = Meteor.require 'bunyan' + +logger = bunyan.createLogger({ + name: 'bbbnode', + streams: [ + { + level: 'debug', + stream: process.stdout, + }, + { + level: 'info', + path: Meteor.config.log.path + } + ] +}) +### \ No newline at end of file diff --git a/labs/meteor-client/server/clientproxy.coffee b/labs/meteor-client/server/clientproxy.coffee new file mode 100644 index 0000000000..085b5f42db --- /dev/null +++ b/labs/meteor-client/server/clientproxy.coffee @@ -0,0 +1,93 @@ + +socketio = Meteor.require('socket.io') + +MESSAGE = "message" + +moduleDeps = ["Controller"] + +class Meteor.ClientProxy + + constructor: -> + Meteor.config.modules.wait moduleDeps, => + @controller = Meteor.config.modules.get("Controller") + + # Listens for events on the websocket and does something when they are triggered. + listen: (app) -> + @io = socketio.listen(app) + @io.set('log level', 1) + @io.sockets.on 'connection', (socket) => + log.debug({ client: socket.id }, "Client has connected.") + socket.on 'message', (jsonMsg) => + log.debug({ message: jsonMsg }, "Received message") + @_handleMessage(socket, jsonMsg) + socket.on 'disconnect', => + @_handleClientDisconnected socket + + # Sends a message in `data` to all the clients that should receive it. + sendToClients: (data, callback) -> + #log.debug({ data: data }, "Sending to client") + + # the channel can be the user_id (send to one user only) or the meeting_id + # (send to everyone in the meeting) + channel = data?.payload?.user_id or data?.payload?.meeting_id + + # if the data has "header":{"name":"some_event_name"} use that name + # otherwise look for "name":"some_event_name" in the top level of the data + eventName = data?.header?.name or data?.name + + # clients = @io.sockets.clients(channel) + # console.log "Found", clients?.length, "clients for the channel", channel + + #log.debug({ channel: channel, eventName: eventName, message: data, clientCount: clients?.length }, + # "Sending message to websocket clients") + + # TODO: if `channel` is undefined, it should not send the message, + # instead if is sending to all users + @io.sockets.in(channel).emit(eventName, data) + callback?() + + _handleClientDisconnected: (socket) -> + console.log "\ntrying to disconnect" + + #if socket.userId? + # log.info("User [#{socket.userId}] has disconnected.") + + _handleMessage: (socket, message) -> + if message.header?.name? + @_handleValidMessage(socket, message) + else + log.error({ message: message }, "Invalid message.") + + _handleValidMessage: (socket, message) => + switch message.header.name + when 'validate_auth_token' + @_handleLoginMessage socket, message + when 'send_public_chat_message' + @controller.sendingChat message + when 'user_leaving_request' + @controller.sendingUsersMessage message + else + log.error({ message: message }, 'Unknown message name.') + + _handleLoginMessage: (socket, data) -> + @controller.processAuthMessage(data, (err, result) -> + if err? + log.debug({ message: result }, "Sending authentication not OK to user and disconnecting socket") + sendMessageToClient(socket, result) + socket.disconnect() + else + log.debug({ userChannel: result.payload.user_id, meetingChannel: result.payload.meeting_id }, + "Subscribing a user to his channels") + socket.join(result.payload.user_id) + socket.join(result.payload.meeting_id) + + # assign the userId to this socket. This way we can + # locate this socket using the userId. + socket.userId = result?.payload?.user_id + + log.debug({ message: result }, "Sending authentication OK reply to user") + sendMessageToClient(socket, result) + ) + +sendMessageToClient = (socket, message) -> + socket.emit(MESSAGE, message) diff --git a/labs/meteor-client/server/controller.coffee b/labs/meteor-client/server/controller.coffee new file mode 100755 index 0000000000..e0e6ba94f0 --- /dev/null +++ b/labs/meteor-client/server/controller.coffee @@ -0,0 +1,39 @@ +moduleDeps = ["MessageBus", "ClientProxy"] + +class Meteor.Controller + + constructor: -> + config.modules.wait moduleDeps, => + @messageBus = config.modules.get("MessageBus") + @clientProxy = config.modules.get("ClientProxy") + + @messageBus.receiveMessages (data) => + @processReceivedMessage(data) + + processReceivedMessage: (data, callback) -> + @clientProxy.sendToClients(data, callback) + + # Processes a message requesting authentication + processAuthMessage: (data, callback) -> + log.info({ data: data }, "Sending an authentication request and waiting for reply") + @messageBus.sendAndWaitForReply data, (err, result) -> + if err? + log.error({ reason: err, result: result, original: data }, "Authentication failure") + callback(err, null) + else + if result.payload?.valid + log.info({ result: result }, "Authentication successful") + callback(null, result) + else + log.info({ result: result }, "Authentication failure") + callback(new Error("Authentication failure"), null) + + # processEndMessage: (data, callback) -> + # @clientProxy.endMeeting() + + sendingChat: (data) => + @messageBus.sendingToRedis(config.redis.channels.toBBBApps.chat, data) + + + sendingUsersMessage: (data) => + @messageBus.sendingToRedis(config.redis.channels.toBBBApps.users, data) diff --git a/labs/meteor-client/server/messagebus.coffee b/labs/meteor-client/server/messagebus.coffee new file mode 100755 index 0000000000..0a79b8c044 --- /dev/null +++ b/labs/meteor-client/server/messagebus.coffee @@ -0,0 +1,40 @@ +crypto = Meteor.require 'crypto' +postal = Meteor.require 'postal' + +moduleDeps = ["RedisPubSub"] + +class Meteor.MessageBus + + constructor: -> + Meteor.config.modules.wait moduleDeps, => + @pubSub = Meteor.config.modules.get("RedisPubSub") + + receiveMessages: (callback) -> + postal.subscribe + channel: Meteor.config.redis.internalChannels.receive + topic: "broadcast" + callback: (msg, envelope) -> + callback(msg) + + sendAndWaitForReply: (data, callback) -> + replyTo = + channel: Meteor.config.redis.internalChannels.reply + topic: 'get.' + crypto.randomBytes(16).toString('hex') + + postal.subscribe( + channel: replyTo.channel + topic: replyTo.topic + callback: (msg, envelope) -> + callback(null, msg) + ).once() + + log.info({ message: data, replyTo: replyTo }, "Sending a message and waiting for reply") + + postal.publish + channel: Meteor.config.redis.internalChannels.publish + topic: 'broadcast' + replyTo: replyTo + data: data + + sendingToRedis: (channel, message) => + @pubSub.publishing(channel, message) diff --git a/labs/meteor-client/server/modules.coffee b/labs/meteor-client/server/modules.coffee new file mode 100644 index 0000000000..a05564f554 --- /dev/null +++ b/labs/meteor-client/server/modules.coffee @@ -0,0 +1,79 @@ + +_ = Meteor.require('lodash') +EventEmitter = Meteor.require('events').EventEmitter + + + +# Helper class to register and store modules. +# It stores a list of objects in an object literal indexed by the name of the module. +# Includes methods for a class to ask for a module and wait until it is ready. +# +# This class is used to prevent errors when modules are required in an order that +# would generate a circular dependency. In these cases, the objects might not be loaded +# entirely. +# @see http://nodejs.org/api/modules.html#modules_cycles +# +# With this class, the class requiring a module can wait until the module is properly +# loaded. More than that, it will return an instanced object, not only a reference to +# a class (as usually done when using `require`). +# +# @example How to use it +# modules = new Modules() +# modules.wait ["db"], -> +# # this callback will only be called when the module "db" is registered +# db = config.modules.get("db") +# ... +# # calls to register a module can be made anywhere in the application +# modules.register "db", new Database() +# +class Meteor.Modules extends EventEmitter + + constructor: -> + # the list of modules registered: + @modules = {} + # list of callbacks waiting for a module to be registered: + @callbacks = [] + + # Registers a new module with the name in `name` and the content in `object`. + # @param name [string] the name of the module + # @param object [string] the instance of the module + # @return [object] the same object in the parameter `object` + register: (name, object) -> + @modules[name] = object + @_checkCallbacks() + object + + # Blocks until a list of modules is registered. + # @param names [Array] an array of strings with the names of all modules that should be waited for + # @param callback [Function] will be called when *all* modules in `names` are registered + wait: (names, callback) -> + names = [names] unless _.isArray(names) + @callbacks.push {modules: names, fn: callback} + @_checkCallbacks() + + # Returns the module with the name in `name`. + # @param name [string] the name of the module to be returned + # @return [object] the module asked for + get: (name) -> + @modules[name] + + # Returns the list of all modules registered. + # @return [Array] all modules registered + all: -> + @modules + + # Run through the list of registered callbacks in `@callbacks` to see if any of + # them are ready to be called (if all modules waited for are available). + # @private + _checkCallbacks: () -> + toRemove = [] + for cb in @callbacks + done = true + for name in cb.modules + unless @modules[name]? + done = false + break + if done + cb.fn() + toRemove.push cb + @callbacks = _.filter(@callbacks, (i) -> i not in toRemove) diff --git a/labs/meteor-client/server/redispubsub.coffee b/labs/meteor-client/server/redispubsub.coffee new file mode 100644 index 0000000000..044bc4ceaf --- /dev/null +++ b/labs/meteor-client/server/redispubsub.coffee @@ -0,0 +1,55 @@ +class Meteor.RedisPubSub + constructor: () -> + console.log "constructor RedisPubSub" + + @pubClient = redis.createClient() + @subClient = redis.createClient() + + @subClient.on "psubscribe", Meteor.bindEnvironment(@_onSubscribe ) + @subClient.on "pmessage", Meteor.bindEnvironment(@_onMessage) + + #log.info + console.log("RPC: Subscribing message on channel: #{Meteor.config.redis.channels.fromBBBApps}") + @subClient.psubscribe(Meteor.config.redis.channels.fromBBBApps) + + # Construct and send a message to bbb-web to validate the user + sendValidateToken: (meetingId, userId, authToken) -> + console.log "\n\n i am sending a validate_auth_token with " + userId + "" + meetingId + + message = { + "payload": { + "auth_token": authToken + "userid": userId + "meeting_id": meetingId + }, + "header": { + "timestamp": new Date().getTime() + "reply_to": meetingId + "/" + userId + "name": "validate_auth_token" + } + } + if authToken? and userId? and meetingId? + @pubClient.publish(Meteor.config.redis.channels.toBBBApps.meeting, JSON.stringify(message)) + else + console.log "did not have enough information to send a validate_auth_token message" + + _onSubscribe: (channel, count) -> + #Meteor.call("addToCollection"); + console.log "Subscribed to #{channel}" + + _onMessage: (pattern, channel, jsonMsg) => + # TODO: this has to be in a try/catch block, otherwise the server will + # crash if the message has a bad format + + message = JSON.parse(jsonMsg) + correlationId = message.payload?.reply_to or message.header?.reply_to + + unless message.header?.name is "keep_alive_reply" + console.log "\nchannel=" + channel + console.log "correlationId=" + correlationId if correlationId? + console.log "eventType=" + message.header?.name + "\n" + #log.debug({ pattern: pattern, channel: channel, message: message}, "Received a message from redis") + console.log jsonMsg + + if message.header?.name is "user_joined_message" + Meteor.call("addToCollection", message.payload.user.userid, message.payload.meeting_id); diff --git a/labs/meteor-client/server/server.js b/labs/meteor-client/server/server.js index ef9c76404e..95821f3dc7 100755 --- a/labs/meteor-client/server/server.js +++ b/labs/meteor-client/server/server.js @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////// // Startup // - +/* Meteor.startup(function () { console.log('server start'); // cleanup collections @@ -22,4 +22,5 @@ Meteor.startup(function () { // Set collection permissions SetCollectionPermissions(); -}); \ No newline at end of file +}); +*/ diff --git a/labs/meteor-client/smart.json b/labs/meteor-client/smart.json index fe196844c7..048f255ba1 100644 --- a/labs/meteor-client/smart.json +++ b/labs/meteor-client/smart.json @@ -1,6 +1,9 @@ { "packages": { + "redis": {}, + "npm": {}, "bootstrap-3": {}, - "iron-router": {} + "iron-router": {}, + "router": {} } } diff --git a/labs/meteor-client/smart.lock b/labs/meteor-client/smart.lock index a617c0cb06..704bd73f54 100644 --- a/labs/meteor-client/smart.lock +++ b/labs/meteor-client/smart.lock @@ -2,10 +2,23 @@ "meteor": {}, "dependencies": { "basePackages": { + "redis": {}, + "npm": {}, "bootstrap-3": {}, - "iron-router": {} + "iron-router": {}, + "router": {} }, "packages": { + "redis": { + "git": "https://github.com/stevemanuel/meteor-redis.git", + "tag": "v0.1.3", + "commit": "8af9aaab84f4a87358ccb4616592a373661f712b" + }, + "npm": { + "git": "https://github.com/arunoda/meteor-npm.git", + "tag": "v0.2.6", + "commit": "177ab6118de5bf8cffb19481343d5762ff7a2aaf" + }, "bootstrap-3": { "git": "https://github.com/mangasocial/meteor-bootstrap-3.git", "tag": "v3.1.1-1", @@ -16,10 +29,25 @@ "tag": "v0.7.1", "commit": "d1ffb3f06ea4c112132b030f2eb1a70b81675ecb" }, + "router": { + "git": "https://github.com/tmeasday/meteor-router.git", + "tag": "v0.6.1", + "commit": "8ec75fec7affdefc787d19f23b36fd6d1d44ef04" + }, "blaze-layout": { "git": "https://github.com/EventedMind/blaze-layout.git", "tag": "v0.2.4", "commit": "b40e9b0612329288d75cf52ad14a7da64bb8618f" + }, + "page-js-ie-support": { + "git": "https://github.com/tmeasday/meteor-page-js-ie-support.git", + "tag": "v1.3.5", + "commit": "b99ed8380aefd10b2afc8f18d9eed4dd0d8ea9cb" + }, + "HTML5-History-API": { + "git": "https://github.com/tmeasday/meteor-HTML5-History-API.git", + "tag": "v4.1.2", + "commit": "b5fca79f9ae8936e5f748a04f475295f4fa1cc7a" } } }