mv com/google/domain/registry google/registry

This change renames directories in preparation for the great package
rename. The repository is now in a broken state because the code
itself hasn't been updated. However this should ensure that git
correctly preserves history for each file.
This commit is contained in:
Justine Tunney 2016-05-13 18:55:08 -04:00
parent a41677aea1
commit 5012893c1d
2396 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,159 @@
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
load("//third_party/closure/compiler:closure_js_binary.bzl", "closure_js_binary")
load("//third_party/closure/compiler:closure_js_deps.bzl", "closure_js_deps")
exports_files(["globals.txt"])
filegroup(
name = "assets_recursive",
srcs = glob(["assets/**"]),
)
filegroup(
name = "js_files_recursive",
srcs = [
"//java/com/google/domain/registry/ui/js:js_files",
"//java/com/google/domain/registry/ui/js/registrar:js_files",
"//java/com/google/domain/registry/ui/soy:js_files",
"//java/com/google/domain/registry/ui/soy/api:js_files",
"//java/com/google/domain/registry/ui/soy/registrar:js_files",
],
)
filegroup(
name = "runfiles",
srcs = [
"brain_bin.js",
"brain_bin_map.js",
"registrar_bin.js",
"registrar_bin_map.js",
":assets_recursive",
"//java/com/google/domain/registry/ui:deps-runfiles.js",
"//java/com/google/domain/registry/ui/css:registrar_bin.css",
"//java/com/google/domain/registry/ui/html:html_files",
"//javascript/closure:js_files_recursive",
"//javascript/template/soy:soy_usegoog_js_files",
],
)
filegroup(
name = "runfiles_debug",
srcs = [
"deps-runfiles.js",
":js_files_recursive",
":runfiles",
"//java/com/google/domain/registry/ui/css:css_files",
"//javascript/closure:js_files_recursive",
"//javascript/template/soy:soy_usegoog_js_files",
"//third_party/javascript/closure:js_files_recursive",
],
)
java_library(
name = "ui",
srcs = glob(["*.java"]),
deps = [
"//java/com/google/common/base",
"//java/com/google/domain/registry/config",
"//third_party/java/appengine:appengine-api",
"//third_party/java/dagger",
"//third_party/java/jsr305_annotations",
],
)
closure_js_deps(
name = "deps",
srcs = [
"//java/com/google/domain/registry/ui/js",
"//java/com/google/domain/registry/ui/js/registrar",
],
)
SOURCEMAP_SCRUB = (" -e 's@b....-out/[^/]*/bin/@@g'" +
" -e 's@b....-out/[^/]*/genfiles/@@g'" +
" -e 's@\"java/@\"/assets/sources/java/@g'" +
" -e 's@\"javascript/@\"/assets/sources/javascript/@g'" +
" -e 's@\"third_party/@\"/assets/sources/third_party/@g'" +
" -e 's@\"external/@\"/assets/sources/external/@g'")
################################################################################
## Registrar Console
# This is the full-blown compiled JavaScript source code for the registrar
# console. Everything, including the soy templates, is compiled into a single
# .js file. The only symbols that will be available are the ones you @export.
# This will also replace calls to goog.getCssName() and {css ...} with their
# non-union minified equivalents.
closure_js_binary(
name = "registrar_bin",
externs_list = ["//java/com/google/domain/registry/ui/externs"],
main = "registry.registrar.main",
deps = [
"//java/com/google/domain/registry/ui/css:registrar_bin",
"//java/com/google/domain/registry/ui/js/registrar",
],
)
# Mangle all the paths in the generated sourcemap so they're absolute; assuming
# the codebase is available under /assets/sources/.
genrule(
name = "registrar_bin_sourcemap",
srcs = ["registrar_bin.sourcemap"],
outs = ["registrar_bin.js.map"],
cmd = "sed $(location registrar_bin.sourcemap) " + SOURCEMAP_SCRUB + " >$@",
)
# The webserver should provide this to trusted admin users, rather than
# registrar_bin.js. This is what makes debugging in production possible.
genrule(
name = "registrar_mapped",
srcs = ["registrar_bin.js"],
outs = ["registrar_bin_map.js"],
cmd = "cat $(location registrar_bin.js) >$@ && " +
"echo '//# sourceMappingURL=registrar_bin.js.map' >>$@",
)
# This target creates a compiled JavaScript file where symbols are renamed to
# include dollar signs. This is useful for testing, because you can still read
# the source code, but it'll fail if your code is incorrect with regard to
# dotted and quoted properties. The same applies to CSS class names, which get
# an extra underscore.
closure_js_binary(
name = "registrar_dbg",
debug = 1,
externs_list = ["//java/com/google/domain/registry/ui/externs"],
formatting = "PRETTY_PRINT",
main = "registry.registrar.main",
deps = [
"//java/com/google/domain/registry/ui/css:registrar_dbg",
"//java/com/google/domain/registry/ui/js/registrar",
],
)
################################################################################
## Braintree Payment Method Frame (Brainframe)
closure_js_binary(
name = "brain_bin",
externs_list = ["//java/com/google/domain/registry/ui/externs"],
main = "registry.registrar.BrainFrame.main",
deps = ["//java/com/google/domain/registry/ui/js/registrar"],
)
genrule(
name = "brain_bin_sourcemap",
srcs = ["brain_bin.sourcemap"],
outs = ["brain_bin.js.map"],
cmd = "sed $(location brain_bin.sourcemap) " + SOURCEMAP_SCRUB + " >$@",
)
genrule(
name = "brain_mapped",
srcs = ["brain_bin.js"],
outs = ["brain_bin_map.js"],
cmd = "cat $(location brain_bin.js) >$@ && " +
"echo '//# sourceMappingURL=brain_bin.js.map' >>$@",
)

View file

@ -0,0 +1,54 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.ui;
import com.google.appengine.api.users.UserService;
import com.google.domain.registry.config.ConfigModule.Config;
import dagger.Module;
import dagger.Provides;
/** Dagger module for UI configuration. */
@Module
public final class ConsoleConfigModule { // TODO(b/26829015): Move to config package.
@Provides
static ConsoleDebug provideConsoleDebug() {
return ConsoleDebug.get();
}
/** URL of Braintree iframe sandbox iframe static HTML file. */
@Provides
@Config("brainframe")
static String provideBrainframe(
ConsoleDebug debug,
UserService userService,
@Config("projectId") String projectId) {
switch (debug) {
case PRODUCTION:
return String.format("https://%s.storage.googleapis.com/%s",
projectId,
userService.isUserLoggedIn() && userService.isUserAdmin()
? "brainframe-map.html"
: "brainframe.html");
case DEBUG:
case RAW:
case TEST:
return "/assets/html/insecure-brainframe.html";
default:
throw new AssertionError(debug.toString());
}
}
}

View file

@ -0,0 +1,47 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.ui;
/** Enum defining which JS/CSS files get rendered in a soy templates. */
public enum ConsoleDebug {
/** Use compiled CSS and JS. */
PRODUCTION,
/** Use debug compiled CSS and JS, where symbols are only <i>slightly</i> mangled. */
DEBUG,
/**
* Use debug compiled CSS and raw JS from internal source code dependency-managed directory
* structure.
*/
RAW,
/** Don't use any CSS or JS. This is used by JSTD unit tests. */
TEST;
private static final String PROPERTY = "console.debug";
private static final String DEFAULT = PRODUCTION.name();
/** Returns value configured by system property {@code #PROPERTY}. */
public static ConsoleDebug get() {
return valueOf(System.getProperty(PROPERTY, DEFAULT));
}
/** Sets the global {@link ConsoleDebug} state. */
public static void set(ConsoleDebug value) {
System.setProperty(PROPERTY, value.toString());
}
}

View file

@ -0,0 +1,8 @@
<!doctype html>
<!-- gsutil cp -a public-read -z html brainframe-alpha.html gs://domain-registry-alpha/brainframe.html -->
<!-- curl https://domain-registry-alpha.storage.googleapis.com/brainframe.html -->
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
<script src="https://domain-registry-alpha.appspot.com/assets/js/brain_bin_map.js"></script>
<body style="margin:0">
<form><div id="brainframe"></div></form>
<script>registry.registrar.BrainFrame.main('https://domain-registry-alpha.appspot.com', 'brainframe');</script>

View file

@ -0,0 +1,13 @@
<!doctype html>
<!-- Source-mapped production iframe sandbox for Braintree iframe -->
<!-- -->
<!-- This is the same thing as brainframe.html except it allows admins of -->
<!-- the App Engine application to see the original source code in Chrome's -->
<!-- debugger if an exception is thrown by the minified JavaScript blob. -->
<!-- -->
<!-- gsutil cp -a public-read -z html brainframe-map.html gs://domain-registry/brainframe-map.html -->
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
<script src="https://domain-registry.appspot.com/assets/js/brain_bin_map.js"></script>
<body style="margin:0">
<form><div id="brainframe"></div></form>
<script>registry.registrar.BrainFrame.main('https://domain-registry.appspot.com', 'brainframe');</script>

View file

@ -0,0 +1,14 @@
<!doctype html>
<!-- Production iframe sandbox for Braintree iframe -->
<!-- -->
<!-- In order to securely embed Braintree's iframe, this iframe must be -->
<!-- served from a separate origin. To do this, we manually deploy this -->
<!-- as a static HTML file to a cloud storage bucket, as follows: -->
<!-- -->
<!-- gsutil cp -a public-read -z html brainframe.html gs://domain-registry/brainframe.html -->
<!-- curl https://domain-registry.storage.googleapis.com/brainframe.html -->
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
<script src="https://domain-registry.appspot.com/assets/js/brain_bin.js"></script>
<body style="margin:0">
<form><div id="brainframe"></div></form>
<script>registry.registrar.BrainFrame.main('https://domain-registry.appspot.com', 'brainframe');</script>

View file

@ -0,0 +1,10 @@
<!doctype html>
<!-- This can only be used by admins in a testing environment. -->
<!-- This iframe provides no isolation of Braintree from Console. -->
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
<script src="/assets/sources/javascript/closure/base.js"></script>
<script src="/assets/sources/deps-runfiles.js"></script>
<body style="margin:0">
<form><div id="brainframe"></div></form>
<script>goog.require('registry.registrar.BrainFrame.main');</script>
<script>registry.registrar.BrainFrame.main('*', 'brainframe');</script>

View file

@ -0,0 +1,4 @@
<!doctype html>
<!-- Brainframe for integration testing. -->
<form><div id="brainframe"></div></form>
<script>window.parent.postMessage('{"type": "payment_method", "method": {"type": "CreditCard", "nonce": "imanonce", "details": {"cardType": "Visa", "lastTwo": "00"}}}', '*');</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,016 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="21px" height="21px" viewBox="0 0 21 21" overflow="visible" enable-background="new 0 0 21 21"
xml:space="preserve">
<defs>
</defs>
<path fill="#FFFFFF" d="M17.646,16.501L14,12.855c0.566-0.811,0.903-1.792,0.903-2.855c0-2.762-2.238-5-5-5c-2.761,0-5,2.238-5,5
s2.239,5,5,5c1.064,0,2.045-0.337,2.856-0.903l3.646,3.646c0.343,0.343,0.898,0.344,1.241,0.001
C17.99,17.399,17.99,16.845,17.646,16.501z M9.903,13.2c-1.767,0-3.199-1.433-3.199-3.2s1.433-3.2,3.199-3.2
c1.77,0,3.199,1.433,3.199,3.2S11.673,13.2,9.903,13.2z"/>
<rect opacity="0" fill="#4387FD" width="21" height="21"/>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="21px" height="21px" viewBox="0 0 21 21" overflow="visible" enable-background="new 0 0 21 21"
xml:space="preserve">
<defs>
</defs>
<path fill="#FFFFFF" d="M17.646,16.501L14,12.855c0.566-0.811,0.903-1.792,0.903-2.855c0-2.762-2.238-5-5-5c-2.761,0-5,2.238-5,5
s2.239,5,5,5c1.064,0,2.045-0.337,2.856-0.903l3.646,3.646c0.343,0.343,0.898,0.344,1.241,0.001
C17.99,17.399,17.99,16.845,17.646,16.501z M9.903,13.2c-1.767,0-3.199-1.433-3.199-3.2s1.433-3.2,3.199-3.2
c1.77,0,3.199,1.433,3.199,3.2S11.673,13.2,9.903,13.2z"/>
<rect opacity="0" fill="#4387FD" width="21" height="21"/>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="21px" height="21px" viewBox="0 0 21 21" overflow="visible" enable-background="new 0 0 21 21"
xml:space="preserve">
<defs>
</defs>
<path d="M16.895,11.64C16.96,11.269,17,10.89,17,10.5s-0.04-0.77-0.106-1.14l1.535-1.129l0.183-0.683l-1.5-2.598l-0.684-0.184
l-1.738,0.762c-0.581-0.49-1.25-0.875-1.979-1.138L12.5,2.5L12,2H9L8.5,2.499L8.29,4.395C7.562,4.658,6.896,5.043,6.314,5.531
L4.571,4.768L3.889,4.951l-1.5,2.598l0.183,0.684l1.535,1.129C4.04,9.731,4,10.111,4,10.5s0.04,0.769,0.106,1.139l-1.535,1.129
l-0.183,0.684l1.5,2.598l0.682,0.184l1.744-0.764c0.58,0.488,1.248,0.873,1.975,1.137l0.21,1.896L9,19h3l0.5-0.5l0.21-1.892
c0.729-0.263,1.398-0.648,1.979-1.138l1.738,0.762l0.684-0.184l1.499-2.598l-0.181-0.683L16.895,11.64z M14.15,10.5
c0,2.016-1.635,3.65-3.65,3.65c-2.016,0-3.65-1.635-3.65-3.65s1.635-3.65,3.65-3.65C12.515,6.85,14.15,8.484,14.15,10.5z"/>
<rect opacity="0" fill="#4387FD" width="21" height="21"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 B

View file

@ -0,0 +1,24 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Test existing solely to run the :check BUILD rule.
*/
goog.setTestOnly();
goog.require('goog.testing.jsunit');
function testNothing() {}

View file

@ -0,0 +1,8 @@
# -*-protobuf-*-
requirement: {
type: BANNED_PROPERTY_WRITE
error_message: 'Assignment to Element.prototype.innerHTML is not allowed. '
'Use goog.dom.safe.setInnerHtml instead. '
value: 'Element.prototype.innerHTML'
}

View file

@ -0,0 +1,50 @@
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
load("//third_party/closure/stylesheets:closure_css_library.bzl", "closure_css_library")
load("//third_party/closure/stylesheets:closure_css_binary.bzl", "closure_css_binary")
filegroup(
name = "css_files",
srcs = glob(["*.css"]),
)
closure_css_library(
name = "kd_components_lib",
srcs = ["kd_components.css"],
)
closure_css_library(
name = "registry_lib",
srcs = [
"console.css",
"forms.css",
"registry.css",
],
deps = [":kd_components_lib"],
)
closure_css_library(
name = "registrar_lib",
srcs = [
"contact-settings.css",
"contact-us.css",
"dashboard.css",
"epp.css",
"resources.css",
"security-settings.css",
],
deps = [":registry_lib"],
)
closure_css_binary(
name = "registrar_bin",
deps = [":registrar_lib"],
)
closure_css_binary(
name = "registrar_dbg",
debug = 1,
deps = [":registrar_lib"],
)

View file

@ -0,0 +1,102 @@
.description {
display: block;
clear: both;
font-size: 12px;
color: #999;
line-height: 140%;
}
.whoAreYou {
width: 50%;
margin: 5em auto;
}
/* Console disabled page. */
.whoAreYou-disabled {
width: 50%;
margin: 5em auto;
}
.whoAreYou-disabled p img {
display: block;
margin: 3em auto;
}
.whoAreYou-disabled h1 {
border-top: solid 1px #ebebeb;
margin-top: 1em;
}
/* XXX: Should be re-enabled when search works. */
#kd-search {
display: none;
}
/* XXX: Should be generalized for use throughout console. */
div.domain-registrar-contact div.tooltip {
visibility: hidden;
/* XXX: Should have auto-width. */
width: 110px;
left: -55px;
height: 1em;
white-space: nowrap;
position: relative;
color: white;
background: #2d2d2d;
padding: 0.5em;
z-index: 2000;
margin-top: -30px;
font-style: normal;
}
div.domain-registrar-contact div.tooltip .pointer {
outline: none;
display: block;
position: relative;
bottom: -7px;
left: 55px;
margin: 0 0 0 -5px;
width: 0;
height: 0;
line-height: 0px;
font-size: 0px;
/* This sets the tooptip pointer color */
border-bottom: transparent;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid #2d2d2d;
}
.reg-cryingAndroid {
float: right;
}
.reg-bullets {
padding-left: 1em;
list-style: disc inside;
}
.reg-payment p {
max-width: 45em;
}
/* Note: Empty definitions are for class name minimization. */
.reg-payment-form {}
.reg-payment-form-method {}
.reg-payment-form-method.kd-formerror {
border: none;
}
.reg-payment-form-method-info {}
.reg-payment-form-submit {}
.reg-payment-form-loader {
vertical-align: middle;
padding-left: 10px;
}
.reg-payment-again {}

View file

@ -0,0 +1,58 @@
.domain-registrar-contacts {
vertical-align: top;
text-align: top;
}
.domain-registrar-contact {
display: table-cell;
padding-right: 4em;
padding-bottom: 2em;
}
.domain-registrar-contact div {
margin: 0.5em 0;
}
.domain-registrar-contact + br {
display: table-row;
}
.domain-registrar-contact-name {
font-weight: bold;
display: inline;
padding-right: 30px;
}
.domain-registrar-contact-name i {
float: right;
width: 16px;
height: 16px;
}
.domain-registrar-contact-name i.domain-registrar-contact-visible-in-whois {
background: url('/assets/images/visibleOn_16.png') no-repeat right;
}
/** Postal style for address. */
td.setting-group-compact div.contact-address-city,
td.setting-group-compact div.contact-address-state,
td.setting-group-compact div.contact-address-zip,
td.setting-group-compact div.contact-address-cc {
width: initial;
display: inline;
}
/** Back to regular box flow for phone. */
td.setting-group-compact input#phoneNumber {
clear: both;
}
/**
* Vertical align shim for contact tds. Using border here instead of
* padding since this is a table and padding is ignored.
*
* @see td.setting p
*/
td.domain-registrar-contacts {
border-top: solid 0.5em white;
}

View file

@ -0,0 +1,17 @@
#domain-registrar-contact-us
.description * {
color: #999 !important;
line-height: 140%;
}
#domain-registrar-contact-us p {
margin-bottom: 1.5em;
line-height: 140%;
}
#registry-phone {
width: 100%;
margin: 0;
padding: 1em;
background-color: #eaeaea;
}

View file

@ -0,0 +1,63 @@
#domain-registrar-dashboard {
text-align: center;
}
#domain-registrar-dashboard p {
color: grey;
}
#domain-registrar-dashboard super {
color: red;
font-size: 0.5em;
vertical-align: super;
font-weight: bold;
padding-left: 0.5em;
}
#domain-registrar-dashboard table {
border-collapse: collapse;
margin: 3em auto;
}
#domain-registrar-dashboard div.dashbox {
width: 230px;
min-width: 230px;
height: 260px;
padding: 1em 2em;
margin: 0 1.5em;
color: #777;
border-radius: 10px;
background-color: #f9f9f9;
text-align: center;
line-height: 140%;
font-size: 1.1em;
}
#domain-registrar-dashboard div.dashbox h2 {
font-weight: bold;
color: #555;
margin: 2em auto 1em auto;
}
#domain-registrar-dashboard div.dashbox a {
color: inherit;
}
#domain-registrar-dashboard table img {
display: block;
margin: 1em auto;
padding: 1em auto;
}
#domain-registrar-dashboard table + p {
margin: 2em auto;
width: 75%;
vertical-align: middle;
text-align: center;
}
#domain-registrar-dashboard p img {
margin-right: 1em;
vertical-align: middle;
text-align: center;
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,61 @@
/* EPP styles */
.reg-add:before {
content: '+ ';
}
.contact h1 {
width: 650px;
background: url('/assets/images/ic_contacts_blue_12.png') no-repeat 0 0.7em;
padding-left: 2em;
margin-left: 0.75em;
}
/* Remove button. */
#contact-postalInfo table button {
margin: 1em;
float: right;
}
#contact-postalInfoHeader {
color: #777;
font-weight: normal;
}
#contact-postalInfo .info {
font-size: 11px;
color: #666;
}
#contact-postalInfo table {
margin: 0 0 1em 0;
padding: 1em;
}
#contact-postalInfo tr {
margin: auto 2em;
}
#contact-postalInfo tr:first-child {
margin-top: 1em;
}
#contact-postalInfo tr:last-child {
margin-bottom: 1em;
}
#contact-postalInfo table,
#contact-postalInfo input[readonly],
#contact-postalInfo textarea[readonly] {
background-color: #f1f1f1;
border-color: #f1f1f1;
}
#contact-postalInfoHeader button {
display: block;
margin: 1em 0;
}
#contact-postalInfoHeader button[disabled] {
display: none;
}

View file

@ -0,0 +1,182 @@
form.set,
form.item,
div.set,
div.item {
width: 700px;
}
.set table,
.item table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
}
/* Going to play some games here to get section leads to float left,
* followed by their next rows in floated right column. */
.item table,
.set table {
padding-top: 2em;
margin-top: 1em;
border-top: solid 1px #ebebeb;
}
.set tr,
.item tr {
margin: 0;
padding: 0;
min-height: 28px;
}
.set td,
.item td {
padding: 0.5em 0;
}
.item tr.section-lead th h2,
.item tr.section-lead th h3 {
font-size: 1em;
font-weight: bold;
color: #15c;
}
tr.subsection h3::first-letter {
text-transform: capitalize;
}
.set td:first-child,
.item td:first-child {
width: 260px;
}
.set td:first-child span.description,
.item td:first-child span.description {
width: 170px;
}
/* Setting groups and labels. */
.set .kd-settings-pane-section td {
border-bottom: solid 1px #ebebeb;
}
td.setting-group-compact {
padding-left: 0.5em;
}
.setting-group-compact div {
line-height: 140%;
}
.item label {
font-weight: bold;
}
.set label + input {
margin-left: 1em;
}
.kd-settings-pane-section td label.setting-label {
padding-top: 0.5em;
}
td.label {
width: 70%;
}
/* Compact sequence of labels. */
.setting label + label {
margin-left: 2em;
}
form label,
.setting-label {
line-height: 13px;
margin-bottom: 5px;
}
.setting label {
display: inline-block;
font-weight: normal;
margin-right: 1em;
vertical-align: top;
}
div.checkbox-with-label {
margin-bottom: 1em;
}
/* Controls */
.set input:not([type="submit"]),
.item input:not([type="submit"]) {
width: 250px;
padding: 0.5em;
}
.item input[type='checkbox'],
.item input[type='radio'] {
width: auto;
}
input[readonly] {
border: solid 1px white;
margin-left: 0.5em;
padding-top: 0 !important;
padding-left: 0 !important;
}
.setting input[type=radio] {
}
.setting input[type=radio],
.setting input[type=checkbox] {
position: relative;
top: -3px;
}
/**
* Consistent top space in second column items. Input elements need to
* switch from using margin to padding when they're edit toggled, so that
* the text doesn't move. The other elements are just defined here for
* consistency.
* @see td.domain-registrar-contacts
*/
input[readonly],
td.setting p {
margin-top: 0.5em;
margin-left: 0.5em;
}
.item button[disabled] {
display: none;
}
div.checkbox-with-label input[type='checkbox']
div.checkbox-with-label input[type='checkbox'] + label {
display: inline-block;
}
/*
* 3 line street has just inputs in a row
* @see td padding-bottom
*/
.setting input + input,
.setting div {
margin-top: 1em;
}
.setting div {
margin-top: 0.5em;
}
.setting input[type='checkbox'] {
display: inline;
}
.setting input + button,
.setting-item-list {
margin-left: 0.5em;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,10 @@
@import 'kd_components.css';
@import 'registry.css';
@import 'console.css';
@import 'forms.css';
@import 'epp.css';
@import 'dashboard.css';
@import 'resources.css';
@import 'security-settings.css';
@import 'contact-settings.css';
@import 'contact-us.css';

View file

@ -0,0 +1,306 @@
body {
overflow-x: hidden;
overflow-y: hidden !important;
font-family: Arial, sans-serif;
font-size: 13px;
color: #222;
}
h1 {
width: 100%;
font-size: 1.5em;
/* Bottom padding to get the paragraph text and Billing & resources to
* line up. */
padding: 0.75em 0.75em 3px 0;
}
#kd-social a,
#kd-social a:visited,
#reg-content a,
#reg-content a:visited {
color: #15c;
}
pre {
font-family: monospace;
white-space: pre-wrap;
}
table {
table-layout: fixed;
}
td {
vertical-align: top;
}
/* Set some explicit form styles so there's no jumping during toggle
* from readonly to editable. */
input, textarea {
border-style: solid;
border-color: lightgrey;
border-width: 1px;
}
input[readonly], textarea[readonly] {
resize: none;
border-color: white;
}
textarea {
margin: 1em 0;
font-family: monospace;
font-size: 13px;
border: solid 1px #ddd;
}
textarea[readonly] {
background-color: #eaeaea;
}
hr {
border: none;
border-top: solid 1px #ebebeb;
}
.hidden {
display: none;
}
#kd-googlebar {
position: fixed;
width: 100%;
min-width: 985px;
padding: 1em 2em;
white-space: nowrap;
height: 37px;
}
#kd-googlebar a.logo,
#kd-searchfield,
#kd-searchbutton {
position: static;
display: inline-block;
}
#kd-search {
width: 470px;
margin-top: 2px;
padding-top: 0px;
padding-left: 3em;
white-space: nowrap;
}
#kd-search form {
width: auto;
}
input#kd-searchfield,
#kd-searchbutton {
height: 29px;
border: none;
}
input#kd-searchfield {
width: 400px;
font-size: 12pt;
}
#kd-searchbutton {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
#kd-social {
position: fixed;
top: 1.25em;
right: 0;
}
#kd-social .kd-name {
float: none;
color: #555;
}
.kd-name a:before {
content: ' | ';
color: #999;
}
/* Logo */
a.logo {
vertical-align: top;
font-size: 30px;
font-weight: 300;
font-family: "open sans", sans-serif;
color: #63666a;
}
a.logo * {
vertical-align: top;
}
a.logo img {
/* Get the baseline of "Google" and "Registry" to line up. */
margin-top: 1px;
}
/* Reg prefix. */
.reg-user-id {
display: block;
height: 1.5em;
padding: 0;
margin-top: -10px;
}
/* Misc. */
#loady {
position: absolute;
top: 5px;
right: -30px;
}
.eppResponse {
color: #333;
margin-top: 1em;
}
.kd-appbar {
padding: 1em 0;
position: fixed;
width: 100%;
}
li.kd-menulistitem {
text-transform: none;
}
#reg-nav.shown {
display: inherit;
}
/* Begin fixed headers and nav selectors */
#reg-app {
float: left;
margin-top: 64px;
width: 100%;
}
.kd-butterbar {
position: absolute;
display: block;
margin-left: inherit;
}
.kd-butterbar.shown {
-webkit-transform: translateX(-50%);
-ms-transform: translateX(-50%);
transform: translateX(-50%);
}
.kd-appbar {
padding: 0.75em 0;
}
#reg-app-buttons {
/* Same as in reg-content below. Lines the left edge of the
appbuttons and content area with the 'R' in Registry. */
padding-left: 173px;
}
.kd-content-sidebar {
margin-left: 15px;
padding-left: 0;
border-left: 0;
}
#reg-nav {
position: fixed;
left: 0;
top: 128px;
width: 155px;
margin: 0 25px 0 0;
z-index: 3;
}
#reg-navlist li {
margin-left: 0;
padding-left: 0;
}
#reg-navlist li ul {
margin-left: 0em;
padding-left: 2em;
}
.reg-navlist-sub {
padding-left: 3px;
}
#reg-navlist a {
margin-left: 0;
padding-left: 2em;
border-left: solid 3px white;
}
#reg-navlist li ul,
#reg-navlist a {
line-height: 24px;
}
#reg-navlist li ul li {
margin-left: -2em;
}
#reg-navlist li ul li a {
padding-left: 3em;
}
#reg-navlist a.domain-active-nav {
border-left: solid 3px red;
font-weight: bold;
color: #bf624B;
}
#reg-content-and-footer {
position: absolute;
top: 105px;
left: 173px;
bottom: 0;
width: 100%;
margin: 0;
padding: 25px 0 1em 0;
overflow-y: scroll !important;
overflow-x: hidden;
}
#reg-content {
margin-bottom: 100px;
}
#reg-content,
.pageFooter {
width: 75%;
}
#debug {
position: absolute;
top: 127px;
right: 0;
width: 15%;
border-left: solid 1px grey;
padding-left: 1em;
}
/* End fixed headers and nav selectors */
#reg-content,
#reg-login {
min-height: 400px;
}
.reg-select {
margin-left: 23px;
}

View file

@ -0,0 +1,28 @@
#domain-registrar-resources h2 {
border-top: solid 1px #eee;
padding-top: 1em;
margin-top: 1em;
}
#domain-registrar-resources h2 img {
vertical-align: middle;
margin-right: 10px;
}
#domain-registrar-resources h2 + p,
#domain-registrar-resources button,
#domain-registrar-resources em {
margin-left: 34px; /* Folder icon + ^^ 10px more */
}
#domain-registrar-resources h2 + p {
padding-top: 1em;
}
#domain-registrar-resources a {
color: white !important;
}
#domain-registrar-resources em {
color: red;
}

View file

@ -0,0 +1,67 @@
/** Security Settings */
#domain-registrar-phone-passcode,
#domain-registrar-phone-passcode input {
color: #3D9200;
}
div#ips div.ip {
width: 209px;
}
#newIp {
width: 187px;
margin-left: 0.5em;
}
div#ips.editing div.ip input,
div#ips.editing div.ip button[type=button] {
display: inline-block;
height: 27px;
line-height: 27px;
background: #ebebeb;
vertical-align: top;
border: none;
border-bottom: solid 3px white;
}
div#ips.editing div.ip input {
width: 169px;
margin: 0;
padding: 0;
color: #555;
padding-left: 5px ! important;
}
div#ips.editing div.ip input[readonly] {
margin-left: 0.5em;
}
div#ips.editing div.ip button[type=button] {
float: right;
margin-left: -2px;
width: 30px;
min-width: 30px;
height: 30px;
color: grey;
font-size: 1.1em;
}
div#ips.editing div.ip button[type=button]:hover {
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
div#ips.editing div.ip button[type=button] i {
font-style: normal;
}
div#ips.editing .kd-errormessage {
margin-left: 0.5em;
}
.item td.certificate h4 {
margin-top: 1em;
text-transform: lowercase;
border: solid 1px red;
}

View file

@ -0,0 +1,14 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<info>
<contact:info
xmlns:contact="urn:ietf:params:xml:ns:contact-1.0">
<contact:id>sh8013</contact:id>
<contact:authInfo>
<contact:pw>2fooBAR</contact:pw>
</contact:authInfo>
</contact:info>
</info>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View file

@ -0,0 +1,14 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<transfer op="request">
<contact:transfer
xmlns:contact="urn:ietf:params:xml:ns:contact-1.0">
<contact:id>sh8013</contact:id>
<contact:authInfo>
<contact:pw>2fooBAR</contact:pw>
</contact:authInfo>
</contact:transfer>
</transfer>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View file

@ -0,0 +1,18 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<create>
<domain:create
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
<domain:period unit="y">2</domain:period>
<domain:registrant>jd1234</domain:registrant>
<domain:contact type="admin">sh8013</domain:contact>
<domain:contact type="tech">sh8013</domain:contact>
<domain:authInfo>
<domain:pw>2fooBAR</domain:pw>
</domain:authInfo>
</domain:create>
</create>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View file

@ -0,0 +1,11 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<delete>
<domain:delete
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
</domain:delete>
</delete>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View file

@ -0,0 +1,17 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
<domain:chg/>
</domain:update>
</update>
<extension>
<rgp:update xmlns:rgp="urn:ietf:params:xml:ns:rgp-1.0">
<rgp:restore op="request"/>
</rgp:update>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<hello/>
</epp>

View file

@ -0,0 +1,18 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<login>
<clID>NewRegistrar</clID>
<pw>foo-BAR2</pw>
<options>
<version>1.0</version>
<lang>en</lang>
</options>
<svcs>
<objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
<objURI>urn:ietf:params:xml:ns:domain-1.0</objURI>
<objURI>urn:ietf:params:xml:ns:contact-1.0</objURI>
</svcs>
</login>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View file

@ -0,0 +1,6 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<logout/>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View file

@ -0,0 +1,6 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<poll op="req"/>
<clTRID>ABC-12345</clTRID>
</command>
</epp>

View file

@ -0,0 +1,12 @@
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
filegroup(
name = "externs",
srcs = glob(["*.js"]),
)
filegroup(
name = "js_files_recursive",
srcs = glob(["*.js"]),
)

View file

@ -0,0 +1,450 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Braintree JS SDK v2 externs. This file tells the Closure
* Compiler how Braintree's API is defined, which allows us to use it with type
* safety and dot-notation.
* @externs
*/
/**
* @type {!braintreepayments.Braintree}
*/
var braintree;
/**
* Fake namespace for pure Closure Compiler types not defined by the SDK.
*/
var braintreepayments = {};
/**
* @constructor
* @final
*/
braintreepayments.Braintree = function() {};
/**
* @param {string} clientTokenFromServer
* @param {string} integrationType Either 'dropin' or 'custom'.
* @param {{container: (string|!Element|undefined),
* dataCollector: (!Object|undefined),
* enableCORS: (boolean|undefined),
* form: (string|undefined),
* hostedFields: (!Object|undefined),
* id: (string|undefined),
* onError: (function(!braintreepayments.Error)|undefined),
* onPaymentMethodReceived:
* (function(!braintreepayments.PaymentMethod)|undefined),
* onReady: (function(!braintreepayments.Integrator)|undefined),
* paypal: (undefined|{
* amount: (number|undefined),
* container: (string|!Element),
* currency: (string|undefined),
* displayName: (string|undefined),
* enableBillingAddress: (boolean|undefined),
* enableShippingAddress: (boolean|undefined),
* headless: (boolean|undefined),
* locale: (string|undefined),
* onCancelled: (function()|undefined),
* onPaymentMethodReceived:
* (function(!braintreepayments.PaymentMethod)|undefined),
* onUnsupported: (function()|undefined),
* paymentMethodNonceInputField: (string|!Element|undefined),
* shippingAddressOverride: (undefined|{
* recipientName: string,
* streetAddress: string,
* extendedAddress: (string|undefined),
* locality: string,
* countryCodeAlpha2: string,
* postalCode: string,
* region: string,
* phone: (string|undefined),
* editable: boolean
* }),
* singleUse: (boolean|undefined)
* })
* }} options
* @see https://developers.braintreepayments.com/guides/client-sdk/javascript/v2#global-setup
*/
braintreepayments.Braintree.prototype.setup =
function(clientTokenFromServer, integrationType, options) {};
/**
* @constructor
* @final
* @see https://developers.braintreepayments.com/guides/drop-in/javascript/v2#onerror
*/
braintreepayments.Error = function() {};
/**
* Describes type of error that occurred (e.g. "CONFIGURATION", "VALIDATION".)
* @type {string}
* @const
*/
braintreepayments.Error.prototype.type;
/**
* Human-readable string describing the error.
* @type {string}
* @const
*/
braintreepayments.Error.prototype.message;
/**
* @type {(!braintreepayments.ErrorDetails|undefined)}
* @const
*/
braintreepayments.Error.prototype.details;
/**
* @constructor
* @final
*/
braintreepayments.ErrorDetails = function() {};
/**
* @type {(!Array<!braintreepayments.ErrorField>|undefined)}
* @const
*/
braintreepayments.ErrorDetails.prototype.invalidFields;
/**
* @constructor
* @final
*/
braintreepayments.ErrorField = function() {};
/**
* Field which failed validation. It will match one of the following: "number",
* "cvv", "expiration", or "postalCode".
* @type {string}
* @const
*/
braintreepayments.ErrorField.prototype.fieldKey;
/**
* This will be {@code true} if the associated input is empty.
* @type {(boolean|undefined)}
* @const
*/
braintreepayments.ErrorField.prototype.isEmpty;
/**
* @constructor
* @final
*/
braintreepayments.Integrator = function() {};
/**
* @type {string}
* @const
*/
braintreepayments.Integrator.prototype.deviceData;
/**
* @type {!braintreepayments.PaypalIntegrator}
* @const
*/
braintreepayments.Integrator.prototype.paypal;
/**
* @param {function()=} opt_callback
* @see https://developers.braintreepayments.com/guides/client-sdk/javascript/v2#teardown
*/
braintreepayments.Integrator.prototype.teardown = function(opt_callback) {};
/**
* @constructor
* @final
*/
braintreepayments.PaypalIntegrator = function() {};
braintreepayments.PaypalIntegrator.prototype.closeAuthFlow = function() {};
braintreepayments.PaypalIntegrator.prototype.initAuthFlow = function() {};
/**
* @constructor
* @final
*/
braintreepayments.PaymentMethod = function() {};
/**
* @type {string}
* @const
*/
braintreepayments.PaymentMethod.prototype.nonce;
/**
* Either 'CreditCard' or 'PayPalAccount'.
* @type {string}
* @const
*/
braintreepayments.PaymentMethod.prototype.type;
/**
* @type {(!braintreepayments.PaymentMethodDetailsCard|
* !braintreepayments.PaymentMethodDetailsPaypal)}
* @const
*/
braintreepayments.PaymentMethod.prototype.details;
/**
* @constructor
* @final
* @see https://developers.braintreepayments.com/guides/client-sdk/javascript/v2#payment-method-details
*/
braintreepayments.PaymentMethodDetailsCard = function() {};
/**
* Can be 'Visa', 'MasterCard', 'Discover', 'Amex', or 'JCB'.
* @type {string}
* @const
*/
braintreepayments.PaymentMethodDetailsCard.prototype.cardType;
/**
* @type {string}
* @const
*/
braintreepayments.PaymentMethodDetailsCard.prototype.lastTwo;
/**
* @constructor
* @final
* @see https://developers.braintreepayments.com/guides/paypal/client-side/javascript/v2#options
*/
braintreepayments.PaymentMethodDetailsPaypal = function() {};
/**
* @type {string}
* @const
*/
braintreepayments.PaymentMethodDetailsPaypal.prototype.email;
/**
* @type {string}
* @const
*/
braintreepayments.PaymentMethodDetailsPaypal.prototype.firstName;
/**
* @type {string}
* @const
*/
braintreepayments.PaymentMethodDetailsPaypal.prototype.lastName;
/**
* @type {string}
* @const
*/
braintreepayments.PaymentMethodDetailsPaypal.prototype.phone;
/**
* @type {string}
* @const
*/
braintreepayments.PaymentMethodDetailsPaypal.prototype.payerID;
/**
* @type {!braintreepayments.PaypalShippingAddress}
* @const
*/
braintreepayments.PaymentMethodDetailsPaypal.prototype.shippingAddress;
/**
* @type {(!braintreepayments.PaypalBillingAddress|undefined)}
* @const
*/
braintreepayments.PaymentMethodDetailsPaypal.prototype.billingAddress;
/**
* @constructor
* @final
*/
braintreepayments.PaypalShippingAddress = function() {};
/**
* @type {number}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.id;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.type;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.recipientName;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.streetAddress;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.extendedAddress;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.locality;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.region;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.postalCode;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.countryCodeAlpha;
/**
* @type {boolean}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.defaultAddress;
/**
* @type {boolean}
* @const
*/
braintreepayments.PaypalShippingAddress.prototype.preferredAddress;
/**
* @constructor
* @final
*/
braintreepayments.PaypalBillingAddress = function() {};
/**
* @type {string}
* @const
*/
braintreepayments.PaypalBillingAddress.prototype.streetAddress;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalBillingAddress.prototype.extendedAddress;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalBillingAddress.prototype.locality;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalBillingAddress.prototype.region;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalBillingAddress.prototype.postalCode;
/**
* @type {string}
* @const
*/
braintreepayments.PaypalBillingAddress.prototype.countryCodeAlpha2;

View file

@ -0,0 +1,116 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview External JSON definitions. The purpose of this file is to give
* type information to the JavaScript compiler so it won't rename these
* properties.
* @externs
*/
/**
* @suppress {duplicate}
*/
var registry = {};
/**
* @suppress {duplicate}
*/
registry.json = {};
/**
* @constructor
* @template T
*/
registry.json.Response = function() {};
/**
* Request state which can be {@code SUCCESS} or {@code ERROR}.
* @type {string}
*/
registry.json.Response.prototype.status;
/**
* @type {string}
*/
registry.json.Response.prototype.message;
/**
* @type {string|undefined}
*/
registry.json.Response.prototype.field;
/**
* @type {!Array.<T>}
*/
registry.json.Response.prototype.results;
// XXX: Might not need undefineds here.
/**
* @typedef {{
* clientIdentifier: string,
* clientCertificate: string?,
* clientCertificateHash: string?,
* failoverClientCertificate: string?,
* failoverClientCertificateHash: string?,
* driveFolderId: string?,
* ianaIdentifier: (number?|undefined),
* icannReferralEmail: string,
* ipAddressWhitelist: !Array<string>,
* emailAddress: string,
* phonePasscode: (string?|undefined),
* phoneNumber: (string?|undefined),
* faxNumber: (string?|undefined),
* localizedAddress: registry.json.RegistrarAddress,
* whoisServer: (string?|undefined),
* referralUrl: (string?|undefined),
* contacts: !Array.<registry.json.RegistrarContact>
* }}
*/
registry.json.Registrar;
/**
* @typedef {{
* street: !Array.<string>,
* city: string,
* state: (string?|undefined),
* zip: (string?|undefined),
* countryCode: string
* }}
*/
registry.json.RegistrarAddress;
/**
* @typedef {{
* name: (string?|undefined),
* emailAddress: string,
* visibleInWhoisAsAdmin: boolean,
* visibleInWhoisAsTech: boolean,
* phoneNumber: (string?|undefined),
* faxNumber: (string?|undefined),
* types: (string?|undefined)
* }}
*/
registry.json.RegistrarContact;

View file

@ -0,0 +1,66 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview RegistrarPaymentAction JSON RPC definitions.
* @externs
*/
/**
* @suppress {duplicate}
*/
var registry = {};
/**
* @suppress {duplicate}
*/
registry.rpc = {};
registry.rpc.Payment = {};
/**
* @typedef {{
* currency: string,
* amount: string,
* paymentMethodNonce: string
* }}
*/
registry.rpc.Payment.Request;
/**
* @typedef {registry.json.Response.<!registry.rpc.Payment.Result>}
*/
registry.rpc.Payment.Response;
/**
* @constructor
*/
registry.rpc.Payment.Result = function() {};
/**
* @type {string}
*/
registry.rpc.Payment.Result.prototype.id;
/**
* @type {string}
*/
registry.rpc.Payment.Result.prototype.formattedAmount;

View file

@ -0,0 +1,68 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview RegistrarPaymentSetupAction JSON RPC definitions.
* @externs
*/
/**
* @suppress {duplicate}
*/
var registry = {};
/**
* @suppress {duplicate}
*/
registry.rpc = {};
registry.rpc.PaymentSetup = {};
/**
* @typedef {Object}
*/
registry.rpc.PaymentSetup.Request;
/**
* @typedef {registry.json.Response.<!registry.rpc.PaymentSetup.Result>}
*/
registry.rpc.PaymentSetup.Response;
/**
* @constructor
*/
registry.rpc.PaymentSetup.Result = function() {};
/**
* @type {string}
*/
registry.rpc.PaymentSetup.Result.prototype.token;
/**
* @type {string}
*/
registry.rpc.PaymentSetup.Result.prototype.brainframe;
/**
* @type {!Array.<string>}
*/
registry.rpc.PaymentSetup.Result.prototype.currencies;

View file

@ -0,0 +1,14 @@
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
java_library(
name = "forms",
srcs = glob(["*.java"]),
visibility = ["//visibility:public"],
deps = [
"//java/com/google/common/annotations",
"//java/com/google/common/base",
"//java/com/google/common/collect",
"//third_party/java/jsr305_annotations",
],
)

View file

@ -0,0 +1,58 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.ui.forms;
import static com.google.common.base.Preconditions.checkNotNull;
import javax.annotation.Detainted;
import javax.annotation.concurrent.NotThreadSafe;
/**
* Exception thrown when a form is invalid. Problems with a specific
* form field should use {@link FormFieldException} instead.
*
* <p>You can safely throw {@code FormException} from within your form
* validator, and the message will automatically be propagated to the
* client form interface.
*/
@NotThreadSafe
public class FormException extends RuntimeException {
/**
* Creates a new {@link FormException}
*
* @param userMessage should be a friendly message that's safe to show to the user.
*/
public FormException(@Detainted String userMessage) {
super(checkNotNull(userMessage, "userMessage"), null);
}
/**
* Creates a new {@link FormException}
*
* @param userMessage should be a friendly message that's safe to show to the user.
* @param cause the original cause of this exception. May be null.
*/
public FormException(@Detainted String userMessage, Throwable cause) {
super(checkNotNull(userMessage, "userMessage"), cause);
}
/** Returns an error message that's safe to display to the user. */
@Override
@Detainted
public String getMessage() {
return super.getMessage();
}
}

View file

@ -0,0 +1,790 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.ui.forms;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Range;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import javax.annotation.Detainted;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.Tainted;
import javax.annotation.concurrent.Immutable;
/**
* Declarative functional fluent form field converter / validator.
*
* <p>This class is responsible for converting arbitrary data, sent to us by the web browser, into
* validated data structures that the server-side code can use. For example:<pre>
*
* private enum Gender { MALE, FEMALE }
*
* private static final FormField<String, String> NAME_FIELD = FormField.named("name")
* .matches("[a-z]+")
* .range(atMost(16))
* .required()
* .build();
*
* private static final FormField<String, Gender> GENDER_FIELD = FormField.named("gender")
* .asEnum(Gender.class)
* .required()
* .build();
*
* public Person makePerson(Map<String, String> params) {
* Person.Builder person = new Person.Builder();
* for (String name : NAME_FIELD.extract(params).asSet()) {
* person.setName(name);
* }
* for (Gender name : GENDER_FIELD.extract(params).asSet()) {
* person.setGender(name);
* }
* return person.build();
* }</pre>
*
* <p>This class provides <b>full type-safety</b> <i>if and only if</i> you statically initialize
* your FormField objects and write a unit test that causes the class to be loaded.
*
* <h3>Exception Handling</h3>
*
* <p>When values passed to {@link #convert} or {@link #extract} don't meet the contract,
* {@link FormFieldException} will be thrown, which provides the field name and a short error
* message that's safe to pass along to the client.
*
* <p>You can safely throw {@code FormFieldException} from within your validator functions, and
* the field name will automatically be propagated into the exception object for you.
*
* <p>In situations when you're validating lists or maps, you'll end up with a hierarchical field
* naming structure. For example, if you were validating a list of maps, an error generated by the
* {@code bar} field of the fifth item in the {@code foo} field would have a fully-qualified field
* name of: {@code foo[5][bar]}.
*
* <h3>Library Definitions</h3>
*
* <p>You should never assign a partially constructed {@code FormField.Builder} to a variable or
* constant. Instead, you should use {@link #asBuilder()} or {@link #asBuilderNamed(String)}.
*
* <p>Here is an example of how you might go about defining library definitions:<pre>
*
* final class FormFields {
* private static final FormField<String, String> COUNTRY_CODE =
* FormField.named("countryCode")
* .range(Range.singleton(2))
* .uppercased()
* .in(ImmutableSet.copyOf(Locale.getISOCountries()))
* .build();
* }
*
* final class Form {
* private static final FormField<String, String> COUNTRY_CODE_FIELD =
* FormFields.COUNTRY_CODE.asBuilder()
* .required()
* .build();
* }</pre>
*
* @param <I> input value type
* @param <O> output value type
*/
@Immutable
public final class FormField<I, O> {
private final String name;
private final Class<I> typeIn;
private final Class<O> typeOut;
private final Function<I, O> converter;
private FormField(String name, Class<I> typeIn, Class<O> typeOut, Function<I, O> converter) {
this.name = name;
this.typeIn = typeIn;
this.typeOut = typeOut;
this.converter = converter;
}
/** Returns an optional string form field named {@code name}. */
public static Builder<String, String> named(String name) {
return named(name, String.class);
}
/** Returns an optional form field named {@code name} with a specific {@code inputType}. */
public static <T> Builder<T, T> named(String name, Class<T> typeIn) {
checkArgument(!name.isEmpty());
return new Builder<>(name, checkNotNull(typeIn), typeIn, Functions.<T>identity());
}
/**
* Returns a form field builder for validating JSON nested maps.
*
* <p>Here's an example of how you'd use this feature:
*
* <pre>
* private static final FormField&lt;String, String&gt; REGISTRAR_NAME_FIELD =
* FormField.named("name")
* .emptyToNull()
* .required()
* .build();
*
* private static final FormField&lt;Map&lt;String, ?&gt;, Registrar&gt; REGISTRAR_FIELD =
* FormField.mapNamed("registrar")
* .transform(Registrar.class, new Function&lt;Map&lt;String, ?&gt;, Registrar&gt;() {
* &#064;Nullable
* &#064;Override
* public Registrar apply(&#064;Nullable Map&lt;String, ?&gt; params) {
* Registrar.Builder builder = new Registrar.Builder();
* for (String name : REGISTRAR_NAME_FIELD.extractUntyped(params).asSet()) {
* builder.setName(name);
* }
* return builder.build();
* }})
* .build();</pre>
*
* <p>When a {@link FormFieldException} is thrown, it'll be propagated to create a fully-qualified
* field name. For example, if the JSON input is <pre>{registrar: {name: ""}}</pre> then the
* {@link FormFieldException#getFieldName() field name} will be {@code registrar.name}.
*/
public static Builder<Map<String, ?>, Map<String, ?>> mapNamed(String name) {
@SuppressWarnings("unchecked")
Class<Map<String, ?>> typeIn = (Class<Map<String, ?>>) (Class<?>) Map.class;
return named(name, typeIn);
}
/** Returns the name of this field. */
public String name() {
return name;
}
/**
* Convert and validate a raw user-supplied value.
*
* @throws FormFieldException if value does not meet expected contracts.
*/
@Detainted
public Optional<O> convert(@Tainted @Nullable I value) {
try {
return Optional.fromNullable(converter.apply(value));
} catch (FormFieldException e) {
throw e.propagate(name);
}
}
/**
* Convert and validate a raw user-supplied value from a map.
*
* <p>This is the same as saying: {@code field.convert(valueMap.get(field.name())}
*
* @throws FormFieldException if value does not meet expected contracts.
*/
@Detainted
public Optional<O> extract(@Tainted Map<String, I> valueMap) {
return convert(valueMap.get(name));
}
/**
* Convert and validate a raw user-supplied value from an untyped JSON map.
*
* @throws FormFieldException if value is wrong type or does not meet expected contracts.
*/
@Detainted
public Optional<O> extractUntyped(@Tainted Map<String, ?> jsonMap) {
Object value = jsonMap.get(name);
I castedValue;
try {
castedValue = typeIn.cast(value);
} catch (ClassCastException e) {
throw new FormFieldException(String.format("Type error: got: %s, expected: %s",
value.getClass().getSimpleName(),
typeIn.getSimpleName())).propagate(name);
}
return convert(castedValue);
}
/**
* Returns a builder of this object, which can be used to further restrict validation.
*
* @see #asBuilderNamed(String)
*/
public Builder<I, O> asBuilder() {
return new Builder<>(name, typeIn, typeOut, converter);
}
/** Same as {@link #asBuilder()} but changes the field name. */
public Builder<I, O> asBuilderNamed(String newName) {
checkArgument(!newName.isEmpty());
return new Builder<>(newName, typeIn, typeOut, converter);
}
/**
* Mutable builder for {@link FormField}.
*
* @param <I> input value type
* @param <O> output value type
*/
public static final class Builder<I, O> {
private final String name;
private final Class<I> typeIn;
private final Class<O> typeOut;
private Function<I, O> converter;
private Builder(String name, Class<I> typeIn, Class<O> typeOut, Function<I, O> converter) {
this.name = name;
this.typeIn = typeIn;
this.typeOut = typeOut;
this.converter = converter;
}
/** Causes {@code defaultValue} to be substituted if value is {@code null}. */
public Builder<I, O> withDefault(O defaultValue) {
return transform(new DefaultFunction<>(checkNotNull(defaultValue)));
}
/** Ensure value is not {@code null}. */
public Builder<I, O> required() {
@SuppressWarnings("unchecked")
Function<O, O> requiredFunction = (Function<O, O>) REQUIRED_FUNCTION;
return transform(requiredFunction);
}
/**
* Transform empty values into {@code null}.
*
* @throws IllegalStateException if current output type is not a {@link CharSequence} or
* {@link Collection}.
*/
public Builder<I, O> emptyToNull() {
checkState(CharSequence.class.isAssignableFrom(typeOut)
|| Collection.class.isAssignableFrom(typeOut));
@SuppressWarnings("unchecked")
Function<O, O> emptyToNullFunction = (Function<O, O>) EMPTY_TO_NULL_FUNCTION;
return transform(emptyToNullFunction);
}
/**
* Modify {@link String} input to remove whitespace around the sides.
*
* <p>{@code null} values are passed through.
*
* @throws IllegalStateException if current output type is not a String.
*/
public Builder<I, String> trimmed() {
checkState(String.class.isAssignableFrom(typeOut));
@SuppressWarnings("unchecked")
Function<O, String> trimFunction = (Function<O, String>) TRIM_FUNCTION;
return transform(String.class, trimFunction);
}
/**
* Modify {@link String} input to be uppercase.
*
* <p>{@code null} values are passed through.
*
* @throws IllegalStateException if current output type is not a String.
*/
public Builder<I, String> uppercased() {
checkState(String.class.isAssignableFrom(typeOut));
@SuppressWarnings("unchecked")
Function<O, String> funk = (Function<O, String>) UPPERCASE_FUNCTION;
return transform(String.class, funk);
}
/**
* Modify {@link String} input to be lowercase.
*
* <p>{@code null} values are passed through.
*
* @throws IllegalStateException if current output type is not a String.
*/
public Builder<I, String> lowercased() {
checkState(String.class.isAssignableFrom(typeOut));
@SuppressWarnings("unchecked")
Function<O, String> funk = (Function<O, String>) LOWERCASE_FUNCTION;
return transform(String.class, funk);
}
/**
* Ensure input matches {@code pattern}.
*
* <p>{@code null} values are passed through.
*
* @param pattern is used to validate the user input. It matches against the whole string, so
* you don't need to use the ^$ characters.
* @param errorMessage is a helpful error message, which should include an example. If this is
* not provided, a default error message will be shown that includes the regexp pattern.
* @throws IllegalStateException if current output type is not a {@link CharSequence}.
* @see #matches(Pattern)
*/
public Builder<I, O> matches(Pattern pattern, @Nullable String errorMessage) {
checkState(CharSequence.class.isAssignableFrom(typeOut));
return transform(
new MatchesFunction<O>(checkNotNull(pattern), Optional.fromNullable(errorMessage)));
}
/** Alias for {@link #matches(Pattern, String) matches(pattern, null)} */
public Builder<I, O> matches(Pattern pattern) {
return matches(pattern, null);
}
/**
* Removes all characters not in {@code matcher}.
*
* <p>{@code null} values are passed through.
*
* @param matcher indicates which characters are to be retained
* @throws IllegalStateException if current output type is not a {@link CharSequence}
*/
public Builder<I, String> retains(CharMatcher matcher) {
checkState(CharSequence.class.isAssignableFrom(typeOut));
@SuppressWarnings("unchecked") // safe due to checkState call
Function<O, String> function =
(Function<O, String>) new RetainFunction(checkNotNull(matcher));
return transform(String.class, function);
}
/**
* Enforce value length/size/value is within {@code range}.
*
* <p>The following input value types are supported:
*
* <ul>
* <li>{@link CharSequence}: Length must be within {@code range}.
* <li>{@link Collection}: Size must be within {@code range}.
* <li>{@link Number}: Value must be within {@code range}.
* </ul>
*
* <p>{@code null} values are passed through. Please note that setting a lower bound on your
* range does not imply {@link #required()}, as range checking only applies to non-{@code null}
* values.
*
* @throws IllegalStateException if current output type is not one of the above types.
*/
public Builder<I, O> range(Range<Integer> range) {
checkState(CharSequence.class.isAssignableFrom(typeOut)
|| Collection.class.isAssignableFrom(typeOut)
|| Number.class.isAssignableFrom(typeOut));
return transform(new RangeFunction<O>(checkNotNull(range)));
}
/**
* Enforce value be a member of {@code values}.
*
* <p>{@code null} values are passed through.
*
* @throws IllegalArgumentException if {@code values} is empty.
*/
public Builder<I, O> in(Set<O> values) {
checkArgument(!values.isEmpty());
return transform(new InFunction<>(values));
}
/**
* Performs arbitrary type transformation from {@code O} to {@code T}.
*
* <p>Your {@code transform} function is expected to pass-through {@code null} values as a
* no-op, since it's up to {@link #required()} to block them. You might also want to consider
* using a try block that rethrows exceptions as {@link FormFieldException}.
*
* <p>Here's an example of how you'd convert from String to Integer:
*
* <pre>
* FormField.named("foo", String.class)
* .transform(Integer.class, new Function&lt;String, Integer&gt;() {
* &#064;Nullable
* &#064;Override
* public Integer apply(&#064;Nullable String input) {
* try {
* return input != null ? Integer.parseInt(input) : null;
* } catch (IllegalArgumentException e) {
* throw new FormFieldException("Invalid number.", e);
* }
* }})
* .build();</pre>
*
* @see #transform(Function)
*/
public <T> Builder<I, T> transform(Class<T> newType, Function<O, T> transform) {
return new Builder<>(name, typeIn, checkNotNull(newType),
Functions.compose(checkNotNull(transform), converter));
}
/**
* Manipulates values without changing type.
*
* <p>Please see {@link #transform(Class, Function)} for information about the contract to
* which {@code transform} is expected to conform.
*/
public Builder<I, O> transform(Function<O, O> transform) {
this.converter = Functions.compose(checkNotNull(transform), converter);
return this;
}
/**
* Uppercases value and converts to an enum field of {@code enumClass}.
*
* <p>{@code null} values are passed through.
*
* @throws IllegalArgumentException if {@code enumClass} is not an enum class.
* @throws IllegalStateException if current output type is not a String.
*/
public <C extends Enum<C>> Builder<I, C> asEnum(Class<C> enumClass) {
checkArgument(enumClass.isEnum());
checkState(String.class.isAssignableFrom(typeOut));
return transform(enumClass, new ToEnumFunction<O, C>(enumClass));
}
/**
* Turns this form field into something that processes lists.
*
* <p>The current object definition will be applied to each item in the list. If a
* {@link FormFieldException} is thrown when processing an item, then its
* {@link FormFieldException#getFieldName() fieldName} will be rewritten to include the index,
* e.g. {@code name} becomes {@code name[0]}.
*
* <p>The outputted list will be an {@link ImmutableList}. This is not reflected in the generic
* typing for the sake of brevity.
*
* <p>A {@code null} value for list will be passed through. List items that convert to
* {@code null} will be discarded (since {@code ImmutableList} does not permit {@code null}
* values).
*/
public Builder<List<I>, List<O>> asList() {
@SuppressWarnings("unchecked") Class<List<I>> in = (Class<List<I>>) (Class<I>) List.class;
@SuppressWarnings("unchecked") Class<List<O>> out = (Class<List<O>>) (Class<O>) List.class;
return new Builder<>(name, in, out, new ToListFunction<>(build()));
}
/**
* Turns this form field into a split string list that applies itself to each item.
*
* <p>The behavior of this method is counter-intuitive. It behaves similar to {@link #asList()}
* in the sense that all transforms specified <i>before</i> this method will be applied to the
* individual resulting list items.
*
* <p>For example, to turn a comma-delimited string into an enum list:<pre> {@code
*
* private static final FormField<String, List<State>> STATES_FIELD =
* FormField.named("states")
* .uppercased()
* .asEnum(State.class)
* .asList(Splitter.on(',').omitEmptyStrings().trimResults())
* .build();}</pre>
*
* <p>You'll notice that the transforms specified before this method are applied to each list
* item. However unlike {@link #asList()}, if an error is thrown on an individual item, then
* {@link FormFieldException#getFieldName()} will <i>not</i> contain the index.
*
* @throws IllegalStateException If either the current input type isn't String.
*/
public Builder<String, List<O>> asList(Splitter splitter) {
checkNotNull(splitter);
checkState(String.class.isAssignableFrom(typeIn));
@SuppressWarnings("unchecked") Class<List<O>> out = (Class<List<O>>) (Class<O>) List.class;
@SuppressWarnings("unchecked") FormField<String, O> inField = (FormField<String, O>) build();
return new Builder<>(name, String.class, out, new SplitToListFunction<>(inField, splitter));
}
/**
* Same as {@link #asList()} but outputs an {@link ImmutableSet} instead.
*
* @throws IllegalStateException if you called asList() before calling this method.
*/
public Builder<List<I>, Set<O>> asSet() {
checkState(!List.class.isAssignableFrom(typeOut));
@SuppressWarnings("unchecked")
Class<Set<O>> setOut = (Class<Set<O>>) (Class<O>) Set.class;
@SuppressWarnings("unchecked")
Function<List<O>, Set<O>> toSetFunction =
(Function<List<O>, Set<O>>) (Function<O, O>) TO_SET_FUNCTION;
return asList().transform(setOut, toSetFunction);
}
/**
* Same as {@link #asList(Splitter)} but outputs an {@link ImmutableSet} instead.
*
* @throws IllegalStateException If the current input type isn't String.
*/
public Builder<String, Set<O>> asSet(Splitter splitter) {
checkNotNull(splitter);
checkState(String.class.isAssignableFrom(typeIn));
@SuppressWarnings("unchecked") Class<Set<O>> out = (Class<Set<O>>) (Class<O>) Set.class;
@SuppressWarnings("unchecked") FormField<String, O> inField = (FormField<String, O>) build();
return new Builder<>(name, String.class, out, new SplitToSetFunction<>(inField, splitter));
}
/** Creates a new {@link FormField} instance. */
public FormField<I, O> build() {
return new FormField<>(name, typeIn, typeOut, converter);
}
private static final Function<List<Object>, Set<Object>> TO_SET_FUNCTION =
new Function<List<Object>, Set<Object>>() {
@Nullable
@Override
public Set<Object> apply(@Nullable List<Object> input) {
return input != null ? ImmutableSet.copyOf(input) : null;
}};
private static final Function<String, String> TRIM_FUNCTION =
new Function<String, String>() {
@Nullable
@Override
public String apply(@Nullable String input) {
return input != null ? input.trim() : null;
}};
private static final Function<String, String> UPPERCASE_FUNCTION =
new Function<String, String>() {
@Nullable
@Override
public String apply(@Nullable String input) {
return input != null ? input.toUpperCase() : null;
}};
private static final Function<String, String> LOWERCASE_FUNCTION =
new Function<String, String>() {
@Nullable
@Override
public String apply(@Nullable String input) {
return input != null ? input.toLowerCase() : null;
}};
private static final Function<Object, Object> REQUIRED_FUNCTION =
new Function<Object, Object>() {
@Nonnull
@Override
public Object apply(@Nullable Object input) {
if (input == null) {
throw new FormFieldException("This field is required.");
}
return input;
}};
private static final Function<Object, Object> EMPTY_TO_NULL_FUNCTION =
new Function<Object, Object>() {
@Nullable
@Override
public Object apply(@Nullable Object input) {
return input instanceof CharSequence && ((CharSequence) input).length() == 0
|| input instanceof Collection && ((Collection<?>) input).isEmpty()
? null : input;
}};
private static final class DefaultFunction<O> implements Function<O, O> {
private final O defaultValue;
DefaultFunction(O defaultValue) {
this.defaultValue = defaultValue;
}
@Nullable
@Override
public O apply(@Nullable O input) {
return input != null ? input : defaultValue;
}
}
private static final class RangeFunction<O> implements Function<O, O> {
private final Range<Integer> range;
RangeFunction(Range<Integer> range) {
this.range = range;
}
@Nullable
@Override
public O apply(@Nullable O input) {
if (input == null) {
return null;
}
if (input instanceof CharSequence) {
checkRangeContains(range, ((CharSequence) input).length(), "Number of characters");
} else if (input instanceof Collection) {
checkRangeContains(range, ((Collection<?>) input).size(), "Number of items");
} else if (input instanceof Number) {
checkRangeContains(range, ((Number) input).intValue(), "Value");
} else {
throw new AssertionError();
}
return input;
}
private void checkRangeContains(Range<Integer> range, int value, String message) {
if (!range.contains(value)) {
throw new FormFieldException(
String.format("%s (%,d) not in range %s", message, value, range));
}
}
}
private static final class InFunction<O> implements Function<O, O> {
private final Set<O> values;
InFunction(Set<O> values) {
this.values = values;
}
@Nullable
@Override
public O apply(@Nullable O input) {
if (input == null) {
return null;
}
if (!values.contains(input)) {
throw new FormFieldException("Unrecognized value.");
}
return input;
}
}
private static final class MatchesFunction<O> implements Function<O, O> {
private final Pattern pattern;
private final Optional<String> errorMessage;
MatchesFunction(Pattern pattern, Optional<String> errorMessage) {
this.pattern = pattern;
this.errorMessage = errorMessage;
}
@Nullable
@Override
public O apply(@Nullable O input) {
if (input == null) {
return null;
}
if (!pattern.matcher((CharSequence) input).matches()) {
throw new FormFieldException(errorMessage.or("Must match pattern: " + pattern));
}
return input;
}
}
private static final class RetainFunction implements Function<CharSequence, String> {
private final CharMatcher matcher;
RetainFunction(CharMatcher matcher) {
this.matcher = matcher;
}
@Nullable
@Override
public String apply(@Nullable CharSequence input) {
if (input == null) {
return null;
}
return matcher.retainFrom(input);
}
}
private static final class ToEnumFunction<O, C extends Enum<C>> implements Function<O, C> {
private final Class<C> enumClass;
ToEnumFunction(Class<C> enumClass) {
this.enumClass = enumClass;
}
@Nullable
@Override
public C apply(@Nullable O input) {
try {
return input != null ? Enum.valueOf(enumClass, ((String) input).toUpperCase()) : null;
} catch (IllegalArgumentException e) {
throw new FormFieldException(
String.format("Enum %s does not contain '%s'", enumClass.getSimpleName(), input));
}
}
}
private static final class ToListFunction<I, O> implements Function<List<I>, List<O>> {
private final FormField<I, O> itemField;
ToListFunction(FormField<I, O> itemField) {
this.itemField = itemField;
}
@Nullable
@Override
public List<O> apply(@Nullable List<I> input) {
if (input == null) {
return null;
}
ImmutableList.Builder<O> builder = new ImmutableList.Builder<>();
for (int i = 0; i < input.size(); i++) {
I inputItem = itemField.typeIn.cast(input.get(i));
O outputItem;
try {
outputItem = itemField.converter.apply(inputItem);
} catch (FormFieldException e) {
throw e.propagate(i);
}
if (outputItem != null) {
builder.add(outputItem);
}
}
return builder.build();
}
}
private static final class SplitToListFunction<O> implements Function<String, List<O>> {
private final FormField<String, O> itemField;
private final Splitter splitter;
SplitToListFunction(FormField<String, O> itemField, Splitter splitter) {
this.itemField = itemField;
this.splitter = splitter;
}
@Nullable
@Override
public List<O> apply(@Nullable String input) {
return input == null ? null : FluentIterable
.from(splitter.split(input))
.transform(itemField.converter)
.toList();
}
}
private static final class SplitToSetFunction<O> implements Function<String, Set<O>> {
private final FormField<String, O> itemField;
private final Splitter splitter;
SplitToSetFunction(FormField<String, O> itemField, Splitter splitter) {
this.itemField = itemField;
this.splitter = splitter;
}
@Nullable
@Override
public Set<O> apply(@Nullable String input) {
return input == null ? null : FluentIterable
.from(splitter.split(input))
.transform(itemField.converter)
.toSet();
}
}
}
}

View file

@ -0,0 +1,176 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.ui.forms;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import javax.annotation.CheckReturnValue;
import javax.annotation.Detainted;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
/**
* Exception thrown when a form field contains a bad value.
*
* <p>You can safely throw {@code FormFieldException} from within your validator functions, and
* the field name will automatically be propagated into the exception object for you.
*
* <p>The way that field names work is a bit complicated, because we need to support complex nested
* field names like {@code foo[3].bar}. So what happens is the original exception will be thrown by
* a {@link FormField} validator without the field set. Then as the exception bubbles up the stack,
* it'll be caught by the {@link FormField#convert(Object) convert} method, which then prepends the
* name of that component. Then when the exception reaches the user, the {@link #getFieldName()}
* method will produce the fully-qualified field name.
*
* <p>This propagation mechanism is also very important when writing
* {@link FormField.Builder#transform(com.google.common.base.Function) transform} functions, which
* oftentimes will not know the name of the field they're validating.
*/
@NotThreadSafe
public final class FormFieldException extends FormException {
private final List<Object> names = new ArrayList<>();
@Nullable
private String lazyFieldName;
/**
* Creates a new {@link FormFieldException}
*
* <p>This exception should only be thrown from within a {@link FormField} converter function.
* The field name will automatically be propagated into the exception object for you.
*
* @param userMessage should be a friendly message that's safe to show to the user.
*/
public FormFieldException(@Detainted String userMessage) {
super(checkNotNull(userMessage, "userMessage"), null);
}
/**
* Creates a new {@link FormFieldException}
*
* <p>This exception should only be thrown from within a {@link FormField} converter function.
* The field name will automatically be propagated into the exception object for you.
*
* @param userMessage should be a friendly message that's safe to show to the user.
* @param cause the original cause of this exception (non-null).
*/
public FormFieldException(@Detainted String userMessage, Throwable cause) {
super(checkNotNull(userMessage, "userMessage"), checkNotNull(cause, "cause"));
}
/**
* Creates a new {@link FormFieldException} for a particular form field.
*
* <p>This exception should only be thrown from within a {@link FormField} MAP converter function
* in situations where you're performing additional manual validation.
*
* @param userMessage should be a friendly message that's safe to show to the user.
*/
public FormFieldException(FormField<?, ?> field, @Detainted String userMessage) {
this(field.name(), userMessage);
}
/**
* Creates a new {@link FormFieldException} for a particular field name.
*
* @param field name corresponding to a {@link FormField#name()}
* @param userMessage friendly message that's safe to show to the user
*/
public FormFieldException(String field, @Detainted String userMessage) {
super(checkNotNull(userMessage, "userMessage"), null);
propagateImpl(field);
}
/** Returns the fully-qualified name (JavaScript syntax) of the form field causing this error. */
public String getFieldName() {
String fieldName = lazyFieldName;
if (fieldName == null) {
lazyFieldName = fieldName = getFieldNameImpl();
}
return fieldName;
}
private String getFieldNameImpl() {
checkState(!names.isEmpty(),
"FormFieldException was thrown outside FormField infrastructure!");
Iterator<Object> namesIterator = Lists.reverse(names).iterator();
StringBuilder result = new StringBuilder((String) namesIterator.next());
while (namesIterator.hasNext()) {
Object name = namesIterator.next();
if (name instanceof String) {
result.append('.').append(name);
} else if (name instanceof Integer) {
result.append('[').append(name).append(']');
} else {
throw new AssertionError();
}
}
return result.toString();
}
/** Returns self with {@code name} prepended, for propagating exceptions up the stack. */
@CheckReturnValue
@VisibleForTesting
public FormFieldException propagate(String name) {
return propagateImpl(name);
}
/** Returns self with {@code index} prepended, for propagating exceptions up the stack. */
@CheckReturnValue
FormFieldException propagate(int index) {
return propagateImpl(index);
}
/** Returns self with {@code name} prepended, for propagating exceptions up the stack. */
private FormFieldException propagateImpl(Object name) {
lazyFieldName = null;
names.add(checkNotNull(name));
return this;
}
@Override
public boolean equals(@Nullable Object obj) {
return this == obj
|| obj instanceof FormFieldException
&& Objects.equals(getCause(), ((FormFieldException) obj).getCause())
&& Objects.equals(getMessage(), ((FormFieldException) obj).getMessage())
&& Objects.equals(names, ((FormFieldException) obj).names);
}
@Override
public int hashCode() {
return Objects.hash(getCause(), getMessage(), getFieldName());
}
@Override
public String toString() {
return toStringHelper(getClass())
.add("fieldName", getFieldName())
.add("message", getMessage())
.add("cause", getCause())
.toString();
}
}

View file

@ -0,0 +1,126 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.domain.registry.ui.forms;
import static com.google.common.collect.Range.atMost;
import static com.google.common.collect.Range.closed;
import static com.google.common.collect.Range.singleton;
import static java.util.Locale.getISOCountries;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
/** Utility class of {@link FormField} objects for validating EPP related things. */
public final class FormFields {
private static final Pattern WHITESPACE = Pattern.compile("[ \\t\\r\\n]+");
private static final Function<String, String> COLLAPSE_WHITESPACE =
new Function<String, String>() {
@Nullable
@Override
public String apply(@Nullable String input) {
return input != null ? WHITESPACE.matcher(input).replaceAll(" ") : null;
}};
/**
* Form field that applies XML Schema Token cleanup to input.
*
* <p>This trims the input and collapses whitespace.
*
* @see "http://www.w3.org/TR/xmlschema11-2/#token"
*/
public static final FormField<String, String> XS_TOKEN = FormField.named("xsToken")
.emptyToNull()
.trimmed()
.transform(COLLAPSE_WHITESPACE)
.build();
/**
* Form field that ensures input does not contain tabs, line feeds, or carriage returns.
*
* @see "http://www.w3.org/TR/xmlschema11-2/#normalizedString"
*/
public static final FormField<String, String> XS_NORMALIZED_STRING =
FormField.named("xsNormalizedString")
.emptyToNull()
.matches(Pattern.compile("[^\\t\\r\\n]*"), "Must not contain tabs or multiple lines.")
.build();
/**
* Form field for +E164 phone numbers with a dot after the country prefix.
*
* @see "http://tools.ietf.org/html/rfc5733#section-4"
*/
public static final FormField<String, String> PHONE_NUMBER =
XS_TOKEN.asBuilderNamed("phoneNumber")
.range(atMost(17))
.matches(Pattern.compile("(\\+[0-9]{1,3}\\.[0-9]{1,14})?"),
"Must be a valid +E.164 phone number, e.g. +1.2125650000")
.build();
/** Form field for EPP client identifiers. */
public static final FormField<String, String> CLID = XS_TOKEN.asBuilderNamed("clid")
.range(closed(3, 16))
.build();
/** Form field for passwords (see pwType in epp.xsd). */
public static final FormField<String, String> PASSWORD = XS_TOKEN.asBuilderNamed("password")
.range(closed(6, 16))
.build();
/** Form field for non-empty tokens (see minToken in eppcom.xsd). */
public static final FormField<String, String> MIN_TOKEN = XS_TOKEN.asBuilderNamed("minToken")
.emptyToNull()
.build();
/** Form field for nameType (see rde-registrar/notification). */
public static final FormField<String, String> NAME = XS_NORMALIZED_STRING.asBuilderNamed("name")
.range(closed(1, 255))
.build();
/** Form field for {@code labelType} from {@code eppcom.xsd}. */
public static final FormField<String, String> LABEL = XS_TOKEN.asBuilderNamed("label")
.range(closed(1, 255))
.build();
/** Email address form field. */
public static final FormField<String, String> EMAIL = XS_TOKEN.asBuilderNamed("email")
.matches(Pattern.compile("[^@]+@[^@.]+\\.[^@]+"), "Please enter a valid email address.")
.build();
/** Two-letter ISO country code form field. */
public static final FormField<String, String> COUNTRY_CODE =
XS_TOKEN.asBuilderNamed("countryCode")
.range(singleton(2))
.uppercased()
.in(ImmutableSet.copyOf(getISOCountries()))
.build();
/**
* Ensure value is an EPP Repository Object IDentifier (ROID).
*
* @see "http://tools.ietf.org/html/rfc5730#section-4.2"
*/
public static final FormField<String, String> ROID = XS_TOKEN.asBuilderNamed("roid")
.matches(Pattern.compile("(\\w|_){1,80}-\\w{1,8}"),
"Please enter a valid EPP ROID, e.g. SH8013-REP")
.build();
private FormFields() {}
}

View file

@ -0,0 +1,17 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/** Web application backend form processing utilities. */
@javax.annotation.ParametersAreNonnullByDefault
package com.google.domain.registry.ui.forms;

View file

@ -0,0 +1,5 @@
DEBUG = null
com.google.domain.registry.ui.ConsoleDebug.PRODUCTION = 0
com.google.domain.registry.ui.ConsoleDebug.DEBUG = 1
com.google.domain.registry.ui.ConsoleDebug.RAW = 2
com.google.domain.registry.ui.ConsoleDebug.TEST = 3

View file

@ -0,0 +1,7 @@
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
filegroup(
name = "html_files",
srcs = glob(["*.html"]),
)

View file

@ -0,0 +1,15 @@
<!doctype html>
<meta charset=utf-8>
<title>Server Error</title>
<style>
*{margin:0;padding:0}
html,code{font:15px/22px arial,sans-serif}
html{background:#fff;color:#222;padding:15px}
body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}
* > body{background:url(/manager/img/ui/robot.png) 100% 5px no-repeat;padding-right:205px}
p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}
a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}
</style>
<a href="/"><img src="/assets/images/logo_sm.gif" alt=Google></a>
<p><b>500.</b> <ins>That's an error.</ins>
<p>Sorry, but the server encountered an error while processing your request.

View file

@ -0,0 +1,6 @@
<!doctype html>
<meta http-equiv="refresh" content="0;URL=/registrar">
<title>Domain Registry</title>
<body>
If this page doesn't change automatically, please go
to <a href="/registrar">http://registry.google.com/registrar</a>

View file

@ -0,0 +1,19 @@
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
load("//third_party/closure/compiler:closure_js_library.bzl", "closure_js_library")
filegroup(
name = "js_files",
srcs = glob(["*.js"]),
)
closure_js_library(
name = "js",
srcs = [":js_files"],
deps = [
"//java/com/google/domain/registry/ui/soy:Console",
"//java/com/google/domain/registry/ui/soy:Forms",
"//javascript/closure",
],
)

View file

@ -0,0 +1,210 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.Component');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.dom.classlist');
goog.require('goog.events');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventType');
goog.require('registry.soy.forms');
goog.require('registry.util');
/**
* Base component for UI.
*
* <pre>
* ui/js/component.js - Base UI class.
* ^
* |
* edit_item.js - Common controls for editable items.
* ^
* \
* |-ui/js/resource_component.js - JSON resources
* | ^
* | \
* | |- ui/js/registrar/settings.js
* |
* |-ui/js/registrar/xml_resource_component.js - EPP resources
* ^
* \
* |- ui/js/registrar/contact.js
* |- ui/js/registrar/domain.js
* |- ui/js/registrar/host.js
* </pre>
*
* @param {!registry.Console} cons the console singleton.
* @constructor
* @extends {goog.events.EventHandler}
*/
registry.Component = function(cons) {
registry.Component.base(this, 'constructor');
/** @type {!registry.Console} */
this.console = cons;
/**
* The hashPath this component is mapped by. This is set by the
* console after construction.
* @type {string}
*/
this.basePath = '';
/**
* Bean counter that's used by {@code addRemBtnHandlers},
* e.g. {@code typeCounts['host']++} when user adds or removes.
* @type {!Object.<string, number>}
* @protected
*/
this.typeCounts = {};
/**
* Stateful UI/server session model.
* @type {?Object.<string, ?>}
*/
this.model = null;
};
goog.inherits(registry.Component, goog.events.EventHandler);
/**
* Subclasses shold override this to implement panel display.
* @param {string} id The target resource id.
*/
registry.Component.prototype.bindToDom = function(id) {
registry.util.unbutter();
};
// XXX: Should clean up the many remove button handler setup funcs.
/**
* Shared functionality for contact and host add button click events
* to add form elements for inputing a new item name. Requires the
* template to have:
*
* <ul>
* <li>an add button with id="domain-[type]-add-button".
* <li>a table row with id="domain-[type]s-footer".
* </ul>
*
* @param {string} type e.g. 'contact', 'host'.
* @param {function(): string} newFieldNameFn generates new form field's name.
* @param {function(string): (?Element)=} opt_onAddFn currying further setup.
* @param {function()=} opt_tmpl input element template.
* @param {!Object=} opt_tmplArgs input element template parameter object.
* @param {boolean=} opt_disable optionally disable the add button.
* @protected
*/
registry.Component.prototype.addRemBtnHandlers = function(
type,
newFieldNameFn,
opt_onAddFn,
opt_tmpl,
opt_tmplArgs,
opt_disable) {
var addBtnId = 'domain-' + type + '-add-button';
var addBtn = goog.dom.getRequiredElement(addBtnId);
if (!opt_disable) {
addBtn.removeAttribute('disabled');
}
var addButtonClickCallback = function() {
var fieldElts = [];
var newFieldName = newFieldNameFn();
var tmpl =
opt_tmpl ? opt_tmpl : registry.soy.forms.inputFieldRow;
var tmplArgs = opt_tmplArgs ? opt_tmplArgs : {
label: 'New ' + type,
name: newFieldName + '.value'
};
var newFieldInputRow = registry.util.renderBeforeRow(
'domain-' + type + 's-footer', tmpl, tmplArgs);
fieldElts.push(newFieldInputRow);
// Save the add/rem op type as a hidden elt for use by
// determine EPP add/remove semantics in subclasses.
// e.g. domain.js#saveItem()
var opElt = goog.dom.createDom(goog.dom.TagName.INPUT, {
'type': 'hidden',
'name': newFieldName + '.op',
'value': 'add'
});
newFieldInputRow.lastChild.appendChild(opElt);
if (opt_onAddFn) {
var elt = opt_onAddFn(newFieldName);
if (elt) {
fieldElts.push(elt);
}
}
this.appendRemoveBtn(
goog.dom.getLastElementChild(newFieldInputRow),
fieldElts,
goog.bind(function() {
this.typeCounts[type]--;
}, this));
this.typeCounts[type]++;
};
goog.events.listen(goog.dom.getRequiredElement(addBtnId),
goog.events.EventType.CLICK,
goog.bind(addButtonClickCallback, this));
};
/**
* Helper for making an element removable.
* @param {Element} parent The element to append the remove button to.
* @param {(Array.<Element>|function())=} opt_eltsToRemove
* Elements to remove when the button is clicked or Function to do
* the removing for full customization.
* @param {function()=} opt_cb callback will be called if no
* customized function is given.
*/
registry.Component.prototype.appendRemoveBtn =
function(parent, opt_eltsToRemove, opt_cb) {
var rmBtn = goog.dom.createDom(goog.dom.TagName.BUTTON,
goog.getCssName('kd-button'),
'Remove');
goog.dom.appendChild(parent, rmBtn);
var clickCb;
if (opt_eltsToRemove instanceof Function) {
clickCb = opt_eltsToRemove;
} else {
var eltsToRemove = opt_eltsToRemove ? opt_eltsToRemove : [parent];
clickCb = function() {
for (var i = 0; i < eltsToRemove.length; i++) {
goog.dom.removeNode(eltsToRemove[i]);
}
if (opt_cb) {
opt_cb();
}
};
}
goog.events.listen(rmBtn, goog.events.EventType.CLICK, clickCb, true);
};
/**
* Bind the remove button action for the given container element.
* @param {!Element} containerElt
*/
registry.Component.prototype.enableRemoveButton = function(containerElt) {
var rmBtn = goog.dom.getElementByClass(
goog.getCssName('remove'), containerElt);
goog.dom.classlist.toggle(rmBtn, goog.getCssName('hidden'));
goog.events.listen(rmBtn, goog.events.EventType.CLICK, function() {
goog.dom.removeNode(goog.dom.getParentElement(rmBtn));
});
};

View file

@ -0,0 +1,123 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.Console');
goog.require('goog.Disposable');
goog.require('goog.History');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.events.KeyCodes');
goog.require('goog.history.EventType');
goog.require('registry.util');
/**
* Abstract console for both admin and registrar console UIs.
* @param {?registry.Session} session server request session.
* @constructor
* @extends {goog.Disposable}
*/
registry.Console = function(session) {
registry.Console.base(this, 'constructor');
/**
* @type {!goog.History}
* @protected
*/
this.history = new goog.History();
goog.events.listen(
this.history,
goog.history.EventType.NAVIGATE,
goog.bind(this.handleHashChange, this));
/**
* @type {?registry.Session} The server session.
*/
this.session = session;
this.bindToDom();
};
goog.inherits(registry.Console, goog.Disposable);
/**
* Helper to setup permanent page elements.
*/
registry.Console.prototype.bindToDom = function() {
registry.util.unbutter();
goog.events.listen(goog.dom.getRequiredElement('kd-searchbutton'),
goog.events.EventType.CLICK,
goog.bind(this.onSearch_, this));
goog.events.listen(goog.dom.getRequiredElement('kd-searchfield'),
goog.events.EventType.KEYUP,
goog.bind(this.onSearchFieldKeyUp_, this));
goog.events.listen(
goog.dom.getElementByClass(goog.getCssName('kd-butterbar-dismiss')),
goog.events.EventType.CLICK,
registry.util.unbutter);
};
/**
* Subclasses should override to visit the hash token given by
* {@code goog.History.getToken()}.
*/
registry.Console.prototype.handleHashChange = goog.abstractMethod;
/**
* @param {string} resourcePath Resource description path.
*/
registry.Console.prototype.view = function(resourcePath) {
this.history.setToken(resourcePath);
};
/**
* Handler for search bar.
* @private
*/
registry.Console.prototype.onSearch_ = function() {
var qElt = goog.dom.getRequiredElement('kd-searchfield');
if (qElt.getAttribute('disabled')) {
return;
}
var query = qElt.value;
if (query == '') {
return;
}
// Filtering this value change event.
qElt.setAttribute('disabled', true);
qElt.value = '';
this.view(query);
qElt.removeAttribute('disabled');
};
/**
* Handler for key press in the search input field.
* @param {!goog.events.KeyEvent} e Key event to handle.
* @return {boolean} Whether the event should be continued or cancelled.
* @private
*/
registry.Console.prototype.onSearchFieldKeyUp_ = function(e) {
if (e.keyCode == goog.events.KeyCodes.ENTER) {
this.onSearch_();
return false;
}
return true;
};

View file

@ -0,0 +1,294 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.EditItem');
goog.require('goog.dom');
goog.require('goog.dom.classlist');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.soy');
goog.require('registry.Component');
goog.require('registry.soy.console');
goog.require('registry.util');
/**
* An editable item, with Edit and Save/Cancel buttons in the appbar.
* @param {!registry.Console} cons
* @param {function()} itemTmpl
* @constructor
* @extends {registry.Component}
*/
registry.EditItem = function(cons, itemTmpl) {
registry.EditItem.base(this, 'constructor', cons);
/**
* @type {!Function}
*/
this.itemTmpl = itemTmpl;
/**
* Optional current target resource id.
* @type {?string}
*/
this.id = null;
/**
* Transitional id for next resource during create.
* @type {?string}
*/
this.nextId = null;
};
goog.inherits(registry.EditItem, registry.Component);
/** @override */
registry.EditItem.prototype.bindToDom = function(id) {
registry.EditItem.base(this, 'bindToDom', id);
this.id = id;
this.nextId = null;
this.setupAppbar();
};
/** Setup appbar save/edit buttons. */
registry.EditItem.prototype.setupAppbar = function() {
goog.soy.renderElement(goog.dom.getRequiredElement('reg-appbar'),
registry.soy.console.appbarButtons);
goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-add'),
goog.events.EventType.CLICK,
goog.bind(this.add, this));
goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-edit'),
goog.events.EventType.CLICK,
goog.bind(this.edit, this));
goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-save'),
goog.events.EventType.CLICK,
goog.bind(this.save, this));
goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-cancel'),
goog.events.EventType.CLICK,
goog.bind(this.cancel, this));
goog.events.listen(goog.dom.getRequiredElement('reg-app-btn-back'),
goog.events.EventType.CLICK,
goog.bind(this.back, this));
if (this.id) {
registry.util.setVisible('reg-app-btns-edit', true);
} else {
registry.util.setVisible('reg-app-btn-add', true);
}
};
/**
* Retrieve item from server. Overrides should callback to {@code
* #handleFetchItem(string, !Object)}.
* @param {string} id item id.
*/
registry.EditItem.prototype.fetchItem = goog.abstractMethod;
/**
* Handle result decoding and display.
* @param {string} id The requested ID/name, for error message.
* @param {!Object} rsp The requested object.
*/
registry.EditItem.prototype.handleFetchItem = goog.abstractMethod;
/** Subclasses should override to continue processing after fetch. */
registry.EditItem.prototype.processItem = function() {};
/**
* Show the item.
* @param {!Object} objArgs
*/
registry.EditItem.prototype.renderItem = function(objArgs) {
goog.soy.renderElement(goog.dom.getRequiredElement('reg-content'),
this.itemTmpl,
objArgs);
};
/**
* @return {boolean} if the component is currently being edited.
*/
registry.EditItem.prototype.isEditing = function() {
return goog.dom.classlist.contains(
goog.dom.getElement('reg-app'), goog.getCssName('editing'));
};
/**
* Toggles the editing state of a component. This will first hide the
* elements of the {@code shown} class, then adds the {@code editing}
* style to the component, then shows the elements that had the {@code
* hidden} class.
*/
registry.EditItem.prototype.toggleEdit = function() {
// Toggle appbar buttons.
var addBtn = goog.dom.getRequiredElement('reg-app-btn-add');
var editBtns = goog.dom.getRequiredElement('reg-app-btns-edit');
var saveBtns = goog.dom.getRequiredElement('reg-app-btns-save');
var editing = goog.dom.classlist.contains(saveBtns,
registry.util.cssShown);
if (editing) {
registry.util.setVisible(saveBtns, false);
if (this.id) {
registry.util.setVisible(editBtns, true);
} else {
registry.util.setVisible(addBtn, true);
}
} else {
if (this.id) {
registry.util.setVisible(editBtns, false);
} else {
registry.util.setVisible(addBtn, false);
}
registry.util.setVisible(saveBtns, true);
}
// Then page contents.
var parentElt = goog.dom.getElement('reg-content');
var shownCssName = goog.getCssName('shown');
var hiddenCssName = goog.getCssName('hidden');
var shown = goog.dom.getElementsByClass(shownCssName, parentElt);
var hidden = goog.dom.getElementsByClass(hiddenCssName, parentElt);
for (var i = 0; i < shown.length; i++) {
goog.dom.classlist.addRemove(shown[i], shownCssName, hiddenCssName);
}
// Then add editing styles.
var editingCssName = goog.getCssName('editing');
var startingEdit = !goog.dom.classlist.contains(parentElt, editingCssName);
if (startingEdit) {
goog.dom.classlist.remove(parentElt, editingCssName);
} else {
goog.dom.classlist.add(parentElt, editingCssName);
}
// The show hiddens.
for (var i = 0; i < hidden.length; i++) {
goog.dom.classlist.addRemove(hidden[i], hiddenCssName, shownCssName);
}
};
/**
* Subclasses should override to enhance the default model.
* @return {!Object.<string, ?>}
*/
registry.EditItem.prototype.newModel = function() {
return {item: {}};
};
// N.B. setting these as abstract precludes their correct binding in
// setupAppbar.
/** Show add item panel. */
registry.EditItem.prototype.add = function() {};
/** Go back from item to collection view. */
registry.EditItem.prototype.back = function() {};
/** Sets up initial edit model state and then called edit(objArgs). */
registry.EditItem.prototype.edit = function() {
var objArgs = this.model;
if (objArgs == null) {
objArgs = this.newModel();
}
// XXX: This is vestigial. In the new msg format, this pollutes the
// server-model state. Should be carried elsewhere.
objArgs.readonly = false;
this.renderItem(objArgs);
this.setupEditor(objArgs);
this.toggleEdit();
};
/**
* Save the current item. Calls either create if creating a new
* object or update otherwise.
*/
registry.EditItem.prototype.save = function() {
if (this.model == null) {
this.sendCreate();
} else {
this.sendUpdate();
}
};
/**
* Resets to non-editing, readonly state, or visits home screen if the
* page was in on a create panel.
*/
registry.EditItem.prototype.cancel = function() {
this.toggleEdit();
// XXX: The presence of a model is sufficent for non-collection pages, but an
// empty id also means go to the collection in collection pages. Should
// be simplified.
if (this.model && this.id != '') {
this.model.readonly = true;
this.renderItem(this.model);
} else {
this.bindToDom('');
}
};
/**
* Called after this.renderItem(), to allow for further setup of
* editing.
* @param {!Object} objArgs
*/
registry.EditItem.prototype.setupEditor = function(objArgs) {};
// XXX: These should really take @param {object} which is the form. Alas the
// only override which doesn't work this way, ResourceComponent.sendCreate
// breaks this opportunity. Hmmm...
/** Subclasses should extract form values and send them to the server. */
registry.EditItem.prototype.sendCreate = goog.abstractMethod;
/** Subclasses should extract form values and send them to the server. */
registry.EditItem.prototype.sendUpdate = goog.abstractMethod;
/** Subclasses should extract form values and send them to the server. */
registry.EditItem.prototype.sendDelete = goog.abstractMethod;
/**
* Sublcasses should override to populate update queryParams with form
* fields as needed. {@code queryParams.nextId} MUST be set to the
* new object's ID.
* @param {!Object} queryParams
*/
registry.EditItem.prototype.prepareUpdate = goog.abstractMethod;
/**
* Subclasses should provide a funtion to parse either XML or JSON response
* from server and return a result object as described below.
* @param {!Object} rsp Decoded XML/JSON response from the server.
* @return {!Object} a result object describing next steps. On
* success, if next is defined, visit(ret.next) is called, otherwise
* if err is set, the butterbar message is set to it.
*/
registry.EditItem.prototype.handleUpdateResponse = goog.abstractMethod;

View file

@ -0,0 +1,135 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.forms');
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.dom.classlist');
goog.require('goog.dom.forms');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.events.KeyCodes');
goog.require('registry.util');
/**
* Sets the focus on a form field (if it exists).
* @param {Element|string} field Form field (or ID) to focus.
*/
registry.forms.focus = function(field) {
field = goog.dom.getElement(field);
if (!goog.isNull(field) && goog.dom.isFocusable(field)) {
goog.dom.forms.focusAndSelect(field);
}
};
/**
* Displays a form field error, or butters if no field is specified.
* @param {string} message Human-readable explanation of why this field is evil.
* @param {string=} opt_field Erroneous field name.
*/
registry.forms.displayError = function(message, opt_field) {
if (!goog.isDef(opt_field)) {
registry.util.butter(message);
return;
}
var input = goog.dom.getElement(opt_field) ||
goog.dom.getElement(opt_field + '[0]');
// XXX: Transitioning to use of form.eltId instead of DOM id. If DOM id
// lookup fails, then search forms for the named field.
if (goog.isDefAndNotNull(opt_field) && goog.isNull(input)) {
for (var fNdx in document.forms) {
var form = document.forms[fNdx];
if (form[opt_field]) {
input = form[opt_field];
break;
}
}
}
if (!goog.isNull(input)) {
goog.dom.classlist.add(input, goog.getCssName('kd-formerror'));
goog.dom.insertSiblingAfter(
goog.dom.createDom(goog.dom.TagName.DIV,
goog.getCssName('kd-errormessage'),
message),
input);
registry.forms.focus(input);
} else {
registry.util.butter(opt_field + ': ' + message);
}
};
/** Removes error markup from whois settings form. */
registry.forms.resetErrors = function() {
registry.util.unbutter();
goog.array.forEach(
goog.dom.getElementsByClass(goog.getCssName('kd-formerror')),
function(field) {
goog.dom.classlist.remove(field, goog.getCssName('kd-formerror'));
});
goog.array.forEach(
goog.dom.getElementsByClass(goog.getCssName('kd-errormessage')),
goog.dom.removeNode);
};
/**
* Adds enter key listeners to all form fields.
* @param {!Element} container Parent element containing INPUT fields.
* @param {function()} callback Called when enter is pressed in a field.
*/
registry.forms.listenFieldsOnEnter = function(container, callback) {
var handler = goog.partial(registry.forms.onFieldKeyUp_, callback);
goog.array.forEach(
goog.dom.getElementsByTagNameAndClass(
goog.dom.TagName.INPUT, undefined, container),
function(field) {
goog.events.listen(field, goog.events.EventType.KEYUP, handler);
});
};
/**
* Event handler that saves form when enter is pressed in a form field.
* @param {function()} callback Called when key pressed is ENTER.
* @param {!goog.events.KeyEvent} e Key event to handle.
* @return {boolean} Whether the event should be continued or cancelled.
* @private
*/
registry.forms.onFieldKeyUp_ = function(callback, e) {
if (e.keyCode == goog.events.KeyCodes.ENTER) {
callback();
return false;
}
return true;
};
/**
* Toggles disabled state of a button or form element.
* @param {!Element} element Form element to disable.
* @param {boolean} enabled Enables element if true, or else disables it.
*/
registry.forms.setEnabled = function(element, enabled) {
if (enabled) {
goog.dom.classlist.remove(element, goog.getCssName('disabled'));
} else {
goog.dom.classlist.add(element, goog.getCssName('disabled'));
element.blur();
}
};

View file

@ -0,0 +1,135 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.MenuButton');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.dom.classlist');
goog.require('goog.events');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventType');
/**
* Kennedy style menu button.
* @param {!Element} button Menu button element.
* @constructor
* @extends {goog.events.EventHandler}
* @final
*/
registry.MenuButton = function(button) {
registry.MenuButton.base(this, 'constructor');
/**
* Outer menu button element.
* @private {!Element}
* @const
*/
this.button_ = button;
this.listen(button, goog.events.EventType.CLICK, this.onButtonClick_);
/**
* Label that displays currently selected item.
* @private {!Element}
* @const
*/
this.label_ =
goog.dom.getRequiredElementByClass(goog.getCssName('label'), button);
/**
* List of selectable items.
* @private {!Element}
* @const
*/
this.menu_ =
goog.dom.getRequiredElementByClass(goog.getCssName('kd-menulist'),
button);
goog.array.forEach(
goog.dom.getElementsByClass(goog.getCssName('kd-menulistitem'), button),
function(item) {
this.listen(item, goog.events.EventType.CLICK, this.onItemClick_);
},
this);
};
goog.inherits(registry.MenuButton, goog.events.EventHandler);
/**
* Returns selected value in menu.
* @return {string}
*/
registry.MenuButton.prototype.getValue = function() {
return goog.dom.getTextContent(
goog.dom.getRequiredElementByClass(
goog.getCssName('selected'),
this.button_));
};
/**
* Main menu button handler.
* @param {!goog.events.BrowserEvent} e
* @private
*/
registry.MenuButton.prototype.onButtonClick_ = function(e) {
if (goog.dom.classlist.contains(this.button_, goog.getCssName('selected'))) {
return;
}
e.stopPropagation();
goog.dom.classlist.add(this.button_, goog.getCssName('selected'));
goog.dom.classlist.add(this.menu_, goog.getCssName('shown'));
this.listenOnce(goog.dom.getDocument().body, goog.events.EventType.CLICK,
this.hideMenu_);
};
/**
* Menu item selection handler.
* @param {!goog.events.BrowserEvent} e
* @private
*/
registry.MenuButton.prototype.onItemClick_ = function(e) {
e.stopPropagation();
if (goog.dom.classlist.contains(this.button_, goog.getCssName('disabled'))) {
return;
}
goog.array.forEach(
goog.dom.getElementsByClass(goog.getCssName('kd-menulistitem'),
this.button_),
function(item) {
goog.dom.classlist.remove(item, goog.getCssName('selected'));
},
this);
goog.asserts.assert(e.target instanceof Element);
goog.dom.classlist.add(e.target, goog.getCssName('selected'));
var text = goog.dom.getTextContent(e.target);
goog.dom.setTextContent(this.label_, text);
goog.events.fireListeners(this.button_, goog.events.EventType.CHANGE,
false, {newValue: text});
this.hideMenu_();
};
/**
* Hide the menu.
* @private
*/
registry.MenuButton.prototype.hideMenu_ = function() {
goog.dom.classlist.remove(this.menu_, goog.getCssName('shown'));
goog.dom.classlist.remove(this.button_, goog.getCssName('selected'));
};

View file

@ -0,0 +1,31 @@
package(default_visibility = ["//java/com/google/domain/registry:registry_project"])
load("//third_party/closure/compiler:closure_js_library.bzl", "closure_js_library")
filegroup(
name = "js_files",
srcs = glob(["*.js"]),
)
closure_js_library(
name = "registrar",
srcs = [":js_files"],
deps = [
"//java/com/google/domain/registry/ui/js",
"//java/com/google/domain/registry/ui/soy:Forms",
"//java/com/google/domain/registry/ui/soy/registrar:Console",
"//java/com/google/domain/registry/ui/soy/registrar:Contact",
"//java/com/google/domain/registry/ui/soy/registrar:ContactEpp",
"//java/com/google/domain/registry/ui/soy/registrar:ContactSettings",
"//java/com/google/domain/registry/ui/soy/registrar:Domain",
"//java/com/google/domain/registry/ui/soy/registrar:DomainEpp",
"//java/com/google/domain/registry/ui/soy/registrar:Epp",
"//java/com/google/domain/registry/ui/soy/registrar:Host",
"//java/com/google/domain/registry/ui/soy/registrar:HostEpp",
"//java/com/google/domain/registry/ui/soy/registrar:Payment",
"//java/com/google/domain/registry/ui/soy/registrar:SecuritySettings",
"//java/com/google/domain/registry/ui/soy/registrar:WhoisSettings",
"//javascript/closure",
],
)

View file

@ -0,0 +1,265 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.BrainFrame');
goog.provide('registry.registrar.BrainFrame.main');
goog.require('goog.Timer');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventType');
goog.require('goog.json');
goog.require('goog.object');
goog.require('goog.style');
/**
* Sandboxed iframe for Braintree JS SDK v2 iframe.
*
* <p>This class adds an additional layer of security between the Registrar
* Console and JavaScript loaded from Braintree's web server.
*
* <p>The main function for this class is compiled into a separate binary,
* which is loaded within an iframe that's hosted on a different domain than
* the production environment. This ensures that cross origin browser security
* policies take effect.
*
* @param {string} origin FQDN of production environment.
* @param {string} containerId ID of Braintree container element.
* @constructor
* @extends {goog.events.EventHandler}
* @final
*/
registry.registrar.BrainFrame = function(origin, containerId) {
registry.registrar.BrainFrame.base(this, 'constructor');
/**
* Hostname of production registry, e.g. domain-registry.appspot.com.
* @private {string}
* @const
*/
this.origin_ = origin;
/**
* Div that wraps Braintree iframe.
* @private {!Element}
* @const
*/
this.container_ = goog.dom.getRequiredElement(containerId);
/**
* Last known height of {@code container_}.
* @private {number}
*/
this.containerHeight_ = 0;
/**
* Timer polling for changes in Braintree iframe height.
* @private {!goog.Timer}
* @const
*/
this.resizeTimer_ = new goog.Timer(1000 / 30);
this.registerDisposable(this.resizeTimer_);
this.listen(this.resizeTimer_, goog.Timer.TICK, this.onResizeTimer_);
/**
* Form that wraps {@code container_}.
* @private {?Element}
* @const
*/
this.form_ = goog.dom.getAncestorByTagNameAndClass(this.container_,
goog.dom.TagName.FORM);
goog.asserts.assert(this.form_ != null);
/**
* State indicating if we're submitting at behest of parent.
* @private {boolean}
*/
this.isSubmitting_ = false;
this.listen(goog.global.window,
goog.events.EventType.MESSAGE,
this.onMessage_);
};
goog.inherits(registry.registrar.BrainFrame, goog.events.EventHandler);
/**
* Runs Braintree sandbox environment.
*/
registry.registrar.BrainFrame.prototype.run = function() {
this.send_(
'type', registry.registrar.BrainFrame.MessageType.TOKEN_REQUEST);
};
/**
* Handles message from parent iframe which sends Braintree token.
* @param {!goog.events.BrowserEvent} e
* @private
*/
registry.registrar.BrainFrame.prototype.onMessage_ = function(e) {
var msg = /** @type {!MessageEvent.<string>} */ (e.getBrowserEvent());
if (msg.source != goog.global.window.parent) {
return;
}
if (this.origin_ != '*' && this.origin_ != msg.origin) {
throw new Error(
'Message origin is "' + msg.origin + '" but wanted: ' + this.origin_);
}
var data = goog.json.parse(msg.data);
switch (goog.object.get(data, 'type')) {
case registry.registrar.BrainFrame.MessageType.TOKEN_RESPONSE:
goog.global.braintree.setup(goog.object.get(data, 'token'), 'dropin', {
container: this.container_,
onPaymentMethodReceived: goog.bind(this.onPaymentMethod_, this),
onReady: goog.bind(this.onReady_, this),
onError: goog.bind(this.onError_, this)
});
this.resizeTimer_.start();
break;
case registry.registrar.BrainFrame.MessageType.SUBMIT_REQUEST:
this.isSubmitting_ = true;
// Trigger Braintree JS SDK submit event listener. It does not appear to
// be possible to do this using the Closure Library. This is IE 9+ only.
this.form_.dispatchEvent(new Event(goog.events.EventType.SUBMIT));
break;
default:
throw Error('Unexpected message: ' + msg.data);
}
};
/**
* Polls for resizes of Braintree iframe and propagates them to the parent
* frame which will then use it to resize this iframe.
* @private
*/
registry.registrar.BrainFrame.prototype.onResizeTimer_ = function() {
var height = goog.style.getSize(this.container_).height;
if (height != this.containerHeight_) {
this.send_(
'type', registry.registrar.BrainFrame.MessageType.RESIZE_REQUEST,
'height', height);
this.containerHeight_ = height;
}
};
/**
* Callback Braintree iframe has fully loaded.
* @private
*/
registry.registrar.BrainFrame.prototype.onReady_ = function() {
this.send_('type', registry.registrar.BrainFrame.MessageType.READY);
};
/**
* Callback Braintree says an error happened.
* @param {!braintreepayments.Error} error
* @private
*/
registry.registrar.BrainFrame.prototype.onError_ = function(error) {
this.isSubmitting_ = false;
this.send_('type', registry.registrar.BrainFrame.MessageType.SUBMIT_ERROR,
'message', error.message);
};
/**
* Callback when user successfully gave Braintree payment details.
* @param {!braintreepayments.PaymentMethod} pm
* @private
*/
registry.registrar.BrainFrame.prototype.onPaymentMethod_ = function(pm) {
// TODO(b/26829319): The Braintree JS SDK does not seem to recognize the
// enter key while embedded inside our sandbox iframe. So
// at this time, this callback will only be invoked after
// we've submitted the form manually at the behest of
// payment.js, which means isSubmitting_ will be true.
this.send_(
'type', registry.registrar.BrainFrame.MessageType.PAYMENT_METHOD,
'submit', this.isSubmitting_,
'method', pm);
this.isSubmitting_ = false;
};
/**
* Sends message to parent iframe.
* @param {...*} var_args Passed along to {@code goog.object.create}.
* @private
*/
registry.registrar.BrainFrame.prototype.send_ = function(var_args) {
goog.asserts.assert(arguments[0] == 'type');
registry.registrar.BrainFrame.postMessage_(
goog.json.serialize(goog.object.create.apply(null, arguments)),
this.origin_);
};
/**
* Delegates to {@code window.parent.postMessage}. This method exists because
* IE will not allow us to mock methods on the window object.
* @param {string} message
* @param {string} origin
* @private
*/
registry.registrar.BrainFrame.postMessage_ = function(message, origin) {
goog.global.window.parent.postMessage(message, origin);
};
/**
* Message types passed between brainframe and payment page.
* @enum {string}
*/
registry.registrar.BrainFrame.MessageType = {
/** Brainframe asks payment page for Braintree token. */
TOKEN_REQUEST: 'token_request',
/** Payment page sends brainframe Braintree token. */
TOKEN_RESPONSE: 'token_response',
/** Brainframe asks payment page to be resized. */
RESIZE_REQUEST: 'resize_request',
/** Brainframe tells payment page it finished loading. */
READY: 'ready',
/** Payment page asks brainframe to submit Braintree payment method form. */
SUBMIT_REQUEST: 'submit_request',
/** Brainframe tells payment page it failed to submit. */
SUBMIT_ERROR: 'submit_error',
/** Brainframe gives payment method info and nonce to payment page. */
PAYMENT_METHOD: 'payment_method'
};
/**
* Entrypoint for {@link registry.registrar.BrainFrame}.
* @param {string} origin
* @param {string} containerId
* @export
*/
registry.registrar.BrainFrame.main = function(origin, containerId) {
new registry.registrar.BrainFrame(origin, containerId).run();
};

View file

@ -0,0 +1,167 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.Console');
goog.require('goog.dispose');
goog.require('goog.dom');
goog.require('goog.dom.classlist');
goog.require('goog.net.XhrIo');
goog.require('registry.Console');
goog.require('registry.registrar.Contact');
goog.require('registry.registrar.ContactSettings');
goog.require('registry.registrar.ContactUs');
goog.require('registry.registrar.Dashboard');
goog.require('registry.registrar.Domain');
goog.require('registry.registrar.EppSession');
goog.require('registry.registrar.Host');
goog.require('registry.registrar.Payment');
goog.require('registry.registrar.Resources');
goog.require('registry.registrar.SecuritySettings');
goog.require('registry.registrar.WhoisSettings');
goog.require('registry.util');
/**
* The Registrar Console.
* @param {string} xsrfToken Populated by server-side soy template.
* @param {string} clientId The logged in GAE user.
* @constructor
* @extends {registry.Console}
* @final
*/
registry.registrar.Console = function(xsrfToken, clientId) {
registry.registrar.Console.base(
this, 'constructor',
new registry.registrar.EppSession(this, xsrfToken, clientId));
/**
* Component that's currently embedded in the page.
* @type {?registry.Component}
* @private
*/
this.component_ = null;
// XXX: This was in parent ctor but was triggering event dispatching before
// ready here.
this.history.setEnabled(true);
/**
* @type {!string}
* @private
*/
this.xsrfToken_ = xsrfToken;
/**
* Last active nav element.
* @type {Element}
*/
this.lastActiveNavElt;
/**
* @type {!Object.<string, function(new:registry.Component,
* !registry.registrar.Console,
* string)>}
*/
this.pageMap = {};
this.pageMap['security-settings'] = registry.registrar.SecuritySettings;
this.pageMap['contact-settings'] = registry.registrar.ContactSettings;
this.pageMap['whois-settings'] = registry.registrar.WhoisSettings;
this.pageMap['contact-us'] = registry.registrar.ContactUs;
this.pageMap['resources'] = registry.registrar.Resources;
this.pageMap['contact'] = registry.registrar.Contact;
this.pageMap['payment'] = registry.registrar.Payment;
this.pageMap['domain'] = registry.registrar.Domain;
this.pageMap['host'] = registry.registrar.Host;
this.pageMap[''] = registry.registrar.Dashboard;
};
goog.inherits(registry.registrar.Console, registry.Console);
/**
* Changes the content area depending on hash path.
*
* <p>Hash path is expected to be of the form:
*
* <pre>
* #type/id
* </pre>
*
* <p>The {@code id} part may be appended by {@code ()} to specify the target
* should be a resource create page.
*
* @override
*/
registry.registrar.Console.prototype.handleHashChange = function() {
var hashToken = this.history.getToken();
// On page reloads, opening a new tab, etc. it's possible that the
// session cookie for a logged-in session exists, but the
// this.session is not yet aware, so come back here after syncing.
//
// XXX: Method should be refactored to avoid this 2-stage behavior.
if (!this.session.isEppLoggedIn()) {
this.session.login(goog.bind(this.handleHashChange, this));
return;
}
// Otherwise, a resource operation.
var parts = hashToken.split('/');
var type = '';
var id = '';
if (parts.length >= 1) {
type = parts[0];
}
if (parts.length == 2) {
id = parts[1];
}
goog.net.XhrIo.cleanup();
var componentCtor = this.pageMap[type];
if (componentCtor == undefined) {
componentCtor = this.pageMap[''];
}
var oldComponent = this.component_;
this.component_ = new componentCtor(this, this.xsrfToken_);
this.registerDisposable(this.component_);
this.component_.basePath = type;
this.component_.bindToDom(id);
this.changeNavStyle();
goog.dispose(oldComponent);
};
/** Change nav style. */
registry.registrar.Console.prototype.changeNavStyle = function() {
var hashToken = this.history.getToken();
// Path except id
var slashNdx = hashToken.lastIndexOf('/');
slashNdx = slashNdx == -1 ? hashToken.length : slashNdx;
var regNavlist = goog.dom.getRequiredElement('reg-navlist');
var path = hashToken.substring(0, slashNdx);
var active = regNavlist.querySelector('a[href="/registrar#' + path + '"]');
if (goog.isNull(active)) {
registry.util.log('Unknown path or path form in changeNavStyle.');
return;
}
if (this.lastActiveNavElt) {
goog.dom.classlist.remove(
this.lastActiveNavElt, goog.getCssName('domain-active-nav'));
}
goog.dom.classlist.add(active, goog.getCssName('domain-active-nav'));
this.lastActiveNavElt = active;
};

View file

@ -0,0 +1,125 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.Contact');
goog.require('goog.dom');
goog.require('registry.registrar.XmlResourceComponent');
goog.require('registry.soy.registrar.contact');
goog.require('registry.soy.registrar.contactepp');
/**
* The {@code Contact} class respresents a registry contact object and
* binds UI CRUD operations to it.
* @param {!registry.registrar.Console} console the
* console singleton.
* @constructor
* @extends {registry.registrar.XmlResourceComponent}
* @final
*/
registry.registrar.Contact = function(console) {
registry.registrar.Contact.base(
this, 'constructor',
registry.soy.registrar.contact.item,
registry.soy.registrar.contactepp,
console);
};
goog.inherits(registry.registrar.Contact,
registry.registrar.XmlResourceComponent);
/** @override */
registry.registrar.Contact.prototype.processItem = function() {
this.model.item = this.model['epp']['response']['resData']['contact:infData'];
if (!goog.isArray(this.model.item['contact:postalInfo'])) {
this.model.item['contact:postalInfo'] =
[this.model.item['contact:postalInfo']];
}
// XXX: Is this code necessary?
var fixPlus = function(val) {
var str = (val || '') + '';
if (str == '' || str.match(/\+.*/)) {
return str;
} else {
return '+' + str;
}
};
// Both of these are optional.
if (this.model.item['contact:voice']) {
this.model.item['contact:voice']['keyValue'] =
fixPlus(this.model.item['contact:voice']['keyValue']);
}
if (this.model.item['contact:voice']) {
this.model.item['contact:fax']['keyValue'] =
fixPlus(this.model.item['contact:fax']['keyValue']);
}
};
/** @override */
registry.registrar.Contact.prototype.setupEditor = function(objArgs) {
// For now always keep the first contact and make it i18n. Toggle button
// disables to enforce state.
//
// XXX: Should be simplified to make more modular.
var postalElt = goog.dom.getRequiredElement('contact-postalInfo');
var addPostalInfoBtn = goog.dom.getRequiredElement(
'domain-contact-postalInfo-add-button');
this.typeCounts['contact-postalInfo'] = postalElt.childNodes.length;
// 4 child nodes means both addresses are present:
// 2 data tables, the footer id elt and a hidden input.
var setupRemoveBtns = this.typeCounts['contact-postalInfo'] == 4;
if (setupRemoveBtns) {
this.appendRemoveBtn(/** @type {!Element} */ (postalElt.childNodes[0]));
this.appendRemoveBtn(/** @type {!Element} */ (postalElt.childNodes[1]));
} else {
addPostalInfoBtn.removeAttribute('disabled');
}
this.addRemBtnHandlers(
'contact-postalInfo',
function() {
return 'contact:postalInfo[1].contact:';
},
function() {
addPostalInfoBtn.setAttribute('disabled', true);
return null;
},
registry.soy.registrar.contact.postalInfo,
{
item: {},
localized: true,
itemPrefix: 'contact:',
namePrefix: 'contact:postalInfo[1].contact:'
},
setupRemoveBtns);
};
/** @override */
registry.registrar.Contact.prototype.prepareCreate = function(params) {
params.nextId = params.item['contact:id'];
return registry.soy.registrar.contactepp.create(params).toString();
};
/** @override */
registry.registrar.Contact.prototype.prepareUpdate = function(params) {
params.nextId = params.item['contact:id'];
return registry.soy.registrar.contactepp.update(params).toString();
};

View file

@ -0,0 +1,240 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.ContactSettings');
goog.require('goog.Uri');
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.json');
goog.require('goog.soy');
goog.require('registry.Resource');
goog.require('registry.ResourceComponent');
goog.require('registry.soy.registrar.contacts');
goog.require('registry.util');
/**
* Contact Settings page. Registrar Contacts are not really a proper
* REST resource as they're still passed from the server as a member
* field of Registrar, so this class behaves like an item page,
* updating only that field of the Registrar object and not
* implementing the create action from edit_item.
* @param {!registry.registrar.Console} console
* @param {string} xsrfToken Security token to pass back to the server.
* @constructor
* @extends {registry.ResourceComponent}
* @final
*/
registry.registrar.ContactSettings = function(console, xsrfToken) {
registry.registrar.ContactSettings.base(
this, 'constructor',
console,
new registry.Resource(new goog.Uri('/registrar-settings'), xsrfToken),
registry.soy.registrar.contacts.contact,
null);
};
goog.inherits(registry.registrar.ContactSettings, registry.ResourceComponent);
/** @override */
registry.registrar.ContactSettings.prototype.setupAppbar = function() {
registry.registrar.ContactSettings.base(this, 'setupAppbar');
// Setup delete only on existing items, not on creates.
if (goog.isDefAndNotNull(this.model)) {
var deleteBtn = goog.dom.createDom('button', {
type: 'button',
id: 'reg-app-btn-delete',
className: goog.getCssName('kd-button')
},
'Delete');
goog.events.listen(deleteBtn, goog.events.EventType.CLICK,
goog.bind(this.sendDelete, this));
goog.dom.insertSiblingBefore(deleteBtn,
goog.dom.getRequiredElement('reg-app-btn-cancel'));
}
};
/** @override */
registry.registrar.ContactSettings.prototype.renderItem = function(rspObj) {
var contentElt = goog.dom.getRequiredElement('reg-content');
/** @type {!registry.json.RegistrarContact} */
var contacts = rspObj.contacts;
if (this.id) {
var targetContactNdx;
var targetContact;
for (var c in contacts) {
var ct = contacts[c];
if (ct.emailAddress == this.id) {
targetContactNdx = c;
targetContact = ct;
break;
}
}
if (!targetContact) {
registry.util.butter('No contact with the given email.');
return;
}
var typesList = targetContact.types.toLowerCase().split(',');
var actualTypesLookup = {};
for (var t in typesList) {
actualTypesLookup[typesList[t]] = true;
}
goog.soy.renderElement(
contentElt,
registry.soy.registrar.contacts.contact,
{
item: targetContact,
namePrefix: 'contacts[' + targetContactNdx + '].',
actualTypesLookup: actualTypesLookup,
readonly: (rspObj.readonly || false)
});
this.setupAppbar();
} else {
var contactsByType = {};
for (var c in contacts) {
var contact = contacts[c];
var types = contact.types;
if (!types) {
continue;
}
types = types.split(',');
for (var t in types) {
var type = types[t].toLowerCase();
var contactsList = contactsByType[type];
if (!contactsList) {
contactsByType[type] = contactsList = [];
}
contactsList.push(contact);
}
}
goog.soy.renderElement(
contentElt,
registry.soy.registrar.contacts.set,
{contactsByType: contactsByType });
}
};
/** @override */
registry.registrar.ContactSettings.prototype.add = function() {
var newContactNdx = this.model.contacts.length;
goog.soy.renderElement(goog.dom.getRequiredElement('reg-content'),
registry.soy.registrar.contacts.contact,
{
item: {},
namePrefix: 'contacts[' + newContactNdx + '].',
actualTypesLookup: {},
readonly: false
});
this.toggleEdit();
};
/** @override */
registry.registrar.ContactSettings.prototype.sendDelete = function() {
var ndxToDel = null;
for (var i = 0; i < this.model.contacts.length; i++) {
var contact = this.model.contacts[i];
if (contact.emailAddress == this.id) {
ndxToDel = i;
}
}
if (goog.isNull(ndxToDel)) {
throw new Error('Email to delete does not match model.');
}
var modelCopy = /** @type {!Object}
*/ (goog.json.parse(goog.json.serialize(this.model)));
goog.array.removeAt(modelCopy.contacts, ndxToDel);
this.resource.update(modelCopy, goog.bind(this.handleDeleteResponse, this));
};
/** @override */
registry.registrar.ContactSettings.prototype.prepareUpdate =
function(modelCopy) {
var form = registry.util.parseForm('item');
var contact;
// Handle update/create.
if (this.id) {
// Update contact, so overwrite it in the model before sending
// back to server.
var once = false;
for (var c in form.contacts) {
if (once) {
throw new Error('More than one contact parsed from form: ' + c);
}
contact = form.contacts[c];
modelCopy.contacts[c] = contact;
once = true;
}
} else {
// Add contact.
contact = form.contacts.pop();
modelCopy.contacts.push(contact);
}
contact.visibleInWhoisAsAdmin = contact.visibleInWhoisAsAdmin == 'true';
contact.visibleInWhoisAsTech = contact.visibleInWhoisAsTech == 'true';
contact.types = '';
for (var tNdx in contact.type) {
if (contact.type[tNdx]) {
if (contact.types.length > 0) {
contact.types += ',';
}
contact.types += ('' + tNdx).toUpperCase();
}
}
delete contact['type'];
this.nextId = contact.emailAddress;
};
// XXX: Should be hoisted up.
/**
* Handler for contact save that navigates to that item on success.
* Does nothing on failure as UI will be left with error messages for
* the user to resolve.
* @param {!Object} rsp Decoded XML/JSON response from the server.
* @override
*/
registry.registrar.ContactSettings.prototype.handleCreateResponse =
function(rsp) {
this.handleUpdateResponse(rsp);
if (rsp.status == 'SUCCESS') {
this.console.view('contact-settings/' + this.nextId);
}
return rsp;
};
/**
* Handler for contact delete that navigates back to the collection on success.
* Does nothing on failure as UI will be left with error messages for
* the user to resolve.
* @param {!Object} rsp Decoded XML/JSON response from the server.
* @override
*/
registry.registrar.ContactSettings.prototype.handleDeleteResponse =
function(rsp) {
this.handleUpdateResponse(rsp);
if (rsp.status == 'SUCCESS') {
this.id = null;
this.console.view('contact-settings');
}
return rsp;
};

View file

@ -0,0 +1,49 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.ContactUs');
goog.require('goog.Uri');
goog.require('goog.dom');
goog.require('registry.Resource');
goog.require('registry.ResourceComponent');
goog.require('registry.soy.registrar.console');
/**
* Contact Us page.
* @param {!registry.registrar.Console} console
* @param {string} xsrfToken Security token to pass back to the server.
* @constructor
* @extends {registry.ResourceComponent}
* @final
*/
registry.registrar.ContactUs = function(console, xsrfToken) {
registry.registrar.ContactUs.base(
this,
'constructor',
console,
new registry.Resource(new goog.Uri('/registrar-settings'), xsrfToken),
registry.soy.registrar.console.contactUs,
null);
};
goog.inherits(registry.registrar.ContactUs, registry.ResourceComponent);
/** @override */
registry.registrar.ContactUs.prototype.bindToDom = function(id) {
registry.registrar.ContactUs.base(this, 'bindToDom', '');
goog.dom.removeChildren(goog.dom.getRequiredElement('reg-app-buttons'));
};

View file

@ -0,0 +1,94 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.Dashboard');
goog.require('goog.Timer');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.soy');
goog.require('goog.style');
goog.require('registry.Component');
goog.require('registry.soy.registrar.console');
/**
* Dashboard for Registrar Console.
* @param {!registry.registrar.Console} console
* @constructor
* @extends {registry.Component}
* @final
*/
registry.registrar.Dashboard = function(console) {
registry.registrar.Dashboard.base(this, 'constructor', console);
/** @private {number} */
this.x_ = 0;
/** @private {Element} */
this.gear_ = null;
/** @private {goog.Timer} */
this.timer_ = null;
};
goog.inherits(registry.registrar.Dashboard, registry.Component);
/** @override */
registry.registrar.Dashboard.prototype.bindToDom = function(id) {
registry.registrar.Dashboard.base(this, 'bindToDom', '');
goog.dom.removeChildren(goog.dom.getRequiredElement('reg-appbar'));
goog.soy.renderElement(goog.dom.getElement('reg-content'),
registry.soy.registrar.console.dashboard);
goog.events.listen(goog.dom.getElement('rotate'),
goog.events.EventType.CLICK,
goog.bind(this.rotate_, this));
this.gear_ = goog.dom.getRequiredElement('gear');
};
/**
* Do EPP logout.
*/
registry.registrar.Dashboard.prototype.doEppLogout = function() {
this.console.session.logout();
};
/**
* Let's do the twist.
* @private
*/
registry.registrar.Dashboard.prototype.rotate_ = function() {
this.timer_ = new goog.Timer(10);
this.registerDisposable(this.timer_);
goog.events.listen(this.timer_, goog.Timer.TICK,
goog.bind(this.rotateCall_, this));
this.timer_.start();
};
/**
* No really this time!
* @private
*/
registry.registrar.Dashboard.prototype.rotateCall_ = function() {
this.x_++;
goog.style.setStyle(this.gear_, 'transform', 'rotate(' + this.x_ + 'deg)');
if (this.x_ == 360) {
this.timer_.stop();
}
};

View file

@ -0,0 +1,202 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.Domain');
goog.require('goog.json');
goog.require('registry.registrar.XmlResourceComponent');
goog.require('registry.soy.forms');
goog.require('registry.soy.registrar.domain');
goog.require('registry.soy.registrar.domainepp');
goog.require('registry.util');
/**
* CRUD for EPP domain objects.
* @param {!registry.registrar.Console} console
* @constructor
* @extends {registry.registrar.XmlResourceComponent}
* @final
*/
registry.registrar.Domain = function(console) {
registry.registrar.Domain.base(
this, 'constructor',
registry.soy.registrar.domain.item,
registry.soy.registrar.domainepp,
console);
};
goog.inherits(registry.registrar.Domain,
registry.registrar.XmlResourceComponent);
/**
* @define {boolean} Launch phase flag. If not SUNRUSH then GA.
*/
goog.define('registry.registrar.Domain.SUNRUSH', false);
/** @override */
registry.registrar.Domain.prototype.newModel = function() {
var newModel = {item: {'domain:period': ''}};
if (registry.registrar.Domain.SUNRUSH) {
newModel.allowSmd = true;
}
return newModel;
};
/**
* Prepare a fetch query for the domain by its domain application id.
* @param {!Object} params should have a name field with a
* possibly extended domain name id of the form "example.tld:1234"
* where the 1234 is the domain application id assigned by the
* backend flows.
* @override
*/
registry.registrar.Domain.prototype.prepareFetch = function(params) {
var xml;
if (registry.registrar.Domain.SUNRUSH) {
var idParts = params.id.split(':');
if (idParts.length != 2) {
registry.util.butter(
'Domain queries during sunrush have the form: ' +
'"example.tld:1234", where 1234 is the application ID.');
throw Error('Invalid domain name for SUNRUSH, lacking application ID');
}
params.name = idParts[0];
params.applicationID = idParts[1];
xml = registry.soy.registrar.domainepp.infoSunrush(params);
} else {
xml = registry.soy.registrar.domainepp.info(params);
}
return xml.toString();
};
/** @override */
registry.registrar.Domain.prototype.processItem = function() {
this.model.item = this.model['epp']['response']['resData']['domain:infData'];
// Hoist extensions for easy soy access.
var extension = this.model['epp']['response']['extension'];
if (extension && extension['launch:infData']) {
var extendedInfData = extension['launch:infData'];
var applicationID = extendedInfData['launch:applicationID'];
if (applicationID) {
this.model.item['launch:applicationID'] = applicationID;
}
var mark = extendedInfData['mark:mark'];
if (mark) {
this.model.item['mark:mark'] = {'keyValue': goog.json.serialize(mark)};
}
}
// Wrap single item into an array.
if (this.model.item['domain:ns'] &&
this.model.item['domain:ns']['domain:hostObj']) {
var hostObj = this.model.item['domain:ns']['domain:hostObj'];
if (!goog.isArray(hostObj)) {
this.model.item['domain:ns']['domain:hostObj'] = [hostObj];
}
}
};
/** @override */
registry.registrar.Domain.prototype.setupEditor = function(objArgs) {
this.typeCounts['contact'] = objArgs.item['domain:contact'] ?
objArgs.item['domain:contact'].length : 0;
var ji = objArgs.item['domain:ns'];
this.typeCounts['host'] =
ji && ji['domain:hostObj'] ? ji['domain:hostObj'].length : 0;
this.formInputRowRemovable(
document.querySelectorAll('input.domain-hostObj[readonly]'));
this.formInputRowRemovable(
document.querySelectorAll('input.domain-contact[readonly]'));
this.addRemBtnHandlers(
'contact',
goog.bind(function() {
return 'domain:contact[' + this.typeCounts['contact'] + ']';
}, this),
goog.bind(function(newFieldName) {
return registry.util.renderBeforeRow(
'domain-contacts-footer',
registry.soy.forms.selectRow, {
label: 'Type',
name: newFieldName + '.@type',
options: ['admin', 'tech', 'billing']
});
}, this));
this.addRemBtnHandlers('host', goog.bind(function() {
return 'domain:ns.domain:hostObj[' + this.typeCounts['host'] + ']';
}, this));
};
/** @override */
registry.registrar.Domain.prototype.prepareCreate = function(params) {
var form = params.item;
params.nextId = form['domain:name'];
// The presence of this field is used to signal extended template, so remove
// if not used.
if (form['smd:encodedSignedMark'] == '') {
delete form['smd:encodedSignedMark'];
}
var xml;
if (registry.registrar.Domain.SUNRUSH) {
xml = registry.soy.registrar.domainepp.createSunrush(params);
} else {
xml = registry.soy.registrar.domainepp.create(params);
}
return xml.toString();
};
/** @override */
registry.registrar.Domain.prototype.prepareUpdate =
function(params) {
var form = params.item;
var nextId = form['domain:name'];
params.nextId = nextId;
if (form['domain:contact']) {
this.addRem(form['domain:contact'], 'Contacts', params);
}
if (form['domain:ns'] && form['domain:ns']['domain:hostObj']) {
this.addRem(form['domain:ns']['domain:hostObj'], 'Hosts', params);
}
var xml;
if (registry.registrar.Domain.SUNRUSH) {
xml = registry.soy.registrar.domainepp.updateSunrush(params);
nextId += ':' + form['launch:applicationID'];
} else {
if (form['domain:ns'] && form['domain:ns']['domain:hostObj']) {
this.addRem(form['domain:ns']['domain:hostObj'], 'Hosts', params);
}
this.addRem(form['domain:contact'], 'Contacts', params);
xml = registry.soy.registrar.domainepp.update(params);
}
return xml.toString();
};

View file

@ -0,0 +1,131 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.EppSession');
goog.require('goog.Uri');
goog.require('registry.Session');
goog.require('registry.soy.registrar.epp');
goog.require('registry.util');
goog.require('registry.xml');
/**
* Session state for console.
* @param {!registry.registrar.Console} console
* @param {string} xsrfToken Populated by server-side soy template.
* @param {string} clientId The logged in GAE user.
* @constructor
* @extends {registry.Session}
* @final
*/
registry.registrar.EppSession = function(console, xsrfToken, clientId) {
registry.registrar.EppSession.base(
this, 'constructor', new goog.Uri('/registrar-xhr'), xsrfToken,
registry.Session.ContentType.EPP);
/**
* @type {!registry.registrar.Console}
*/
this.console = console;
/**
* @type {!boolean}
* @private
*/
this.isEppLoggedIn_ = false;
/**
* @type {string}
* @private
*/
this.clientId_ = clientId;
};
goog.inherits(registry.registrar.EppSession, registry.Session);
/**
* Whether the session has received an EPP success response to an EPP
* login attempt.
* @return {boolean} Whether the user is logged into an EPP session.
*/
registry.registrar.EppSession.prototype.isEppLoggedIn = function() {
return this.isEppLoggedIn_;
};
/**
* Get the clientId if the user is logged in, or throw Error.
* @throws Error if the user is not logged in.
* @return {string} the clientId.
*/
registry.registrar.EppSession.prototype.getClientId = function() {
return this.clientId_;
};
/**
* Login or display butterbar info about error.
* @param {function()} successCb to be called on success.
*/
registry.registrar.EppSession.prototype.login = function(successCb) {
var eppArgs = {clId: this.clientId_, clTrid: 'asdf-1235'};
this.send(
registry.soy.registrar.epp.login(eppArgs).getContent(),
goog.bind(function(xml) {
var result = xml['epp']['response']['result'];
var eppCode = result['@code'];
if (eppCode == '1000' || eppCode == '2002') {
// Success || Already logged in.
this.isEppLoggedIn_ = true;
successCb();
} else {
// Failure.
this.isEppLoggedIn_ = false;
registry.util.butter('login error: ' + eppCode);
}
}, this));
};
/**
* Logout or display butterbar info about error.
*/
registry.registrar.EppSession.prototype.logout = function() {
this.send(
registry.soy.registrar.epp.logout(
{clTrid: 'asdf-1235'}).getContent(),
goog.bind(function(xml) {
var result = xml['epp']['response']['result'];
var eppCode = result['@code'];
registry.util.butter(
'logout ' + eppCode + ': ' + result['msg']['keyValue']);
// Going to be safe here and force a login either way.
this.isEppLoggedIn_ = false;
}, this));
};
/**
* Send xml to the server.
* @param {string} xml Request document.
* @param {function(!Object)} callback For XhrIo result throws.
*/
registry.registrar.EppSession.prototype.send = function(xml, callback) {
var toXmlJsonCb = function(rspXml) {
callback(registry.xml.convertToJson(rspXml));
};
this.sendXhrIo(xml, toXmlJsonCb);
};

View file

@ -0,0 +1,103 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.Host');
goog.require('registry.registrar.XmlResourceComponent');
goog.require('registry.soy.registrar.host');
goog.require('registry.soy.registrar.hostepp');
/**
* CRUD for EPP host objects.
* @param {!registry.registrar.Console} console
* @constructor
* @extends {registry.registrar.XmlResourceComponent}
* @final
*/
registry.registrar.Host = function(console) {
registry.registrar.Host.base(
this, 'constructor',
registry.soy.registrar.host.item,
registry.soy.registrar.hostepp,
console);
};
goog.inherits(registry.registrar.Host,
registry.registrar.XmlResourceComponent);
/** @override */
registry.registrar.Host.prototype.processItem = function() {
this.model.item = this.model['epp']['response']['resData']['host:infData'];
if (this.model.item['host:addr']) {
if (!goog.isArray(this.model.item['host:addr'])) {
this.model.item['host:addr'] = [this.model.item['host:addr']];
}
} else {
this.model.item['host:addr'] = [];
}
};
/** @override */
registry.registrar.Host.prototype.setupEditor = function(objArgs) {
this.typeCounts['host-addr'] =
objArgs.item['host:addr'] ? objArgs.item['host:addr'].length : 0;
this.addRemBtnHandlers('host-addr', goog.bind(function() {
return 'host:addr[' + this.typeCounts['host-addr'] + ']';
}, this));
this.formInputRowRemovable(document.querySelectorAll('input[readonly]'));
};
/** @override */
registry.registrar.Host.prototype.prepareCreate = function(params) {
params.nextId = params.item['host:name'];
return registry.soy.registrar.hostepp.create(params).toString();
};
/** @override */
registry.registrar.Host.prototype.prepareUpdate = function(params) {
var form = params.item;
var addAddrs = [];
var remAddrs = [];
if (form['host:addr']) {
var oldAddrs = form['host:oldAddr'] || [];
var newAddrs = form['host:addr'];
var length = Math.max(oldAddrs.length, newAddrs.length);
for (var i = 0; i < length; i++) {
if (i >= oldAddrs.length) {
addAddrs.push(newAddrs[i]['value']);
} else if (i >= newAddrs.length) {
remAddrs.push(oldAddrs[i]['value']);
} else {
if (newAddrs[i]['value'] == oldAddrs[i]['value']) {
// Do nothing.
} else if (newAddrs[i]['value'] == '') {
remAddrs.push(oldAddrs[i]['value']);
} else {
remAddrs.push(oldAddrs[i]['value']);
addAddrs.push(newAddrs[i]['value']);
}
}
}
}
params.addAddrs = addAddrs;
params.remAddrs = remAddrs;
params.nextId = form['host:chgName'];
return registry.soy.registrar.hostepp.update(params).toString();
};

View file

@ -0,0 +1,35 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Entry point for the registrar console.
*/
goog.provide('registry.registrar.main');
goog.require('registry.registrar.Console');
/**
* Instantiates a registry object, which syncs with the server.
* Sub-templates are invoked based on location/pathname, to choose
* either Registry or Registrar pages.
*
* @param {string} xsrfToken populated by server-side soy template.
* @param {string} clientId The registrar clientId.
* @export
*/
registry.registrar.main = function(xsrfToken, clientId) {
new registry.registrar.Console(xsrfToken, clientId);
};

View file

@ -0,0 +1,412 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.Payment');
goog.require('goog.Uri');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.events.EventType');
goog.require('goog.json');
goog.require('goog.object');
goog.require('goog.soy');
goog.require('registry.Component');
goog.require('registry.MenuButton');
goog.require('registry.Session');
goog.require('registry.forms');
goog.require('registry.registrar.BrainFrame');
goog.require('registry.soy.registrar.console');
goog.require('registry.soy.registrar.payment');
goog.require('registry.util');
/**
* Page allowing registrar to send money to registry.
*
* <p>This page contains a form that asks the user to enter an arbitrary amount
* and a payment method, which can be credit card or PayPal. Multiple
* currencies are supported.
*
* <h3>PCI Compliance</h3>
*
* <p>We don't have any access whatsoever to the credit card information. We
* embed an iframe run by Braintree Payments. The user can then provide his
* credit card (or PayPal) details directly to Braintree. Braintree then gives
* us a nonce value representing the payment method, which we can use to issue
* the transaction.
*
* <h3>Bidirectional Protection</h3>
*
* <p>To use Braintree's iframe, we need to load a script from their server. We
* don't want that script to have access to the Registrar Console. If Braintree
* got pwnd, the attacker would be able to issue EPP commands as a registrar.
*
* <p>We fix this problem by embedding the Braintree iframe inside another
* sandbox iframe that's hosted from a Cloud Storage bucket. This frame is
* defined by {@code brainframe.html}. It's basically an empty shell that sends
* a request back to the production environment for {@code brainframe.js}.
*
* <p>The importance of the Cloud Storage bucket is that it is served from a
* separate domain. This causes the browser to forbid the iframe from accessing
* the contents of the parent frame. The HTML5 {@code sandbox} attribute does
* this too, but we can't use it, because the Venmo functionality in the
* Braintree JS SDK needs to be able to access {@code document.cookie}, which
* is forbidden in a sandbox environment. This HTML5 {@code sandbox} feature is
* also not available in older versions of Internet Explorer.
*
* <h3>Business Logic</h3>
*
* <p>This page starts off as a loading glyph, while we issue an RPC to the
* backend. We ask for a Braintree token, which currencies are available, and
* the location of the brainframe HTML file. Once we get that data, we render
* the form.
*
* <p>Once the sandbox iframe inside that form has loaded, it'll send us a
* message asking for the token. We give it the token, which it uses to load
* the Braintree iframe.
*
* <p>To make sure the sandbox iframe is the same size as the Braintree iframe,
* the sandbox iframe will send us messages on occasion asking to be resized.
*
* <p>The disabled state of the submit button is managed judiciously. It's
* disabled initially, until we get a READY message from the sandbox iframe,
* indicating that the Braintree iframe is fully loaded. We also disable the
* submit button during the submit process.
*
* <p>When the user presses the submit button, we send a message to the sandbox
* iframe asking it to submit the Braintree iframe. When the Braintree iframe
* is submitted, it gives the sandbox iframe the the payment method nonce,
* which it passes along to us. Then we pass the form data to the backend via
* the payment RPC, which invokes the Braintree Java API to issue the
* transaction.
*
* <p>If the payment RPC fails, we'll either show an error on a field, or in a
* bloody butterbar. If it succeeds, then the backend will give us the
* transaction ID assigned by Braintree, which we then render on a success
* page.
*
* <p>The success page contains a "Make Another Payment" button which, if
* clicked, will reset the state of this page back to the beginning.
*
* @param {!registry.registrar.Console} console
* @param {string} xsrfToken Security token to pass back to the server.
* @constructor
* @extends {registry.Component}
* @final
*/
registry.registrar.Payment = function(console, xsrfToken) {
registry.registrar.Payment.base(this, 'constructor', console);
/**
* Element in which this page is rendered.
* @private {!Element}
* @const
*/
this.content_ = goog.dom.getRequiredElement('reg-content');
/**
* Braintree API nonce token generated by the backend. This value is a
* prerequisite to rendering the Braintree iframe.
* @private {string}
*/
this.token_ = '';
/**
* Braintree API nonce value for payment method selected by user.
* @private {string}
*/
this.paymentMethodNonce_ = '';
/**
* Currency drop-down widget in form.
* @private {?registry.MenuButton}
*/
this.currencyMenu_ = null;
/**
* XHR client to {@code RegistrarPaymentSetupAction}.
* @private {!registry.Session.<!registry.rpc.PaymentSetup.Request,
* !registry.rpc.PaymentSetup.Response>}
* @const
*/
this.setupRpc_ =
new registry.Session(new goog.Uri('/registrar-payment-setup'),
xsrfToken,
registry.Session.ContentType.JSON);
/**
* XHR client to {@code RegistrarPaymentAction}.
* @private {!registry.Session.<!registry.rpc.Payment.Request,
* !registry.rpc.Payment.Response>}
* @const
*/
this.paymentRpc_ =
new registry.Session(new goog.Uri('/registrar-payment'),
xsrfToken,
registry.Session.ContentType.JSON);
this.listen(goog.global.window,
goog.events.EventType.MESSAGE,
this.onMessage_);
};
goog.inherits(registry.registrar.Payment, registry.Component);
/** @override */
registry.registrar.Payment.prototype.bindToDom = function(id) {
registry.registrar.Payment.base(this, 'bindToDom', id);
if (!goog.isNull(goog.dom.getElement('reg-app-buttons'))) {
goog.dom.removeChildren(goog.dom.getElement('reg-app-buttons'));
}
if (!registry.registrar.Payment.isBrowserSupported_()) {
goog.soy.renderElement(this.content_,
registry.soy.registrar.payment.unsupported);
return;
}
goog.soy.renderElement(this.content_, registry.soy.registrar.console.loading);
this.setupRpc_.sendXhrIo({}, goog.bind(this.onSetup_, this));
};
/**
* Handler invoked when we receive information from our backend, such as a
* Braintree token, which is necessary for us to render the payment form.
* @param {!registry.rpc.PaymentSetup.Response} response
* @private
*/
registry.registrar.Payment.prototype.onSetup_ = function(response) {
if (response.status != 'SUCCESS') {
if (response.message == 'not-using-cc-billing') {
goog.soy.renderElement(this.content_,
registry.soy.registrar.payment.notUsingCcBilling);
} else {
registry.forms.displayError(response.message);
}
return;
}
var result = response.results[0];
this.token_ = result.token;
this.paymentMethodNonce_ = '';
goog.soy.renderElement(this.content_,
registry.soy.registrar.payment.form,
result);
this.listen(
goog.dom.getRequiredElementByClass(goog.getCssName('reg-payment-form')),
goog.events.EventType.SUBMIT,
this.onSubmit_);
this.currencyMenu_ =
new registry.MenuButton(goog.dom.getRequiredElement('currency'));
this.registerDisposable(this.currencyMenu_);
};
/**
* Handler invoked when payment form is submitted.
* @param {!goog.events.BrowserEvent} e
* @private
*/
registry.registrar.Payment.prototype.onSubmit_ = function(e) {
e.preventDefault();
this.submit_();
};
/**
* Submits payment form.
* @private
*/
registry.registrar.Payment.prototype.submit_ = function() {
registry.forms.resetErrors();
registry.registrar.Payment.setEnabled_(false);
if (this.paymentMethodNonce_ == '') {
this.send_(
'type', registry.registrar.BrainFrame.MessageType.SUBMIT_REQUEST);
return;
}
this.paymentRpc_.sendXhrIo(
{
amount: goog.dom.getRequiredElement('amount').value,
currency: this.currencyMenu_.getValue(),
paymentMethodNonce: this.paymentMethodNonce_
},
goog.bind(this.onPayment_, this));
};
/**
* Callback for backend payment RPC that issues the transaction.
* @param {!registry.rpc.Payment.Response} response
* @private
*/
registry.registrar.Payment.prototype.onPayment_ = function(response) {
registry.registrar.Payment.setEnabled_(true);
if (response.status != 'SUCCESS') {
registry.forms.displayError(response.message, response.field);
return;
}
goog.soy.renderElement(this.content_,
registry.soy.registrar.payment.success,
response.results[0]);
this.listenOnce(
goog.dom.getRequiredElementByClass(goog.getCssName('reg-payment-again')),
goog.events.EventType.CLICK,
this.bindToDom);
};
/**
* Handler invoked when {@code brainframe.js} sends us a message.
* @param {!goog.events.BrowserEvent} e
* @private
*/
registry.registrar.Payment.prototype.onMessage_ = function(e) {
var msg = /** @type {!MessageEvent.<string>} */ (e.getBrowserEvent());
var brainframe =
goog.dom.getElementByClass(goog.getCssName('reg-payment-form-method'));
if (brainframe == null ||
msg.source != goog.dom.getFrameContentWindow(brainframe)) {
return;
}
var data;
try {
data = goog.json.parse(msg.data);
} catch (ex) {
// TODO(b/26876003): Figure out why it's possible that the Braintree iframe
// is able to propagate messages up to our level.
registry.util.log(ex, msg.source, msg.data);
return;
}
switch (goog.object.get(data, 'type')) {
case registry.registrar.BrainFrame.MessageType.TOKEN_REQUEST:
goog.asserts.assert(this.token_ != '');
this.send_(
'type', registry.registrar.BrainFrame.MessageType.TOKEN_RESPONSE,
'token', this.token_);
break;
case registry.registrar.BrainFrame.MessageType.RESIZE_REQUEST:
brainframe.height = goog.object.get(data, 'height');
break;
case registry.registrar.BrainFrame.MessageType.READY:
registry.registrar.Payment.setEnabled_(true);
break;
case registry.registrar.BrainFrame.MessageType.SUBMIT_ERROR:
registry.registrar.Payment.setEnabled_(true);
registry.forms.displayError(goog.object.get(data, 'message'), 'method');
break;
case registry.registrar.BrainFrame.MessageType.PAYMENT_METHOD:
registry.registrar.Payment.setEnabled_(true);
this.setPaymentMethod_(
/** @type {!braintreepayments.PaymentMethod} */ (
goog.object.get(data, 'method')));
if (goog.object.get(data, 'submit')) {
this.submit_();
}
break;
default:
throw Error('Unexpected message: ' + msg.data);
}
};
/**
* Updates UI to display selected payment method.
*
* <p>We remove the iframe from the page as soon as this happens, because the
* UI would be busted otherwise. The Braintree UI for changing the payment
* method (after it's been entered) does not appear to stop respond to submit
* events. It also causes ugly scroll bars to appear inside the iframe.
*
* <p>This approach is also advantageous for screenshot testing. We do not want
* our continuous integration testing system to talk to Braintree's servers. So
* we mock out the brainframe with {@code integration-test-brainframe.html}
* which <i>only</i> sends us a METHOD message, which we then render ourselves.
*
* @param {!braintreepayments.PaymentMethod} pm
* @private
*/
registry.registrar.Payment.prototype.setPaymentMethod_ = function(pm) {
registry.forms.resetErrors();
goog.dom.removeNode(
goog.dom.getElementByClass(goog.getCssName('reg-payment-form-method')));
var paymentMethodInfoBox =
goog.dom.getRequiredElementByClass(
goog.getCssName('reg-payment-form-method-info'));
switch (pm.type) {
case 'CreditCard':
goog.soy.renderElement(
paymentMethodInfoBox,
registry.soy.registrar.payment.methodInfoCard,
{cardType: pm.details.cardType, lastTwo: pm.details.lastTwo});
break;
case 'PayPalAccount':
goog.soy.renderElement(
paymentMethodInfoBox,
registry.soy.registrar.payment.methodInfoPaypal,
{email: pm.details.email});
break;
default:
throw Error('Unknown payment method: ' + pm.type);
}
registry.util.setVisible(paymentMethodInfoBox, true);
this.paymentMethodNonce_ = pm.nonce;
};
/**
* Sends message to brainframe.
* @param {...*} var_args Passed along to {@code goog.object.create}.
* @private
*/
registry.registrar.Payment.prototype.send_ = function(var_args) {
goog.asserts.assert(arguments[0] == 'type');
var brainframeWindow =
goog.dom.getFrameContentWindow(
goog.dom.getRequiredElementByClass(
goog.getCssName('reg-payment-form-method')));
// We send a string value to support older versions of IE.
brainframeWindow.postMessage(
goog.json.serialize(goog.object.create.apply(null, arguments)),
'*');
};
/**
* Enables submit button and hides mini loading glyph.
* @param {boolean} enabled
* @private
*/
registry.registrar.Payment.setEnabled_ = function(enabled) {
registry.forms.setEnabled(
goog.dom.getRequiredElementByClass(
goog.getCssName('reg-payment-form-submit')), enabled);
registry.util.setVisible(
goog.dom.getRequiredElementByClass(
goog.getCssName('reg-payment-form-loader')), !enabled);
};
/**
* Returns {@code true} if browser has all the features we need.
* @return {boolean}
* @private
* @see "http://caniuse.com/#feat=dispatchevent"
*/
registry.registrar.Payment.isBrowserSupported_ = function() {
// dispatchEvent is used by brainframe.js and is IE 9+.
return goog.object.containsKey(
goog.dom.createElement(goog.dom.TagName.FORM),
'dispatchEvent');
};

View file

@ -0,0 +1,50 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.Resources');
goog.require('goog.Uri');
goog.require('goog.dom');
goog.require('registry.Resource');
goog.require('registry.ResourceComponent');
goog.require('registry.soy.registrar.console');
/**
* Resources and billing page.
* @param {!registry.registrar.Console} console
* @param {string} xsrfToken Security token to pass back to the server.
* @constructor
* @extends {registry.ResourceComponent}
* @final
*/
registry.registrar.Resources = function(console, xsrfToken) {
registry.registrar.Resources.base(
this,
'constructor',
console,
new registry.Resource(new goog.Uri('/registrar-settings'), xsrfToken),
registry.soy.registrar.console.resources,
null);
};
goog.inherits(registry.registrar.Resources,
registry.ResourceComponent);
/** @override */
registry.registrar.Resources.prototype.bindToDom = function(id) {
registry.registrar.Resources.base(this, 'bindToDom', '');
goog.dom.removeChildren(goog.dom.getRequiredElement('reg-app-buttons'));
};

View file

@ -0,0 +1,110 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.SecuritySettings');
goog.require('goog.Uri');
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.dom.classlist');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.soy');
goog.require('registry.Resource');
goog.require('registry.ResourceComponent');
goog.require('registry.soy.registrar.security');
/**
* Security Settings page.
* @param {!registry.registrar.Console} console
* @param {string} xsrfToken Security token to pass back to the server.
* @constructor
* @extends {registry.ResourceComponent}
* @final
*/
registry.registrar.SecuritySettings = function(console, xsrfToken) {
registry.registrar.SecuritySettings.base(
this,
'constructor',
console,
new registry.Resource(new goog.Uri('/registrar-settings'), xsrfToken),
registry.soy.registrar.security.settings,
null);
};
goog.inherits(registry.registrar.SecuritySettings, registry.ResourceComponent);
/** @override */
registry.registrar.SecuritySettings.prototype.bindToDom = function(id) {
registry.registrar.SecuritySettings.base(this, 'bindToDom', 'fake');
goog.dom.removeNode(goog.dom.getRequiredElement('reg-app-btn-back'));
};
/** @override */
registry.registrar.SecuritySettings.prototype.setupEditor =
function(objArgs) {
goog.dom.classlist.add(goog.dom.getRequiredElement('ips'),
goog.getCssName('editing'));
var ips = goog.dom.getElementsByClass(goog.getCssName('ip'),
goog.dom.getRequiredElement('ips'));
goog.array.forEach(ips, function(ip) {
var remBtn = goog.dom.getChildren(ip)[0];
goog.events.listen(remBtn,
goog.events.EventType.CLICK,
goog.bind(this.onIpRemove_, this, remBtn));
}, this);
this.typeCounts['reg-ips'] = objArgs.ipAddressWhitelist ?
objArgs.ipAddressWhitelist.length : 0;
goog.events.listen(goog.dom.getRequiredElement('btn-add-ip'),
goog.events.EventType.CLICK,
this.onIpAdd_,
false,
this);
};
/**
* Click handler for IP add button.
* @private
*/
registry.registrar.SecuritySettings.prototype.onIpAdd_ = function() {
var ipInputElt = goog.dom.getRequiredElement('newIp');
var ipElt = goog.soy.renderAsFragment(registry.soy.registrar.security.ip, {
name: 'ipAddressWhitelist[' + this.typeCounts['reg-ips'] + ']',
ip: ipInputElt.value
});
goog.dom.appendChild(goog.dom.getRequiredElement('ips'), ipElt);
var remBtn = goog.dom.getFirstElementChild(ipElt);
goog.dom.classlist.remove(remBtn, goog.getCssName('hidden'));
goog.events.listen(remBtn, goog.events.EventType.CLICK,
goog.bind(this.onIpRemove_, this, remBtn));
this.typeCounts['reg-ips']++;
ipInputElt.value = '';
};
/**
* Click handler for IP remove button.
* @param {!Element} remBtn The remove button.
* @private
*/
registry.registrar.SecuritySettings.prototype.onIpRemove_ =
function(remBtn) {
goog.dom.removeNode(goog.dom.getParentElement(remBtn));
this.typeCounts['reg-ips']--;
};

View file

@ -0,0 +1,49 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.WhoisSettings');
goog.require('goog.Uri');
goog.require('goog.dom');
goog.require('registry.Resource');
goog.require('registry.ResourceComponent');
goog.require('registry.soy.registrar.whois');
/**
* WHOIS Settings page.
* @param {!registry.registrar.Console} console
* @param {string} xsrfToken Cross-site request forgery protection token.
* @constructor
* @extends {registry.ResourceComponent}
* @final
*/
registry.registrar.WhoisSettings = function(console, xsrfToken) {
registry.registrar.WhoisSettings.base(
this,
'constructor',
console,
new registry.Resource(new goog.Uri('/registrar-settings'), xsrfToken),
registry.soy.registrar.whois.settings,
null);
};
goog.inherits(registry.registrar.WhoisSettings, registry.ResourceComponent);
/** @override */
registry.registrar.WhoisSettings.prototype.bindToDom = function(id) {
registry.registrar.WhoisSettings.base(this, 'bindToDom', 'fake');
goog.dom.removeNode(goog.dom.getRequiredElement('reg-app-btn-back'));
};

View file

@ -0,0 +1,217 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.registrar.XmlResourceComponent');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.dom.classlist');
goog.require('registry.EditItem');
goog.require('registry.util');
/**
* The ResourceComponent class respresents server state for a named
* resource and binds UI CRUD operations on it, or its constituent
* collection.
* @param {function()} itemTmpl
* @param {!Object} eppTmpls Epp xml templates for info requests.
* @param {!registry.registrar.Console} console
* @constructor
* @extends {registry.EditItem}
*/
registry.registrar.XmlResourceComponent = function(
itemTmpl, eppTmpls, console) {
registry.registrar.XmlResourceComponent.base(
this, 'constructor', console, itemTmpl);
/** @type {!Object} */
this.eppTmpls = eppTmpls;
};
goog.inherits(registry.registrar.XmlResourceComponent, registry.EditItem);
/** @override */
registry.registrar.XmlResourceComponent.prototype.bindToDom =
function(id) {
// XXX: EPP resources still use null state.
registry.registrar.XmlResourceComponent.base(
this, 'bindToDom', (id || ''));
if (id) {
this.fetchItem(id);
} else {
// Start edit of empty object.
this.edit();
}
};
/** @override */
registry.registrar.XmlResourceComponent.prototype.fetchItem = function(id) {
var queryParams = {id: id, clTrid: 'abc-1234'};
var xml = this.prepareFetch(queryParams);
this.console.session.send(xml, goog.bind(this.handleFetchItem, this, id));
};
/** @override */
registry.registrar.XmlResourceComponent.prototype.handleFetchItem =
function(id, rsp) {
this.model = rsp;
var result = rsp['epp']['response']['result'];
var resCode = result['@code'];
// XXX: Should use enum.
if (resCode == 1000) { // OK
this.model.readonly = true;
this.processItem();
this.renderItem(this.model);
} else if (resCode == 2303) { // Missing
registry.util.butter(
'Could not find: "' + id + '". Please enter as "type/id"');
} else {
// includes general failure.
registry.util.butter(resCode + ':' + result['msg']['keyValue']);
}
};
/**
* Sublcasses should override to populate create queryParams with form
* fields as needed. {@code queryParams.nextId} MUST be set to the
* new object's ID.
* @param {!Object} queryParams
*/
registry.registrar.XmlResourceComponent.prototype.prepareCreate =
goog.abstractMethod;
/**
* Calls prepareCreate with template params and then send the returned
* XML to the server
* @override
*/
registry.registrar.XmlResourceComponent.prototype.sendCreate = function() {
var form = registry.util.parseForm('item');
var queryParams = {item: form, clTrid: 'abc-1234'};
var xml = this.prepareCreate(queryParams);
this.nextId = queryParams.nextId;
this.console.session.send(xml, goog.bind(this.handleUpdateResponse, this));
};
/**
* Calls prepareUpdate with template params and then send the returned
* XML to the server.
* @override
*/
registry.registrar.XmlResourceComponent.prototype.sendUpdate = function() {
var form = registry.util.parseForm('item');
var queryParams = {item: form, clTrid: 'abc-1234'};
var xml = this.prepareUpdate(queryParams);
this.nextId = queryParams.nextId;
this.console.session.send(xml, goog.bind(this.handleUpdateResponse, this));
};
/**
* Sublcasses should override to populate fetch queryParams with form
* fields as needed.
* @param {!Object} queryParams
* @return {string} EPP xml string
*/
registry.registrar.XmlResourceComponent.prototype.prepareFetch =
function(queryParams) {
return this.eppTmpls.info(queryParams).toString();
};
/** @override */
registry.registrar.XmlResourceComponent.prototype.handleUpdateResponse =
function(rsp) {
var result = rsp['epp']['response']['result'];
if (result['@code'] == 1000) {
// XXX: Consider timer, probably just as a seconds arg with impl in butter.
registry.util.butter('Saved.');
this.fetchItem(this.nextId || '');
this.nextId = null;
this.toggleEdit();
} else {
registry.util.butter(result['msg']['keyValue']);
}
return rsp;
};
/**
* Helper to add add/remove hosts/contacts on queryParams:
*
* queryParams[op + fieldType] = formFields.
*
* @param {Array.<Object>} formFields named form item representations
* that have an associated {op: (add|rem)} attribute.
* @param {string} fieldType capitalized pluralized type name, e.g. "Contacts".
* @param {!Object} queryParams
*/
registry.registrar.XmlResourceComponent.prototype.addRem =
function(formFields, fieldType, queryParams) {
var add = [];
var rem = [];
for (var i = 0; i < formFields.length; i++) {
var formField = formFields[i];
var op = formField['op'];
switch (op) {
case 'add':
add.push(formField);
break;
case 'rem':
rem.push(formField);
break;
default:
registry.util.log(
'Unknown op attribute ' + op + ' on form field:',
formField);
}
}
if (add.length > 0) {
queryParams['add' + fieldType] = add;
}
if (rem.length > 0) {
queryParams['rem' + fieldType] = rem;
}
};
/**
* Make the given form input rows removable.
* @param {NodeList} elts array of input elements.
* @param {function()=} opt_onclickCb called when remove button is clicked.
*/
registry.registrar.XmlResourceComponent.prototype.formInputRowRemovable =
function(elts, opt_onclickCb) {
for (var i = 0; i < elts.length; i++) {
var parent = goog.dom.getParentElement(elts[i]);
this.appendRemoveBtn(parent, goog.partial(function(elt) {
var eppRemInput = goog.dom.createDom(goog.dom.TagName.INPUT, {
'name': elt.name.replace(/\.value/, '.op'),
'value': 'rem',
'type': 'hidden'
});
goog.dom.appendChild(goog.dom.getParentElement(elt), eppRemInput);
goog.dom.classlist.add(
goog.dom.getParentElement(
goog.dom.getParentElement(elt)), goog.getCssName('hidden'));
}, elts[i]));
}
};

View file

@ -0,0 +1,92 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.Resource');
goog.require('goog.json');
goog.require('registry.Session');
/**
* Provide a CRUD view of a server resource.
*
* @param {!goog.Uri} baseUri Target RESTful resource.
* @param {string} xsrfToken Security token to pass back to the server.
* @extends {registry.Session}
* @constructor
*/
registry.Resource = function(baseUri, xsrfToken) {
registry.Resource.base(this, 'constructor', baseUri, xsrfToken,
registry.Session.ContentType.JSON);
};
goog.inherits(registry.Resource, registry.Session);
/**
* Get the resource from the server.
*
* @param {!Object} args Params for server. Do not set the 'op' field on args.
* @param {!Function} callback For retrieved resource.
*/
registry.Resource.prototype.read = function(args, callback) {
this.send_('read', args, callback);
};
/**
* Create the resource on the server.
*
* @param {!Object} args params for server. Do not set the 'op' field on args.
* @param {!Function} callback on success.
* @param {string} newId name for the new resource.
* @throws {!Exception} if the 'op' field is set on args.
*/
registry.Resource.prototype.create = function(args, callback, newId) {
this.send_('create', args, callback, newId);
};
/**
* Create the resource on the server.
*
* @param {!Object} args params for server. Do not set the 'op' field on args.
* @param {!Function} callback on success.
* @throws {!Exception} if the 'op' field is set on args.
*/
registry.Resource.prototype.update = function(args, callback) {
this.send_('update', args, callback);
};
/**
* RESTful access to resources on the server.
*
* @param {string} opCode One of (create|read|update)
* @param {!Object} argsObj arguments for the operation.
* @param {!Function} callback For XhrIo result throws.
* @param {string=} opt_newId name for the new resource.
* @private
*/
registry.Resource.prototype.send_ =
function(opCode, argsObj, callback, opt_newId) {
// NB: must be declared this way in order to avoid compiler renaming
var req = {};
req['op'] = opCode;
req['args'] = argsObj;
if (opt_newId) {
this.uri.setPath(this.uri.getPath() + '/' + opt_newId);
}
this.sendXhrIo(goog.json.serialize(req), callback);
};

View file

@ -0,0 +1,175 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.ResourceComponent');
goog.require('goog.dom');
goog.require('goog.json');
goog.require('registry.EditItem');
goog.require('registry.forms');
goog.require('registry.util');
/**
* The ResourceComponent class respresents server state for a named
* resource and binds UI CRUD operations on it, or its constituent
* collection.
* @param {!registry.Console} console console singleton.
* @param {!registry.Resource} resource the RESTful resource.
* @param {!Function} itemTmpl
* @param {Function} renderSetCb may be null if this resource is only
* ever an item.
* @constructor
* @extends {registry.EditItem}
*/
registry.ResourceComponent = function(
console,
resource,
itemTmpl,
renderSetCb) {
registry.ResourceComponent.base(this, 'constructor', console, itemTmpl);
/** @type {!registry.Resource} */
this.resource = resource;
/** @type {Function} */
this.renderSetCb = renderSetCb;
};
goog.inherits(registry.ResourceComponent, registry.EditItem);
/** @override */
registry.ResourceComponent.prototype.bindToDom = function(id) {
registry.ResourceComponent.base(this, 'bindToDom', id);
this.fetchItem(id);
};
/** @override */
registry.ResourceComponent.prototype.back = function() {
this.console.view(this.basePath);
};
/** @override */
registry.ResourceComponent.prototype.fetchItem = function(id) {
this.resource.read({}, goog.bind(this.handleFetchItem, this, id));
};
/** @override */
registry.ResourceComponent.prototype.handleFetchItem = function(id, rsp) {
// XXX: Two different protocols are supported here. The new style used in the
// registrar console is first, followed by the item/set style used by
// the admin console.
if ('status' in rsp) {
// New style.
if (rsp.status == 'SUCCESS') {
this.model = rsp.results[0];
this.model.readonly = true;
this.processItem();
this.renderItem(this.model);
} else {
// XXX: Happens if the server restarts while the client has an
// open-session. This should retry.
registry.forms.resetErrors();
registry.forms.displayError(rsp['message'], rsp['field']);
}
} else if ('item' in rsp) {
this.model = rsp.item;
this.model.readonly = true;
this.processItem();
this.renderItem(this.model);
} else if ('set' in rsp && this.renderSetCb != null) {
// XXX: This conditional logic should be hoisted to edit_item when
// collection support is improved.
goog.dom.removeChildren(goog.dom.getRequiredElement('reg-appbar'));
this.renderSetCb(goog.dom.getRequiredElement('reg-content'), rsp);
} else {
registry.util.log('unknown message type in handleFetchItem');
}
};
/** @override */
registry.ResourceComponent.prototype.sendUpdate = function() {
var modelCopy = /** @type {!Object}
*/ (goog.json.parse(goog.json.serialize(this.model)));
this.prepareUpdate(modelCopy);
if (this.id) {
this.resource.update(modelCopy, goog.bind(this.handleUpdateResponse, this));
} else {
this.resource.update(modelCopy, goog.bind(this.handleCreateResponse, this));
}
};
/** @override */
registry.ResourceComponent.prototype.prepareUpdate = function(modelCopy) {
var form = registry.util.parseForm('item');
for (var ndx in form) {
modelCopy[ndx] = form[ndx];
}
};
/** @override */
registry.ResourceComponent.prototype.handleUpdateResponse = function(rsp) {
if (rsp.status) {
if (rsp.status != 'SUCCESS') {
registry.forms.resetErrors();
registry.forms.displayError(rsp['message'], rsp['field']);
return rsp;
}
// XXX: Vestigial state from admin console. Shouldn't be possible to be
// null.
if (this.id) {
this.fetchItem(this.id || '');
this.toggleEdit();
}
return rsp;
}
// XXX: Should be removed when admin console uses new response format.
if (rsp instanceof Object && 'results' in rsp) {
registry.util.butter(rsp['results']);
this.bindToDom(this.nextId || '');
this.nextId = null;
} else {
registry.util.butter(rsp.toString());
}
return rsp;
};
/**
* Handle resource create response.
* @param {!Object} rsp Decoded JSON response from the server.
* @return {!Object} a result object describing next steps. On
* success, if next is defined, visit(ret.next) is called, otherwise
* if err is set, the butterbar message is set to it.
*/
registry.ResourceComponent.prototype.handleCreateResponse =
goog.abstractMethod;
/**
* Handle resource delete response.
* @param {!Object} rsp Decoded JSON response from the server.
* @return {!Object} a result object describing next steps. On
* success, if next is defined, visit(ret.next) is called, otherwise
* if err is set, the butterbar message is set to it.
*/
registry.ResourceComponent.prototype.handleDeleteResponse =
goog.abstractMethod;

View file

@ -0,0 +1,125 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.Session');
goog.require('goog.json');
goog.require('goog.net.XhrIo');
goog.require('goog.structs.Map');
goog.require('registry.util');
/**
* XHR launcher for both JSON and XML requests.
* @param {!goog.Uri} defaultUri URI to which requests are POSTed.
* @param {string} xsrfToken Cross-site request forgery protection token.
* @param {!registry.Session.ContentType} contentType Payload mode.
* @constructor
* @template REQUEST, RESPONSE
*/
registry.Session = function(defaultUri, xsrfToken, contentType) {
/**
* URI to which requests are posted.
* @protected {!goog.Uri}
* @const
*/
this.uri = defaultUri;
/**
* Content type set in request body.
* @private {!registry.Session.ContentType}
* @const
*/
this.contentType_ = contentType;
/**
* XHR request headers.
* @private {!goog.structs.Map.<string, string>}
* @const
*/
this.headers_ = new goog.structs.Map(
'Content-Type', contentType,
'X-CSRF-Token', xsrfToken,
'X-Requested-With', 'XMLHttpRequest');
};
/**
* Payload modes supported by this class.
* @enum {string}
*/
registry.Session.ContentType = {
JSON: 'application/json; charset=utf-8',
EPP: 'application/epp+xml'
};
/**
* Abstract method to send a request to the server.
* @param {REQUEST} body HTTP request body as a string or JSON object.
* @param {function(RESPONSE)} onSuccess XHR success callback.
* @param {function(string)=} opt_onError XHR error callback. The default action
* is to show a bloody butterbar.
* @final
*/
registry.Session.prototype.sendXhrIo =
function(body, onSuccess, opt_onError) {
goog.net.XhrIo.send(
this.uri.toString(),
goog.bind(this.onXhrComplete_, this, onSuccess,
opt_onError || goog.bind(this.displayError_, this)),
'POST',
goog.isObject(body) ? goog.json.serialize(body) : body,
this.headers_);
};
/**
* Handler invoked when an asynchronous request is complete.
* @param {function(RESPONSE)} onSuccess Success callback.
* @param {function(string)} onError Success callback.
* @param {{target: !goog.net.XhrIo}} e XHR event.
* @private
*/
registry.Session.prototype.onXhrComplete_ = function(onSuccess, onError, e) {
if (e.target.isSuccess()) {
onSuccess(/** @type {!RESPONSE} */ (
this.contentType_ == registry.Session.ContentType.JSON ?
e.target.getResponseJson(registry.Session.PARSER_BREAKER_) :
e.target.getResponseXml()));
} else {
onError(e.target.getLastError());
}
};
/**
* JSON response prefix which prevents evaluation.
* @private {string}
* @const
*/
registry.Session.PARSER_BREAKER_ = ')]}\'\n';
/**
* Displays {@code message} to user in bloody butterbar.
* @param {string} message
* @private
*/
registry.Session.prototype.displayError_ = function(message) {
registry.util.butter(
this.uri.toString() + ': ' + message + '. Please reload.', true);
};

View file

@ -0,0 +1,216 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.util');
goog.require('goog.dom');
goog.require('goog.dom.classlist');
goog.require('goog.soy');
/**
* Logging function that delegates to {@code console.log()}.
* @param {...*} var_args
*/
registry.util.log = function(var_args) {
if (goog.DEBUG) {
if (goog.isDef(goog.global.console) &&
goog.isDef(goog.global.console['log'])) {
goog.global.console.log.apply(goog.global.console, arguments);
}
}
};
/**
* CSS class for hiding an element whose visibility can be toggled.
* @type {string}
* @const
*/
registry.util.cssHidden = goog.getCssName('hidden');
/**
* CSS class for showing an element whose visibility can be toggled.
* @type {string}
* @const
*/
registry.util.cssShown = goog.getCssName('shown');
/**
* Changes element visibility by toggling CSS {@code shown} to {@code hidden}.
* @param {!Element|string} element Element or id attribute of element.
* @param {boolean} visible Shows {@code element} if true, or else hides it.
*/
registry.util.setVisible = function(element, visible) {
goog.dom.classlist.addRemove(
goog.dom.getElement(element),
visible ? registry.util.cssHidden : registry.util.cssShown,
visible ? registry.util.cssShown : registry.util.cssHidden);
};
/**
* Show a buttebar with the given message. A dismiss link will be added.
* @param {string} message the message to show the user.
* @param {boolean=} opt_isFatal indicates butterbar should be blood red. This
* should only be used when an RPC returns a non-200 status.
*/
registry.util.butter = function(message, opt_isFatal) {
goog.dom.setTextContent(
goog.dom.getElementByClass(goog.getCssName('kd-butterbar-text')),
message);
var butterbar =
goog.dom.getRequiredElementByClass(goog.getCssName('kd-butterbar'));
registry.util.setVisible(butterbar, true);
if (opt_isFatal) {
goog.dom.classlist.add(butterbar, goog.getCssName('fatal'));
} else {
goog.dom.classlist.remove(butterbar, goog.getCssName('fatal'));
}
};
/**
* Hides the butterbar.
*/
registry.util.unbutter = function() {
registry.util.setVisible(
goog.dom.getRequiredElementByClass(goog.getCssName('kd-butterbar')),
false);
};
/**
* Renders the tmpl at and then moves it before the given elt.
* @param {Element|string} id dom id of the refElt to render before.
* @param {function()} tmpl template to render.
* @param {!Object} tmplParams params to pass to the template.
* @return {!Element} the rendered row.
*/
registry.util.renderBeforeRow = function(id, tmpl, tmplParams) {
var refElt = goog.dom.getElement(id);
goog.soy.renderElement(refElt, tmpl, tmplParams);
var prevSib = goog.dom.getPreviousElementSibling(refElt);
goog.dom.removeNode(refElt);
refElt.removeAttribute('id');
goog.dom.insertSiblingAfter(refElt, prevSib);
var newAnchorRefElt = goog.dom.createDom(refElt.tagName, {'id': id});
goog.dom.insertSiblingAfter(newAnchorRefElt, refElt);
return refElt;
};
/**
* Turns an HTML form's named elements into a JSON data structure with
* Pablo's black magick.
* @param {string} formName the name of the form to be parsed.
* @throws {Error} if {@code formName} couldn't be found.
* @return {!Object} the parsed form values as an object.
*/
registry.util.parseForm = function(formName) {
var form = /** @type {HTMLFormElement}
*/ (document.querySelector('form[name=' + formName + ']'));
if (form == null) {
throw new Error('No such form named ' + formName);
}
var obj = {};
// Find names first, since e.g. radio buttons have two elts with the
// same name.
var eltNames = {};
for (var i = 0; i < form.elements.length; i++) {
var elt = form.elements[i];
if (elt.name == '') {
continue;
}
eltNames[elt.name] = null;
}
for (var eltName in eltNames) {
var elt = form.elements[eltName];
var val;
if (elt.type == 'checkbox') {
val = elt.checked;
} else {
val = elt.value;
}
registry.util.expandObject_(obj, eltName, val);
}
return obj;
};
/**
* Give the object a value at the given path. Paths are split on dot
* and array elements are recognized.
*
* <ul>
* <li>a = 1
* <li>foo:a = 1.5
* <li>b.c = 2
* <li>b.d = 3
* <li>c[0] = 4
* <li>c[1] = 5
* <li>d[0].a = 6
* <li>d[1].foo:b = 7
* <li>d[1].@b = 8
* </ul>
*
* Yields the following object:
* <pre>
* {
* a: '1',
* 'foo:a': '1.5',
* b: {
* c: '2',
* d: '3'
* },
* c: ['4','5'],
* d: [{a:'6'},{'foo:b':'7'},{'@b':'8'}]
* }
* </pre>
*
* @param {!Object} obj
* @param {string} pathSpec
* @param {string|Array.<string>|null} val
* @private
*/
registry.util.expandObject_ = function(obj, pathSpec, val) {
var path = pathSpec.split('.');
for (var p = 0; p < path.length; p++) {
var fieldName = path[p];
var arrElt = fieldName.match(/^([\w:]+)\[(\d+)\]$/);
if (arrElt) {
var arrName = arrElt[1];
var arrNdx = arrElt[2];
if (!obj[arrName]) {
obj[arrName] = [];
}
obj = obj[arrName];
fieldName = arrNdx;
if (!obj[arrNdx]) {
obj[arrNdx] = {};
}
} else {
if (!obj[fieldName]) {
obj[fieldName] = {};
}
}
if (p == path.length - 1) {
obj[fieldName] = val;
} else {
obj = obj[fieldName];
}
}
};

View file

@ -0,0 +1,89 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('registry.xml');
goog.provide('registry.xml.XmlJson');
goog.require('goog.dom.NodeType');
goog.require('goog.object');
/**
* Turns XML document into a JSON data structure. This function is similar to
* <a href="https://developer.mozilla.org/en-US/docs/JXON">Mozilla JXON</a>
* except it handles text differently. This routine will not coalesce text
* interspersed with elements. This routine does not coerce string types to
* number, boolean, or null.
* @param {!Node} node
* @return {!Object<string, registry.xml.XmlJson>}
* @throws {Error} upon encountering interspersed text.
*/
registry.xml.convertToJson = function(node) {
var result = goog.object.create();
if (goog.isDefAndNotNull(node.attributes)) {
for (var i = 0; i < node.attributes.length; i++) {
var attr = node.attributes.item(i);
goog.object.set(result, '@' + attr.name, attr.value || '');
}
}
for (var j = 0; j < node.childNodes.length; j++) {
var child = node.childNodes.item(j);
switch (child.nodeType) {
case goog.dom.NodeType.TEXT:
case goog.dom.NodeType.CDATA_SECTION:
var text = String(child.nodeValue).trim();
if (text != '') {
var curr = goog.object.get(result, registry.xml.jsonValueFieldName_);
if (goog.isDef(curr)) {
throw new Error(
'XML text "' + curr + '" interspersed with "' + text + '"');
}
goog.object.set(result, registry.xml.jsonValueFieldName_, text);
}
break;
case goog.dom.NodeType.ELEMENT:
var json = registry.xml.convertToJson(child);
var name = child.nodeName;
if (goog.object.containsKey(result, name)) {
var field = goog.object.get(result, name);
if (goog.isArray(field)) {
field.push(json);
} else {
goog.object.set(result, name, [field, json]);
}
} else {
goog.object.set(result, name, json);
}
break;
}
}
return result;
};
/**
* XML JSON object recursive type definition.
* @typedef {(string|
* !Array<registry.xml.XmlJson>|
* !Object<string, registry.xml.XmlJson>)}
*/
registry.xml.XmlJson;
/**
* XML JSON value field name, inherited from JXON.
* @private {string}
* @const
*/
registry.xml.jsonValueFieldName_ = 'keyValue';

View file

@ -0,0 +1,16 @@
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
@javax.annotation.ParametersAreNonnullByDefault
package com.google.domain.registry.ui;

Some files were not shown because too many files have changed in this diff Show more